diff --git a/src/legacy/ui/public/agg_types/agg_param.d.ts b/src/legacy/ui/public/agg_types/agg_param.d.ts
new file mode 100644
index 0000000000000..4e1af89c9e613
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/agg_param.d.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+interface AggParam {
+ type: string;
+ name: string;
+ displayName?: string;
+}
+
+export { AggParam };
diff --git a/src/legacy/ui/public/agg_types/agg_type.js b/src/legacy/ui/public/agg_types/agg_type.js
index 955ecbd80e472..859541956ef93 100644
--- a/src/legacy/ui/public/agg_types/agg_type.js
+++ b/src/legacy/ui/public/agg_types/agg_type.js
@@ -20,6 +20,7 @@
import _ from 'lodash';
import { AggParams } from './agg_params';
import { fieldFormats } from '../registry/field_formats';
+import { i18n } from '@kbn/i18n';
/**
* Generic AggType Constructor
@@ -118,6 +119,7 @@ function AggType(config) {
if (config.customLabels !== false) {
this.params.push({
name: 'customLabel',
+ displayName: i18n.translate('common.ui.aggTypes.string.customLabel', { defaultMessage: 'Custom label' }),
type: 'string',
write: _.noop
});
diff --git a/src/legacy/ui/public/agg_types/buckets/terms.js b/src/legacy/ui/public/agg_types/buckets/terms.js
index 56eea97d88a3b..3358caa7d9886 100644
--- a/src/legacy/ui/public/agg_types/buckets/terms.js
+++ b/src/legacy/ui/public/agg_types/buckets/terms.js
@@ -311,6 +311,7 @@ export const termsBucketAgg = new BucketAggType({
},
{
name: 'exclude',
+ displayName: i18n.translate('common.ui.aggTypes.buckets.terms.excludeLabel', { defaultMessage: 'Exclude' }),
type: 'string',
advanced: true,
disabled: isNotType('string'),
@@ -318,6 +319,7 @@ export const termsBucketAgg = new BucketAggType({
},
{
name: 'include',
+ displayName: i18n.translate('common.ui.aggTypes.buckets.terms.includeLabel', { defaultMessage: 'Include' }),
type: 'string',
advanced: true,
disabled: isNotType('string'),
diff --git a/src/legacy/ui/public/agg_types/controls/string.html b/src/legacy/ui/public/agg_types/controls/string.html
deleted file mode 100644
index 80accf1eb5c55..0000000000000
--- a/src/legacy/ui/public/agg_types/controls/string.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/src/legacy/ui/public/agg_types/controls/string.tsx b/src/legacy/ui/public/agg_types/controls/string.tsx
new file mode 100644
index 0000000000000..c29a54cebd319
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/string.tsx
@@ -0,0 +1,42 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+
+import { EuiFieldText, EuiFormRow } from '@elastic/eui';
+import { AggParamEditorProps } from '../../vis/editors/default';
+
+function StringParamEditor({ agg, aggParam, value, setValue }: AggParamEditorProps) {
+ return (
+
+ setValue(ev.target.value)}
+ fullWidth={true}
+ />
+
+ );
+}
+
+export { StringParamEditor };
diff --git a/src/legacy/ui/public/agg_types/index.d.ts b/src/legacy/ui/public/agg_types/index.d.ts
index 81d52bb8e788b..6832c4ebcbd4a 100644
--- a/src/legacy/ui/public/agg_types/index.d.ts
+++ b/src/legacy/ui/public/agg_types/index.d.ts
@@ -17,4 +17,5 @@
* under the License.
*/
+export { AggParam } from './agg_param';
export { AggType } from './agg_type';
diff --git a/src/legacy/ui/public/agg_types/param_types/string.js b/src/legacy/ui/public/agg_types/param_types/string.js
index 68c7c3aa4bfd5..19c9bfe132deb 100644
--- a/src/legacy/ui/public/agg_types/param_types/string.js
+++ b/src/legacy/ui/public/agg_types/param_types/string.js
@@ -17,16 +17,16 @@
* under the License.
*/
-import editorHtml from '../controls/string.html';
-import { BaseParamType } from './base';
import { createLegacyClass } from '../../utils/legacy_class';
+import { StringParamEditor } from '../controls/string';
+import { BaseParamType } from './base';
createLegacyClass(StringParamType).inherits(BaseParamType);
function StringParamType(config) {
StringParamType.Super.call(this, config);
}
-StringParamType.prototype.editor = editorHtml;
+StringParamType.prototype.editorComponent = StringParamEditor;
/**
* Write the aggregation parameter.
@@ -45,3 +45,4 @@ StringParamType.prototype.write = function (aggConfig, output) {
};
export { StringParamType };
+
diff --git a/src/legacy/ui/public/vis/editors/default/__tests__/agg_params.js b/src/legacy/ui/public/vis/editors/default/__tests__/agg_params.js
index 9e84cdf4b90ad..3538b1ab5ae70 100644
--- a/src/legacy/ui/public/vis/editors/default/__tests__/agg_params.js
+++ b/src/legacy/ui/public/vis/editors/default/__tests__/agg_params.js
@@ -104,7 +104,7 @@ describe('Vis-Editor-Agg-Params plugin directive', function () {
aggFilter: aggFilter
});
- const customLabelElement = $elem.find('label:contains("Custom Label")');
+ const customLabelElement = $elem.find('label:contains("Custom label")');
expect(customLabelElement.length).to.be(1);
});
@@ -117,7 +117,7 @@ describe('Vis-Editor-Agg-Params plugin directive', function () {
aggFilter: aggFilter
});
- const customLabelElement = $elem.find('label:contains("Custom Label")');
+ const customLabelElement = $elem.find('label:contains("Custom label")');
expect(customLabelElement.length).to.be(0);
});
});
diff --git a/src/legacy/ui/public/vis/editors/default/agg_param.js b/src/legacy/ui/public/vis/editors/default/agg_param.js
index 36e3d8c680fdd..f00a38c1c71ef 100644
--- a/src/legacy/ui/public/vis/editors/default/agg_param.js
+++ b/src/legacy/ui/public/vis/editors/default/agg_param.js
@@ -17,32 +17,80 @@
* under the License.
*/
-import _ from 'lodash';
+import { isFunction } from 'lodash';
+import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from '../../../modules';
+import { AggParamReactWrapper } from './agg_param_react_wrapper';
uiModules
.get('app/visualize')
+ .directive('visAggParamReactWrapper', reactDirective => reactDirective(wrapInI18nContext(AggParamReactWrapper), [
+ ['agg', { watchDepth: 'collection' }],
+ ['aggParam', { watchDepth: 'reference' }],
+ ['paramEditor', { wrapApply: false }],
+ ['onChange', { watchDepth: 'reference' }],
+ 'value',
+ ]))
.directive('visAggParamEditor', function (config) {
return {
restrict: 'E',
+ // We can't use scope binding here yet, since quiet a lot of child directives arbitrary access
+ // parent scope values right now. So we cannot easy change this, until we remove the whole directive.
scope: true,
- template: function ($el) {
+ require: '?^ngModel',
+ template: function ($el, attrs) {
+ if (attrs.editorComponent) {
+ // Why do we need the `ng-if` here?
+ // Short answer: Preventing black magic
+ // Longer answer: The way this component is mounted in agg_params.js (by manually compiling)
+ // and adding to some array, once you switch an aggregation type, this component will once
+ // render once with a "broken state" (something like new aggParam, but still old template),
+ // before agg_params.js actually removes it from the DOM and create a correct version with
+ // the correct template. That ng-if check prevents us from crashing during that broken render.
+ return ``;
+ }
+
return $el.html();
},
link: {
pre: function ($scope, $el, attr) {
$scope.$bind('aggParam', attr.aggParam);
+ $scope.$bind('agg', attr.agg);
+ $scope.$bind('editorComponent', attr.editorComponent);
},
- post: function ($scope) {
+ post: function ($scope, $el, attr, ngModelCtrl) {
$scope.config = config;
$scope.optionEnabled = function (option) {
- if (option && _.isFunction(option.enabled)) {
+ if (option && isFunction(option.enabled)) {
return option.enabled($scope.agg);
}
return true;
};
+
+ $scope.$watch('agg.params[aggParam.name]', (value) => {
+ // Whenever the value of the parameter changed (e.g. by a reset or actually by calling)
+ // we store the new value in $scope.paramValue, which will be passed as a new value to the react component.
+ $scope.paramValue = value;
+ }, true);
+
+ $scope.onChange = (value) => {
+ // This is obviously not a good code quality, but without using scope binding (which we can't see above)
+ // to bind function values, this is right now the best temporary fix, until all of this will be gone.
+ $scope.$parent.onParamChange($scope.agg, $scope.aggParam.name, value);
+
+ if(ngModelCtrl) {
+ ngModelCtrl.$setDirty();
+ }
+ };
}
}
};
diff --git a/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts b/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts
new file mode 100644
index 0000000000000..3e7623b6c25fc
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { AggParam } from '../../../agg_types';
+import { AggConfig } from '../../agg_config';
+
+interface AggParamEditorProps {
+ agg: AggConfig;
+ aggParam: AggParam;
+ value: T;
+ setValue(value: T): void;
+}
+
+export { AggParamEditorProps };
diff --git a/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx b/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx
new file mode 100644
index 0000000000000..4b5380c92559c
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+
+import { AggParam } from '../../../agg_types';
+import { AggConfig } from '../../agg_config';
+import { AggParamEditorProps } from './agg_param_editor_props';
+
+interface AggParamReactWrapperProps {
+ agg: AggConfig;
+ aggParam: AggParam;
+ paramEditor: React.FunctionComponent>;
+ value: T;
+ onChange(value: T): void;
+}
+
+function AggParamReactWrapper(props: AggParamReactWrapperProps) {
+ const { agg, aggParam, paramEditor: ParamEditor, onChange, value } = props;
+ return ;
+}
+
+export { AggParamReactWrapper };
diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.js b/src/legacy/ui/public/vis/editors/default/agg_params.js
index 9df0029bc9f8e..2d82f11975435 100644
--- a/src/legacy/ui/public/vis/editors/default/agg_params.js
+++ b/src/legacy/ui/public/vis/editors/default/agg_params.js
@@ -18,18 +18,18 @@
*/
import $ from 'jquery';
-import { has, get } from 'lodash';
-import aggSelectHtml from './agg_select.html';
-import advancedToggleHtml from './advanced_toggle.html';
-import '../../../filters/match_any';
-import './agg_param';
+import { get, has } from 'lodash';
import { aggTypes } from '../../../agg_types';
-import { uiModules } from '../../../modules';
-import { documentationLinks } from '../../../documentation_links/documentation_links';
-import aggParamsTemplate from './agg_params.html';
import { aggTypeFilters } from '../../../agg_types/filter';
-import { editorConfigProviders } from '../config/editor_config_providers';
import { aggTypeFieldFilters } from '../../../agg_types/param_types/filter';
+import { documentationLinks } from '../../../documentation_links/documentation_links';
+import '../../../filters/match_any';
+import { uiModules } from '../../../modules';
+import { editorConfigProviders } from '../config/editor_config_providers';
+import advancedToggleHtml from './advanced_toggle.html';
+import './agg_param';
+import aggParamsTemplate from './agg_params.html';
+import aggSelectHtml from './agg_select.html';
uiModules
.get('app/visualize')
@@ -56,6 +56,12 @@ uiModules
updateEditorConfig('default');
});
+ $scope.onParamChange = (agg, paramName, value) => {
+ if(agg.params[paramName] !== value) {
+ agg.params[paramName] = value;
+ }
+ };
+
function updateEditorConfig(property = 'fixedValue') {
$scope.editorConfig = editorConfigProviders.getConfigForAgg(
aggTypes.byType[$scope.groupName],
@@ -185,18 +191,27 @@ uiModules
// build HTML editor given an aggParam and index
function getAggParamHTML(param, idx) {
// don't show params without an editor
- if (!param.editor) {
+ if (!param.editor && !param.editorComponent) {
return;
}
const attrs = {
- 'agg-param': 'agg.type.params[' + idx + ']'
+ 'agg-param': 'agg.type.params[' + idx + ']',
+ 'agg': 'agg',
};
if (param.advanced) {
attrs['ng-show'] = 'advancedToggled';
}
+ if (param.editorComponent) {
+ attrs['editor-component'] = `agg.type.params[${idx}].editorComponent`;
+ // The form should interact with reactified components as well.
+ // So we set the ng-model (using a random ng-model variable) to have the method to set dirty
+ // inside the agg_param.js directive, which can get access to the ngModelController to manipulate it.
+ attrs['ng-model'] = `_internalNgModelState${$scope.agg.id}${param.name}`;
+ }
+
return $('')
.attr(attrs)
.append(param.editor)
diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts
new file mode 100644
index 0000000000000..7167a09492b5c
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { AggParamEditorProps } from './agg_param_editor_props';
diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js
index c811a3ef6485f..d7506442aabc8 100644
--- a/test/functional/page_objects/visualize_page.js
+++ b/test/functional/page_objects/visualize_page.js
@@ -499,7 +499,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
}
async setCustomLabel(label, index = 1) {
- const customLabel = await find.byCssSelector(`#visEditorStringInput${index}customLabel`);
+ const customLabel = await testSubjects.find(`visEditorStringInput${index}customLabel`);
customLabel.type(label);
}