mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Vis Editor] EUIfication of agg and agg-group directives (#40866)
* Create default_editor_agg.tsx * Create default_editor_agg_group * Apply drag and drop * Remove unused dragula dependency * Remove old mocha tests * Add ts for state * Update functional tests * Update touched condition * Apply styles for accordion button content * Apply truncate for agg description * Remove unused styles * Separate common props * Move aggGroupNamesMap to agg_group.js * Update _sidebar.scss * Pass schemas prop * Prevent scroll bar and add space * Remove unused min from stats * Add OnAggParamsChange type * Show error as an icon * Update background color * Update title size * Remove Schema.deprecate since it's not used
This commit is contained in:
parent
91adbfc88b
commit
85f9ef6433
51 changed files with 1033 additions and 1514 deletions
|
@ -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",
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<EuiText size="xs">
|
||||
<EuiLink href={getDocLink('date.dateMath')} target="_blank" rel="noopener">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.acceptedDateFormatsLinkText"
|
||||
defaultMessage="Acceptable date formats"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow compressed>
|
||||
<>
|
||||
<EuiText size="xs">
|
||||
<EuiLink href={getDocLink('date.dateMath')} target="_blank" rel="noopener">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.acceptedDateFormatsLinkText"
|
||||
defaultMessage="Acceptable date formats"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{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;
|
||||
{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 (
|
||||
<Fragment key={id}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldText
|
||||
aria-label={i18n.translate('common.ui.aggTypes.dateRanges.fromColumnLabel', {
|
||||
defaultMessage: 'From',
|
||||
description: 'Beginning of a date range, e.g. *From* 2018-02-26 To 2018-02-28',
|
||||
})}
|
||||
compressed
|
||||
fullWidth={true}
|
||||
isInvalid={areBothEmpty || !validateDateMath(from)}
|
||||
placeholder={FROM_PLACEHOLDER}
|
||||
value={from || ''}
|
||||
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="sortRight" color="subdued" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldText
|
||||
aria-label={i18n.translate('common.ui.aggTypes.dateRanges.toColumnLabel', {
|
||||
defaultMessage: 'To',
|
||||
description: 'End of a date range, e.g. From 2018-02-26 *To* 2018-02-28',
|
||||
})}
|
||||
compressed
|
||||
fullWidth={true}
|
||||
isInvalid={areBothEmpty || !validateDateMath(to)}
|
||||
placeholder={TO_PLACEHOLDER}
|
||||
value={to || ''}
|
||||
onChange={ev => onChangeRange(id, 'to', ev.target.value)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
title={deleteBtnTitle}
|
||||
aria-label={deleteBtnTitle}
|
||||
disabled={value.length === 1}
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => onRemoveRange(id)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldText
|
||||
aria-label={i18n.translate('common.ui.aggTypes.dateRanges.fromColumnLabel', {
|
||||
defaultMessage: 'From',
|
||||
description:
|
||||
'Beginning of a date range, e.g. *From* 2018-02-26 To 2018-02-28',
|
||||
})}
|
||||
compressed
|
||||
fullWidth={true}
|
||||
isInvalid={areBothEmpty || !validateDateMath(from)}
|
||||
placeholder={FROM_PLACEHOLDER}
|
||||
value={from || ''}
|
||||
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="sortRight" color="subdued" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldText
|
||||
aria-label={i18n.translate('common.ui.aggTypes.dateRanges.toColumnLabel', {
|
||||
defaultMessage: 'To',
|
||||
description: 'End of a date range, e.g. From 2018-02-26 *To* 2018-02-28',
|
||||
})}
|
||||
compressed
|
||||
fullWidth={true}
|
||||
isInvalid={areBothEmpty || !validateDateMath(to)}
|
||||
placeholder={TO_PLACEHOLDER}
|
||||
value={to || ''}
|
||||
onChange={ev => onChangeRange(id, 'to', ev.target.value)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
title={deleteBtnTitle}
|
||||
aria-label={deleteBtnTitle}
|
||||
disabled={value.length === 1}
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => onRemoveRange(id)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{hasInvalidRange && (
|
||||
<EuiFormErrorText>
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.errorMessage"
|
||||
defaultMessage="Each range should have at least one valid date."
|
||||
/>
|
||||
</EuiFormErrorText>
|
||||
)}
|
||||
{hasInvalidRange && (
|
||||
<EuiFormErrorText>
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.errorMessage"
|
||||
defaultMessage="Each range should have at least one valid date."
|
||||
/>
|
||||
</EuiFormErrorText>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty iconType="plusInCircleFilled" onClick={onAddRange} size="xs">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.addRangeButtonLabel"
|
||||
defaultMessage="Add range"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty iconType="plusInCircleFilled" onClick={onAddRange} size="xs">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.dateRanges.addRangeButtonLabel"
|
||||
defaultMessage="Add range"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Ra
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{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,
|
||||
},
|
||||
}
|
||||
);
|
||||
<EuiFormRow compressed>
|
||||
<>
|
||||
{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 (
|
||||
<Fragment key={id}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={i18n.translate('common.ui.aggTypes.ranges.fromLabel', {
|
||||
defaultMessage: 'From',
|
||||
})}
|
||||
value={isEmpty(from) ? '' : from}
|
||||
placeholder={FROM_PLACEHOLDER}
|
||||
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
|
||||
fullWidth={true}
|
||||
compressed={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="sortRight" color="subdued" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={i18n.translate('common.ui.aggTypes.ranges.toLabel', {
|
||||
defaultMessage: 'To',
|
||||
})}
|
||||
value={isEmpty(to) ? '' : to}
|
||||
placeholder={TO_PLACEHOLDER}
|
||||
onChange={ev => onChangeRange(id, 'to', ev.target.value)}
|
||||
fullWidth={true}
|
||||
compressed={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
title={deleteBtnTitle}
|
||||
aria-label={deleteBtnTitle}
|
||||
disabled={value.length === 1}
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => onRemoveRange(id)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={i18n.translate('common.ui.aggTypes.ranges.fromLabel', {
|
||||
defaultMessage: 'From',
|
||||
})}
|
||||
value={isEmpty(from) ? '' : from}
|
||||
placeholder={FROM_PLACEHOLDER}
|
||||
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
|
||||
fullWidth={true}
|
||||
compressed={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="sortRight" color="subdued" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldNumber
|
||||
aria-label={i18n.translate('common.ui.aggTypes.ranges.toLabel', {
|
||||
defaultMessage: 'To',
|
||||
})}
|
||||
value={isEmpty(to) ? '' : to}
|
||||
placeholder={TO_PLACEHOLDER}
|
||||
onChange={ev => onChangeRange(id, 'to', ev.target.value)}
|
||||
fullWidth={true}
|
||||
compressed={true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
title={deleteBtnTitle}
|
||||
aria-label={deleteBtnTitle}
|
||||
disabled={value.length === 1}
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => onRemoveRange(id)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty iconType="plusInCircleFilled" onClick={onAddRange} size="xs">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.ranges.addRangeButtonLabel"
|
||||
defaultMessage="Add range"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty iconType="plusInCircleFilled" onClick={onAddRange} size="xs">
|
||||
<FormattedMessage
|
||||
id="common.ui.aggTypes.ranges.addRangeButtonLabel"
|
||||
defaultMessage="Add range"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
9
src/legacy/ui/public/vis/agg_configs.d.ts
vendored
9
src/legacy/ui/public/vis/agg_configs.d.ts
vendored
|
@ -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<AggConfig> {
|
||||
bySchemaGroup: {
|
||||
[key: string]: AggConfig[];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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(`<div draggable-container="items">`);
|
||||
$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(`<div>`);
|
||||
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(`<div>`);
|
||||
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(`<div draggable-container="items">`);
|
||||
const item = angular.element(`<div draggable-item="items[0]">`);
|
||||
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(`
|
||||
<div draggable-item="items[0]">
|
||||
<div draggable-handle></div>
|
||||
</div>
|
||||
`);
|
||||
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(`
|
||||
<div draggable-item="items[0]">
|
||||
<div draggable-handle></div>
|
||||
</div>
|
||||
`);
|
||||
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(`<div draggable-item="items[0]">`);
|
||||
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(`<div draggable-handle>`);
|
||||
const scope = $rootScope.$new();
|
||||
expect(() => {
|
||||
$compile(handle)(scope);
|
||||
scope.$apply();
|
||||
}).to.throwException(/controller(.+)draggableItem(.+)required/i);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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(
|
||||
'<ng-form ><div vis-editor-agg ng-model="name"></div></ng-form>'
|
||||
);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
|
@ -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('<button keyboard-move="callback(direction)">Test</button>')(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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.visEditorAggSelect__helpLink {
|
||||
@include euiFontSizeXS;
|
||||
}
|
||||
|
||||
.visEditorAggSelect__formRow {
|
||||
margin-bottom: $euiSizeS;
|
||||
}
|
|
@ -10,4 +10,3 @@ $vis-editor-resizer-width: $euiSizeM;
|
|||
// Components
|
||||
@import './agg';
|
||||
@import './agg_params';
|
||||
@import './agg_select';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
<div class="visEditorSidebar__section visEditorSidebar__collapsible visEditorSidebar__collapsible--marginBottom">
|
||||
<!-- header -->
|
||||
<div class="visEditorSidebar__collapsibleTitle">
|
||||
|
||||
<!-- open/close editor -->
|
||||
<button
|
||||
aria-label="{{::'common.ui.vis.editors.agg.toggleEditorButtonAriaLabel' | i18n: { defaultMessage: 'Toggle {schema} editor', values: { schema: agg.schema.title } } }}"
|
||||
ng-click="editorOpen = !editorOpen"
|
||||
aria-expanded="{{ !!editorOpen }}"
|
||||
aria-controls="visAggEditorParams{{agg.id}}"
|
||||
type="button"
|
||||
data-test-subj="toggleEditor"
|
||||
class="visEditorSidebar__collapsibleTitleLabel">
|
||||
|
||||
<icon aria-hidden="true" ng-if="editorOpen" type="'arrowDown'" size="'s'"></icon>
|
||||
<icon aria-hidden="true" ng-if="!editorOpen" type="'arrowRight'" size="'s'"></icon>
|
||||
|
||||
<!-- title -->
|
||||
<span class="visEditorSidebar__collapsibleTitleText">
|
||||
{{ agg.schema.title }}
|
||||
</span>
|
||||
|
||||
</button>
|
||||
|
||||
<!-- description -->
|
||||
<span ng-if="!editorOpen && aggForm.softErrorCount() < 1" class="visEditorSidebar__collapsibleTitleDescription" title="{{describe()}}">
|
||||
{{ describe() }}
|
||||
</span>
|
||||
|
||||
<!-- error -->
|
||||
<span
|
||||
ng-if="!editorOpen && aggForm.softErrorCount() > 0"
|
||||
class="visEditorSidebar__collapsibleTitleDescription visEditorSidebar__collapsibleTitleDescription--danger"
|
||||
title="{{::'common.ui.vis.editors.agg.errorsText' | i18n: { defaultMessage: 'Errors' } }}"
|
||||
i18n-id="common.ui.vis.editors.agg.errorsText"
|
||||
i18n-default-message="Errors"
|
||||
>
|
||||
</span>
|
||||
|
||||
<!-- controls !!!actually disabling buttons will break tooltips¡¡¡ -->
|
||||
<div class="visEditorAggHeader__controls kuiButtonGroup kuiButtonGroup--united">
|
||||
<!-- disable aggregation -->
|
||||
<button
|
||||
ng-if="agg.enabled && canRemove(agg)"
|
||||
ng-click="agg.enabled = false"
|
||||
aria-label="{{::'common.ui.vis.editors.agg.disableAggButtonAriaLabel' | i18n: { defaultMessage: 'Disable aggregation' } }}"
|
||||
tooltip="{{::'common.ui.vis.editors.agg.disableAggButtonTooltip' | i18n: { defaultMessage: 'Disable aggregation' } }}"
|
||||
tooltip-append-to-body="true"
|
||||
data-test-subj="disableAggregationBtn"
|
||||
type="button"
|
||||
class="kuiButton kuiButton--basic kuiButton--small">
|
||||
<i aria-hidden="true" class="fa fa-toggle-on"></i>
|
||||
</button>
|
||||
|
||||
<!-- enable aggregation -->
|
||||
<button
|
||||
ng-if="!agg.enabled"
|
||||
ng-click="agg.enabled = true"
|
||||
aria-label="{{::'common.ui.vis.editors.agg.enableAggButtonAriaLabel' | i18n: { defaultMessage: 'Enable aggregation' } }}"
|
||||
tooltip="{{::'common.ui.vis.editors.agg.enableAggButtonTooltip' | i18n: { defaultMessage: 'Enable aggregation' } }}"
|
||||
tooltip-append-to-body="true"
|
||||
data-test-subj="disableAggregationBtn"
|
||||
type="button"
|
||||
class="kuiButton kuiButton--basic kuiButton--small">
|
||||
<i aria-hidden="true" class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
|
||||
<!-- drag handle -->
|
||||
<button
|
||||
draggable-handle
|
||||
ng-if="stats.count > 1"
|
||||
tooltip="{{::'common.ui.vis.editors.agg.modifyPriorityButtonTooltip' | i18n: { defaultMessage: 'Modify Priority by Dragging' } }}"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
keyboard-move="onPriorityReorder(direction)"
|
||||
class="kuiButton kuiButton--basic kuiButton--small">
|
||||
<i aria-hidden="true" class="fa fa-arrows-v"></i>
|
||||
<span class="euiScreenReaderOnly"
|
||||
i18n-id="common.ui.vis.editors.howToModifyScreenReaderPriorityDescription"
|
||||
i18n-default-message="Use up and down key on this button to move this aggregation up and down in the priority order."
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- remove button -->
|
||||
<button
|
||||
data-test-subj="removeDimensionBtn"
|
||||
ng-if="canRemove(agg)"
|
||||
aria-label="{{::'common.ui.vis.editors.agg.removeDimensionButtonAriaLabel' | i18n: { defaultMessage: 'Remove Dimension' } }}"
|
||||
ng-if="stats.count > stats.min"
|
||||
ng-click="remove(agg)"
|
||||
tooltip="{{::'common.ui.vis.editors.agg.removeDimensionButtonTooltip' | i18n: { defaultMessage: 'Remove Dimension' } }}"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="kuiButton kuiButton--basic kuiButton--small">
|
||||
<icon aria-hidden="true" color="'danger'" type="'cross'" size="'m'"></icon>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<vis-agg-control-react-wrapper
|
||||
ng-if="agg.schema.editorComponent"
|
||||
ng-show="editorOpen"
|
||||
agg-params="agg.params"
|
||||
component="agg.schema.editorComponent"
|
||||
editor-state-params="state.params"
|
||||
set-value="onAggParamsChange"
|
||||
/>
|
||||
|
||||
<!-- should be rendered after link function run, i.e. 'onAggTypeChange' is defined -->
|
||||
<vis-editor-agg-params
|
||||
ng-if="onAggTypeChange"
|
||||
ng-show="editorOpen"
|
||||
agg="agg"
|
||||
agg-index="$index"
|
||||
agg-is-too-low="aggIsTooLow"
|
||||
agg-params="agg.params"
|
||||
agg-error="error"
|
||||
disabled-params="disabledParams"
|
||||
editor-config="editorConfig"
|
||||
form-is-touched="formIsTouched"
|
||||
group-name="groupName"
|
||||
index-pattern="vis.indexPattern"
|
||||
metric-aggs="metricAggs"
|
||||
state="state"
|
||||
on-agg-type-change="onAggTypeChange"
|
||||
on-agg-params-change="onAggParamsChange"
|
||||
set-touched="setTouched"
|
||||
set-validity="setValidity">
|
||||
</vis-editor-agg-params>
|
||||
</div>
|
||||
|
||||
<vis-editor-agg-add
|
||||
ng-if="$index + 1 === stats.count"
|
||||
ng-hide="dragging"
|
||||
group="group"
|
||||
group-name="groupName"
|
||||
schemas="schemas"
|
||||
stats="stats"
|
||||
add-schema="addSchema">
|
||||
</vis-editor-agg-add>
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
<div class="visEditorSidebar__section">
|
||||
<div class="visEditorSidebar__sectionTitle">
|
||||
{{ groupNameLabel }}
|
||||
</div>
|
||||
|
||||
<div ng-class="groupName" draggable-container="group">
|
||||
<div ng-repeat="agg in group track by agg.id" data-test-subj="aggregationEditor{{agg.id}}" draggable-item="agg">
|
||||
<!-- agg.html - controls for aggregation -->
|
||||
<ng-form name="aggForm">
|
||||
<div vis-editor-agg ng-model="_internalNgModelState"></div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
<vis-editor-agg-add
|
||||
ng-if="stats.count === 0"
|
||||
group="group"
|
||||
group-name="groupName"
|
||||
schemas="schemas"
|
||||
stats="stats"
|
||||
add-schema="addSchema">
|
||||
</vis-editor-agg-add>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
|
@ -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 `<vis-editor-agg-group-wrapper
|
||||
ng-if="setValidity"
|
||||
form-is-touched="formIsTouched"
|
||||
group-name="groupName"
|
||||
last-parent-pipeline-agg-title="lastParentPipelineAggTitle"
|
||||
metric-aggs="metricAggs"
|
||||
state="state"
|
||||
schemas="schemas"
|
||||
add-schema="addSchema"
|
||||
on-agg-params-change="onAggParamsChange"
|
||||
on-agg-type-change="onAggTypeChange"
|
||||
on-toggle-enable-agg="onToggleEnableAgg"
|
||||
remove-agg="removeAgg"
|
||||
reorder-aggs="reorderAggs"
|
||||
set-validity="setValidity"
|
||||
set-touched="setTouched"
|
||||
></vis-editor-agg-group-wrapper>`;
|
||||
},
|
||||
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.$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.$watchMulti([
|
||||
'schemas',
|
||||
'[]group'
|
||||
], function () {
|
||||
const stats = $scope.stats = {
|
||||
min: 0,
|
||||
max: 0,
|
||||
count: $scope.group ? $scope.group.length : 0
|
||||
};
|
||||
|
||||
if (!$scope.schemas) return;
|
||||
|
||||
$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.state.aggs.push(aggConfig);
|
||||
$scope.setValidity = isValid => {
|
||||
ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setTouched = isTouched => {
|
||||
if (isTouched) {
|
||||
ngModelCtrl.$setTouched();
|
||||
} else {
|
||||
ngModelCtrl.$setUntouched();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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' })
|
||||
});
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
]));
|
|
@ -46,39 +46,43 @@ exports[`DefaultEditorAggParams component should init with the default set of pa
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent="Advanced"
|
||||
id="advancedAccordion"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<DefaultEditorAggParam
|
||||
aggParam={
|
||||
Object {
|
||||
"advanced": true,
|
||||
"name": "json",
|
||||
"type": "json",
|
||||
<EuiAccordion
|
||||
buttonContent="Advanced"
|
||||
id="advancedAccordion"
|
||||
initialIsOpen={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<DefaultEditorAggParam
|
||||
aggParam={
|
||||
Object {
|
||||
"advanced": true,
|
||||
"name": "json",
|
||||
"type": "json",
|
||||
}
|
||||
}
|
||||
}
|
||||
key="jsonundefined"
|
||||
onChange={[MockFunction]}
|
||||
setTouched={[Function]}
|
||||
setValidity={[Function]}
|
||||
showValidation={false}
|
||||
subAggParams={
|
||||
Object {
|
||||
"formIsTouched": false,
|
||||
"onAggParamsChange": [MockFunction],
|
||||
"onAggTypeChange": [MockFunction],
|
||||
key="jsonundefined"
|
||||
onChange={[MockFunction]}
|
||||
setTouched={[Function]}
|
||||
setValidity={[Function]}
|
||||
showValidation={false}
|
||||
subAggParams={
|
||||
Object {
|
||||
"formIsTouched": false,
|
||||
"onAggParamsChange": [MockFunction],
|
||||
"onAggTypeChange": [MockFunction],
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
`;
|
||||
|
|
|
@ -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 (
|
||||
<div {...dragHandleProps}>
|
||||
{actionIcons.map(icon => {
|
||||
if (icon.id === 'dragHandle') {
|
||||
return (
|
||||
<EuiIconTip
|
||||
key={icon.id}
|
||||
type={icon.type}
|
||||
content={icon.tooltip}
|
||||
iconProps={{
|
||||
['aria-label']: icon.tooltip,
|
||||
['data-test-subj']: icon.dataTestSubj,
|
||||
}}
|
||||
position="bottom"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip key={icon.id} position="bottom" content={icon.tooltip}>
|
||||
<EuiButtonIcon
|
||||
iconType={icon.type}
|
||||
color={icon.color as Color}
|
||||
onClick={icon.onClick}
|
||||
aria-label={icon.tooltip}
|
||||
data-test-subj={icon.dataTestSubj}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
{agg.schema.title} {showDescription && <span>{aggDescription}</span>}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id={`visEditorAggAccordion${agg.id}`}
|
||||
initialIsOpen={isEditorOpen}
|
||||
buttonContent={buttonContent}
|
||||
buttonClassName="eui-textTruncate"
|
||||
buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate"
|
||||
className="visEditorSidebar__section visEditorSidebar__collapsible visEditorSidebar__collapsible--marginBottom"
|
||||
aria-label={i18n.translate('common.ui.vis.editors.agg.toggleEditorButtonAriaLabel', {
|
||||
defaultMessage: 'Toggle {schema} editor',
|
||||
values: { schema: agg.schema.title },
|
||||
})}
|
||||
data-test-subj={`visEditorAggAccordion${agg.id}`}
|
||||
extraAction={renderAggButtons()}
|
||||
onToggle={onToggle}
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
{SchemaComponent && (
|
||||
<SchemaComponent
|
||||
aggParams={agg.params}
|
||||
editorStateParams={state.params}
|
||||
setValue={onAggParamsChange}
|
||||
/>
|
||||
)}
|
||||
<DefaultEditorAggParams
|
||||
agg={agg}
|
||||
aggError={aggError}
|
||||
aggIndex={aggIndex}
|
||||
aggIsTooLow={aggIsTooLow}
|
||||
disabledParams={disabledParams}
|
||||
formIsTouched={formIsTouched}
|
||||
groupName={groupName}
|
||||
indexPattern={agg.getIndexPattern()}
|
||||
metricAggs={metricAggs}
|
||||
state={state}
|
||||
onAggParamsChange={onAggParamsChange}
|
||||
onAggTypeChange={onAggTypeChange}
|
||||
setTouched={setTouched}
|
||||
setValidity={onSetValidity}
|
||||
/>
|
||||
</>
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
|
||||
export { DefaultEditorAgg };
|
|
@ -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 (
|
||||
<EuiFlexGroup justifyContent="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
|
@ -92,14 +90,14 @@ function DefaultEditorAggAdd({
|
|||
closePopover={() => setIsPopoverOpen(false)}
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
{(groupName !== AggGroupNames.Buckets || (!stats.count && !stats.deprecate)) && (
|
||||
{(groupName !== AggGroupNames.Buckets || !stats.count) && (
|
||||
<FormattedMessage
|
||||
id="common.ui.vis.editors.aggAdd.addGroupButtonLabel"
|
||||
defaultMessage="Add {groupNameLabel}"
|
||||
values={{ groupNameLabel }}
|
||||
/>
|
||||
)}
|
||||
{groupName === AggGroupNames.Buckets && stats.count > 0 && !stats.deprecate && (
|
||||
{groupName === AggGroupNames.Buckets && stats.count > 0 && (
|
||||
<FormattedMessage
|
||||
id="common.ui.vis.editors.aggAdd.addSubGroupButtonLabel"
|
||||
defaultMessage="Add sub-{groupNameLabel}"
|
||||
|
@ -108,24 +106,21 @@ function DefaultEditorAggAdd({
|
|||
)}
|
||||
</EuiPopoverTitle>
|
||||
<EuiContextMenuPanel
|
||||
items={schemas.map(
|
||||
schema =>
|
||||
!schema.deprecate && (
|
||||
<EuiContextMenuItem
|
||||
key={`${schema.name}_${schema.title}`}
|
||||
data-test-subj={`visEditorAdd_${groupName}_${schema.title}`}
|
||||
disabled={isPopoverOpen && isSchemaDisabled(schema)}
|
||||
onClick={() => onSelectSchema(schema)}
|
||||
>
|
||||
{schema.title}
|
||||
</EuiContextMenuItem>
|
||||
)
|
||||
)}
|
||||
items={schemas.map(schema => (
|
||||
<EuiContextMenuItem
|
||||
key={`${schema.name}_${schema.title}`}
|
||||
data-test-subj={`visEditorAdd_${groupName}_${schema.title}`}
|
||||
disabled={isPopoverOpen && isSchemaDisabled(schema)}
|
||||
onClick={() => onSelectSchema(schema)}
|
||||
>
|
||||
{schema.title}
|
||||
</EuiContextMenuItem>
|
||||
))}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
);
|
||||
}
|
||||
|
||||
export { DefaultEditorAggAdd };
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<EuiDragDropContext onDragEnd={onDragEnd}>
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs">
|
||||
<div>{groupNameLabel}</div>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiDroppable droppableId={`agg_group_dnd_${groupName}`}>
|
||||
<>
|
||||
{group.map((agg: AggConfig, index: number) => (
|
||||
<EuiDraggable
|
||||
key={agg.id}
|
||||
index={index}
|
||||
draggableId={`agg_group_dnd_${groupName}_${agg.id}`}
|
||||
customDragHandle={true}
|
||||
>
|
||||
{provided => (
|
||||
<DefaultEditorAgg
|
||||
agg={agg}
|
||||
aggIndex={index}
|
||||
aggIsTooLow={calcAggIsTooLow(agg, index, group)}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
formIsTouched={aggsState[agg.id] ? aggsState[agg.id].touched : false}
|
||||
groupName={groupName}
|
||||
isDraggable={stats.count > 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)}
|
||||
/>
|
||||
)}
|
||||
</EuiDraggable>
|
||||
))}
|
||||
</>
|
||||
</EuiDroppable>
|
||||
{stats.max > stats.count && (
|
||||
<DefaultEditorAggAdd
|
||||
group={group}
|
||||
groupName={groupName}
|
||||
schemas={schemas}
|
||||
stats={stats}
|
||||
addSchema={addSchema}
|
||||
/>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EuiDragDropContext>
|
||||
);
|
||||
}
|
||||
|
||||
export { DefaultEditorAggGroup };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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<T> extends AggParamCommonProps<T> {
|
||||
paramEditor: React.ComponentType<AggParamEditorProps<T>>;
|
||||
onChange(aggParams: AggParams, paramName: string, value?: T): void;
|
||||
onChange: OnAggParamsChange;
|
||||
}
|
||||
|
||||
function DefaultEditorAggParam<T>(props: DefaultEditorAggParamProps<T>) {
|
||||
|
|
|
@ -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 ? (
|
||||
<>
|
||||
<EuiFormRow>
|
||||
<EuiAccordion
|
||||
id="advancedAccordion"
|
||||
buttonContent={i18n.translate(
|
||||
|
@ -236,9 +237,8 @@ function DefaultEditorAggParams({
|
|||
defaultMessage: 'Advanced',
|
||||
}
|
||||
)}
|
||||
paddingSize="none"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="s" />
|
||||
{params.advanced.map((param: ParamInstance) => {
|
||||
const model = paramsState[param.aggParam.name] || {
|
||||
touched: false,
|
||||
|
@ -247,8 +247,7 @@ function DefaultEditorAggParams({
|
|||
return renderParam(param, model);
|
||||
})}
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
</EuiForm>
|
||||
);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 && (
|
||||
<EuiLink
|
||||
href={aggHelpLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="visEditorAggSelect__helpLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="common.ui.vis.defaultEditor.aggSelect.helpLinkLabel"
|
||||
defaultMessage="{aggTitle} help"
|
||||
values={{ aggTitle: value ? value.title : '' }}
|
||||
/>
|
||||
<EuiLink href={aggHelpLink} target="_blank" rel="noopener">
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="common.ui.vis.defaultEditor.aggSelect.helpLinkLabel"
|
||||
defaultMessage="{aggTitle} help"
|
||||
values={{ aggTitle: value ? value.title : '' }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
|
|
|
@ -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'
|
||||
]));
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import 'ui/angular-bootstrap';
|
||||
import './fancy_forms';
|
||||
import './sidebar';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import './vis_options';
|
||||
|
|
|
@ -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 = [
|
||||
{
|
|
@ -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:
|
||||
*
|
||||
* <button keyboard-move="onMoved(direction)">...</button>
|
||||
*
|
||||
* 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');
|
||||
});
|
||||
}
|
||||
}));
|
|
@ -22,7 +22,6 @@ import { AggGroupNames } from './agg_groups';
|
|||
|
||||
export interface Schema {
|
||||
aggFilter: string | string[];
|
||||
deprecate: boolean;
|
||||
editor: boolean | string;
|
||||
group: AggGroupNames;
|
||||
max: number;
|
||||
|
|
|
@ -51,7 +51,6 @@ class Schemas {
|
|||
aggFilter: '*',
|
||||
editor: false,
|
||||
params: [],
|
||||
deprecate: false
|
||||
});
|
||||
|
||||
// convert the params into a params registry
|
||||
|
|
|
@ -151,10 +151,24 @@
|
|||
|
||||
<div class="visEditorSidebar__config" ng-show="sidebar.section == 'data'">
|
||||
<!-- metrics -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.metrics" data-test-subj="metricsAggGroup" group-name="metrics"></vis-editor-agg-group>
|
||||
|
||||
<vis-editor-agg-group
|
||||
class="visEditorSidebar__aggGroup"
|
||||
ng-if="vis.type.schemas.metrics"
|
||||
group-name="metrics"
|
||||
ng-model="_internalNgModelState"
|
||||
data-test-subj="metricsAggGroup"
|
||||
schemas="vis.type.schemas.metrics"
|
||||
></vis-editor-agg-group>
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
<!-- buckets -->
|
||||
<vis-editor-agg-group ng-if="vis.type.schemas.buckets" data-test-subj="bucketsAggGroup" group-name="buckets"></vis-editor-agg-group>
|
||||
<vis-editor-agg-group
|
||||
class="visEditorSidebar__aggGroup"
|
||||
ng-if="vis.type.schemas.buckets"
|
||||
group-name="buckets"
|
||||
ng-model="_internalNgModelState"
|
||||
data-test-subj="bucketsAggGroup"
|
||||
schemas="vis.type.schemas.buckets"
|
||||
></vis-editor-agg-group>
|
||||
</div>
|
||||
|
||||
<div class="visEditorSidebar__config" ng-repeat="tab in vis.type.editorConfig.optionTabs" ng-show="sidebar.section == tab.name">
|
||||
|
|
|
@ -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.onAggParamsChange = (params, paramName, value) => {
|
||||
if (params[paramName] !== value) {
|
||||
params[paramName] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
$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.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);
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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[];
|
||||
|
|
3
src/legacy/ui/public/vis/vis.d.ts
vendored
3
src/legacy/ui/public/vis/vis.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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": "変更を適用",
|
||||
|
|
|
@ -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": "应用更改",
|
||||
|
|
38
yarn.lock
38
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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue