diff --git a/package.json b/package.json index 2f8a07ab7b29a..8c638f19b9853 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,6 @@ "d3": "3.5.17", "d3-cloud": "1.2.5", "del": "^4.0.0", - "dragula": "3.7.2", "elasticsearch": "^16.2.0", "elasticsearch-browser": "^16.2.0", "encode-uri-query": "1.0.1", diff --git a/src/legacy/core_plugins/kibana/public/management/_hacks.scss b/src/legacy/core_plugins/kibana/public/management/_hacks.scss index b2ffca9ef964a..59af9c9617a30 100644 --- a/src/legacy/core_plugins/kibana/public/management/_hacks.scss +++ b/src/legacy/core_plugins/kibana/public/management/_hacks.scss @@ -23,21 +23,6 @@ kbn-management-objects-view { .ace_editor { height: 300px; } } -// SASSTODO: These are some dragula settings. -.gu-handle { - cursor: move; - cursor: grab; - cursor: -moz-grab; - cursor: -webkit-grab; -} - -.gu-mirror, -.gu-mirror .gu-handle { - cursor: grabbing; - cursor: -moz-grabbing; - cursor: -webkit-grabbing; -} - // Hack because the management wrapper is flat HTML and needs a class .mgtPage__body { max-width: map-get($euiBreakpoints, 'xl'); diff --git a/src/legacy/ui/public/agg_types/controls/date_ranges.tsx b/src/legacy/ui/public/agg_types/controls/date_ranges.tsx index 2a985dce6f61c..7190ae20c0b03 100644 --- a/src/legacy/ui/public/agg_types/controls/date_ranges.tsx +++ b/src/legacy/ui/public/agg_types/controls/date_ranges.tsx @@ -30,6 +30,7 @@ import { EuiLink, EuiSpacer, EuiText, + EuiFormRow, } from '@elastic/eui'; import dateMath from '@elastic/datemath'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -110,96 +111,99 @@ function DateRangesParamEditor({ ); return ( - <> - - - - - - - - {ranges.map(({ from, to, id }) => { - const deleteBtnTitle = i18n.translate( - 'common.ui.aggTypes.dateRanges.removeRangeButtonAriaLabel', - { - defaultMessage: 'Remove the range of {from} to {to}', - values: { from: from || FROM_PLACEHOLDER, to: to || TO_PLACEHOLDER }, - } - ); - const areBothEmpty = !from && !to; - - return ( - - - - onChangeRange(id, 'from', ev.target.value)} - /> - - - - - - onChangeRange(id, 'to', ev.target.value)} - /> - - - onRemoveRange(id)} - /> - - - - - ); - })} - - {hasInvalidRange && ( - - - - )} - - - - - - - - + + <> + + + + + + + + {ranges.map(({ from, to, id }) => { + const deleteBtnTitle = i18n.translate( + 'common.ui.aggTypes.dateRanges.removeRangeButtonAriaLabel', + { + defaultMessage: 'Remove the range of {from} to {to}', + values: { from: from || FROM_PLACEHOLDER, to: to || TO_PLACEHOLDER }, + } + ); + const areBothEmpty = !from && !to; + + return ( + + + + onChangeRange(id, 'from', ev.target.value)} + /> + + + + + + onChangeRange(id, 'to', ev.target.value)} + /> + + + onRemoveRange(id)} + /> + + + + + ); + })} + + {hasInvalidRange && ( + + + + )} + + + + + + + + + ); } diff --git a/src/legacy/ui/public/agg_types/controls/ranges.tsx b/src/legacy/ui/public/agg_types/controls/ranges.tsx index 91bc4eb50997e..a528ba1fb5c2a 100644 --- a/src/legacy/ui/public/agg_types/controls/ranges.tsx +++ b/src/legacy/ui/public/agg_types/controls/ranges.tsx @@ -27,6 +27,7 @@ import { EuiIcon, EuiSpacer, EuiButtonEmpty, + EuiFormRow, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -88,75 +89,77 @@ function RangesParamEditor({ agg, value = [], setValue }: AggParamEditorProps - {ranges.map(({ from, to, id }) => { - const deleteBtnTitle = i18n.translate( - 'common.ui.aggTypes.ranges.removeRangeButtonAriaLabel', - { - defaultMessage: 'Remove the range of {from} to {to}', - values: { - from: isEmpty(from) ? FROM_PLACEHOLDER : from, - to: isEmpty(to) ? TO_PLACEHOLDER : to, - }, - } - ); + + <> + {ranges.map(({ from, to, id }) => { + const deleteBtnTitle = i18n.translate( + 'common.ui.aggTypes.ranges.removeRangeButtonAriaLabel', + { + defaultMessage: 'Remove the range of {from} to {to}', + values: { + from: isEmpty(from) ? FROM_PLACEHOLDER : from, + to: isEmpty(to) ? TO_PLACEHOLDER : to, + }, + } + ); - return ( - - - - onChangeRange(id, 'from', ev.target.value)} - fullWidth={true} - compressed={true} - /> - - - - - - onChangeRange(id, 'to', ev.target.value)} - fullWidth={true} - compressed={true} - /> - - - onRemoveRange(id)} - /> - - - - - ); - })} + return ( + + + + onChangeRange(id, 'from', ev.target.value)} + fullWidth={true} + compressed={true} + /> + + + + + + onChangeRange(id, 'to', ev.target.value)} + fullWidth={true} + compressed={true} + /> + + + onRemoveRange(id)} + /> + + + + + ); + })} - - - - - - - + + + + + + + + ); } diff --git a/src/legacy/ui/public/vis/agg_configs.d.ts b/src/legacy/ui/public/vis/agg_configs.d.ts index c9557aef4da02..d6223712a7448 100644 --- a/src/legacy/ui/public/vis/agg_configs.d.ts +++ b/src/legacy/ui/public/vis/agg_configs.d.ts @@ -17,4 +17,11 @@ * under the License. */ -export type AggConfigs = any; +import { IndexedArray } from '../indexed_array'; +import { AggConfig } from './agg_config'; + +export interface AggConfigs extends IndexedArray { + bySchemaGroup: { + [key: string]: AggConfig[]; + }; +} diff --git a/src/legacy/ui/public/vis/draggable/__tests__/draggable.js b/src/legacy/ui/public/vis/draggable/__tests__/draggable.js deleted file mode 100644 index fc0601a9b67e9..0000000000000 --- a/src/legacy/ui/public/vis/draggable/__tests__/draggable.js +++ /dev/null @@ -1,139 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -let init; -let $rootScope; -let $compile; - -describe(`draggable_* directives`, function () { - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function ($injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - init = function init(markup = '') { - const $parentScope = $rootScope.$new(); - $parentScope.items = [ - { name: 'item_1' }, - { name: 'item_2' }, - { name: 'item_3' } - ]; - - // create the markup - const $elem = angular.element(`
`); - $elem.html(markup); - - // compile the directive - $compile($elem)($parentScope); - $parentScope.$apply(); - - const $scope = $elem.scope(); - - return { $parentScope, $scope, $elem }; - }; - })); - - describe(`draggable_container directive`, function () { - it(`should expose the drake`, function () { - const { $scope } = init(); - expect($scope.drake).to.be.an(Object); - }); - - it(`should expose the controller`, function () { - const { $scope } = init(); - expect($scope.draggableContainerCtrl).to.be.an(Object); - }); - - it(`should pull item list from directive attribute`, function () { - const { $scope, $parentScope } = init(); - expect($scope.draggableContainerCtrl.getList()).to.eql($parentScope.items); - }); - - it(`should not be able to move extraneous DOM elements`, function () { - const bare = angular.element(`
`); - const { $scope } = init(); - expect($scope.drake.canMove(bare[0])).to.eql(false); - }); - - it(`should not be able to move non-[draggable-item] elements`, function () { - const bare = angular.element(`
`); - const { $scope, $elem } = init(); - $elem.append(bare); - expect($scope.drake.canMove(bare[0])).to.eql(false); - }); - - it(`shouldn't be able to move extraneous [draggable-item] elements`, function () { - const anotherParent = angular.element(`
`); - const item = angular.element(`
`); - const scope = $rootScope.$new(); - anotherParent.append(item); - $compile(anotherParent)(scope); - $compile(item)(scope); - scope.$apply(); - const { $scope } = init(); - expect($scope.drake.canMove(item[0])).to.eql(false); - }); - - it(`shouldn't be able to move [draggable-item] if it has a handle`, function () { - const { $scope, $elem } = init(` -
-
-
- `); - const item = $elem.find(`[draggable-item]`); - expect($scope.drake.canMove(item[0])).to.eql(false); - }); - - it(`should be able to move [draggable-item] by its handle`, function () { - const { $scope, $elem } = init(` -
-
-
- `); - const handle = $elem.find(`[draggable-handle]`); - expect($scope.drake.canMove(handle[0])).to.eql(true); - }); - }); - - describe(`draggable_item`, function () { - it(`should be required to be a child to [draggable-container]`, function () { - const item = angular.element(`
`); - const scope = $rootScope.$new(); - expect(() => { - $compile(item)(scope); - scope.$apply(); - }).to.throwException(/controller(.+)draggableContainer(.+)required/i); - }); - }); - - describe(`draggable_handle`, function () { - it('should be required to be a child to [draggable-item]', function () { - const handle = angular.element(`
`); - const scope = $rootScope.$new(); - expect(() => { - $compile(handle)(scope); - scope.$apply(); - }).to.throwException(/controller(.+)draggableItem(.+)required/i); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/draggable/draggable_container.js b/src/legacy/ui/public/vis/draggable/draggable_container.js deleted file mode 100644 index d1e11fc9740b3..0000000000000 --- a/src/legacy/ui/public/vis/draggable/draggable_container.js +++ /dev/null @@ -1,133 +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 dragula from 'dragula'; -import 'dragula/dist/dragula.css'; -import { uiModules } from '../../modules'; -import { move } from '../../utils/collection'; - -uiModules - .get('kibana') - .directive('draggableContainer', function () { - - const $scopes = new WeakMap(); - - return { - restrict: 'A', - scope: true, - controllerAs: 'draggableContainerCtrl', - controller($scope, $attrs, $parse, $element) { - $scopes.set($element.get(0), $scope); - this.linkDraggableItem = (el, $scope) => { - $scopes.set(el, $scope); - }; - - this.getList = () => $parse($attrs.draggableContainer)($scope); - }, - link($scope, $el) { - const drake = dragula({ - containers: $el.toArray(), - moves(el, source, handle) { - const itemScope = $scopes.get(el); - if (!itemScope || !('draggableItemCtrl' in itemScope)) { - return; // only [draggable-item] is draggable - } - return itemScope.draggableItemCtrl.moves(handle); - } - }); - - const drakeEvents = [ - 'cancel', - 'cloned', - 'drag', - 'dragend', - 'drop', - 'out', - 'over', - 'remove', - 'shadow' - ]; - const prettifiedDrakeEvents = { - drag: 'start', - dragend: 'end' - }; - - drakeEvents.forEach(type => { - drake.on(type, (el, ...args) => forwardEvent(type, el, ...args)); - }); - drake.on('drag', markDragging(true)); - drake.on('dragend', markDragging(false)); - drake.on('drop', drop); - $scope.$on('$destroy', drake.destroy); - $scope.drake = drake; - - function markDragging(isDragging) { - return el => { - const scope = $scopes.get(el); - if (!scope) return; - scope.isDragging = isDragging; - scope.$apply(); - }; - } - - function forwardEvent(type, el, ...args) { - const name = `drag-${prettifiedDrakeEvents[type] || type}`; - const scope = $scopes.get(el); - if (!scope) return; - scope.$broadcast(name, el, ...args); - } - - function drop(el, target, source, sibling) { - const list = $scope.draggableContainerCtrl.getList(); - const itemScope = $scopes.get(el); - if (!itemScope) return; - const item = itemScope.draggableItemCtrl.getItem(); - const fromIndex = list.indexOf(item); - const siblingIndex = getItemIndexFromElement(list, sibling); - - const toIndex = getTargetIndex(list, fromIndex, siblingIndex); - move(list, item, toIndex); - } - - function getTargetIndex(list, fromIndex, siblingIndex) { - if (siblingIndex === -1) { - // means the item was dropped at the end of the list - return list.length - 1; - } else if (fromIndex < siblingIndex) { - // An item moving from a lower index to a higher index will offset the - // index of the earlier items by one. - return siblingIndex - 1; - } - return siblingIndex; - } - - function getItemIndexFromElement(list, element) { - if (!element) return -1; - - const scope = $scopes.get(element); - if (!scope) return; - const item = scope.draggableItemCtrl.getItem(); - const index = list.indexOf(item); - - return index; - } - } - }; - - }); diff --git a/src/legacy/ui/public/vis/draggable/draggable_handle.js b/src/legacy/ui/public/vis/draggable/draggable_handle.js deleted file mode 100644 index ce8815c2a749f..0000000000000 --- a/src/legacy/ui/public/vis/draggable/draggable_handle.js +++ /dev/null @@ -1,33 +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 { uiModules } from '../../modules'; - -uiModules - .get('kibana') - .directive('draggableHandle', function () { - return { - restrict: 'A', - require: '^draggableItem', - link($scope, $el, attr, ctrl) { - ctrl.registerHandle($el); - $el.addClass('gu-handle'); - } - }; - }); diff --git a/src/legacy/ui/public/vis/draggable/draggable_item.js b/src/legacy/ui/public/vis/draggable/draggable_item.js deleted file mode 100644 index 943438aa61b55..0000000000000 --- a/src/legacy/ui/public/vis/draggable/draggable_item.js +++ /dev/null @@ -1,49 +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 $ from 'jquery'; -import { uiModules } from '../../modules'; - -uiModules - .get('kibana') - .directive('draggableItem', function () { - return { - restrict: 'A', - require: '^draggableContainer', - scope: true, - controllerAs: 'draggableItemCtrl', - controller($scope, $attrs, $parse) { - const dragHandles = $(); - - this.getItem = () => $parse($attrs.draggableItem)($scope); - this.registerHandle = $el => { - dragHandles.push(...$el); - }; - this.moves = handle => { - const $handle = $(handle); - const $anywhereInParentChain = $handle.parents().addBack(); - const movable = dragHandles.is($anywhereInParentChain); - return movable; - }; - }, - link($scope, $el, attr, draggableController) { - draggableController.linkDraggableItem($el.get(0), $scope); - } - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/__tests__/agg.js b/src/legacy/ui/public/vis/editors/default/__tests__/agg.js deleted file mode 100644 index 5ca17a7c9b96c..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/__tests__/agg.js +++ /dev/null @@ -1,101 +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 _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../agg'; - - -describe('Vis-Editor-Agg plugin directive', function () { - const $parentScope = {}; - let $elem; - - function makeConfig(which) { - const schemaMap = { - radius: { - title: 'Dot Size', - min: 0, - max: 1 - }, - metric: { - title: 'Y-Axis', - min: 1, - max: Infinity - } - }; - const typeOptions = ['count', 'avg', 'sum', 'min', 'max', 'cardinality']; - which = which || 'metric'; - - const schema = schemaMap[which]; - - return { - min: schema.min, - max: schema.max, - name: which, - title: schema.title, - group: 'metrics', - aggFilter: typeOptions, - // AggParams object - params: [] - }; - } - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function ($rootScope, $compile) { - $parentScope.agg = { - id: 1, - params: {}, - schema: makeConfig() - }; - $parentScope.groupName = 'metrics'; - $parentScope.group = [{ - id: '1', - schema: makeConfig() - }, { - id: '2', - schema: makeConfig('radius') - }]; - - // share the scope - _.defaults($parentScope, $rootScope, Object.getPrototypeOf($rootScope)); - - // make the element - $elem = angular.element( - '
' - ); - - // compile the html - $compile($elem)($parentScope); - - // Digest everything - $elem.scope().$digest(); - })); - - it('should only add the close button if there is more than the minimum', function () { - expect($parentScope.canRemove($parentScope.agg)).to.be(false); - $parentScope.group.push({ - id: '3', - schema: makeConfig() - }); - expect($parentScope.canRemove($parentScope.agg)).to.be(true); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/__tests__/keyboard_move.js b/src/legacy/ui/public/vis/editors/default/__tests__/keyboard_move.js deleted file mode 100644 index b24e2918ac07d..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/__tests__/keyboard_move.js +++ /dev/null @@ -1,63 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import { Direction } from '../keyboard_move'; -import { keyCodes } from '@elastic/eui'; - -describe('keyboardMove directive', () => { - - let $compile; - let $rootScope; - - function createTestButton(callback) { - const scope = $rootScope.$new(); - scope.callback = callback; - return $compile('')(scope); - } - - function createKeydownEvent(keyCode) { - const e = angular.element.Event('keydown'); // eslint-disable-line new-cap - e.which = keyCode; - return e; - } - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject((_$rootScope_, _$compile_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - })); - - it('should call the callback when pressing up', () => { - const spy = sinon.spy(); - const button = createTestButton(spy); - button.trigger(createKeydownEvent(keyCodes.UP)); - expect(spy.calledWith(Direction.up)).to.be(true); - }); - - it('should call the callback when pressing down', () => { - const spy = sinon.spy(); - const button = createTestButton(spy); - button.trigger(createKeydownEvent(keyCodes.DOWN)); - expect(spy.calledWith(Direction.down)).to.be(true); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/_agg.scss b/src/legacy/ui/public/vis/editors/default/_agg.scss index e5bcc31c5b534..801999c35c00e 100644 --- a/src/legacy/ui/public/vis/editors/default/_agg.scss +++ b/src/legacy/ui/public/vis/editors/default/_agg.scss @@ -25,13 +25,6 @@ } } -/** - * 1. Hack to split child elements evenly. - */ -.visEditorAgg__formRow--split { - flex: 1 1 0 !important; /* 1 */ -} - .visEditorAgg__sliderValue { @include euiFontSize; align-self: center; diff --git a/src/legacy/ui/public/vis/editors/default/_agg_select.scss b/src/legacy/ui/public/vis/editors/default/_agg_select.scss deleted file mode 100644 index 0ecbccade6044..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/_agg_select.scss +++ /dev/null @@ -1,7 +0,0 @@ -.visEditorAggSelect__helpLink { - @include euiFontSizeXS; -} - -.visEditorAggSelect__formRow { - margin-bottom: $euiSizeS; -} diff --git a/src/legacy/ui/public/vis/editors/default/_index.scss b/src/legacy/ui/public/vis/editors/default/_index.scss index 3f04e89846f47..4d578086e6113 100644 --- a/src/legacy/ui/public/vis/editors/default/_index.scss +++ b/src/legacy/ui/public/vis/editors/default/_index.scss @@ -10,4 +10,3 @@ $vis-editor-resizer-width: $euiSizeM; // Components @import './agg'; @import './agg_params'; -@import './agg_select'; diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss index db9c3337beed4..4ad13f4417692 100644 --- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss +++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss @@ -119,7 +119,7 @@ // Collapsible section .visEditorSidebar__collapsible { - background-color: transparentize($euiColorLightShade, .85); + background-color: lightOrDarkTheme($euiPageBackgroundColor, $euiColorLightestShade); } .visEditorSidebar__collapsible--margin { @@ -170,12 +170,6 @@ @include euiTextTruncate; } -.visEditorSidebar__collapsibleTitleDescription--danger { - color: $euiColorDanger; - font-weight: $euiFontWeightBold; -} - - // // FORMS // @@ -225,3 +219,11 @@ margin-top: $euiSizeS; margin-bottom: $euiSizeS; } + +.visEditorSidebar__aggGroupAccordionButtonContent { + font-size: $euiFontSizeS; + + span { + color: $euiColorDarkShade; + } +} diff --git a/src/legacy/ui/public/vis/editors/default/agg.html b/src/legacy/ui/public/vis/editors/default/agg.html deleted file mode 100644 index 4c010d575f194..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg.html +++ /dev/null @@ -1,143 +0,0 @@ -
- -
- - - - - - - {{ describe() }} - - - - - - - -
- - - - - - - - - - - - -
- -
- - - - - - -
- - - diff --git a/src/legacy/ui/public/vis/editors/default/agg.js b/src/legacy/ui/public/vis/editors/default/agg.js deleted file mode 100644 index f3f835043c864..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg.js +++ /dev/null @@ -1,188 +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 { i18n } from '@kbn/i18n'; -import './agg_params'; -import './agg_add'; -import './controls/agg_controls'; -import { Direction } from './keyboard_move'; -import _ from 'lodash'; -import './fancy_forms'; -import { uiModules } from '../../../modules'; -import aggTemplate from './agg.html'; -import { move } from '../../../utils/collection'; - -uiModules - .get('app/visualize') - .directive('visEditorAgg', () => { - return { - restrict: 'A', - template: aggTemplate, - require: ['^form', '^ngModel'], - link: function ($scope, $el, attrs, [kbnForm, ngModelCtrl]) { - $scope.editorOpen = !!$scope.agg.brandNew; - $scope.aggIsTooLow = false; - - $scope.$watch('editorOpen', function (open) { - // make sure that all of the form inputs are "touched" - // so that their errors propagate - if (!open) kbnForm.$setTouched(); - }); - - $scope.$watchMulti([ - '$index', - 'group.length' - ], function () { - $scope.aggIsTooLow = calcAggIsTooLow(); - }); - - if ($scope.groupName === 'buckets') { - $scope.$watchMulti([ - '$last', - 'lastParentPipelineAggTitle', - 'agg.type' - ], function ([isLastBucket, lastParentPipelineAggTitle, aggType]) { - $scope.error = null; - $scope.disabledParams = []; - - if (!lastParentPipelineAggTitle || !isLastBucket || !aggType) { - return; - } - - if (['date_histogram', 'histogram'].includes(aggType.name)) { - $scope.onAggParamsChange( - $scope.agg.params, - 'min_doc_count', - // "histogram" agg has an editor for "min_doc_count" param, which accepts boolean - // "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value - aggType.name === 'histogram' ? true : 0); - $scope.disabledParams = ['min_doc_count']; - } else { - $scope.error = i18n.translate('common.ui.aggTypes.metrics.wrongLastBucketTypeErrorMessage', { - defaultMessage: 'Last bucket aggregation must be "Date Histogram" or "Histogram" when using "{type}" metric aggregation.', - values: { type: lastParentPipelineAggTitle }, - description: 'Date Histogram and Histogram should not be translated', - }); - } - }); - } - - /** - * Describe the aggregation, for display in the collapsed agg header - * @return {[type]} [description] - */ - $scope.describe = function () { - if (!$scope.agg.type || !$scope.agg.type.makeLabel) return ''; - const label = $scope.agg.type.makeLabel($scope.agg); - return label ? label : ''; - }; - - $scope.$on('drag-start', () => { - $scope.editorWasOpen = $scope.editorOpen; - $scope.editorOpen = false; - $scope.$emit('agg-drag-start', $scope.agg); - }); - - $scope.$on('drag-end', () => { - $scope.editorOpen = $scope.editorWasOpen; - $scope.$emit('agg-drag-end', $scope.agg); - }); - - /** - * Move aggregations down/up in the priority list by pressing arrow keys. - */ - $scope.onPriorityReorder = function (direction) { - const positionOffset = direction === Direction.down ? 1 : -1; - - const currentPosition = $scope.group.indexOf($scope.agg); - const newPosition = Math.max(0, Math.min(currentPosition + positionOffset, $scope.group.length - 1)); - move($scope.group, currentPosition, newPosition); - $scope.$emit('agg-reorder'); - }; - - $scope.remove = function (agg) { - const aggs = $scope.state.aggs; - const index = aggs.indexOf(agg); - - if (index === -1) { - return; - } - - aggs.splice(index, 1); - }; - - $scope.canRemove = function (aggregation) { - const metricCount = _.reduce($scope.group, function (count, agg) { - return (agg.schema.name === aggregation.schema.name) ? ++count : count; - }, 0); - - // make sure the the number of these aggs is above the min - return metricCount > aggregation.schema.min; - }; - - function calcAggIsTooLow() { - if (!$scope.agg.schema.mustBeFirst) { - return false; - } - - const firstDifferentSchema = _.findIndex($scope.group, function (agg) { - return agg.schema !== $scope.agg.schema; - }); - - if (firstDifferentSchema === -1) { - return false; - } - - return $scope.$index > firstDifferentSchema; - } - - // The model can become touched either onBlur event or when the form is submitted. - // We watch $touched to identify when the form is submitted. - $scope.$watch(() => { - return ngModelCtrl.$touched; - }, (value) => { - $scope.formIsTouched = value; - }, true); - - $scope.onAggTypeChange = (agg, value) => { - if (agg.type !== value) { - agg.type = value; - } - }; - - $scope.onAggParamsChange = (params, paramName, value) => { - if (params[paramName] !== value) { - params[paramName] = value; - } - }; - - $scope.setValidity = (isValid) => { - ngModelCtrl.$setValidity(`aggParams${$scope.agg.id}`, isValid); - }; - - $scope.setTouched = (isTouched) => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - } - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.html b/src/legacy/ui/public/vis/editors/default/agg_group.html deleted file mode 100644 index b703d23b4f149..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg_group.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- {{ groupNameLabel }} -
- -
-
- - -
-
-
- - - -
-
- -
diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js index 054f4e086b912..e357da8156b9f 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_group.js +++ b/src/legacy/ui/public/vis/editors/default/agg_group.js @@ -17,79 +17,80 @@ * under the License. */ -import _ from 'lodash'; -import './agg'; -import './agg_add'; - +import 'ngreact'; +import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from '../../../modules'; -import aggGroupTemplate from './agg_group.html'; -import { move } from '../../../utils/collection'; -import { aggGroupNameMaps } from './agg_group_names'; -import { AggConfig } from '../../agg_config'; - -import '../../draggable/draggable_container'; -import '../../draggable/draggable_item'; -import '../../draggable/draggable_handle'; +import { DefaultEditorAggGroup } from './components/default_editor_agg_group'; uiModules .get('app/visualize') + .directive('visEditorAggGroupWrapper', reactDirective => + reactDirective(wrapInI18nContext(DefaultEditorAggGroup), [ + ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects + ['schemas', { watchDepth: 'collection' }], + ['state', { watchDepth: 'reference' }], + ['addSchema', { watchDepth: 'reference' }], + ['onAggParamsChange', { watchDepth: 'reference' }], + ['onAggTypeChange', { watchDepth: 'reference' }], + ['onToggleEnableAgg', { watchDepth: 'reference' }], + ['removeAgg', { watchDepth: 'reference' }], + ['reorderAggs', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], + ['setValidity', { watchDepth: 'reference' }], + 'groupName', + 'formIsTouched', + 'lastParentPipelineAggTitle', + ]) + ) .directive('visEditorAggGroup', function () { - return { restrict: 'E', - template: aggGroupTemplate, scope: true, - link: function ($scope, $el, attr) { + require: '?^ngModel', + template: function () { + return ``; + }, + link: function ($scope, $el, attr, ngModelCtrl) { $scope.groupName = attr.groupName; - $scope.groupNameLabel = aggGroupNameMaps()[$scope.groupName]; - $scope.$bind('group', 'state.aggs.bySchemaGroup["' + $scope.groupName + '"]'); - $scope.$bind('schemas', 'vis.type.schemas["' + $scope.groupName + '"]'); - - $scope.$watchMulti([ - 'schemas', - '[]group' - ], function () { - const stats = $scope.stats = { - min: 0, - max: 0, - count: $scope.group ? $scope.group.length : 0 - }; - - if (!$scope.schemas) return; + $scope.$bind('schemas', attr.schemas); + // The model can become touched either onBlur event or when the form is submitted. + // We also watch $touched to identify when the form is submitted. + $scope.$watch( + () => { + return ngModelCtrl.$touched; + }, + value => { + $scope.formIsTouched = value; + } + ); - $scope.schemas.forEach(function (schema) { - stats.min += schema.min; - stats.max += schema.max; - stats.deprecate = schema.deprecate; - }); - }); - - function reorderFinished() { - //the aggs have been reordered in [group] and we need - //to apply that ordering to [vis.aggs] - const indexOffset = $scope.state.aggs.indexOf($scope.group[0]); - _.forEach($scope.group, (agg, index) => { - move($scope.state.aggs, agg, indexOffset + index); - }); - } - - $scope.$on('agg-reorder', reorderFinished); - $scope.$on('agg-drag-start', () => $scope.dragging = true); - $scope.$on('agg-drag-end', () => { - $scope.dragging = false; - reorderFinished(); - }); - - $scope.addSchema = function (schema) { - const aggConfig = new AggConfig($scope.state.aggs, { - schema, - id: AggConfig.nextId($scope.state.aggs), - }); - aggConfig.brandNew = true; + $scope.setValidity = isValid => { + ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); + }; - $scope.state.aggs.push(aggConfig); + $scope.setTouched = isTouched => { + if (isTouched) { + ngModelCtrl.$setTouched(); + } else { + ngModelCtrl.$setUntouched(); + } }; - } + }, }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/agg_group_names.js b/src/legacy/ui/public/vis/editors/default/agg_group_names.js deleted file mode 100644 index e67f0459ff764..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg_group_names.js +++ /dev/null @@ -1,25 +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 { i18n } from '@kbn/i18n'; - -export const aggGroupNameMaps = () => ({ - metrics: i18n.translate('common.ui.vis.editors.aggGroups.metricsText', { defaultMessage: 'metrics' }), - buckets: i18n.translate('common.ui.vis.editors.aggGroups.bucketsText', { defaultMessage: 'buckets' }) -}); diff --git a/src/legacy/ui/public/vis/editors/default/agg_groups.ts b/src/legacy/ui/public/vis/editors/default/agg_groups.ts index 9bfa99f8d4c94..f55e6ecd79155 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_groups.ts +++ b/src/legacy/ui/public/vis/editors/default/agg_groups.ts @@ -17,7 +17,18 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; + export enum AggGroupNames { Buckets = 'buckets', Metrics = 'metrics', } + +export const aggGroupNamesMap = () => ({ + [AggGroupNames.Metrics]: i18n.translate('common.ui.vis.editors.aggGroups.metricsText', { + defaultMessage: 'Metrics', + }), + [AggGroupNames.Buckets]: i18n.translate('common.ui.vis.editors.aggGroups.bucketsText', { + defaultMessage: 'Buckets', + }), +}); diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.js b/src/legacy/ui/public/vis/editors/default/agg_params.js deleted file mode 100644 index fe942d9eb2272..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg_params.js +++ /dev/null @@ -1,43 +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 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from '../../../modules'; -import { DefaultEditorAggParams } from './components/default_editor_agg_params'; - -uiModules - .get('app/visualize') - .directive('visEditorAggParams', reactDirective => reactDirective(wrapInI18nContext(DefaultEditorAggParams), [ - ['agg', { watchDepth: 'reference' }], - ['aggParams', { watchDepth: 'collection' }], - ['indexPattern', { watchDepth: 'reference' }], - ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects - ['state', { watchDepth: 'reference' }], - ['onAggTypeChange', { watchDepth: 'reference' }], - ['onAggParamsChange', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - 'aggError', - 'aggIndex', - 'disabledParams', - 'groupName', - 'aggIsTooLow', - 'formIsTouched', - ])); diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_params.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_params.test.tsx.snap index b4d796443b554..018fe0b7dbd3c 100644 --- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_params.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_params.test.tsx.snap @@ -46,39 +46,43 @@ exports[`DefaultEditorAggParams component should init with the default set of pa } } /> - - - + + - - + /> + + `; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx new file mode 100644 index 0000000000000..7d6bc34ac06c9 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg.tsx @@ -0,0 +1,263 @@ +/* + * 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, { useState, useEffect } from 'react'; +import { + EuiAccordion, + EuiToolTip, + EuiButtonIcon, + EuiSpacer, + EuiIconTip, + Color, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { AggConfig } from '../../../'; +import { DefaultEditorAggParams } from './default_editor_agg_params'; +import { DefaultEditorAggCommonProps } from './default_editor_agg_common_props'; + +interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { + agg: AggConfig; + aggIndex: number; + aggIsTooLow: boolean; + dragHandleProps: {} | null; + isDraggable: boolean; + isLastBucket: boolean; + isRemovable: boolean; +} + +function DefaultEditorAgg({ + agg, + aggIndex, + aggIsTooLow, + dragHandleProps, + formIsTouched, + groupName, + isDraggable, + isLastBucket, + isRemovable, + metricAggs, + lastParentPipelineAggTitle, + state, + onAggParamsChange, + onAggTypeChange, + onToggleEnableAgg, + removeAgg, + setTouched, + setValidity, +}: DefaultEditorAggProps) { + const [isEditorOpen, setIsEditorOpen] = useState(agg.brandNew); + const [validState, setValidState] = useState(true); + const showDescription = !isEditorOpen && validState; + const showError = !isEditorOpen && !validState; + let disabledParams; + let aggError; + // When a Parent Pipeline agg is selected and this agg is the last bucket. + const isLastBucketAgg = isLastBucket && lastParentPipelineAggTitle && agg.type; + + const SchemaComponent = agg.schema.editorComponent; + + if (isLastBucketAgg) { + if (['date_histogram', 'histogram'].includes(agg.type.name)) { + disabledParams = ['min_doc_count']; + } else { + aggError = i18n.translate('common.ui.aggTypes.metrics.wrongLastBucketTypeErrorMessage', { + defaultMessage: + 'Last bucket aggregation must be "Date Histogram" or "Histogram" when using "{type}" metric aggregation.', + values: { type: lastParentPipelineAggTitle }, + description: 'Date Histogram and Histogram should not be translated', + }); + } + } + + useEffect(() => { + if (isLastBucketAgg && ['date_histogram', 'histogram'].includes(agg.type.name)) { + onAggParamsChange( + agg.params, + 'min_doc_count', + // "histogram" agg has an editor for "min_doc_count" param, which accepts boolean + // "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value + agg.type.name === 'histogram' ? true : 0 + ); + } + }, [lastParentPipelineAggTitle, isLastBucket, agg.type]); + + // A description of the aggregation, for displaying in the collapsed agg header + const aggDescription = agg.type && agg.type.makeLabel ? agg.type.makeLabel(agg) : ''; + + const onToggle = (isOpen: boolean) => { + setIsEditorOpen(isOpen); + if (!isOpen) { + setTouched(true); + } + }; + + const onSetValidity = (isValid: boolean) => { + setValidity(isValid); + setValidState(isValid); + }; + + const renderAggButtons = () => { + const actionIcons = []; + + if (showError) { + actionIcons.push({ + id: 'hasErrors', + color: 'danger', + type: 'alert', + tooltip: i18n.translate('common.ui.vis.editors.agg.errorsAriaLabel', { + defaultMessage: 'Aggregation has errors', + }), + dataTestSubj: 'hasErrorsAggregationIcon', + }); + } + + if (agg.enabled && isRemovable) { + actionIcons.push({ + id: 'disableAggregation', + color: 'text', + type: 'eye', + onClick: () => onToggleEnableAgg(agg, false), + tooltip: i18n.translate('common.ui.vis.editors.agg.disableAggButtonTooltip', { + defaultMessage: 'Disable aggregation', + }), + dataTestSubj: 'toggleDisableAggregationBtn', + }); + } + if (!agg.enabled) { + actionIcons.push({ + id: 'enableAggregation', + color: 'text', + type: 'eyeClosed', + onClick: () => onToggleEnableAgg(agg, true), + tooltip: i18n.translate('common.ui.vis.editors.agg.enableAggButtonTooltip', { + defaultMessage: 'Enable aggregation', + }), + dataTestSubj: 'toggleDisableAggregationBtn', + }); + } + if (isDraggable) { + actionIcons.push({ + id: 'dragHandle', + type: 'grab', + tooltip: i18n.translate('common.ui.vis.editors.agg.modifyPriorityButtonTooltip', { + defaultMessage: 'Modify priority by dragging', + }), + dataTestSubj: 'dragHandleBtn', + }); + } + if (isRemovable) { + actionIcons.push({ + id: 'removeDimension', + color: 'danger', + type: 'cross', + onClick: () => removeAgg(agg), + tooltip: i18n.translate('common.ui.vis.editors.agg.removeDimensionButtonTooltip', { + defaultMessage: 'Remove dimension', + }), + dataTestSubj: 'removeDimensionBtn', + }); + } + return ( +
+ {actionIcons.map(icon => { + if (icon.id === 'dragHandle') { + return ( + + ); + } + + return ( + + + + ); + })} +
+ ); + }; + + const buttonContent = ( + <> + {agg.schema.title} {showDescription && {aggDescription}} + + ); + + return ( + + <> + + {SchemaComponent && ( + + )} + + + + ); +} + +export { DefaultEditorAgg }; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_add.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_add.tsx index 1d80d330e893e..f07b363d355b2 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_add.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_add.tsx @@ -39,9 +39,7 @@ interface DefaultEditorAggAddProps { schemas: Schema[]; stats: { max: number; - min: number; count: number; - deprecate: boolean; }; addSchema(schema: Schema): void; } @@ -80,7 +78,7 @@ function DefaultEditorAggAdd({ return count >= schema.max; }; - return stats.max > stats.count ? ( + return ( setIsPopoverOpen(false)} > - {(groupName !== AggGroupNames.Buckets || (!stats.count && !stats.deprecate)) && ( + {(groupName !== AggGroupNames.Buckets || !stats.count) && ( )} - {groupName === AggGroupNames.Buckets && stats.count > 0 && !stats.deprecate && ( + {groupName === AggGroupNames.Buckets && stats.count > 0 && ( - !schema.deprecate && ( - onSelectSchema(schema)} - > - {schema.title} - - ) - )} + items={schemas.map(schema => ( + onSelectSchema(schema)} + > + {schema.title} + + ))} /> - ) : null; + ); } export { DefaultEditorAggAdd }; diff --git a/src/legacy/ui/public/vis/editors/default/agg_add.js b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_common_props.ts similarity index 51% rename from src/legacy/ui/public/vis/editors/default/agg_add.js rename to src/legacy/ui/public/vis/editors/default/components/default_editor_agg_common_props.ts index a60e9146b01a2..14ea0a8083e5e 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_add.js +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_common_props.ts @@ -17,18 +17,26 @@ * under the License. */ -import { uiModules } from '../../../modules'; -import { DefaultEditorAggAdd } from './components/default_editor_agg_add'; -import { wrapInI18nContext } from 'ui/i18n'; +import { AggType } from 'ui/agg_types'; +import { AggConfig, VisState, AggParams, VisParams } from '../../../'; +import { AggGroupNames } from '../agg_groups'; -uiModules - .get('kibana') - .directive('visEditorAggAdd', reactDirective => - reactDirective(wrapInI18nContext(DefaultEditorAggAdd), [ - ['group', { watchDepth: 'collection' }], - ['schemas', { watchDepth: 'collection' }], - ['stats', { watchDepth: 'reference' }], - 'groupName', - 'addSchema' - ]) - ); +export type OnAggParamsChange = ( + params: AggParams | VisParams, + paramName: string, + value: unknown +) => void; + +export interface DefaultEditorAggCommonProps { + formIsTouched: boolean; + groupName: AggGroupNames; + lastParentPipelineAggTitle?: string; + metricAggs: AggConfig[]; + state: VisState; + onAggParamsChange: OnAggParamsChange; + onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; + onToggleEnableAgg: (agg: AggConfig, isEnable: boolean) => void; + removeAgg: (agg: AggConfig) => void; + setTouched: (isTouched: boolean) => void; + setValidity: (isValid: boolean) => void; +} diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx new file mode 100644 index 0000000000000..4a5f012f8783c --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group.tsx @@ -0,0 +1,193 @@ +/* + * 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, useReducer } from 'react'; +import { + EuiTitle, + EuiDragDropContext, + EuiDroppable, + EuiDraggable, + EuiSpacer, + EuiPanel, +} from '@elastic/eui'; + +import { AggConfig } from '../../../agg_config'; +import { aggGroupNamesMap, AggGroupNames } from '../agg_groups'; +import { DefaultEditorAgg } from './default_editor_agg'; +import { DefaultEditorAggAdd } from './default_editor_agg_add'; +import { DefaultEditorAggCommonProps } from './default_editor_agg_common_props'; +import { + isInvalidAggsTouched, + isAggRemovable, + calcAggIsTooLow, +} from './default_editor_agg_group_helper'; +import { aggGroupReducer, initAggsState, AGGS_ACTION_KEYS } from './default_editor_agg_group_state'; +import { Schema } from '../schemas'; + +interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { + schemas: Schema[]; + addSchema: (schems: Schema) => void; + reorderAggs: (group: AggConfig[]) => void; +} + +function DefaultEditorAggGroup({ + formIsTouched, + groupName, + lastParentPipelineAggTitle, + metricAggs, + state, + schemas = [], + addSchema, + onAggParamsChange, + onAggTypeChange, + onToggleEnableAgg, + removeAgg, + reorderAggs, + setTouched, + setValidity, +}: DefaultEditorAggGroupProps) { + const groupNameLabel = aggGroupNamesMap()[groupName]; + // e.g. buckets can have no aggs + const group: AggConfig[] = state.aggs.bySchemaGroup[groupName] || []; + + const stats = { + max: 0, + count: group.length, + }; + + schemas.forEach((schema: Schema) => { + stats.max += schema.max; + }); + + const [aggsState, setAggsState] = useReducer(aggGroupReducer, group, initAggsState); + + const isGroupValid = Object.values(aggsState).every(item => item.valid); + const isAllAggsTouched = isInvalidAggsTouched(aggsState); + + useEffect(() => { + // when isAllAggsTouched is true, it means that all invalid aggs are touched and we will set ngModel's touched to true + // which indicates that Apply button can be changed to Error button (when all invalid ngModels are touched) + setTouched(isAllAggsTouched); + }, [isAllAggsTouched]); + + useEffect(() => { + // when not all invalid aggs are touched and formIsTouched becomes true, it means that Apply button was clicked. + // and in such case we set touched state to true for all aggs + if (formIsTouched && !isAllAggsTouched) { + Object.keys(aggsState).map(([aggId]) => { + setAggsState({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: true, + aggId: Number(aggId), + }); + }); + } + }, [formIsTouched]); + + useEffect(() => { + setValidity(isGroupValid); + }, [isGroupValid]); + + interface DragDropResultProps { + source: { index: number }; + destination?: { index: number } | null; + } + const onDragEnd = ({ source, destination }: DragDropResultProps) => { + if (source && destination) { + const orderedGroup = Array.from(group); + const [removed] = orderedGroup.splice(source.index, 1); + orderedGroup.splice(destination.index, 0, removed); + + reorderAggs(orderedGroup); + } + }; + + const setTouchedHandler = (aggId: number, touched: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: touched, + aggId, + }); + }; + + const setValidityHandler = (aggId: number, valid: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.VALID, + payload: valid, + aggId, + }); + }; + + return ( + + + +
{groupNameLabel}
+
+ + + <> + {group.map((agg: AggConfig, index: number) => ( + + {provided => ( + 1} + isLastBucket={groupName === AggGroupNames.Buckets && index === group.length - 1} + isRemovable={isAggRemovable(agg, group)} + lastParentPipelineAggTitle={lastParentPipelineAggTitle} + metricAggs={metricAggs} + state={state} + onAggParamsChange={onAggParamsChange} + onAggTypeChange={onAggTypeChange} + onToggleEnableAgg={onToggleEnableAgg} + removeAgg={removeAgg} + setTouched={isTouched => setTouchedHandler(agg.id, isTouched)} + setValidity={isValid => setValidityHandler(agg.id, isValid)} + /> + )} + + ))} + + + {stats.max > stats.count && ( + + )} +
+
+ ); +} + +export { DefaultEditorAggGroup }; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.tsx new file mode 100644 index 0000000000000..f4f3964e4927f --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_helper.tsx @@ -0,0 +1,62 @@ +/* + * 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 { findIndex, reduce, isEmpty } from 'lodash'; +import { AggConfig } from '../../../agg_config'; +import { AggsState } from './default_editor_agg_group_state'; + +const isAggRemovable = (agg: AggConfig, group: AggConfig[]) => { + const metricCount = reduce( + group, + (count, aggregation: AggConfig) => { + return aggregation.schema.name === agg.schema.name ? ++count : count; + }, + 0 + ); + // make sure the the number of these aggs is above the min + return metricCount > agg.schema.min; +}; + +const calcAggIsTooLow = (agg: AggConfig, aggIndex: number, group: AggConfig[]) => { + if (!agg.schema.mustBeFirst) { + return false; + } + + const firstDifferentSchema = findIndex(group, (aggr: AggConfig) => { + return aggr.schema !== agg.schema; + }); + + if (firstDifferentSchema === -1) { + return false; + } + + return aggIndex > firstDifferentSchema; +}; + +function isInvalidAggsTouched(aggsState: AggsState) { + const invalidAggs = Object.values(aggsState).filter(agg => !agg.valid); + + if (isEmpty(invalidAggs)) { + return false; + } + + return invalidAggs.every(agg => agg.touched); +} + +export { isAggRemovable, calcAggIsTooLow, isInvalidAggsTouched }; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_state.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_state.tsx new file mode 100644 index 0000000000000..cba7f09a2be0f --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_group_state.tsx @@ -0,0 +1,62 @@ +/* + * 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 { AggConfig } from '../../../agg_config'; + +export enum AGGS_ACTION_KEYS { + TOUCHED = 'aggsTouched', + VALID = 'aggsValid', +} + +interface AggsItem { + touched: boolean; + valid: boolean; +} + +export interface AggsState { + [aggId: number]: AggsItem; +} + +interface AggsAction { + type: AGGS_ACTION_KEYS; + payload: boolean; + aggId: number; + newState?: AggsState; +} + +function aggGroupReducer(state: AggsState, action: AggsAction): AggsState { + const aggState = state[action.aggId] || { touched: false, valid: true }; + switch (action.type) { + case AGGS_ACTION_KEYS.TOUCHED: + return { ...state, [action.aggId]: { ...aggState, touched: action.payload } }; + case AGGS_ACTION_KEYS.VALID: + return { ...state, [action.aggId]: { ...aggState, valid: action.payload } }; + default: + throw new Error(); + } +} + +function initAggsState(group: AggConfig[]): AggsState { + return group.reduce((state, agg) => { + state[agg.id] = { touched: false, valid: true }; + return state; + }, {}); +} + +export { aggGroupReducer, initAggsState }; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_param.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_param.tsx index e2f2e5b90acbb..0e7fdf2cc37c8 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_param.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_param.tsx @@ -19,12 +19,12 @@ import React, { useEffect } from 'react'; -import { AggParams } from '../agg_params'; import { AggParamEditorProps, AggParamCommonProps } from './default_editor_agg_param_props'; +import { OnAggParamsChange } from './default_editor_agg_common_props'; interface DefaultEditorAggParamProps extends AggParamCommonProps { paramEditor: React.ComponentType>; - onChange(aggParams: AggParams, paramName: string, value?: T): void; + onChange: OnAggParamsChange; } function DefaultEditorAggParam(props: DefaultEditorAggParamProps) { diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params.tsx index a5055170e411c..ecd6c82f26d6b 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params.tsx @@ -18,11 +18,11 @@ */ import React, { useReducer, useEffect } from 'react'; -import { EuiForm, EuiAccordion, EuiSpacer } from '@elastic/eui'; +import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { aggTypes, AggType, AggParam } from 'ui/agg_types'; -import { AggConfig, VisState, AggParams } from 'ui/vis'; +import { AggConfig, VisState } from 'ui/vis'; import { IndexPattern } from 'ui/index_patterns'; import { DefaultEditorAggSelect } from './default_editor_agg_select'; import { DefaultEditorAggParam } from './default_editor_agg_param'; @@ -47,6 +47,7 @@ import { FixedParam, TimeIntervalParam, EditorParamConfig } from '../../config/t // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { useUnmount } from '../../../../../../../plugins/kibana_react/public/util/use_unmount'; import { AggGroupNames } from '../agg_groups'; +import { OnAggParamsChange } from './default_editor_agg_common_props'; const FIXED_VALUE_PROP = 'fixedValue'; const DEFAULT_PROP = 'default'; @@ -55,12 +56,12 @@ type EditorParamConfigType = EditorParamConfig & { }; export interface SubAggParamsProp { formIsTouched: boolean; - onAggParamsChange: (agg: AggParams, paramName: string, value: unknown) => void; + onAggParamsChange: OnAggParamsChange; onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; } export interface DefaultEditorAggParamsProps extends SubAggParamsProp { agg: AggConfig; - aggError?: string | null; + aggError?: string; aggIndex?: number; aggIsTooLow?: boolean; className?: string; @@ -227,7 +228,7 @@ function DefaultEditorAggParams({ })} {params.advanced.length ? ( - <> + - + {params.advanced.map((param: ParamInstance) => { const model = paramsState[param.aggParam.name] || { touched: false, @@ -247,8 +247,7 @@ function DefaultEditorAggParams({ return renderParam(param, model); })} - - + ) : null} ); diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.test.ts index 786ec688bd2bd..285a694f55b7c 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.test.ts +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.test.ts @@ -172,12 +172,6 @@ describe('DefaultEditorAggParams helpers', () => { expect(errors).toEqual(['"Split series" aggs must run before all other buckets!']); }); - - it('should push an error if a schema is deprecated', () => { - const errors = getError({ schema: { title: 'Split series', deprecate: true } }, false); - - expect(errors).toEqual(['"Split series" has been deprecated.']); - }); }); describe('getAggTypeOptions', () => { diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.ts b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.ts index 5c8acac4e56b3..1e09bdf694fa0 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.ts +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.ts @@ -108,16 +108,6 @@ function getError(agg: AggConfig, aggIsTooLow: boolean) { }) ); } - if (agg.schema.deprecate) { - errors.push( - agg.schema.deprecateMessage - ? agg.schema.deprecateMessage - : i18n.translate('common.ui.vis.editors.aggParams.errors.schemaIsDeprecatedErrorMessage', { - defaultMessage: '"{schema}" has been deprecated.', - values: { schema: agg.schema.title }, - }) - ); - } return errors; } diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx index 536bcdb7891ef..a853f8fa15773 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx @@ -19,7 +19,7 @@ import { get, has } from 'lodash'; import React, { useEffect } from 'react'; -import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink } from '@elastic/eui'; +import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AggType } from 'ui/agg_types'; @@ -28,7 +28,7 @@ import { documentationLinks } from '../../../../documentation_links/documentatio import { ComboBoxGroupedOption } from '../default_editor_utils'; interface DefaultEditorAggSelectProps { - aggError?: string | null; + aggError?: string; aggTypeOptions: AggType[]; id: string; indexPattern: IndexPattern; @@ -72,17 +72,14 @@ function DefaultEditorAggSelect({ } const helpLink = value && aggHelpLink && ( - - + + + + ); diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_controls.js b/src/legacy/ui/public/vis/editors/default/controls/agg_controls.js deleted file mode 100644 index 860c33bc99400..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_controls.js +++ /dev/null @@ -1,31 +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 { 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' - ])); diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index 6fdbbd1d150ab..da8ab50b7805c 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -18,6 +18,7 @@ */ import 'ui/angular-bootstrap'; +import './fancy_forms'; import './sidebar'; import { i18n } from '@kbn/i18n'; import './vis_options'; diff --git a/src/legacy/ui/public/vis/editors/default/__tests__/default_editor_utils.test.tsx b/src/legacy/ui/public/vis/editors/default/default_editor_utils.test.tsx similarity index 97% rename from src/legacy/ui/public/vis/editors/default/__tests__/default_editor_utils.test.tsx rename to src/legacy/ui/public/vis/editors/default/default_editor_utils.test.tsx index a028ea9701e49..3c0a2873c7484 100644 --- a/src/legacy/ui/public/vis/editors/default/__tests__/default_editor_utils.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/default_editor_utils.test.tsx @@ -17,8 +17,8 @@ * under the License. */ -import { groupAggregationsBy } from '../default_editor_utils'; -import { AggGroupNames } from '../agg_groups'; +import { groupAggregationsBy } from './default_editor_utils'; +import { AggGroupNames } from './agg_groups'; const aggs = [ { diff --git a/src/legacy/ui/public/vis/editors/default/keyboard_move.js b/src/legacy/ui/public/vis/editors/default/keyboard_move.js deleted file mode 100644 index 9f5b1eefa0ceb..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/keyboard_move.js +++ /dev/null @@ -1,75 +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. - */ - -/** - * The keyboardMove directive can be attached to elements, that can receive keydown events. - * It will call the passed callback function and pass the direction in which an - * arrow key was pressed to the callback (as the argument with the name `direction`). - * The passed value will be one of `Direction.up` or `Direction.down`, which can be - * imported to compare against those values. The directive will also make sure, that - * the pressed button will get the focus back (e.g. if it was lost due to a ng-repeat - * reordering). - * - * Usage example: - * - * - * - * import { Direction } from './keyboard_move'; - * function onMoved(dir) { - * if (dir === Direction.up) { - * // moved up - * } else if (dir === Direction.down) { - * // moved down - * } - * } - */ -import { uiModules } from '../../../modules'; -import { keyCodes } from '@elastic/eui'; - -export const Direction = { - up: 'up', - down: 'down' -}; - -const directionMapping = { - [keyCodes.UP]: Direction.up, - [keyCodes.DOWN]: Direction.down -}; - -uiModules.get('kibana') - .directive('keyboardMove', ($parse, $timeout) => ({ - restrict: 'A', - link(scope, el, attr) { - const callbackFn = $parse(attr.keyboardMove); - el.keydown((ev) => { - if (ev.which in directionMapping) { - ev.preventDefault(); - const direction = directionMapping[ev.which]; - scope.$apply(() => callbackFn(scope, { direction })); - // Keep focus on that element, even though it might be attached somewhere - // else in the DOM (e.g. because it has a new position in an ng-repeat). - $timeout(() => el.focus()); - } - }); - - scope.$on('$destroy', () => { - el.off('keydown'); - }); - } - })); diff --git a/src/legacy/ui/public/vis/editors/default/schemas.d.ts b/src/legacy/ui/public/vis/editors/default/schemas.d.ts index ae4aae9fe1061..faf6120cfb893 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.d.ts +++ b/src/legacy/ui/public/vis/editors/default/schemas.d.ts @@ -22,7 +22,6 @@ import { AggGroupNames } from './agg_groups'; export interface Schema { aggFilter: string | string[]; - deprecate: boolean; editor: boolean | string; group: AggGroupNames; max: number; diff --git a/src/legacy/ui/public/vis/editors/default/schemas.js b/src/legacy/ui/public/vis/editors/default/schemas.js index 4c3da3bb336ca..313fdfd19a284 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.js +++ b/src/legacy/ui/public/vis/editors/default/schemas.js @@ -51,7 +51,6 @@ class Schemas { aggFilter: '*', editor: false, params: [], - deprecate: false }); // convert the params into a params registry diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.html b/src/legacy/ui/public/vis/editors/default/sidebar.html index 5b54352e21b4d..14e9bd46c376d 100644 --- a/src/legacy/ui/public/vis/editors/default/sidebar.html +++ b/src/legacy/ui/public/vis/editors/default/sidebar.html @@ -151,10 +151,24 @@
- - + +
- +
diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js index e21987b3dd4fc..30e42d1957698 100644 --- a/src/legacy/ui/public/vis/editors/default/sidebar.js +++ b/src/legacy/ui/public/vis/editors/default/sidebar.js @@ -23,36 +23,79 @@ import './vis_options'; import 'ui/directives/css_truncate'; import { uiModules } from '../../../modules'; import sidebarTemplate from './sidebar.html'; +import { move } from '../../../utils/collection'; +import { AggConfig } from '../../agg_config'; -uiModules - .get('app/visualize') - .directive('visEditorSidebar', function () { - return { - restrict: 'E', - template: sidebarTemplate, - scope: true, - controllerAs: 'sidebar', - controller: function ($scope) { - $scope.$watch('vis.type', (visType) => { - if (visType) { - this.showData = visType.schemas.buckets || visType.schemas.metrics; - if (_.has(visType, 'editorConfig.optionTabs')) { - const activeTabs = visType.editorConfig.optionTabs.filter((tab) => { - return _.get(tab, 'active', false); - }); - if (activeTabs.length > 0) { - this.section = activeTabs[0].name; - } +uiModules.get('app/visualize').directive('visEditorSidebar', function () { + return { + restrict: 'E', + template: sidebarTemplate, + scope: true, + require: '?^ngModel', + controllerAs: 'sidebar', + controller: function ($scope) { + $scope.$watch('vis.type', visType => { + if (visType) { + this.showData = visType.schemas.buckets || visType.schemas.metrics; + if (_.has(visType, 'editorConfig.optionTabs')) { + const activeTabs = visType.editorConfig.optionTabs.filter(tab => { + return _.get(tab, 'active', false); + }); + if (activeTabs.length > 0) { + this.section = activeTabs[0].name; } - this.section = this.section || (this.showData ? 'data' : _.get(visType, 'editorConfig.optionTabs[0].name')); } + this.section = + this.section || + (this.showData ? 'data' : _.get(visType, 'editorConfig.optionTabs[0].name')); + } + }); + + $scope.onAggTypeChange = (agg, value) => { + if (agg.type !== value) { + agg.type = value; + } + }; + + $scope.onAggParamsChange = (params, paramName, value) => { + if (params[paramName] !== value) { + params[paramName] = value; + } + }; + + $scope.addSchema = function (schema) { + const aggConfig = new AggConfig($scope.state.aggs, { + schema, + id: AggConfig.nextId($scope.state.aggs), }); + aggConfig.brandNew = true; - $scope.onAggParamsChange = (params, paramName, value) => { - if (params[paramName] !== value) { - params[paramName] = value; - } - }; - } - }; - }); + $scope.state.aggs.push(aggConfig); + }; + + $scope.removeAgg = function (agg) { + const aggs = $scope.state.aggs; + const index = aggs.indexOf(agg); + + if (index === -1) { + return; + } + + aggs.splice(index, 1); + }; + + $scope.onToggleEnableAgg = (agg, isEnable) => { + agg.enabled = isEnable; + }; + + $scope.reorderAggs = (group) => { + //the aggs have been reordered in [group] and we need + //to apply that ordering to [vis.aggs] + const indexOffset = $scope.state.aggs.indexOf(group[0]); + _.forEach(group, (agg, index) => { + move($scope.state.aggs, agg, indexOffset + index); + }); + }; + }, + }; +}); diff --git a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts index f2475065a869c..f6768007503c5 100644 --- a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts +++ b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts @@ -24,12 +24,12 @@ import { SearchSource } from '../../courier'; import { QueryFilter } from '../../filter_manager/query_filter'; import { Adapters } from '../../inspector/types'; import { PersistedState } from '../../persisted_state'; -import { AggConfigs } from '../agg_configs'; +import { AggConfig } from '../agg_config'; import { Vis } from '../vis'; export interface RequestHandlerParams { searchSource: SearchSource; - aggs: AggConfigs; + aggs: AggConfig[]; timeRange?: TimeRange; query?: Query; filters?: Filter[]; diff --git a/src/legacy/ui/public/vis/vis.d.ts b/src/legacy/ui/public/vis/vis.d.ts index 9e6107ed7594f..22881a6fda18a 100644 --- a/src/legacy/ui/public/vis/vis.d.ts +++ b/src/legacy/ui/public/vis/vis.d.ts @@ -18,6 +18,7 @@ */ import { VisType } from './vis_types/vis_type'; +import { AggConfigs } from './agg_configs'; export interface Vis { type: VisType; @@ -39,5 +40,5 @@ export interface VisState { title: string; type: VisType; params: VisParams; - aggs: any[]; + aggs: AggConfigs; } diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts index b56a63c4c0928..4e41dd54855ea 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts @@ -20,7 +20,7 @@ import { mockDataLoaderFetch, timefilter } from './embedded_visualize_handler.te // @ts-ignore import MockState from '../../../../../fixtures/mock_state'; -import { RequestHandlerParams, Vis } from '../../vis'; +import { RequestHandlerParams, Vis, AggConfig } from '../../vis'; import { VisResponseData } from './types'; import { Inspector } from '../../inspector'; @@ -49,7 +49,7 @@ describe('EmbeddedVisualizeHandler', () => { jest.clearAllMocks(); dataLoaderParams = { - aggs: [], + aggs: [] as AggConfig[], filters: undefined, forceFetch: false, inspectorAdapters: {}, diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 6b14cabb54f99..a1d5a49f1a602 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -57,7 +57,6 @@ export default function ({ getService, getPageObjects }) { }); it('should show Split Gauges', async function () { - await PageObjects.visualize.clickMetricEditor(); log.debug('Bucket = Split Group'); await PageObjects.visualize.clickBucket('Split group'); log.debug('Aggregation = Terms'); @@ -81,7 +80,6 @@ export default function ({ getService, getPageObjects }) { it('should show correct values for fields with fieldFormatters', async function () { const expectedTexts = [ '2,904', 'win 8: Count', '0B', 'win 8: Min bytes' ]; - await PageObjects.visualize.clickMetricEditor(); await PageObjects.visualize.selectAggregation('Terms'); await PageObjects.visualize.selectField('machine.os.raw'); await PageObjects.visualize.setSize('1'); diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index bb75ef6648d7f..237ee1ef50b1e 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -183,7 +183,6 @@ export default function ({ getService, getPageObjects }) { }); it('should allow filtering with buckets', async function () { - await PageObjects.visualize.clickMetricEditor(); log.debug('Bucket = Split Group'); await PageObjects.visualize.clickBucket('Split group'); log.debug('Aggregation = Terms'); diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index c84debcfecbde..11bf93f02e802 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -407,7 +407,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli } async clickMetricEditor() { - await find.clickByCssSelector('button[data-test-subj="toggleEditor"]'); + await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button'); } async clickMetricByIndex(index) { @@ -450,8 +450,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async selectAggregation(myString, groupName = 'buckets', childAggregationType = null) { const comboBoxElement = await find.byCssSelector(` [group-name="${groupName}"] - vis-editor-agg-params:not(.ng-hide) - [data-test-subj="visAggEditorParams"] + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen ${childAggregationType ? '.visEditorAgg__subAgg' : ''} [data-test-subj="defaultEditorAggSelect"] `); @@ -479,7 +478,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async toggleOpenEditor(index, toState = 'true') { // index, see selectYAxisAggregation - const toggle = await find.byCssSelector(`button[aria-controls="visAggEditorParams${index}"]`); + const toggle = await find.byCssSelector(`button[aria-controls="visEditorAggAccordion${index}"]`); const toggleOpen = await toggle.getAttribute('aria-expanded'); log.debug(`toggle ${index} expand = ${toggleOpen}`); if (toggleOpen !== toState) { @@ -497,12 +496,10 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli // select our agg const aggSelect = await find - .byCssSelector(`[data-test-subj="aggregationEditor${index}"] - vis-editor-agg-params:not(.ng-hide) [data-test-subj="defaultEditorAggSelect"]`); + .byCssSelector(`#visEditorAggAccordion${index} [data-test-subj="defaultEditorAggSelect"]`); await comboBox.setElement(aggSelect, agg); - const fieldSelect = await find.byCssSelector(`[data-test-subj="aggregationEditor${index}"] - vis-editor-agg-params:not(.ng-hide) [data-test-subj="visDefaultEditorField"]`); + const fieldSelect = await find.byCssSelector(`#visEditorAggAccordion${index} [data-test-subj="visDefaultEditorField"]`); // select our field await comboBox.setElement(fieldSelect, field); // enter custom label @@ -553,7 +550,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli log.debug(`selectField ${fieldValue}`); const selector = ` [group-name="${groupName}"] - vis-editor-agg-params:not(.ng-hide) + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen [data-test-subj="visAggEditorParams"] ${childAggregationType ? '.visEditorAgg__subAgg' : ''} [data-test-subj="visDefaultEditorField"] @@ -593,26 +590,26 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli } async setSize(newValue, aggId) { - const dataTestSubj = aggId ? `aggregationEditor${aggId} sizeParamEditor` : 'sizeParamEditor'; + const dataTestSubj = aggId ? `visEditorAggAccordion${aggId} sizeParamEditor` : 'sizeParamEditor'; await testSubjects.setValue(dataTestSubj, String(newValue)); } async toggleDisabledAgg(agg) { - await testSubjects.click(`aggregationEditor${agg} disableAggregationBtn`); + await testSubjects.click(`visEditorAggAccordion${agg} toggleDisableAggregationBtn`); await PageObjects.header.waitUntilLoadingHasFinished(); } async toggleAggregationEditor(agg) { - await testSubjects.click(`aggregationEditor${agg} toggleEditor`); + await find.clickByCssSelector(`[data-test-subj="visEditorAggAccordion${agg}"] .euiAccordion__button`); await PageObjects.header.waitUntilLoadingHasFinished(); } async toggleOtherBucket(agg = 2) { - return await testSubjects.click(`aggregationEditor${agg} otherBucketSwitch`); + return await testSubjects.click(`visEditorAggAccordion${agg} otherBucketSwitch`); } async toggleMissingBucket(agg = 2) { - return await testSubjects.click(`aggregationEditor${agg} missingBucketSwitch`); + return await testSubjects.click(`visEditorAggAccordion${agg} missingBucketSwitch`); } async isApplyEnabled() { @@ -1271,7 +1268,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli } async removeDimension(agg) { - await testSubjects.click(`aggregationEditor${agg} removeDimensionBtn`); + await testSubjects.click(`visEditorAggAccordion${agg} removeDimensionBtn`); } } diff --git a/test/functional/services/inspector.js b/test/functional/services/inspector.js index 67d3c1113103b..9c25ebea48b4f 100644 --- a/test/functional/services/inspector.js +++ b/test/functional/services/inspector.js @@ -85,7 +85,7 @@ export function InspectorProvider({ getService }) { // The buttons for setting table page size are in a popover element. This popover // element appears as if it's part of the inspectorPanel but it's really attached // to the body element by a portal. - const tableSizesPopover = await find.byCssSelector('.euiPanel'); + const tableSizesPopover = await find.byCssSelector('.euiPanel .euiContextMenuPanel'); await find.clickByButtonText(`${size} rows`, tableSizesPopover); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a99104e6fb7e5..459752668b884 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -570,12 +570,9 @@ "common.ui.vis.defaultEditor.aggSelect.subAggregationLabel": "サブ集約", "common.ui.vis.defaultFeedbackMessage": "フィードバックがありますか?{link} で問題を報告してください。", "common.ui.vis.editors.advancedToggle.advancedLinkLabel": "高度な設定", - "common.ui.vis.editors.agg.disableAggButtonAriaLabel": "集約を無効にする", "common.ui.vis.editors.agg.disableAggButtonTooltip": "集約を無効にする", - "common.ui.vis.editors.agg.enableAggButtonAriaLabel": "集約を有効にする", "common.ui.vis.editors.agg.enableAggButtonTooltip": "集約を有効にする", "common.ui.vis.editors.agg.modifyPriorityButtonTooltip": "ドラッグして優先順位を変更します", - "common.ui.vis.editors.agg.removeDimensionButtonAriaLabel": "ディメンションを削除", "common.ui.vis.editors.agg.removeDimensionButtonTooltip": "ディメンションを削除", "common.ui.vis.editors.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", "common.ui.vis.editors.aggAdd.addGroupButtonLabel": "{groupNameLabel} を追加", @@ -583,8 +580,6 @@ "common.ui.vis.editors.aggGroups.bucketsText": "バケット", "common.ui.vis.editors.aggGroups.metricsText": "メトリック", "common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "「{schema}」集約は他のバケットの前に実行する必要があります!", - "common.ui.vis.editors.aggParams.errors.schemaIsDeprecatedErrorMessage": "「{schema}」は廃止されました。", - "common.ui.vis.editors.howToModifyScreenReaderPriorityDescription": "このボタンの上下の矢印キーで、集約の優先順位の上下を変更します。", "common.ui.vis.editors.resizeAriaLabels": "左右のキーでエディターのサイズを変更します", "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "ビジュアライゼーションを変更と共に更新します", "common.ui.vis.editors.sidebar.applyChangesTooltip": "変更を適用", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dbca7c77c6e21..86f107c1c8951 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -570,12 +570,9 @@ "common.ui.vis.defaultEditor.aggSelect.subAggregationLabel": "子聚合", "common.ui.vis.defaultFeedbackMessage": "想反馈?请在“{link}中创建问题。", "common.ui.vis.editors.advancedToggle.advancedLinkLabel": "高级", - "common.ui.vis.editors.agg.disableAggButtonAriaLabel": "禁用聚合", "common.ui.vis.editors.agg.disableAggButtonTooltip": "禁用聚合", - "common.ui.vis.editors.agg.enableAggButtonAriaLabel": "启用聚合", "common.ui.vis.editors.agg.enableAggButtonTooltip": "启用聚合", "common.ui.vis.editors.agg.modifyPriorityButtonTooltip": "通过拖动来修改优先级", - "common.ui.vis.editors.agg.removeDimensionButtonAriaLabel": "删除维度", "common.ui.vis.editors.agg.removeDimensionButtonTooltip": "删除维度", "common.ui.vis.editors.agg.toggleEditorButtonAriaLabel": "切换 {schema} 编辑器", "common.ui.vis.editors.aggAdd.addGroupButtonLabel": "添加{groupNameLabel}", @@ -583,8 +580,6 @@ "common.ui.vis.editors.aggGroups.bucketsText": "存储桶", "common.ui.vis.editors.aggGroups.metricsText": "指标", "common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "“{schema}” 聚合必须在所有其他存储桶之前运行!", - "common.ui.vis.editors.aggParams.errors.schemaIsDeprecatedErrorMessage": "‘’{schema}”已弃用。", - "common.ui.vis.editors.howToModifyScreenReaderPriorityDescription": "使用此按钮上的向上和向下键上移和下移此聚合的优先级顺序。", "common.ui.vis.editors.resizeAriaLabels": "按向左/向右键以调整编辑器的大小", "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "使用您的更改更新可视化", "common.ui.vis.editors.sidebar.applyChangesTooltip": "应用更改", diff --git a/yarn.lock b/yarn.lock index 55bdcb9e4363d..ab5f154bb3a8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6072,11 +6072,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atoa@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/atoa/-/atoa-1.0.0.tgz#0cc0e91a480e738f923ebc103676471779b34a49" - integrity sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk= - atob-lite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" @@ -8853,14 +8848,6 @@ contour_plot@^0.0.1: resolved "https://registry.yarnpkg.com/contour_plot/-/contour_plot-0.0.1.tgz#475870f032b8e338412aa5fc507880f0bf495c77" integrity sha1-R1hw8DK44zhBKqX8UHiA8L9JXHc= -contra@1.9.4: - version "1.9.4" - resolved "https://registry.yarnpkg.com/contra/-/contra-1.9.4.tgz#f53bde42d7e5b5985cae4d99a8d610526de8f28d" - integrity sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0= - dependencies: - atoa "1.0.0" - ticky "1.0.1" - convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -9234,13 +9221,6 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" -crossvent@1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.4.tgz#da2c4f8f40c94782517bf2beec1044148194ab92" - integrity sha1-2ixPj0DJR4JRe/K+7BBEFIGUq5I= - dependencies: - custom-event "1.0.0" - cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -9487,11 +9467,6 @@ custom-event-polyfill@^0.3.0: resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-0.3.0.tgz#99807839be62edb446b645832e0d80ead6fa1888" integrity sha1-mYB4Ob5i7bRGtkWDLg2A6tb6GIg= -custom-event@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.0.tgz#2e4628be19dc4b214b5c02630c5971e811618062" - integrity sha1-LkYovhncSyFLXAJjDFlx6BFhgGI= - custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -10711,14 +10686,6 @@ dragselect@1.8.1: resolved "https://registry.yarnpkg.com/dragselect/-/dragselect-1.8.1.tgz#63f71a6f980f710c87e28b328e175b7afc9e162b" integrity sha512-4YbJCcS6zwK8vMX2GiIX3tUrXFSo9a6xmV2z66EIJ8nj+iMHP1o4j0PeFdf5zjfhqVZJi+6zuVKPZInnrTLMbw== -dragula@3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/dragula/-/dragula-3.7.2.tgz#4a35c9d3981ffac1a949c29ca7285058e87393ce" - integrity sha1-SjXJ05gf+sGpScKcpyhQWOhzk84= - dependencies: - contra "1.9.4" - crossvent "1.5.4" - duplexer2@0.0.2, duplexer2@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" @@ -26958,11 +26925,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" integrity sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E= -ticky@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ticky/-/ticky-1.0.1.tgz#b7cfa71e768f1c9000c497b9151b30947c50e46d" - integrity sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0= - tildify@^1.0.0, tildify@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a"