[Vis: Default editor] Euificate schema editors (#39331) (#39564)

* EUIficate schema editors

* Fix types

* Fix comments and functional tests

* Add percentage to a tooltip

* Remove extra ts-ignore
This commit is contained in:
Daniil Suleiman 2019-06-25 13:44:41 +03:00 committed by GitHub
parent d178af0cfd
commit 4e0a2f4691
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 248 additions and 126 deletions

View file

@ -1,10 +0,0 @@
<div>
<label>
Dot size ratio
<icon-tip
content="'Change the ratio of the radius of the smallest point to the largest point'"
position="'right'"
></icon-tip>
</label>
<input type="range" step="2" min="1" max="100" class="form-control" ng-model="editorState.params.radiusRatio" />
</div>

View file

@ -1,20 +0,0 @@
<div class="form-group">
<div class="kuiButtonGroup">
<button
type="button"
class="kuiButton kuiButton--basic kuiButton--small"
ng-model="agg.params.row"
data-test-subj="splitBy-row"
btn-radio="true">
Rows
</button>
<button
type="button"
class="kuiButton kuiButton--basic kuiButton--small"
ng-model="agg.params.row"
data-test-subj="splitBy-column"
btn-radio="false">
Columns
</button>
</div>
</div>

View file

@ -44,12 +44,6 @@ uiModules
$scope.groupNameLabel = aggGroupNameMaps()[$scope.groupName];
$scope.$bind('group', 'state.aggs.bySchemaGroup["' + $scope.groupName + '"]');
$scope.$bind('schemas', 'vis.type.schemas["' + $scope.groupName + '"]');
// We use `editorState` to access the state of the editor in the options panels.
// There are some aggregations (dot size metric) that needs to set parameters on the
// editorState too. Since we have the editor state here available as `state`, we're just
// binding it to the same name `editorState` so the controls look the same if they are in
// the data tab or within any other options tab.
$scope.$bind('editorState', 'state');
$scope.$watchMulti([
'schemas',

View file

@ -106,7 +106,7 @@ uiModules
$scope.onChange = (value) => {
$scope.paramValue = value;
$scope.onParamChange($scope.agg, $scope.aggParam.name, value);
$scope.onParamChange($scope.agg.params, $scope.aggParam.name, value);
$scope.showValidation = true;
ngModelCtrl.$setDirty();
};

View file

@ -27,6 +27,14 @@
</p>
</div>
<vis-agg-control-react-wrapper
ng-if="agg.schema.editorComponent"
agg-params="agg.params"
component="agg.schema.editorComponent"
editor-state-params="state.params"
set-value="onParamChange"
/>
<vis-agg-select
agg="agg"
is-sub-aggregation="isSubAggregation"

View file

@ -27,6 +27,7 @@ import { editorConfigProviders } from '../config/editor_config_providers';
import advancedToggleHtml from './advanced_toggle.html';
import './agg_param';
import './agg_select';
import './controls/agg_controls';
import aggParamsTemplate from './agg_params.html';
import { groupAggregationsBy } from './default_editor_utils';
@ -64,9 +65,10 @@ uiModules
}
};
$scope.onParamChange = (agg, paramName, value) => {
if(agg.params[paramName] !== value) {
agg.params[paramName] = value;
// params could be either agg.params or state.params
$scope.onParamChange = (params, paramName, value) => {
if(params[paramName] !== value) {
params[paramName] = value;
}
};
@ -95,19 +97,6 @@ uiModules
updateEditorConfig();
$scope.$watchCollection('agg.params', updateEditorConfig);
// this will contain the controls for the schema (rows or columns?), which are unrelated to
// controls for the agg, which is why they are first
addSchemaEditor();
function addSchemaEditor() {
const $schemaEditor = $('<div>').addClass('schemaEditors form-group').appendTo($el);
if ($scope.agg.schema.editor) {
$schemaEditor.append($scope.agg.schema.editor);
$compile($schemaEditor)($scope.$new());
}
}
// params for the selected agg, these are rebuilt every time the agg in $aggSelect changes
let $aggParamEditors; // container for agg type param editors
let $aggParamEditorsScope;

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
// aggParams and editorStateParams should be described while EUIficate agg_params.js
export interface AggControlProps<T> {
aggParams: any;
editorStateParams: any;
setValue(params: any, paramName: string, value: T): void;
}

View file

@ -0,0 +1,34 @@
/*
* 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 { AggControlProps } from './agg_control_props';
interface AggControlReactWrapperProps<T> extends AggControlProps<T> {
component: React.FunctionComponent<AggControlProps<T>>;
}
function AggControlReactWrapper({
component: Component,
...rest
}: AggControlReactWrapperProps<boolean | number>) {
return <Component {...rest} />;
}
export { AggControlReactWrapper };

View file

@ -0,0 +1,31 @@
/*
* 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 { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from '../../../../modules';
import { AggControlReactWrapper } from './agg_control_react_wrapper';
uiModules
.get('app/visualize')
.directive('visAggControlReactWrapper', reactDirective => reactDirective(wrapInI18nContext(AggControlReactWrapper), [
['aggParams', { watchDepth: 'collection' }],
['editorStateParams', { watchDepth: 'collection' }],
['component', { wrapApply: false }],
'setValue'
]));

View file

@ -0,0 +1,72 @@
/*
* 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, { useEffect } from 'react';
import { EuiFormRow, EuiIconTip, EuiRange } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { AggControlProps } from './agg_control_props';
const DEFAULT_VALUE = 50;
const PARAM_NAME = 'radiusRatio';
function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps<number>) {
const label = (
<>
<FormattedMessage
id="common.ui.vis.defaultEditor.controls.dotSizeRatioLabel"
defaultMessage="Dot size ratio"
/>{' '}
<EuiIconTip
content={i18n.translate('common.ui.vis.defaultEditor.controls.dotSizeRatioHelpText', {
defaultMessage:
'Change the ratio of the radius of the smallest point to the largest point.',
})}
position="right"
/>
</>
);
useEffect(() => {
if (!editorStateParams.radiusRatio) {
setValue(editorStateParams, PARAM_NAME, DEFAULT_VALUE);
}
}, []);
return (
<EuiFormRow fullWidth={true} label={label}>
{
// @ts-ignore: valueAppend does not exist in EuiRange prop types
<EuiRange
compressed
fullWidth={true}
min={1}
max={100}
value={editorStateParams.radiusRatio || DEFAULT_VALUE}
onChange={e => setValue(editorStateParams, PARAM_NAME, parseFloat(e.target.value))}
showRange
showValue
valueAppend="%"
/>
}
</EuiFormRow>
);
}
export { RadiusRatioOptionControl };

View file

@ -0,0 +1,64 @@
/*
* 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 { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggControlProps } from './agg_control_props';
const PARAMS = {
NAME: 'row',
ROWS: 'visEditorSplitBy__true',
COLUMNS: 'visEditorSplitBy__false',
};
function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps<boolean>) {
const idSelected = `visEditorSplitBy__${aggParams.row}`;
const options = [
{
id: PARAMS.ROWS,
label: i18n.translate('common.ui.vis.defaultEditor.controls.rowsLabel', {
defaultMessage: 'Rows',
}),
},
{
id: PARAMS.COLUMNS,
label: i18n.translate('common.ui.vis.defaultEditor.controls.columnsLabel', {
defaultMessage: 'Columns',
}),
},
];
return (
<EuiFormRow className="visEditorSidebar__aggParamFormRow" fullWidth={true}>
<EuiButtonGroup
data-test-subj="visEditorSplitBy"
legend={i18n.translate('common.ui.vis.defaultEditor.controls.splitByLegend', {
defaultMessage: 'Split chart by rows or columns.',
})}
options={options}
isFullWidth={true}
idSelected={idSelected}
onChange={optionId => setValue(aggParams, PARAMS.NAME, optionId === PARAMS.ROWS)}
/>
</EuiFormRow>
);
}
export { RowsOrColumnsControl };

View file

@ -18,11 +18,10 @@
*/
import _ from 'lodash';
import '../directives/buttons';
import { IndexedArray } from '../../../indexed_array';
import { AggParams } from '../../../agg_types/agg_params';
import rowsOrColumnsHtml from 'plugins/kbn_vislib_vis_types/controls/rows_or_columns.html';
import radiusRatioOptionHtml from 'plugins/kbn_vislib_vis_types/controls/radius_ratio_option.html';
import { RowsOrColumnsControl } from './controls/rows_or_columns';
import { RadiusRatioOptionControl } from './controls/radius_ratio_option';
class Schemas {
constructor(schemas) {
@ -38,9 +37,9 @@ class Schemas {
default: true
}
];
schema.editor = rowsOrColumnsHtml;
schema.editorComponent = RowsOrColumnsControl;
} else if (schema.name === 'radius') {
schema.editor = radiusRatioOptionHtml;
schema.editorComponent = RadiusRatioOptionControl;
}
_.defaults(schema, {

View file

@ -1,60 +0,0 @@
/*
* 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 angular from 'angular';
import { uiModules } from 'ui/modules';
const module = uiModules.get('kibana');
module.constant('buttonConfig', {
activeClass: 'active',
toggleEvent: 'click'
});
module.controller('ButtonsController', ['buttonConfig', function (buttonConfig) {
this.activeClass = buttonConfig.activeClass || 'active';
this.toggleEvent = buttonConfig.toggleEvent || 'click';
}]);
module.directive('btnRadio', function () {
return {
require: ['btnRadio', 'ngModel'],
controller: 'ButtonsController',
link: function (scope, element, attrs, ctrls) {
const buttonsCtrl = ctrls[0];
const ngModelCtrl = ctrls[1];
//model -> UI
ngModelCtrl.$render = function () {
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
};
//ui->model
element.bind(buttonsCtrl.toggleEvent, function () {
const isActive = element.hasClass(buttonsCtrl.activeClass);
if (!isActive || angular.isDefined(attrs.uncheckable)) {
scope.$apply(function () {
ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
ngModelCtrl.$render();
});
}
});
}
};
});

View file

@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }) {
log.debug('Field = extension');
await PageObjects.visualize.selectField('extension.raw');
log.debug('switch from Rows to Columns');
await PageObjects.visualize.clickColumns();
await PageObjects.visualize.clickSplitDirection('Columns');
await PageObjects.visualize.clickGo();
};

View file

@ -999,10 +999,6 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
return await markdown.getVisibleText();
}
async clickColumns() {
await find.clickByCssSelector('div.schemaEditors > div > div > button:nth-child(2)');
}
async getVisualizationRenderingCount() {
const visualizationLoader = await testSubjects.find('visualizationLoader');
const renderingCount = await visualizationLoader.getAttribute('data-rendering-count');
@ -1232,9 +1228,9 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
}
async clickSplitDirection(direction) {
const activeParamPanel = await find.byCssSelector('vis-editor-agg-params[aria-hidden="false"]');
const button = await testSubjects.findDescendant(`splitBy-${direction}`, activeParamPanel);
await button.click();
const control = await testSubjects.find('visEditorSplitBy');
const radioBtn = await control.findByCssSelector(`[title="${direction}"]`);
await radioBtn.click();
}
async countNestedTables() {