+
{children}
);
diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx
index fef65a954bd600..c9d7b01f87967b 100644
--- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx
+++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx
@@ -17,14 +17,17 @@
* under the License.
*/
-import React, { Children, ReactNode, useRef, useState } from 'react';
+import React, { Children, ReactNode, useRef, useState, useCallback } from 'react';
+import { keyCodes } from '@elastic/eui';
import { PanelContextProvider } from '../context';
-import { Resizer } from '../components/resizer';
+import { Resizer, ResizerMouseEvent, ResizerKeyDownEvent } from '../components/resizer';
import { PanelRegistry } from '../registry';
export interface Props {
children: ReactNode;
+ className?: string;
+ resizerClassName?: string;
onPanelWidthChange?: (arrayOfPanelWidths: number[]) => any;
}
@@ -37,7 +40,12 @@ const initialState: State = { isDragging: false, currentResizerPos: -1 };
const pxToPercent = (proportion: number, whole: number) => (proportion / whole) * 100;
-export function PanelsContainer({ children, onPanelWidthChange }: Props) {
+export function PanelsContainer({
+ children,
+ className,
+ onPanelWidthChange,
+ resizerClassName,
+}: Props) {
const [firstChild, secondChild] = Children.toArray(children);
const registryRef = useRef(new PanelRegistry());
@@ -48,18 +56,48 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) {
return containerRef.current!.getBoundingClientRect().width;
};
+ const handleMouseDown = useCallback(
+ (event: ResizerMouseEvent) => {
+ setState({
+ ...state,
+ isDragging: true,
+ currentResizerPos: event.clientX,
+ });
+ },
+ [state]
+ );
+
+ const handleKeyDown = useCallback(
+ (ev: ResizerKeyDownEvent) => {
+ const { keyCode } = ev;
+
+ if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) {
+ ev.preventDefault();
+
+ const { current: registry } = registryRef;
+ const [left, right] = registry.getPanels();
+
+ const leftPercent = left.width - (keyCode === keyCodes.LEFT ? 1 : -1);
+ const rightPercent = right.width - (keyCode === keyCodes.RIGHT ? 1 : -1);
+
+ left.setWidth(leftPercent);
+ right.setWidth(rightPercent);
+
+ if (onPanelWidthChange) {
+ onPanelWidthChange([leftPercent, rightPercent]);
+ }
+ }
+ },
+ [onPanelWidthChange]
+ );
+
const childrenWithResizer = [
firstChild,
{
- event.preventDefault();
- setState({
- ...state,
- isDragging: true,
- currentResizerPos: event.clientX,
- });
- }}
+ className={resizerClassName}
+ onKeyDown={handleKeyDown}
+ onMouseDown={handleMouseDown}
/>,
secondChild,
];
@@ -67,6 +105,7 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) {
return (
{
diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts
index 5f06ab8915270f..e275da9e2ac744 100644
--- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts
+++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts
@@ -20,7 +20,7 @@
export interface PanelController {
setWidth: (percent: number) => void;
getWidth: () => number;
- initialWidth: string;
+ width: number;
}
export class PanelRegistry {
@@ -35,6 +35,6 @@ export class PanelRegistry {
}
getPanels() {
- return this.panels.map(panel => ({ ...panel }));
+ return this.panels;
}
}
diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx
index 304535421a78a6..02153d1a1d3cd4 100644
--- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx
+++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx
@@ -65,8 +65,8 @@ describe('Split panel', () => {
const panelContainer = mount(
- {testComponentA}
- {testComponentB}
+ {testComponentA}
+ {testComponentB}
);
diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx
index 56449bfb454174..7be1382760eb91 100644
--- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx
+++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx
@@ -55,10 +55,10 @@ export const Editor = ({ loading }: Props) => {
if (!currentTextObject) return null;
return (
-
+
{loading ? (
@@ -68,7 +68,7 @@ export const Editor = ({ loading }: Props) => {
{loading ? : }
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap
index 278811ca85df9d..249f42a6ebf3f9 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap
@@ -44,11 +44,10 @@ exports[`renders ControlsTab 1`] = `
}
}
getIndexPattern={[Function]}
- handleCheckboxOptionChange={[Function]}
handleFieldNameChange={[Function]}
handleIndexPatternChange={[Function]}
handleLabelChange={[Function]}
- handleNumberOptionChange={[Function]}
+ handleOptionsChange={[Function]}
handleParentChange={[Function]}
handleRemoveControl={[Function]}
key="1"
@@ -101,11 +100,10 @@ exports[`renders ControlsTab 1`] = `
}
}
getIndexPattern={[Function]}
- handleCheckboxOptionChange={[Function]}
handleFieldNameChange={[Function]}
handleIndexPatternChange={[Function]}
handleLabelChange={[Function]}
- handleNumberOptionChange={[Function]}
+ handleOptionsChange={[Function]}
handleParentChange={[Function]}
handleRemoveControl={[Function]}
key="2"
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx
index dbac5d9d943710..2bd0baea6eff8a 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx
@@ -29,7 +29,6 @@ import {
EuiFormRow,
EuiPanel,
EuiSpacer,
- EuiSwitchEvent,
} from '@elastic/eui';
import { RangeControlEditor } from './range_control_editor';
@@ -41,33 +40,28 @@ import { InputControlVisDependencies } from '../../plugin';
interface ControlEditorUiProps {
controlIndex: number;
controlParams: ControlParams;
- handleLabelChange: (controlIndex: number, event: ChangeEvent) => void;
+ handleLabelChange: (controlIndex: number, value: string) => void;
moveControl: (controlIndex: number, direction: number) => void;
handleRemoveControl: (controlIndex: number) => void;
handleIndexPatternChange: (controlIndex: number, indexPatternId: string) => void;
handleFieldNameChange: (controlIndex: number, fieldName: string) => void;
getIndexPattern: (indexPatternId: string) => Promise;
- handleCheckboxOptionChange: (
+ handleOptionsChange: (
controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: EuiSwitchEvent
- ) => void;
- handleNumberOptionChange: (
- controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: ChangeEvent
+ optionName: T,
+ value: ControlParamsOptions[T]
) => void;
parentCandidates: Array<{
value: string;
text: string;
}>;
- handleParentChange: (controlIndex: number, event: ChangeEvent) => void;
+ handleParentChange: (controlIndex: number, parent: string) => void;
deps: InputControlVisDependencies;
}
class ControlEditorUi extends PureComponent {
changeLabel = (event: ChangeEvent) => {
- this.props.handleLabelChange(this.props.controlIndex, event);
+ this.props.handleLabelChange(this.props.controlIndex, event.target.value);
};
removeControl = () => {
@@ -101,8 +95,7 @@ class ControlEditorUi extends PureComponent
);
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx
index 56381ef7d1570d..214cff4ddf9d55 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { PureComponent, ChangeEvent } from 'react';
+import React, { PureComponent } from 'react';
import { InjectedIntlProps } from 'react-intl';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
@@ -28,7 +28,6 @@ import {
EuiFormRow,
EuiPanel,
EuiSelect,
- EuiSwitchEvent,
} from '@elastic/eui';
import { ControlEditor } from './control_editor';
@@ -73,44 +72,44 @@ class ControlsTabUi extends PureComponent this.props.setValue('controls', value);
- handleLabelChange = (controlIndex: number, event: ChangeEvent) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- updatedControl.label = event.target.value;
+ handleLabelChange = (controlIndex: number, label: string) => {
+ const updatedControl = {
+ ...this.props.stateParams.controls[controlIndex],
+ label,
+ };
this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
};
- handleIndexPatternChange = (controlIndex: number, indexPatternId: string) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- updatedControl.indexPattern = indexPatternId;
- updatedControl.fieldName = '';
+ handleIndexPatternChange = (controlIndex: number, indexPattern: string) => {
+ const updatedControl = {
+ ...this.props.stateParams.controls[controlIndex],
+ indexPattern,
+ fieldName: '',
+ };
this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
};
handleFieldNameChange = (controlIndex: number, fieldName: string) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- updatedControl.fieldName = fieldName;
+ const updatedControl = {
+ ...this.props.stateParams.controls[controlIndex],
+ fieldName,
+ };
this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
};
- handleCheckboxOptionChange = (
+ handleOptionsChange = (
controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: EuiSwitchEvent
+ optionName: T,
+ value: ControlParamsOptions[T]
) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- // @ts-ignore
- updatedControl.options[optionName] = event.target.checked;
- this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
- };
-
- handleNumberOptionChange = (
- controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: ChangeEvent
- ) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- // @ts-ignore
- updatedControl.options[optionName] = parseFloat(event.target.value);
+ const control = this.props.stateParams.controls[controlIndex];
+ const updatedControl = {
+ ...control,
+ options: {
+ ...control.options,
+ [optionName]: value,
+ },
+ };
this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
};
@@ -126,9 +125,11 @@ class ControlsTabUi extends PureComponent) => {
- const updatedControl = this.props.stateParams.controls[controlIndex];
- updatedControl.parent = event.target.value;
+ handleParentChange = (controlIndex: number, parent: string) => {
+ const updatedControl = {
+ ...this.props.stateParams.controls[controlIndex],
+ parent,
+ };
this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl));
};
@@ -151,8 +152,7 @@ class ControlsTabUi extends PureComponent {
handleFieldNameChange = sinon.spy();
handleIndexPatternChange = sinon.spy();
- handleCheckboxOptionChange = sinon.spy();
- handleNumberOptionChange = sinon.spy();
+ handleOptionsChange = sinon.spy();
});
describe('renders', () => {
@@ -82,8 +80,7 @@ describe('renders', () => {
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -107,8 +104,7 @@ describe('renders', () => {
controlParams={controlParamsBase}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={parentCandidates}
/>
@@ -143,8 +139,7 @@ describe('renders', () => {
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -178,8 +173,7 @@ describe('renders', () => {
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -213,8 +207,7 @@ describe('renders', () => {
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -227,7 +220,7 @@ describe('renders', () => {
});
});
-test('handleCheckboxOptionChange - multiselect', async () => {
+test('handleOptionsChange - multiselect', async () => {
const component = mountWithIntl(
{
controlParams={controlParamsBase}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -249,25 +241,12 @@ test('handleCheckboxOptionChange - multiselect', async () => {
checkbox.simulate('click');
sinon.assert.notCalled(handleFieldNameChange);
sinon.assert.notCalled(handleIndexPatternChange);
- sinon.assert.notCalled(handleNumberOptionChange);
const expectedControlIndex = 0;
const expectedOptionName = 'multiselect';
- sinon.assert.calledWith(
- handleCheckboxOptionChange,
- expectedControlIndex,
- expectedOptionName,
- sinon.match(event => {
- // Synthetic `event.target.checked` does not get altered by EuiSwitch,
- // but its aria attribute is correctly updated
- if (event.target.getAttribute('aria-checked') === 'true') {
- return true;
- }
- return false;
- }, 'unexpected checkbox input event')
- );
+ sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName);
});
-test('handleNumberOptionChange - size', async () => {
+test('handleOptionsChange - size', async () => {
const component = mountWithIntl(
{
controlParams={controlParamsBase}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
@@ -286,23 +264,12 @@ test('handleNumberOptionChange - size', async () => {
await updateComponent(component);
const input = findTestSubject(component, 'listControlSizeInput');
- input.simulate('change', { target: { value: 7 } });
- sinon.assert.notCalled(handleCheckboxOptionChange);
+ input.simulate('change', { target: { valueAsNumber: 7 } });
sinon.assert.notCalled(handleFieldNameChange);
sinon.assert.notCalled(handleIndexPatternChange);
const expectedControlIndex = 0;
const expectedOptionName = 'size';
- sinon.assert.calledWith(
- handleNumberOptionChange,
- expectedControlIndex,
- expectedOptionName,
- sinon.match(event => {
- if (event.target.value === 7) {
- return true;
- }
- return false;
- }, 'unexpected input event')
- );
+ sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 7);
});
test('field name change', async () => {
@@ -314,8 +281,7 @@ test('field name change', async () => {
controlParams={controlParamsBase}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleCheckboxOptionChange={handleCheckboxOptionChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
handleParentChange={() => {}}
parentCandidates={[]}
/>
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx
index ed68894d39ae42..9772cb5fc25488 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx
@@ -17,10 +17,10 @@
* under the License.
*/
-import React, { PureComponent, ChangeEvent, ComponentType } from 'react';
+import React, { PureComponent, ComponentType } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect, EuiSwitchEvent } from '@elastic/eui';
+import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect } from '@elastic/eui';
import { IndexPatternSelectFormRow } from './index_pattern_select_form_row';
import { FieldSelect } from './field_select';
@@ -45,17 +45,12 @@ interface ListControlEditorProps {
controlParams: ControlParams;
handleFieldNameChange: (fieldName: string) => void;
handleIndexPatternChange: (indexPatternId: string) => void;
- handleCheckboxOptionChange: (
+ handleOptionsChange: (
controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: EuiSwitchEvent
+ optionName: T,
+ value: ControlParamsOptions[T]
) => void;
- handleNumberOptionChange: (
- controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: ChangeEvent
- ) => void;
- handleParentChange: (controlIndex: number, event: ChangeEvent) => void;
+ handleParentChange: (controlIndex: number, parent: string) => void;
parentCandidates: React.ComponentProps['options'];
deps: InputControlVisDependencies;
}
@@ -177,7 +172,7 @@ export class ListControlEditor extends PureComponent<
options={parentCandidatesOptions}
value={this.props.controlParams.parent}
onChange={event => {
- this.props.handleParentChange(this.props.controlIndex, event);
+ this.props.handleParentChange(this.props.controlIndex, event.target.value);
}}
/>
@@ -204,7 +199,11 @@ export class ListControlEditor extends PureComponent<
}
checked={this.props.controlParams.options.multiselect ?? true}
onChange={event => {
- this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', event);
+ this.props.handleOptionsChange(
+ this.props.controlIndex,
+ 'multiselect',
+ event.target.checked
+ );
}}
data-test-subj="listControlMultiselectInput"
/>
@@ -237,7 +236,11 @@ export class ListControlEditor extends PureComponent<
}
checked={this.props.controlParams.options.dynamicOptions ?? false}
onChange={event => {
- this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', event);
+ this.props.handleOptionsChange(
+ this.props.controlIndex,
+ 'dynamicOptions',
+ event.target.checked
+ );
}}
disabled={this.state.isStringField ? false : true}
data-test-subj="listControlDynamicOptionsSwitch"
@@ -268,7 +271,11 @@ export class ListControlEditor extends PureComponent<
min={1}
value={this.props.controlParams.options.size}
onChange={event => {
- this.props.handleNumberOptionChange(this.props.controlIndex, 'size', event);
+ this.props.handleOptionsChange(
+ this.props.controlIndex,
+ 'size',
+ event.target.valueAsNumber
+ );
}}
data-test-subj="listControlSizeInput"
/>
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx
index e7f9b6083890c4..55c4c71ce430b2 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import { shallow } from 'enzyme';
-import { SinonSpy, spy, assert, match } from 'sinon';
+import { SinonSpy, spy, assert } from 'sinon';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
// @ts-ignore
@@ -46,12 +46,12 @@ const controlParams: ControlParams = {
const deps = getDepsMock();
let handleFieldNameChange: SinonSpy;
let handleIndexPatternChange: SinonSpy;
-let handleNumberOptionChange: SinonSpy;
+let handleOptionsChange: SinonSpy;
beforeEach(() => {
handleFieldNameChange = spy();
handleIndexPatternChange = spy();
- handleNumberOptionChange = spy();
+ handleOptionsChange = spy();
});
test('renders RangeControlEditor', async () => {
@@ -63,7 +63,7 @@ test('renders RangeControlEditor', async () => {
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
/>
);
@@ -72,7 +72,7 @@ test('renders RangeControlEditor', async () => {
expect(component).toMatchSnapshot(); // eslint-disable-line
});
-test('handleNumberOptionChange - step', async () => {
+test('handleOptionsChange - step', async () => {
const component = mountWithIntl(
{
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
/>
);
await updateComponent(component);
findTestSubject(component, 'rangeControlSizeInput0').simulate('change', {
- target: { value: 0.5 },
+ target: { valueAsNumber: 0.5 },
});
assert.notCalled(handleFieldNameChange);
assert.notCalled(handleIndexPatternChange);
const expectedControlIndex = 0;
const expectedOptionName = 'step';
- assert.calledWith(
- handleNumberOptionChange,
- expectedControlIndex,
- expectedOptionName,
- match(event => {
- if (event.target.value === 0.5) {
- return true;
- }
- return false;
- }, 'unexpected input event')
- );
+ assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 0.5);
});
-test('handleNumberOptionChange - decimalPlaces', async () => {
+test('handleOptionsChange - decimalPlaces', async () => {
const component = mountWithIntl(
{
controlParams={controlParams}
handleFieldNameChange={handleFieldNameChange}
handleIndexPatternChange={handleIndexPatternChange}
- handleNumberOptionChange={handleNumberOptionChange}
+ handleOptionsChange={handleOptionsChange}
/>
);
await updateComponent(component);
findTestSubject(component, 'rangeControlDecimalPlacesInput0').simulate('change', {
- target: { value: 2 },
+ target: { valueAsNumber: 2 },
});
assert.notCalled(handleFieldNameChange);
assert.notCalled(handleIndexPatternChange);
const expectedControlIndex = 0;
const expectedOptionName = 'decimalPlaces';
- assert.calledWith(
- handleNumberOptionChange,
- expectedControlIndex,
- expectedOptionName,
- match(event => {
- if (event.target.value === 2) {
- return true;
- }
- return false;
- }, 'unexpected input event')
- );
+ assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 2);
});
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx
index 44477eafda6b17..97850879a2d382 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { Component, Fragment, ChangeEvent, ComponentType } from 'react';
+import React, { Component, Fragment, ComponentType } from 'react';
import { EuiFormRow, EuiFieldNumber } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -37,10 +37,10 @@ interface RangeControlEditorProps {
getIndexPattern: (indexPatternId: string) => Promise;
handleFieldNameChange: (fieldName: string) => void;
handleIndexPatternChange: (indexPatternId: string) => void;
- handleNumberOptionChange: (
+ handleOptionsChange: (
controlIndex: number,
- optionName: keyof ControlParamsOptions,
- event: ChangeEvent
+ optionName: T,
+ value: ControlParamsOptions[T]
) => void;
deps: InputControlVisDependencies;
}
@@ -109,7 +109,11 @@ export class RangeControlEditor extends Component<
{
- this.props.handleNumberOptionChange(this.props.controlIndex, 'step', event);
+ this.props.handleOptionsChange(
+ this.props.controlIndex,
+ 'step',
+ event.target.valueAsNumber
+ );
}}
data-test-subj={`rangeControlSizeInput${this.props.controlIndex}`}
/>
@@ -128,7 +132,11 @@ export class RangeControlEditor extends Component<
min={0}
value={this.props.controlParams.options.decimalPlaces}
onChange={event => {
- this.props.handleNumberOptionChange(this.props.controlIndex, 'decimalPlaces', event);
+ this.props.handleOptionsChange(
+ this.props.controlIndex,
+ 'decimalPlaces',
+ event.target.valueAsNumber
+ );
}}
data-test-subj={`rangeControlDecimalPlacesInput${this.props.controlIndex}`}
/>
diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
index b6774aa87b43c2..9473ea5a20b356 100644
--- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
+++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts
@@ -50,7 +50,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende
pinFilters: false,
},
},
- editor: 'default',
editorConfig: {
optionTabs: [
{
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts
index 4d5101e1f9e5f7..d9a93b2ceedc31 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts
@@ -28,6 +28,12 @@ import { ViewMode } from 'src/plugins/embeddable/public';
jest.mock('ui/state_management/state', () => ({
State: {},
}));
+jest.mock('ui/agg_types', () => ({
+ aggTypes: {
+ metrics: [],
+ buckets: [],
+ },
+}));
describe('DashboardState', function() {
let dashboardState: DashboardStateManager;
diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts
index a389a44197baf4..f113c81256f8e4 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/index.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts
@@ -17,9 +17,6 @@
* under the License.
*/
-import 'ui/collapsible_sidebar'; // used in default editor
-import 'ui/vis/editors/default/sidebar';
-
import {
IPrivate,
legacyChrome,
diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
index 189624fdbb2b3e..7d0a07323378a1 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
@@ -66,8 +66,6 @@ export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url';
export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
-// @ts-ignore
-export { defaultEditor } from 'ui/vis/editors/default/default';
export { VisType } from 'ui/vis';
export { wrapInI18nContext } from 'ui/i18n';
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss
index f738820677beb4..2f48ecc322fea5 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss
@@ -30,9 +30,4 @@ a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */
@include flex-parent();
width: 100%;
z-index: 0;
-
- // overrides for tablet and desktop
- @include euiBreakpoint('l', 'xl') {
- flex-direction: row;
- }
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
index a80aed5302d1ff..0e085b8553bf02 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
@@ -116,6 +116,11 @@ function VisualizeAppController(
dirty: !savedVis.id,
});
+ vis.on('dirtyStateChange', ({ isDirty }) => {
+ vis.dirty = isDirty;
+ $scope.$digest();
+ });
+
$scope.topNavMenu = [
...(visualizeCapabilities.save
? [
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
index f47a54baac9a1c..2e0eaeb484c0a5 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
@@ -17,7 +17,16 @@
* under the License.
*/
-import { VisSavedObject } from '../legacy_imports';
+import { TimeRange, Query, esFilters } from 'src/plugins/data/public';
+import { VisSavedObject, AppState, PersistedState } from '../legacy_imports';
+
+export interface EditorRenderProps {
+ appState: AppState;
+ filters: esFilters.Filter[];
+ uiState: PersistedState;
+ timeRange: TimeRange;
+ query?: Query;
+}
export interface SavedVisualizations {
urlFor: (id: string) => string;
diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
index 660e8169664c4d..80e17b1631f3e4 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts
@@ -43,7 +43,6 @@ import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../../plugins/home/public';
-import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports';
import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public';
import { createSavedVisLoader } from './saved_visualizations/saved_visualizations';
// @ts-ignore
@@ -155,8 +154,6 @@ export class VisualizePlugin implements Plugin {
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
});
-
- VisEditorTypesRegistryProvider.register(defaultEditor);
}
public start(
diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/timelion/public/vis/index.tsx
index 1edcb0a5ce71c0..11d1a7385c408c 100644
--- a/src/legacy/core_plugins/timelion/public/vis/index.tsx
+++ b/src/legacy/core_plugins/timelion/public/vis/index.tsx
@@ -19,9 +19,9 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-// @ts-ignore
-import { DefaultEditorSize } from 'ui/vis/editor_size';
+
import { VisOptionsProps } from 'ui/vis/editors/default';
+import { DefaultEditorSize } from '../../../visualizations/public';
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
import { getTimelionRequestHandler } from './timelion_request_handler';
import visConfigTemplate from './timelion_vis.html';
diff --git a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss
index da38d6d2ed2114..fb0a3d05e5e857 100644
--- a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss
+++ b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss
@@ -4,13 +4,8 @@
}
.visEditor--markdown {
- vis-editor-vis-options, vis-options-react-wrapper {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- }
-
- .visEditor--markdown__textarea {
+ .visEditorSidebar__config > *,
+ .visEditor--markdown__textarea {
flex-grow: 1;
}
diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx
index be82b52dee0fc3..33d7480de5a8e3 100644
--- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx
+++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx
@@ -34,7 +34,6 @@ import { totalAggregations, isAggConfigNumeric } from './utils';
function TableOptions({
aggs,
- aggsLabels,
stateParams,
setValidity,
setValue,
@@ -51,7 +50,7 @@ function TableOptions({
.filter(col => isAggConfigNumeric(get(col, 'aggConfig.type.name'), stateParams.dimensions))
.map(({ name }) => ({ value: name, text: name })),
],
- [aggs, aggsLabels, stateParams.percentageCol, stateParams.dimensions]
+ [aggs, stateParams.percentageCol, stateParams.dimensions]
);
const isPerPageValid = stateParams.perPage === '' || stateParams.perPage > 0;
diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
index 7adaa21cac5938..a792fc98842f10 100644
--- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
+++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts
@@ -19,6 +19,7 @@
import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular';
import $ from 'jquery';
+import { isEqual } from 'lodash';
import { Vis, VisParams } from '../../visualizations/public';
import { npStart } from './legacy_imports';
@@ -75,6 +76,11 @@ export class TableVisualizationController {
this.$scope.vis = this.vis;
this.$scope.visState = this.vis.getState();
this.$scope.esResponse = esResponse;
+
+ if (!isEqual(this.$scope.visParams, visParams)) {
+ this.vis.emit('updateEditorStateParams', visParams);
+ }
+
this.$scope.visParams = visParams;
this.$scope.renderComplete = resolve;
this.$scope.renderFailed = reject;
diff --git a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss
index f4276541d5d9ec..709aaa2030f68d 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss
+++ b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss
@@ -1,13 +1,6 @@
.visEditor--vega {
.visEditorSidebar__config {
padding: 0;
- position: relative;
- }
-
- .visEditorSidebar__options {
- @include euiScrollBar;
- flex-shrink: 1;
- overflow-y: auto;
}
}
@@ -22,8 +15,8 @@
.vgaEditor__aceEditorActions {
position: absolute;
z-index: $euiZLevel1;
- top: 0;
- // Adjust for possible scrollbars
- right: $euiSize;
+ top: $euiSizeS;
+ // Adjust for sidebar collapse button
+ right: $euiSizeXXL;
line-height: 1;
}
diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
index 9ab5f820cec317..81c98c6ddb96b1 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
+++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts
@@ -19,10 +19,8 @@
import { i18n } from '@kbn/i18n';
// @ts-ignore
-import { DefaultEditorSize } from 'ui/vis/editor_size';
-// @ts-ignore
import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
-import { Status } from '../../visualizations/public';
+import { Status, DefaultEditorSize } from '../../visualizations/public';
import { VegaVisualizationDependencies } from './plugin';
import { VegaVisEditor } from './components';
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
index 256df603a7f33a..d34cf930f7e611 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap
@@ -17,11 +17,10 @@ exports[`MetricsAxisOptions component should init with the default set of props
"bySchemaName": [Function],
}
}
- aggsLabels=""
changeValueAxis={[Function]}
+ isTabSelected={true}
setParamByIndex={[Function]}
setValue={[MockFunction]}
- setVisType={[MockFunction]}
stateParams={
Object {
"categoryAxes": Array [
@@ -95,6 +94,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
}
vis={
Object {
+ "setVisType": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
@@ -127,13 +127,12 @@ exports[`MetricsAxisOptions component should init with the default set of props
"bySchemaName": [Function],
}
}
- aggsLabels=""
isCategoryAxisHorizontal={true}
+ isTabSelected={true}
onValueAxisPositionChanged={[Function]}
removeValueAxis={[Function]}
setParamByIndex={[Function]}
setValue={[MockFunction]}
- setVisType={[MockFunction]}
stateParams={
Object {
"categoryAxes": Array [
@@ -207,6 +206,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
}
vis={
Object {
+ "setVisType": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
@@ -238,7 +238,6 @@ exports[`MetricsAxisOptions component should init with the default set of props
"bySchemaName": [Function],
}
}
- aggsLabels=""
axis={
Object {
"id": "CategoryAxis-1",
@@ -260,10 +259,10 @@ exports[`MetricsAxisOptions component should init with the default set of props
"type": "category",
}
}
+ isTabSelected={true}
onPositionChanged={[Function]}
setCategoryAxis={[Function]}
setValue={[MockFunction]}
- setVisType={[MockFunction]}
stateParams={
Object {
"categoryAxes": Array [
@@ -337,6 +336,7 @@ exports[`MetricsAxisOptions component should init with the default set of props
}
vis={
Object {
+ "setVisType": [MockFunction],
"type": Object {
"schemas": Object {
"metrics": Array [
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
index df1920bd4013c9..514b957765a99e 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx
@@ -63,7 +63,6 @@ const createAggs = (aggs: any[]) => ({
describe('MetricsAxisOptions component', () => {
let setValue: jest.Mock;
- let setVisType: jest.Mock;
let defaultProps: ValidationVisOptionsProps;
let axis: ValueAxis;
let axisRight: ValueAxis;
@@ -71,7 +70,6 @@ describe('MetricsAxisOptions component', () => {
beforeEach(() => {
setValue = jest.fn();
- setVisType = jest.fn();
axis = {
...valueAxis,
@@ -91,12 +89,13 @@ describe('MetricsAxisOptions component', () => {
defaultProps = {
aggs: createAggs([aggCount]),
- aggsLabels: '',
+ isTabSelected: true,
vis: {
type: {
type: ChartTypes.AREA,
schemas: { metrics: [{ name: 'metric' }] },
},
+ setVisType: jest.fn(),
},
stateParams: {
valueAxes: [axis],
@@ -105,7 +104,6 @@ describe('MetricsAxisOptions component', () => {
grid: { valueAxis: defaultValueAxisId },
},
setValue,
- setVisType,
} as any;
});
@@ -120,7 +118,6 @@ describe('MetricsAxisOptions component', () => {
const comp = mount();
comp.setProps({
aggs: createAggs([aggCount, aggAverage]),
- aggsLabels: `${aggCount.makeLabel()}, ${aggAverage.makeLabel()}`,
});
const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }];
@@ -135,7 +132,7 @@ describe('MetricsAxisOptions component', () => {
});
const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }];
- expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries);
+ expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries);
});
it('should update visType when one seriesParam', () => {
@@ -149,7 +146,7 @@ describe('MetricsAxisOptions component', () => {
},
});
- expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE);
+ expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE);
});
it('should set histogram visType when multiple seriesParam', () => {
@@ -163,7 +160,7 @@ describe('MetricsAxisOptions component', () => {
},
});
- expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM);
+ expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM);
});
});
@@ -177,7 +174,6 @@ describe('MetricsAxisOptions component', () => {
};
comp.setProps({
aggs: createAggs([newAgg]),
- aggsLabels: `${newAgg.makeLabel()}`,
});
const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }];
expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues);
@@ -192,11 +188,14 @@ describe('MetricsAxisOptions component', () => {
};
comp.setProps({
aggs: createAggs([agg]),
- aggsLabels: agg.makeLabel(),
});
+ const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }];
const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }];
- expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues);
+
+ expect(setValue).toHaveBeenCalledTimes(3);
+ expect(setValue).toHaveBeenNthCalledWith(2, SERIES_PARAMS, updatedSeriesParams);
+ expect(setValue).toHaveBeenNthCalledWith(3, VALUE_AXES, updatedValues);
});
it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => {
@@ -204,7 +203,6 @@ describe('MetricsAxisOptions component', () => {
const agg = { id: aggCount.id, makeLabel: () => 'Custom label' };
comp.setProps({
aggs: createAggs([agg, aggAverage]),
- aggsLabels: `${agg.makeLabel()}, ${aggAverage.makeLabel()}`,
stateParams: {
...defaultProps.stateParams,
seriesParams: [chart, chart],
@@ -224,48 +222,10 @@ describe('MetricsAxisOptions component', () => {
};
comp.setProps({
aggs: createAggs([agg]),
- aggsLabels: agg.makeLabel(),
});
expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES);
});
-
- it('should overwrite the custom title when the agg type changes', () => {
- defaultProps.stateParams.valueAxes[0].title.text = 'Custom title';
- const comp = mount();
- const agg = {
- id: aggCount.id,
- type: { name: 'max' },
- makeLabel: () => 'Max',
- };
- comp.setProps({
- aggs: createAggs([agg]),
- aggsLabels: agg.makeLabel(),
- });
-
- const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }];
- expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues);
- });
-
- it('should overwrite the custom title when the agg field changes', () => {
- defaultProps.stateParams.valueAxes[0].title.text = 'Custom title';
- const agg = {
- id: aggCount.id,
- type: { name: 'max' },
- makeLabel: () => 'Max',
- } as AggConfig;
- defaultProps.aggs = createAggs([agg]) as any;
- const comp = mount();
- agg.params = { field: { name: 'Field' } };
- agg.makeLabel = () => 'Max, Field';
- comp.setProps({
- aggs: createAggs([agg]),
- aggsLabels: agg.makeLabel(),
- });
-
- const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }];
- expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues);
- });
});
it('should add value axis', () => {
diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
index 85077ed4923310..c4dcbfaa47265d 100644
--- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
+++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx
@@ -52,7 +52,7 @@ export type ChangeValueAxis = (
const VALUE_AXIS_PREFIX = 'ValueAxis-';
function MetricsAxisOptions(props: ValidationVisOptionsProps) {
- const { stateParams, setValue, aggs, aggsLabels, setVisType, vis } = props;
+ const { stateParams, setValue, aggs, vis, isTabSelected } = props;
const [isCategoryAxisHorizontal, setIsCategoryAxisHorizontal] = useState(true);
@@ -89,9 +89,11 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
}
);
- const updateAxisTitle = () => {
+ const updateAxisTitle = (seriesParams?: SeriesParam[]) => {
+ const series = seriesParams || stateParams.seriesParams;
const axes = cloneDeep(stateParams.valueAxes);
let isAxesChanged = false;
+ let lastValuesChanged = false;
const lastLabels = { ...lastCustomLabels };
const lastMatchingSeriesAgg = { ...lastSeriesAgg };
@@ -99,8 +101,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
let newCustomLabel = '';
const matchingSeries: AggConfig[] = [];
- stateParams.seriesParams.forEach((series, seriesIndex) => {
- if ((axisNumber === 0 && !series.valueAxis) || series.valueAxis === axis.id) {
+ series.forEach((serie, seriesIndex) => {
+ if ((axisNumber === 0 && !serie.valueAxis) || serie.valueAxis === axis.id) {
const aggByIndex = aggs.bySchemaName('metric')[seriesIndex];
matchingSeries.push(aggByIndex);
}
@@ -125,6 +127,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
field: matchingSeriesAggField,
};
lastLabels[axis.id] = newCustomLabel;
+ lastValuesChanged = true;
if (
Object.keys(lastCustomLabels).length !== 0 &&
@@ -147,8 +150,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
setValue('valueAxes', axes);
}
- setLastSeriesAgg(lastMatchingSeriesAgg);
- setLastCustomLabels(lastLabels);
+ if (lastValuesChanged) {
+ setLastSeriesAgg(lastMatchingSeriesAgg);
+ setLastCustomLabels(lastLabels);
+ }
};
const onValueAxisPositionChanged = useCallback(
@@ -242,7 +247,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
const metrics = useMemo(() => {
const schemaName = vis.type.schemas.metrics[0].name;
return aggs.bySchemaName(schemaName);
- }, [vis.type.schemas.metrics[0].name, aggs, aggsLabels]);
+ }, [vis.type.schemas.metrics[0].name, aggs]);
const firstValueAxesId = stateParams.valueAxes[0].id;
@@ -272,7 +277,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
});
setValue('seriesParams', updatedSeries);
- }, [aggsLabels, metrics, firstValueAxesId]);
+ updateAxisTitle(updatedSeries);
+ }, [metrics, firstValueAxesId]);
const visType = useMemo(() => {
const types = uniq(stateParams.seriesParams.map(({ type }) => type));
@@ -280,14 +286,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
}, [stateParams.seriesParams]);
useEffect(() => {
- setVisType(visType);
- }, [visType]);
-
- useEffect(() => {
- updateAxisTitle();
- }, [aggsLabels]);
+ vis.setVisType(visType);
+ }, [vis, visType]);
- return (
+ return isTabSelected ? (
<>
@@ -307,7 +309,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps)
setCategoryAxis={setCategoryAxis}
/>
>
- );
+ ) : null;
}
export { MetricsAxisOptions };
diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
index e10cbe9aefea60..b750557c24b94a 100644
--- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
+++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
@@ -27,6 +27,7 @@ export {
} from '../../../ui/public/agg_types/buckets/date_histogram';
export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities';
export { I18nContext } from '../../../ui/public/i18n';
+export { DefaultEditorController } from '../../../ui/public/vis/editors/default/default_editor_controller';
import chrome from '../../../ui/public/chrome';
export { chrome as legacyChrome };
import '../../../ui/public/directives/bind';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js
index 2f60de3b806147..f849cbfb290ca9 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js
@@ -18,7 +18,9 @@
*/
import _ from 'lodash';
+
import { createFiltersFromEvent, onBrushEvent } from '../filters';
+import { DefaultEditorController } from '../../../legacy_imports';
export class BaseVisType {
constructor(opts = {}) {
@@ -46,7 +48,7 @@ export class BaseVisType {
},
requestHandler: 'courier', // select one from registry or pass a function
responseHandler: 'none',
- editor: 'default',
+ editor: DefaultEditorController,
editorConfig: {
collections: {}, // collections used for configuration (list of positions, ...)
},
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js
index 115bbd27318402..4f1526c20cb6f7 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js
@@ -97,6 +97,10 @@ class Vis extends EventEmitter {
}
}
+ setVisType(type) {
+ this.type.type = type;
+ }
+
updateState() {
this.setState(this.getCurrentState(true));
this.emit('update');
@@ -118,18 +122,6 @@ class Vis extends EventEmitter {
};
}
- getSerializableState(state) {
- return {
- title: state.title,
- type: state.type,
- params: _.cloneDeep(state.params),
- aggs: state.aggs.aggs
- .map(agg => agg.toJSON())
- .filter(agg => agg.enabled)
- .filter(Boolean),
- };
- }
-
copyCurrentState(includeDisabled = false) {
const state = this.getCurrentState(includeDisabled);
state.aggs = new AggConfigs(
diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts
index 07e0d46e4eb70c..0edf7823188629 100644
--- a/src/legacy/ui/public/agg_types/agg_config.ts
+++ b/src/legacy/ui/public/agg_types/agg_config.ts
@@ -123,6 +123,7 @@ export class AggConfig {
public enabled: boolean;
public params: any;
public parent?: AggConfigs;
+ public brandNew?: boolean;
private __schema: Schema;
private __type: AggType;
diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts
index 6e811afb1849db..bd2f261c0bf1d8 100644
--- a/src/legacy/ui/public/agg_types/agg_configs.ts
+++ b/src/legacy/ui/public/agg_types/agg_configs.ts
@@ -126,10 +126,10 @@ export class AggConfigs {
return aggConfigs;
}
- createAggConfig(
+ createAggConfig = (
params: AggConfig | AggConfigOptions,
{ addToAggConfigs = true } = {}
- ) {
+ ) => {
let aggConfig;
if (params instanceof AggConfig) {
aggConfig = params;
@@ -141,7 +141,7 @@ export class AggConfigs {
this.aggs.push(aggConfig);
}
return aggConfig as T;
- }
+ };
/**
* Data-by-data comparison of this Aggregation
diff --git a/src/legacy/ui/public/styles/_mixins.scss b/src/legacy/ui/public/styles/_mixins.scss
index ae529a4678d5df..2d787686848418 100644
--- a/src/legacy/ui/public/styles/_mixins.scss
+++ b/src/legacy/ui/public/styles/_mixins.scss
@@ -25,6 +25,7 @@
// Standardizes the look and layout of resizable area handles
@mixin kbnResizer($size: ($euiSizeM + 2px), $direction: horizontal) {
+ position: relative;
display: flex;
flex: 0 0 $size;
background-color: $euiPageBackgroundColor;
@@ -34,10 +35,8 @@
user-select: none;
@if ($direction == horizontal) {
- cursor: ew-resize;
width: $size;
} @else if ($direction == vertical) {
- cursor: ns-resize;
height: $size;
width: 100%;
} @else {
@@ -53,6 +52,21 @@
background-color: $euiColorPrimary;
color: $euiColorEmptyShade;
}
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ @if ($direction == horizontal) {
+ cursor: ew-resize;
+ } @else if ($direction == vertical) {
+ cursor: ns-resize;
+ }
+ }
}
@mixin kibanaFullScreenGraphics($euiZLevel: $euiZLevel9) {
diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.d.ts b/src/legacy/ui/public/vis/editor_size.ts
similarity index 69%
rename from src/legacy/ui/public/vis/editors/default/agg_params.d.ts
rename to src/legacy/ui/public/vis/editor_size.ts
index 89896c0e1be3e8..5fdda4c2dad411 100644
--- a/src/legacy/ui/public/vis/editors/default/agg_params.d.ts
+++ b/src/legacy/ui/public/vis/editor_size.ts
@@ -17,6 +17,20 @@
* under the License.
*/
-export interface AggParams {
- [key: string]: unknown;
+export enum DefaultEditorSize {
+ SMALL = 'small',
+ MEDIUM = 'medium',
+ LARGE = 'large',
}
+
+export const getInitialWidth = (size: DefaultEditorSize) => {
+ switch (size) {
+ case DefaultEditorSize.SMALL:
+ return 15;
+ case DefaultEditorSize.LARGE:
+ return 50;
+ case DefaultEditorSize.MEDIUM:
+ default:
+ return 30;
+ }
+};
diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts
index fa15f8642c4dcf..0a5d0ea748b5ec 100644
--- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts
+++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts
@@ -19,7 +19,6 @@
import { EditorConfigProviderRegistry } from './editor_config_providers';
import { EditorParamConfig, FixedParam, NumericIntervalParam, TimeIntervalParam } from './types';
-import { AggType } from '../../../agg_types';
import { AggConfig } from '../..';
jest.mock('ui/new_platform');
@@ -49,10 +48,9 @@ describe('EditorConfigProvider', () => {
const provider = jest.fn(() => ({}));
registry.register(provider);
expect(provider).not.toHaveBeenCalled();
- const aggType = {} as AggType;
const aggConfig = {} as AggConfig;
- registry.getConfigForAgg(aggType, indexPattern, aggConfig);
- expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig);
+ registry.getConfigForAgg(indexPattern, aggConfig);
+ expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig);
});
it('should call all registered providers with given parameters', () => {
@@ -62,11 +60,10 @@ describe('EditorConfigProvider', () => {
registry.register(provider2);
expect(provider).not.toHaveBeenCalled();
expect(provider2).not.toHaveBeenCalled();
- const aggType = {} as AggType;
const aggConfig = {} as AggConfig;
- registry.getConfigForAgg(aggType, indexPattern, aggConfig);
- expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig);
- expect(provider2).toHaveBeenCalledWith(aggType, indexPattern, aggConfig);
+ registry.getConfigForAgg(indexPattern, aggConfig);
+ expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig);
+ expect(provider2).toHaveBeenCalledWith(indexPattern, aggConfig);
});
describe('merging configs', () => {
@@ -75,7 +72,7 @@ describe('EditorConfigProvider', () => {
}
function getOutputConfig(reg: EditorConfigProviderRegistry) {
- return reg.getConfigForAgg({} as AggType, indexPattern, {} as AggConfig).singleParam;
+ return reg.getConfigForAgg(indexPattern, {} as AggConfig).singleParam;
}
it('should have hidden true if at least one config was hidden true', () => {
diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts
index c7fb937b974245..80dc2bcd68f08f 100644
--- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts
+++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts
@@ -19,18 +19,13 @@
import { TimeIntervalParam } from 'ui/vis/editors/config/types';
import { AggConfig } from '../..';
-import { AggType } from '../../../agg_types';
import { IndexPattern } from '../../../../../../plugins/data/public';
import { leastCommonMultiple } from '../../lib/least_common_multiple';
import { parseEsInterval } from '../../../../../core_plugins/data/public';
import { leastCommonInterval } from '../../lib/least_common_interval';
import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types';
-type EditorConfigProvider = (
- aggType: AggType,
- indexPattern: IndexPattern,
- aggConfig: AggConfig
-) => EditorConfig;
+type EditorConfigProvider = (indexPattern: IndexPattern, aggConfig: AggConfig) => EditorConfig;
class EditorConfigProviderRegistry {
private providers: Set = new Set();
@@ -39,14 +34,8 @@ class EditorConfigProviderRegistry {
this.providers.add(configProvider);
}
- public getConfigForAgg(
- aggType: AggType,
- indexPattern: IndexPattern,
- aggConfig: AggConfig
- ): EditorConfig {
- const configs = Array.from(this.providers).map(provider =>
- provider(aggType, indexPattern, aggConfig)
- );
+ public getConfigForAgg(indexPattern: IndexPattern, aggConfig: AggConfig): EditorConfig {
+ const configs = Array.from(this.providers).map(provider => provider(indexPattern, aggConfig));
return this.mergeConfigs(configs);
}
diff --git a/src/legacy/ui/public/vis/editors/default/_default.scss b/src/legacy/ui/public/vis/editors/default/_default.scss
index 2a32eadd55b1ef..7562583b037df8 100644
--- a/src/legacy/ui/public/vis/editors/default/_default.scss
+++ b/src/legacy/ui/public/vis/editors/default/_default.scss
@@ -1,8 +1,4 @@
.visEditor--default {
- // Prevent the default editor from overflowing. Without that you can cause
- // a weird issue where the complete page can be scrolled out of view if
- // the editor within the sidebar is too height.
- overflow-y: hidden;
flex: 1 1 auto;
display: flex;
@@ -16,12 +12,11 @@
*/
.visEditor__collapsibleSidebar {
- @include flex-parent(0, 0, auto);
- margin-right: $euiSizeL;
- flex-direction: row;
+ background: $euiColorLightestShade;
min-width: $vis-editor-sidebar-min-width;
- width: $vis-editor-sidebar-min-width;
max-width: 100%;
+ position: relative;
+ flex-shrink: 0;
@include euiBreakpoint('xs', 's', 'm') {
// If we are on a small screen we force the editor to take 100% width.
@@ -33,35 +28,25 @@
}
}
-.visEditor__collapsibleSidebar.closed {
+// !importants on width are required to override resizable panel inline widths
+.visEditor__collapsibleSidebar-isClosed {
min-width: 0;
-}
-
-.visEditor__collapsibleSidebar--small {
- width: 15%;
-}
+ width: $euiSizeXL !important; // Just enough room for the collapse button
-.visEditor__collapsibleSidebar--medium {
- width: 30%;
-}
+ .visEditorSidebar {
+ display: none;
+ }
-.visEditor__collapsibleSidebar--large {
- width: 50%;
+ @include euiBreakpoint('xs', 's', 'm') {
+ height: $euiSizeXXL; // Just enough room for the collapse button
+ width: 100% !important;
+ }
}
-
-/**
- * Actual sidebar
- */
-
-.visEditor__sidebar {
- @include flex-parent(1, 0, auto);
-
- // overridden for tablet and desktop
- @include euiBreakpoint('l', 'xl') {
- flex-basis: $vis-editor-sidebar-basis;
- max-width: calc(100% - #{$vis-editor-resizer-width});
- }
+.visEditor__collapsibleSidebarButton {
+ position: absolute;
+ right: $euiSizeXS;
+ top: $euiSizeS;
}
/**
@@ -69,17 +54,30 @@
*/
.visEditor__resizer {
- @include kbnResizer($vis-editor-resizer-width);
-
+ @include kbnResizer($euiSizeM);
@include euiBreakpoint('xs', 's', 'm') {
display: none;
}
}
+.visEditor__resizer-isHidden {
+ display: none;
+}
+
/**
* Canvas area
*/
+.visEditor__visualization {
+ display: flex;
+ flex-basis: 100%;
+
+ @include euiBreakpoint('xs', 's', 'm') {
+ // If we are on a small screen we force the visualization to take 100% width.
+ width: 100% !important;
+ }
+}
+
.visEditor__canvas {
background-color: $euiColorEmptyShade;
display: flex;
diff --git a/src/legacy/ui/public/vis/editors/default/_index.scss b/src/legacy/ui/public/vis/editors/default/_index.scss
index d5938a0298d36d..6abb45dc546a30 100644
--- a/src/legacy/ui/public/vis/editors/default/_index.scss
+++ b/src/legacy/ui/public/vis/editors/default/_index.scss
@@ -1,6 +1,4 @@
-$vis-editor-sidebar-basis: (100/12) * 2%; // two of twelve columns
$vis-editor-sidebar-min-width: 350px;
-$vis-editor-resizer-width: $euiSizeM;
// Main layout
@import './default';
diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss
index e6b75b1a1f783c..cbe7172d623415 100644
--- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss
+++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss
@@ -2,18 +2,22 @@
// LAYOUT
//
-.visEditorSidebar__container {
- @include flex-parent(1, 1, auto);
- background-color: $euiColorLightestShade;
+.visEditorSidebar {
+ min-width: $vis-editor-sidebar-min-width;
}
.visEditorSidebar__form {
@include flex-parent(1, 1, auto);
+ max-width: 100%;
}
.visEditorSidebar__config {
padding: $euiSizeS;
+ > * {
+ flex-grow: 0;
+ }
+
@include euiBreakpoint('l', 'xl') {
@include flex-parent(1, 1, 1px);
@include euiScrollBar;
@@ -21,64 +25,26 @@
}
}
+.visEditorSidebar__config-isHidden {
+ display: none;
+}
+
//
// NAVIGATION
//
.visEditorSidebar__indexPattern {
- font-weight: $euiFontWeightBold;
- padding: $euiSizeXS $euiSizeS;
- background-color: shadeOrTint($euiColorPrimary, 60%, 60%);
- color: $euiColorEmptyShade;
- line-height: $euiSizeL;
+ @include euiTextTruncate;
+ padding: $euiSizeS $euiSizeXL $euiSizeS $euiSizeS; // Extra padding on the right for the collapse button
}
-.visEditorSidebar__nav {
- min-height: 0;
-
- .navbar-right {
- // Match correct bootstrap container spacing to pull buttons fully right
- margin-right: -15px;
- }
+.visEditorSidebar__indexPatternPlaceholder {
+ min-height: $euiSizeXXL;
+ border-bottom: $euiBorderThin;
}
-/**
- * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap.
- */
-.visEditorSidebar__navLink {
- padding: 2px $euiSizeS !important; /* 1 */
- color: $euiColorDarkShade !important; /* 1 */
-
- &.visEditorSidebar__navLink-isSelected {
- border-bottom: 2px solid $euiColorPrimary;
- border-color: $euiColorPrimary !important;
- color: $euiColorPrimary !important;
-
- &:before {
- display: none;
- }
-
- &:hover {
- background-color: transparent;
- }
- }
-}
-
-/**
- * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap.
- */
-.visEditorSidebar__navLink--danger {
- color: $euiColorEmptyShade !important; /* 1 */
- background-color: $euiColorDanger;
-
- &:hover {
- background-color: shadeOrTint($euiColorDanger, 12%, 0%) !important; /* 1 */
- }
-}
-
-.visEditorSidebar__navButtonLink {
- // Make the line-height the same size as the icon for better alignment
- line-height: $euiSize;
+.visEditorSidebar__nav {
+ flex-grow: 0;
}
//
@@ -104,15 +70,6 @@
}
}
-.visEditorSidebar__sectionTitle {
- @include euiFontSizeL;
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: $euiSizeM;
- text-transform: capitalize;
-}
-
// Collapsible section
.visEditorSidebar__collapsible {
@@ -127,15 +84,6 @@
// FORMS
//
-.visEditorSidebar__input,
-.visEditorSidebar__select {
- @include __legacyInputStyles__bad;
-}
-
-.visEditorSidebar__select {
- @include __legacySelectStyles__bad;
-}
-
.visEditorSidebar__formRow {
display: flex;
align-items: center;
@@ -155,14 +103,6 @@
flex: 1 1 60%;
}
-.visEditorSidebar__formRow--expand {
- .visEditorSidebar__formLabel,
- .visEditorSidebar__formControl {
- flex-basis: auto;
- flex-grow: 0;
- }
-}
-
.visEditorSidebar__aggGroupAccordionButtonContent {
font-size: $euiFontSizeS;
@@ -170,3 +110,15 @@
color: $euiColorDarkShade;
}
}
+
+.visEditorSidebar__controls {
+ border-top: $euiBorderThin;
+ padding: $euiSizeS;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+
+ .visEditorSidebar__autoApplyButton {
+ margin-left: $euiSizeM;
+ }
+}
diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js
deleted file mode 100644
index 8fc4934022cf62..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/agg_group.js
+++ /dev/null
@@ -1,98 +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 { DefaultEditorAggGroup } from './components/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',
- 'currentTab',
- ])
- )
- .directive('visEditorAggGroup', function() {
- return {
- restrict: 'E',
- scope: true,
- require: '?^ngModel',
- template: function() {
- return ``;
- },
- link: function($scope, $el, attr, ngModelCtrl) {
- $scope.groupName = attr.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.setValidity = isValid => {
- ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid);
- };
-
- $scope.setTouched = isTouched => {
- if (isTouched) {
- ngModelCtrl.$setTouched();
- } else {
- ngModelCtrl.$setUntouched();
- }
- };
- },
- };
- });
diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap
index b78503b298d046..aed0285fd34058 100644
--- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap
+++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap
@@ -57,9 +57,9 @@ exports[`DefaultEditorAgg component should init with the default set of props 1`
groupName="metrics"
indexPattern={Object {}}
metricAggs={Array []}
- onAggParamsChange={[MockFunction]}
onAggTypeChange={[Function]}
- setTouched={[MockFunction]}
+ setAggParamValue={[MockFunction]}
+ setTouched={[Function]}
setValidity={[Function]}
state={Object {}}
/>
diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap
index 29af0887db2b8c..373ff6b4c3ee41 100644
--- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap
+++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap
@@ -5,6 +5,7 @@ exports[`DefaultEditorAgg component should init with the default set of props 1`
onDragEnd={[Function]}
>
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
index c19f101fa2c32d..6849d00158b063 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
@@ -26,6 +26,7 @@ import { act } from 'react-dom/test-utils';
import { DefaultEditorAggParams } from './agg_params';
import { AggType } from 'ui/agg_types';
import { IndexPattern } from '../../../../../../../plugins/data/public';
+import { AGGS_ACTION_KEYS } from './agg_group_state';
jest.mock('./agg_params', () => ({
DefaultEditorAggParams: () => null,
@@ -33,18 +34,18 @@ jest.mock('./agg_params', () => ({
describe('DefaultEditorAgg component', () => {
let defaultProps: DefaultEditorAggProps;
- let onAggParamsChange: jest.Mock;
- let setTouched: jest.Mock;
+ let setAggParamValue: jest.Mock;
+ let setStateParamValue: jest.Mock;
let onToggleEnableAgg: jest.Mock;
let removeAgg: jest.Mock;
- let setValidity: jest.Mock;
+ let setAggsState: jest.Mock;
beforeEach(() => {
- onAggParamsChange = jest.fn();
- setTouched = jest.fn();
+ setAggParamValue = jest.fn();
+ setStateParamValue = jest.fn();
onToggleEnableAgg = jest.fn();
removeAgg = jest.fn();
- setValidity = jest.fn();
+ setAggsState = jest.fn();
defaultProps = {
agg: {
@@ -66,10 +67,10 @@ describe('DefaultEditorAgg component', () => {
isRemovable: false,
metricAggs: [],
state: {} as VisState,
- onAggParamsChange,
+ setAggParamValue,
+ setStateParamValue,
onAggTypeChange: () => {},
- setValidity,
- setTouched,
+ setAggsState,
onToggleEnableAgg,
removeAgg,
};
@@ -98,7 +99,11 @@ describe('DefaultEditorAgg component', () => {
.setValidity(false);
});
comp.update();
- expect(setValidity).toBeCalledWith(false);
+ expect(setAggsState).toBeCalledWith({
+ type: AGGS_ACTION_KEYS.VALID,
+ payload: false,
+ aggId: defaultProps.agg.id,
+ });
expect(
comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists()
@@ -119,7 +124,11 @@ describe('DefaultEditorAgg component', () => {
.setValidity(true);
});
comp.update();
- expect(setValidity).toBeCalledWith(true);
+ expect(setAggsState).toBeCalledWith({
+ type: AGGS_ACTION_KEYS.VALID,
+ payload: true,
+ aggId: defaultProps.agg.id,
+ });
expect(comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').text()).toBe(
'Agg description'
@@ -128,16 +137,20 @@ describe('DefaultEditorAgg component', () => {
it('should call setTouched when accordion is collapsed', () => {
const comp = mount();
- expect(defaultProps.setTouched).toBeCalledTimes(0);
+ expect(defaultProps.setAggsState).toBeCalledTimes(0);
comp.find('.euiAccordion__button').simulate('click');
// make sure that the accordion is collapsed
expect(comp.find('.euiAccordion-isOpen').exists()).toBeFalsy();
- expect(defaultProps.setTouched).toBeCalledWith(true);
+ expect(defaultProps.setAggsState).toBeCalledWith({
+ type: AGGS_ACTION_KEYS.TOUCHED,
+ payload: true,
+ aggId: defaultProps.agg.id,
+ });
});
- it('should call setValidity inside onSetValidity', () => {
+ it('should call setAggsState inside setValidity', () => {
const comp = mount();
act(() => {
@@ -147,7 +160,11 @@ describe('DefaultEditorAgg component', () => {
.setValidity(false);
});
- expect(setValidity).toBeCalledWith(false);
+ expect(setAggsState).toBeCalledWith({
+ type: AGGS_ACTION_KEYS.VALID,
+ payload: false,
+ aggId: defaultProps.agg.id,
+ });
expect(
comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists()
@@ -197,7 +214,7 @@ describe('DefaultEditorAgg component', () => {
const comp = mount();
comp.find('[data-test-subj="toggleDisableAggregationBtn disable"] button').simulate('click');
- expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, false);
+ expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, false);
});
it('should disable the disableAggregation button', () => {
@@ -217,7 +234,7 @@ describe('DefaultEditorAgg component', () => {
const comp = mount();
comp.find('[data-test-subj="toggleDisableAggregationBtn enable"] button').simulate('click');
- expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, true);
+ expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, true);
});
it('should call removeAgg', () => {
@@ -225,7 +242,7 @@ describe('DefaultEditorAgg component', () => {
const comp = mount();
comp.find('[data-test-subj="removeDimensionBtn"] button').simulate('click');
- expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg);
+ expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg.id);
});
});
@@ -269,8 +286,8 @@ describe('DefaultEditorAgg component', () => {
const comp = mount();
comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'histogram' } } });
- expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith(
- defaultProps.agg.params,
+ expect(defaultProps.setAggParamValue).toHaveBeenCalledWith(
+ defaultProps.agg.id,
'min_doc_count',
true
);
@@ -283,8 +300,8 @@ describe('DefaultEditorAgg component', () => {
const comp = mount();
comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'date_histogram' } } });
- expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith(
- defaultProps.agg.params,
+ expect(defaultProps.setAggParamValue).toHaveBeenCalledWith(
+ defaultProps.agg.id,
'min_doc_count',
0
);
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.tsx
index 345c9254ff6c15..5c5905abdb9f07 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import {
EuiAccordion,
EuiToolTip,
@@ -31,6 +31,7 @@ import { i18n } from '@kbn/i18n';
import { AggConfig } from '../../..';
import { DefaultEditorAggParams } from './agg_params';
import { DefaultEditorAggCommonProps } from './agg_common_props';
+import { AGGS_ACTION_KEYS, AggsAction } from './agg_group_state';
export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps {
agg: AggConfig;
@@ -41,6 +42,7 @@ export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps {
isDraggable: boolean;
isLastBucket: boolean;
isRemovable: boolean;
+ setAggsState: React.Dispatch;
}
function DefaultEditorAgg({
@@ -57,12 +59,12 @@ function DefaultEditorAgg({
metricAggs,
lastParentPipelineAggTitle,
state,
- onAggParamsChange,
+ setAggParamValue,
+ setStateParamValue,
onAggTypeChange,
onToggleEnableAgg,
removeAgg,
- setTouched,
- setValidity,
+ setAggsState,
}: DefaultEditorAggProps) {
const [isEditorOpen, setIsEditorOpen] = useState((agg as any).brandNew);
const [validState, setValidState] = useState(true);
@@ -103,8 +105,8 @@ function DefaultEditorAgg({
useEffect(() => {
if (isLastBucketAgg && ['date_histogram', 'histogram'].includes(agg.type.name)) {
- onAggParamsChange(
- agg.params,
+ setAggParamValue(
+ agg.id,
'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
@@ -113,17 +115,38 @@ function DefaultEditorAgg({
}
}, [lastParentPipelineAggTitle, isLastBucket, agg.type]);
- const onToggle = (isOpen: boolean) => {
- setIsEditorOpen(isOpen);
- if (!isOpen) {
- setTouched(true);
- }
- };
+ const setTouched = useCallback(
+ (touched: boolean) => {
+ setAggsState({
+ type: AGGS_ACTION_KEYS.TOUCHED,
+ payload: touched,
+ aggId: agg.id,
+ });
+ },
+ [setAggsState]
+ );
- const onSetValidity = (isValid: boolean) => {
- setValidity(isValid);
- setValidState(isValid);
- };
+ const setValidity = useCallback(
+ (isValid: boolean) => {
+ setAggsState({
+ type: AGGS_ACTION_KEYS.VALID,
+ payload: isValid,
+ aggId: agg.id,
+ });
+ setValidState(isValid);
+ },
+ [setAggsState]
+ );
+
+ const onToggle = useCallback(
+ (isOpen: boolean) => {
+ setIsEditorOpen(isOpen);
+ if (!isOpen) {
+ setTouched(true);
+ }
+ },
+ [setTouched]
+ );
const renderAggButtons = () => {
const actionIcons = [];
@@ -146,7 +169,7 @@ function DefaultEditorAgg({
color: 'text',
disabled: isDisabled,
type: 'eye',
- onClick: () => onToggleEnableAgg(agg, false),
+ onClick: () => onToggleEnableAgg(agg.id, false),
tooltip: i18n.translate('common.ui.vis.editors.agg.disableAggButtonTooltip', {
defaultMessage: 'Disable aggregation',
}),
@@ -158,7 +181,7 @@ function DefaultEditorAgg({
id: 'enableAggregation',
color: 'text',
type: 'eyeClosed',
- onClick: () => onToggleEnableAgg(agg, true),
+ onClick: () => onToggleEnableAgg(agg.id, true),
tooltip: i18n.translate('common.ui.vis.editors.agg.enableAggButtonTooltip', {
defaultMessage: 'Enable aggregation',
}),
@@ -180,7 +203,7 @@ function DefaultEditorAgg({
id: 'removeDimension',
color: 'danger',
type: 'cross',
- onClick: () => removeAgg(agg),
+ onClick: () => removeAgg(agg.id),
tooltip: i18n.translate('common.ui.vis.editors.agg.removeDimensionButtonTooltip', {
defaultMessage: 'Remove dimension',
}),
@@ -248,9 +271,10 @@ function DefaultEditorAgg({
{SchemaComponent && (
)}
>
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts
index 232eaba76f3a1f..a0ddc9a757cc72 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts
@@ -18,29 +18,32 @@
*/
import { AggType } from 'ui/agg_types';
-import { AggConfig, VisState, VisParams } from '../../..';
-import { AggParams } from '../agg_params';
+import { AggConfig, VisState, VisParams } from 'ui/vis';
import { AggGroupNames } from '../agg_groups';
+import { Schema } from '../schemas';
-export type OnAggParamsChange = <
- Params extends AggParams | VisParams,
- ParamName extends keyof Params
->(
- params: Params,
- paramName: ParamName,
- value: Params[ParamName]
-) => void;
+type AggId = AggConfig['id'];
+type AggParams = AggConfig['params'];
-export interface DefaultEditorAggCommonProps {
+export type AddSchema = (schemas: Schema) => void;
+export type ReorderAggs = (sourceAgg: AggConfig, destinationAgg: AggConfig) => void;
+
+export interface DefaultEditorCommonProps {
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;
+ setAggParamValue: (
+ aggId: AggId,
+ paramName: T,
+ value: AggParams[T]
+ ) => void;
+ onAggTypeChange: (aggId: AggId, aggType: AggType) => void;
+}
+
+export interface DefaultEditorAggCommonProps extends DefaultEditorCommonProps {
+ lastParentPipelineAggTitle?: string;
+ setStateParamValue: (paramName: T, value: VisParams[T]) => void;
+ onToggleEnableAgg: (aggId: AggId, isEnable: boolean) => void;
+ removeAgg: (aggId: AggId) => void;
}
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx
index 05e8c44b9c720d..ae36503c161336 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx
@@ -109,8 +109,9 @@ describe('DefaultEditorAgg component', () => {
reorderAggs,
addSchema: () => {},
removeAgg: () => {},
- onAggParamsChange: () => {},
- onAggTypeChange: () => {},
+ setAggParamValue: jest.fn(),
+ setStateParamValue: jest.fn(),
+ onAggTypeChange: jest.fn(),
onToggleEnableAgg: () => {},
};
});
@@ -127,46 +128,6 @@ describe('DefaultEditorAgg component', () => {
expect(setTouched).toBeCalledWith(false);
});
- it('should mark group as touched when all invalid aggs are touched', () => {
- defaultProps.groupName = AggGroupNames.Buckets;
- const comp = mount();
- act(() => {
- const aggProps = comp.find(DefaultEditorAgg).props();
- aggProps.setValidity(false);
- aggProps.setTouched(true);
- });
-
- expect(setTouched).toBeCalledWith(true);
- });
-
- it('should mark group as touched when the form applied', () => {
- const comp = mount();
- act(() => {
- comp
- .find(DefaultEditorAgg)
- .first()
- .props()
- .setValidity(false);
- });
- expect(setTouched).toBeCalledWith(false);
- comp.setProps({ formIsTouched: true });
-
- expect(setTouched).toBeCalledWith(true);
- });
-
- it('should mark group as invalid when at least one agg is invalid', () => {
- const comp = mount();
- act(() => {
- comp
- .find(DefaultEditorAgg)
- .first()
- .props()
- .setValidity(false);
- });
-
- expect(setValidity).toBeCalledWith(false);
- });
-
it('should last bucket has truthy isLastBucket prop', () => {
defaultProps.groupName = AggGroupNames.Buckets;
const comp = mount();
@@ -182,10 +143,10 @@ describe('DefaultEditorAgg component', () => {
comp.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } });
});
- expect(reorderAggs).toHaveBeenCalledWith([
- defaultProps.state.aggs.aggs[1],
+ expect(reorderAggs).toHaveBeenCalledWith(
defaultProps.state.aggs.aggs[0],
- ]);
+ defaultProps.state.aggs.aggs[1]
+ );
});
it('should show add button when schemas count is less than max', () => {
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx
index 1c8690f6deb795..7416c36bd5cf1d 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { useEffect, useReducer, useMemo } from 'react';
+import React, { useEffect, useReducer, useMemo, useCallback } from 'react';
import {
EuiTitle,
EuiDragDropContext,
@@ -34,7 +34,7 @@ import { AggConfig } from '../../../../agg_types/agg_config';
import { aggGroupNamesMap, AggGroupNames } from '../agg_groups';
import { DefaultEditorAgg } from './agg';
import { DefaultEditorAggAdd } from './agg_add';
-import { DefaultEditorAggCommonProps } from './agg_common_props';
+import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props';
import {
isInvalidAggsTouched,
isAggRemovable,
@@ -46,8 +46,10 @@ import { Schema } from '../schemas';
export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps {
schemas: Schema[];
- addSchema: (schemas: Schema) => void;
- reorderAggs: (group: AggConfig[]) => void;
+ addSchema: AddSchema;
+ reorderAggs: ReorderAggs;
+ setValidity(modelName: string, value: boolean): void;
+ setTouched(isTouched: boolean): void;
}
function DefaultEditorAggGroup({
@@ -58,7 +60,8 @@ function DefaultEditorAggGroup({
state,
schemas = [],
addSchema,
- onAggParamsChange,
+ setAggParamValue,
+ setStateParamValue,
onAggTypeChange,
onToggleEnableAgg,
removeAgg,
@@ -68,8 +71,10 @@ function DefaultEditorAggGroup({
}: DefaultEditorAggGroupProps) {
const groupNameLabel = (aggGroupNamesMap() as any)[groupName];
// e.g. buckets can have no aggs
- const group: AggConfig[] =
- state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || [];
+ const group: AggConfig[] = useMemo(
+ () => state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || [],
+ [state.aggs.aggs]
+ );
const stats = {
max: 0,
@@ -101,7 +106,7 @@ function DefaultEditorAggGroup({
// 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]);
+ }, [isAllAggsTouched, setTouched]);
useEffect(() => {
// when not all invalid aggs are touched and formIsTouched becomes true, it means that Apply button was clicked.
@@ -118,38 +123,21 @@ function DefaultEditorAggGroup({
}, [formIsTouched]);
useEffect(() => {
- setValidity(isGroupValid);
- }, [isGroupValid]);
-
- const onDragEnd: DragDropContextProps['onDragEnd'] = ({ source, destination }) => {
- 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: string, touched: boolean) => {
- setAggsState({
- type: AGGS_ACTION_KEYS.TOUCHED,
- payload: touched,
- aggId,
- });
- };
-
- const setValidityHandler = (aggId: string, valid: boolean) => {
- setAggsState({
- type: AGGS_ACTION_KEYS.VALID,
- payload: valid,
- aggId,
- });
- };
+ setValidity(`aggGroup__${groupName}`, isGroupValid);
+ }, [groupName, isGroupValid, setValidity]);
+
+ const onDragEnd: DragDropContextProps['onDragEnd'] = useCallback(
+ ({ source, destination }) => {
+ if (source && destination) {
+ reorderAggs(group[source.index], group[destination.index]);
+ }
+ },
+ [reorderAggs, group]
+ );
return (
-
+
{groupNameLabel}
@@ -184,12 +172,12 @@ function DefaultEditorAggGroup({
lastParentPipelineAggTitle={lastParentPipelineAggTitle}
metricAggs={metricAggs}
state={state}
- onAggParamsChange={onAggParamsChange}
+ setAggParamValue={setAggParamValue}
+ setStateParamValue={setStateParamValue}
onAggTypeChange={onAggTypeChange}
onToggleEnableAgg={onToggleEnableAgg}
removeAgg={removeAgg}
- setTouched={isTouched => setTouchedHandler(agg.id, isTouched)}
- setValidity={isValid => setValidityHandler(agg.id, isValid)}
+ setAggsState={setAggsState}
/>
)}
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
index 980889743c20d1..0b787e45a5008e 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx
@@ -33,7 +33,7 @@ export interface AggsState {
[aggId: string]: AggsItem;
}
-interface AggsAction {
+export interface AggsAction {
type: AGGS_ACTION_KEYS;
payload: boolean;
aggId: string;
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx
index 72c802d7d237e0..d3bbf3cc9903a1 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx
@@ -17,18 +17,59 @@
* under the License.
*/
-import React, { useEffect } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { AggParamEditorProps, AggParamCommonProps } from './agg_param_props';
-import { OnAggParamsChange } from './agg_common_props';
+import { DefaultEditorAggCommonProps } from './agg_common_props';
+import { AGG_PARAMS_ACTION_KEYS, AggParamsAction } from './agg_params_state';
interface DefaultEditorAggParamProps extends AggParamCommonProps {
paramEditor: React.ComponentType>;
- onChange: OnAggParamsChange;
+ setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue'];
+ onChangeParamsState: React.Dispatch;
}
function DefaultEditorAggParam(props: DefaultEditorAggParamProps) {
- const { agg, aggParam, paramEditor: ParamEditor, onChange, setValidity, ...rest } = props;
+ const {
+ agg,
+ aggParam,
+ paramEditor: ParamEditor,
+ setAggParamValue,
+ onChangeParamsState,
+ ...rest
+ } = props;
+
+ const setValidity = useCallback(
+ (valid: boolean) => {
+ onChangeParamsState({
+ type: AGG_PARAMS_ACTION_KEYS.VALID,
+ paramName: aggParam.name,
+ payload: valid,
+ });
+ },
+ [onChangeParamsState, aggParam.name]
+ );
+
+ // setTouched can be called from sub-agg which passes a parameter
+ const setTouched = useCallback(
+ (isTouched: boolean = true) => {
+ onChangeParamsState({
+ type: AGG_PARAMS_ACTION_KEYS.TOUCHED,
+ paramName: aggParam.name,
+ payload: isTouched,
+ });
+ },
+ [onChangeParamsState, aggParam.name]
+ );
+
+ const setValue = useCallback(
+ (value: T) => {
+ if (props.value !== value) {
+ setAggParamValue(agg.id, aggParam.name, value);
+ }
+ },
+ [setAggParamValue, agg.id, aggParam.name, props.value]
+ );
useEffect(() => {
if (aggParam.shouldShow && !aggParam.shouldShow(agg)) {
@@ -45,7 +86,8 @@ function DefaultEditorAggParam(props: DefaultEditorAggParamProps) {
agg={agg}
aggParam={aggParam}
setValidity={setValidity}
- setValue={(value: T) => onChange(agg.params, aggParam.name, value)}
+ setTouched={setTouched}
+ setValue={setValue}
{...rest}
/>
);
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts
index df77ea9e43eabc..953f49d84f5c55 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts
@@ -22,28 +22,27 @@ import { AggConfig } from '../../../../agg_types/agg_config';
import { ComboBoxGroupedOptions } from '../utils';
import { EditorConfig } from '../../config/types';
import { VisState } from '../../..';
-import { SubAggParamsProp } from './agg_params';
import { Field } from '../../../../../../../plugins/data/public';
// NOTE: we cannot export the interface with export { InterfaceName }
// as there is currently a bug on babel typescript transform plugin for it
// https://github.com/babel/babel/issues/7641
//
-export interface AggParamCommonProps {
+export interface AggParamCommonProps {
agg: AggConfig;
- aggParam: AggParam;
+ aggParam: P;
disabled?: boolean;
editorConfig: EditorConfig;
+ formIsTouched: boolean;
indexedFields?: ComboBoxGroupedOptions;
showValidation: boolean;
state: VisState;
value?: T;
metricAggs: AggConfig[];
- subAggParams: SubAggParamsProp;
- setValidity(isValid: boolean): void;
- setTouched(): void;
}
-export interface AggParamEditorProps extends AggParamCommonProps {
+export interface AggParamEditorProps extends AggParamCommonProps {
setValue(value?: T): void;
+ setValidity(isValid: boolean): void;
+ setTouched(): void;
}
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx
index 9f3f5bff3f56e8..8c59d69da94789 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx
@@ -21,6 +21,7 @@ import React from 'react';
import { mount, shallow } from 'enzyme';
import { AggConfig, VisState } from '../../..';
import { DefaultEditorAggParams, DefaultEditorAggParamsProps } from './agg_params';
+import { AggGroupNames } from '../agg_groups';
import { IndexPattern } from '../../../../../../../plugins/data/public';
const mockEditorConfig = {
@@ -79,7 +80,7 @@ jest.mock('./agg_param', () => ({
}));
describe('DefaultEditorAggParams component', () => {
- let onAggParamsChange: jest.Mock;
+ let setAggParamValue: jest.Mock;
let onAggTypeChange: jest.Mock;
let setTouched: jest.Mock;
let setValidity: jest.Mock;
@@ -87,7 +88,7 @@ describe('DefaultEditorAggParams component', () => {
let defaultProps: DefaultEditorAggParamsProps;
beforeEach(() => {
- onAggParamsChange = jest.fn();
+ setAggParamValue = jest.fn();
onAggTypeChange = jest.fn();
setTouched = jest.fn();
setValidity = jest.fn();
@@ -99,13 +100,16 @@ describe('DefaultEditorAggParams component', () => {
params: [{ name: 'interval', deserialize: intervalDeserialize }],
},
params: {},
+ schema: {
+ title: '',
+ },
} as any) as AggConfig,
- groupName: 'metrics',
+ groupName: AggGroupNames.Metrics,
formIsTouched: false,
indexPattern: {} as IndexPattern,
metricAggs: [],
state: {} as VisState,
- onAggParamsChange,
+ setAggParamValue,
onAggTypeChange,
setTouched,
setValidity,
@@ -131,16 +135,16 @@ describe('DefaultEditorAggParams component', () => {
it('should set fixed and default values when editorConfig is defined (works in rollup index)', () => {
mount();
- expect(onAggParamsChange).toHaveBeenNthCalledWith(
+ expect(setAggParamValue).toHaveBeenNthCalledWith(
1,
- defaultProps.agg.params,
+ defaultProps.agg.id,
'useNormalizedEsInterval',
false
);
expect(intervalDeserialize).toHaveBeenCalledWith('1m');
- expect(onAggParamsChange).toHaveBeenNthCalledWith(
+ expect(setAggParamValue).toHaveBeenNthCalledWith(
2,
- defaultProps.agg.params,
+ defaultProps.agg.id,
'interval',
'deserialized'
);
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx
index c62e2837908c74..0f47d9a555d214 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx
@@ -17,60 +17,48 @@
* under the License.
*/
-import React, { useReducer, useEffect, useMemo } from 'react';
+import React, { useCallback, useReducer, useEffect, useMemo } from 'react';
import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import useUnmount from 'react-use/lib/useUnmount';
-import { VisState } from 'ui/vis';
-import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/';
+import { AggConfig } from 'ui/agg_types/';
import { IndexPattern } from '../../../../../../../plugins/data/public';
import { DefaultEditorAggSelect } from './agg_select';
import { DefaultEditorAggParam } from './agg_param';
import {
getAggParamsToRender,
- getError,
getAggTypeOptions,
- ParamInstance,
isInvalidParamsTouched,
} from './agg_params_helper';
import {
aggTypeReducer,
- AGG_TYPE_ACTION_KEYS,
aggParamsReducer,
AGG_PARAMS_ACTION_KEYS,
initAggParamsState,
- AggParamsItem,
} from './agg_params_state';
import { editorConfigProviders } from '../../config/editor_config_providers';
import { FixedParam, TimeIntervalParam, EditorParamConfig } from '../../config/types';
import { AggGroupNames } from '../agg_groups';
-import { OnAggParamsChange } from './agg_common_props';
+import { DefaultEditorCommonProps } from './agg_common_props';
const FIXED_VALUE_PROP = 'fixedValue';
const DEFAULT_PROP = 'default';
type EditorParamConfigType = EditorParamConfig & {
[key: string]: unknown;
};
-export interface SubAggParamsProp {
- formIsTouched: boolean;
- onAggParamsChange: OnAggParamsChange;
- onAggTypeChange: (agg: AggConfig, aggType: AggType) => void;
-}
-export interface DefaultEditorAggParamsProps extends SubAggParamsProp {
+
+export interface DefaultEditorAggParamsProps extends DefaultEditorCommonProps {
agg: AggConfig;
aggError?: string;
aggIndex?: number;
aggIsTooLow?: boolean;
className?: string;
disabledParams?: string[];
- groupName: string;
indexPattern: IndexPattern;
- metricAggs: AggConfig[];
- state: VisState;
- setTouched: (isTouched: boolean) => void;
setValidity: (isValid: boolean) => void;
+ setTouched: (isTouched: boolean) => void;
}
function DefaultEditorAggParams({
@@ -84,20 +72,34 @@ function DefaultEditorAggParams({
formIsTouched,
indexPattern,
metricAggs,
- state = {} as VisState,
- onAggParamsChange,
+ state,
+ setAggParamValue,
onAggTypeChange,
setTouched,
setValidity,
}: DefaultEditorAggParamsProps) {
- const groupedAggTypeOptions = getAggTypeOptions(agg, indexPattern, groupName);
- const errors = getError(agg, aggIsTooLow);
-
- const editorConfig = useMemo(
- () => editorConfigProviders.getConfigForAgg((aggTypes as any)[groupName], indexPattern, agg),
- [groupName, agg.type]
- );
- const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state });
+ const groupedAggTypeOptions = useMemo(() => getAggTypeOptions(agg, indexPattern, groupName), [
+ agg,
+ indexPattern,
+ groupName,
+ ]);
+ const error = aggIsTooLow
+ ? i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', {
+ defaultMessage: '"{schema}" aggs must run before all other buckets!',
+ values: { schema: agg.schema.title },
+ })
+ : '';
+
+ const editorConfig = useMemo(() => editorConfigProviders.getConfigForAgg(indexPattern, agg), [
+ indexPattern,
+ agg,
+ ]);
+ const params = useMemo(() => getAggParamsToRender({ agg, editorConfig, metricAggs, state }), [
+ agg,
+ editorConfig,
+ metricAggs,
+ state,
+ ]);
const allParams = [...params.basic, ...params.advanced];
const [paramsState, onChangeParamsState] = useReducer(
aggParamsReducer,
@@ -107,21 +109,30 @@ function DefaultEditorAggParams({
const [aggType, onChangeAggType] = useReducer(aggTypeReducer, { touched: false, valid: true });
const isFormValid =
- !errors.length &&
+ !error &&
aggType.valid &&
Object.entries(paramsState).every(([, paramState]) => paramState.valid);
const isAllInvalidParamsTouched =
- !!errors.length || isInvalidParamsTouched(agg.type, aggType, paramsState);
+ !!error || isInvalidParamsTouched(agg.type, aggType, paramsState);
+
+ const onAggSelect = useCallback(
+ value => {
+ if (agg.type !== value) {
+ onAggTypeChange(agg.id, value);
+ // reset touched and valid of params
+ onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET });
+ }
+ },
+ [onAggTypeChange, agg]
+ );
// reset validity before component destroyed
useUnmount(() => setValidity(true));
useEffect(() => {
Object.entries(editorConfig).forEach(([param, paramConfig]) => {
- const paramOptions = agg.type.params.find(
- (paramOption: AggParam) => paramOption.name === param
- );
+ const paramOptions = agg.type.params.find(paramOption => paramOption.name === param);
const hasFixedValue = paramConfig.hasOwnProperty(FIXED_VALUE_PROP);
const hasDefault = paramConfig.hasOwnProperty(DEFAULT_PROP);
@@ -142,7 +153,11 @@ function DefaultEditorAggParams({
} else {
newValue = typedParamConfig[property];
}
- onAggParamsChange(agg.params, param, newValue);
+
+ // this check is obligatory to avoid infinite render, because setAggParamValue creates a brand new agg object
+ if (agg.params[param] !== newValue) {
+ setAggParamValue(agg.id, param, newValue);
+ }
}
});
}, [editorConfig]);
@@ -160,43 +175,11 @@ function DefaultEditorAggParams({
setTouched(isAllInvalidParamsTouched);
}, [isAllInvalidParamsTouched]);
- const renderParam = (paramInstance: ParamInstance, model: AggParamsItem) => {
- return (
- {
- onChangeParamsState({
- type: AGG_PARAMS_ACTION_KEYS.VALID,
- paramName: paramInstance.aggParam.name,
- payload: valid,
- });
- }}
- // setTouched can be called from sub-agg which passes a parameter
- setTouched={(isTouched: boolean = true) => {
- onChangeParamsState({
- type: AGG_PARAMS_ACTION_KEYS.TOUCHED,
- paramName: paramInstance.aggParam.name,
- payload: isTouched,
- });
- }}
- subAggParams={{
- onAggParamsChange,
- onAggTypeChange,
- formIsTouched,
- }}
- {...paramInstance}
- />
- );
- };
-
return (
= 1 && groupName === AggGroupNames.Buckets}
showValidation={formIsTouched || aggType.touched}
- setValue={value => {
- onAggTypeChange(agg, value);
- // reset touched and valid of params
- onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET });
- }}
- setTouched={() => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true })}
- setValidity={valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid })}
+ setValue={onAggSelect}
+ onChangeAggType={onChangeAggType}
/>
- {params.basic.map((param: ParamInstance) => {
+ {params.basic.map(param => {
const model = paramsState[param.aggParam.name] || {
touched: false,
valid: true,
};
- return renderParam(param, model);
+ return (
+
+ );
})}
{params.advanced.length ? (
@@ -238,12 +226,23 @@ function DefaultEditorAggParams({
)}
>
- {params.advanced.map((param: ParamInstance) => {
+ {params.advanced.map(param => {
const model = paramsState[param.aggParam.name] || {
touched: false,
valid: true,
};
- return renderParam(param, model);
+
+ return (
+
+ );
})}
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
index fb37dd5d94618c..c983bb7813de7f 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts
@@ -22,7 +22,6 @@ import { AggType } from 'ui/agg_types';
import { IndexedArray } from 'ui/indexed_array';
import {
getAggParamsToRender,
- getError,
getAggTypeOptions,
isInvalidParamsTouched,
} from './agg_params_helper';
@@ -162,20 +161,6 @@ describe('DefaultEditorAggParams helpers', () => {
});
});
- describe('getError', () => {
- it('should not have any errors', () => {
- const errors = getError({ schema: { title: 'Split series' } } as AggConfig, false);
-
- expect(errors).toEqual([]);
- });
-
- it('should push an error if an agg is too low', () => {
- const errors = getError({ schema: { title: 'Split series' } } as AggConfig, true);
-
- expect(errors).toEqual(['"Split series" aggs must run before all other buckets!']);
- });
- });
-
describe('getAggTypeOptions', () => {
it('should return agg type options grouped by subtype', () => {
const indexPattern = {} as IndexPattern;
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts
index e0e014f69ef3f2..3970238a684353 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts
@@ -18,7 +18,6 @@
*/
import { get, isEmpty } from 'lodash';
-import { i18n } from '@kbn/i18n';
import { aggTypeFilters } from 'ui/agg_types/filter';
import { aggTypes, AggParam, FieldParamType, AggType } from 'ui/agg_types';
import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter';
@@ -91,27 +90,13 @@ function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamIns
metricAggs,
state,
value: agg.params[param.name],
- } as ParamInstance);
+ });
}
});
return params;
}
-function getError(agg: AggConfig, aggIsTooLow: boolean) {
- const errors = [];
- if (aggIsTooLow) {
- errors.push(
- i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', {
- defaultMessage: '"{schema}" aggs must run before all other buckets!',
- values: { schema: agg.schema.title },
- })
- );
- }
-
- return errors;
-}
-
function getAggTypeOptions(
agg: AggConfig,
indexPattern: IndexPattern,
@@ -148,4 +133,4 @@ function isInvalidParamsTouched(
return invalidParams.every(param => param.touched);
}
-export { getAggParamsToRender, getError, getAggTypeOptions, isInvalidParamsTouched };
+export { getAggParamsToRender, getAggTypeOptions, isInvalidParamsTouched };
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx
index 443c655912a553..6b6bb93b29b3e0 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { get, has } from 'lodash';
-import React, { useEffect } from 'react';
+import React, { useEffect, useCallback } from 'react';
import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -26,6 +26,7 @@ import { AggType } from 'ui/agg_types';
import { documentationLinks } from '../../../../documentation_links/documentation_links';
import { ComboBoxGroupedOptions } from '../utils';
import { IndexPattern } from '../../../../../../../plugins/data/public';
+import { AGG_TYPE_ACTION_KEYS, AggTypeAction } from './agg_params_state';
interface DefaultEditorAggSelectProps {
aggError?: string;
@@ -35,9 +36,8 @@ interface DefaultEditorAggSelectProps {
showValidation: boolean;
isSubAggregation: boolean;
value: AggType;
- setValidity: (isValid: boolean) => void;
+ onChangeAggType: React.Dispatch;
setValue: (aggType: AggType) => void;
- setTouched: () => void;
}
function DefaultEditorAggSelect({
@@ -49,8 +49,7 @@ function DefaultEditorAggSelect({
aggTypeOptions,
showValidation,
isSubAggregation,
- setTouched,
- setValidity,
+ onChangeAggType,
}: DefaultEditorAggSelectProps) {
const selectedOptions: ComboBoxGroupedOptions = value
? [{ label: value.title, target: value }]
@@ -101,6 +100,25 @@ function DefaultEditorAggSelect({
const isValid = !!value && !errors.length;
+ const onChange = useCallback(
+ (options: EuiComboBoxOptionProps[]) => {
+ const selectedOption = get(options, '0.target');
+ if (selectedOption) {
+ setValue(selectedOption as AggType);
+ }
+ },
+ [setValue]
+ );
+
+ const setTouched = useCallback(
+ () => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true }),
+ [onChangeAggType]
+ );
+ const setValidity = useCallback(
+ valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid }),
+ [onChangeAggType]
+ );
+
useEffect(() => {
setValidity(isValid);
}, [isValid]);
@@ -111,13 +129,6 @@ function DefaultEditorAggSelect({
}
}, [errors.length]);
- const onChange = (options: EuiComboBoxOptionProps[]) => {
- const selectedOption = get(options, '0.target');
- if (selectedOption) {
- setValue(selectedOption as AggType);
- }
- };
-
return (
;
+ vis: Vis;
+}
+
+function DefaultEditorControls({
+ applyChanges,
+ isDirty,
+ isInvalid,
+ isTouched,
+ dispatch,
+ vis,
+}: DefaultEditorControlsProps) {
+ const { enableAutoApply } = vis.type.editorConfig;
+ const [autoApplyEnabled, setAutoApplyEnabled] = useState(false);
+ const toggleAutoApply = useCallback(e => setAutoApplyEnabled(e.target.checked), []);
+ const onClickDiscard = useCallback(() => dispatch(discardChanges(vis)), [dispatch, vis]);
+
+ useDebounce(
+ () => {
+ if (autoApplyEnabled && isDirty) {
+ applyChanges();
+ }
+ },
+ 300,
+ [isDirty, autoApplyEnabled, applyChanges]
+ );
+
+ return (
+
+ {!autoApplyEnabled && (
+
+
+
+
+
+
+
+
+ {isInvalid && isTouched ? (
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ )}
+ {enableAutoApply && (
+
+
+
+ )}
+
+ );
+}
+
+export { DefaultEditorControls };
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx
new file mode 100644
index 00000000000000..4481ec5cb9b835
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx
@@ -0,0 +1,135 @@
+/*
+ * 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, { useMemo, useCallback } from 'react';
+import { findLast } from 'lodash';
+import { EuiSpacer } from '@elastic/eui';
+
+import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper';
+import { AggConfig } from 'ui/agg_types';
+import { MetricAggType } from 'ui/agg_types/metrics/metric_agg_type';
+import { VisState } from 'ui/vis';
+import { DefaultEditorAggGroup } from '../agg_group';
+import { AggGroupNames } from '../../agg_groups';
+import {
+ EditorAction,
+ addNewAgg,
+ removeAgg,
+ reorderAggs,
+ setAggParamValue,
+ changeAggType,
+ toggleEnabledAgg,
+} from './state';
+import { ISchemas } from '../../schemas';
+import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props';
+
+export interface DefaultEditorDataTabProps {
+ dispatch: React.Dispatch;
+ formIsTouched: boolean;
+ isTabSelected: boolean;
+ metricAggs: AggConfig[];
+ schemas: ISchemas;
+ state: VisState;
+ setTouched(isTouched: boolean): void;
+ setValidity(modelName: string, value: boolean): void;
+ setStateValue: DefaultEditorAggCommonProps['setStateParamValue'];
+}
+
+function DefaultEditorDataTab({
+ dispatch,
+ formIsTouched,
+ metricAggs,
+ schemas,
+ state,
+ setTouched,
+ setValidity,
+ setStateValue,
+}: DefaultEditorDataTabProps) {
+ const lastParentPipelineAgg = useMemo(
+ () =>
+ findLast(
+ metricAggs,
+ ({ type }: { type: MetricAggType }) => type.subtype === parentPipelineAggHelper.subtype
+ ),
+ [metricAggs]
+ );
+ const lastParentPipelineAggTitle = lastParentPipelineAgg && lastParentPipelineAgg.type.title;
+
+ const addSchema: AddSchema = useCallback(schema => dispatch(addNewAgg(schema)), [dispatch]);
+
+ const onAggRemove: DefaultEditorAggCommonProps['removeAgg'] = useCallback(
+ aggId => dispatch(removeAgg(aggId)),
+ [dispatch]
+ );
+
+ const onReorderAggs: ReorderAggs = useCallback((...props) => dispatch(reorderAggs(...props)), [
+ dispatch,
+ ]);
+
+ const onAggParamValueChange: DefaultEditorAggCommonProps['setAggParamValue'] = useCallback(
+ (...props) => dispatch(setAggParamValue(...props)),
+ [dispatch]
+ );
+
+ const onAggTypeChange: DefaultEditorAggCommonProps['onAggTypeChange'] = useCallback(
+ (...props) => dispatch(changeAggType(...props)),
+ [dispatch]
+ );
+
+ const onToggleEnableAgg: DefaultEditorAggCommonProps['onToggleEnableAgg'] = useCallback(
+ (...props) => dispatch(toggleEnabledAgg(...props)),
+ [dispatch]
+ );
+
+ const commonProps = {
+ addSchema,
+ formIsTouched,
+ lastParentPipelineAggTitle,
+ metricAggs,
+ state,
+ reorderAggs: onReorderAggs,
+ setAggParamValue: onAggParamValueChange,
+ setStateParamValue: setStateValue,
+ onAggTypeChange,
+ onToggleEnableAgg,
+ setValidity,
+ setTouched,
+ removeAgg: onAggRemove,
+ };
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
+
+export { DefaultEditorDataTab };
diff --git a/src/legacy/ui/public/vis/editor_size.js b/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts
similarity index 85%
rename from src/legacy/ui/public/vis/editor_size.js
rename to src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts
index 5383772b3ad002..31228aad85d1ec 100644
--- a/src/legacy/ui/public/vis/editor_size.js
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts
@@ -17,8 +17,6 @@
* under the License.
*/
-export const DefaultEditorSize = {
- SMALL: 'small',
- MEDIUM: 'medium',
- LARGE: 'large',
-};
+export { DefaultEditorSideBar } from './sidebar';
+export { DefaultEditorDataTab } from './data_tab';
+export { OptionTab } from './navbar';
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx
new file mode 100644
index 00000000000000..a1b5003a092f76
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { EuiTabs, EuiTab } from '@elastic/eui';
+
+import { VisOptionsProps } from '../../vis_options_props';
+import { DefaultEditorDataTabProps } from './data_tab';
+
+export interface OptionTab {
+ editor: React.ComponentType;
+ name: string;
+ title: string;
+}
+
+interface DefaultEditorNavBarProps {
+ optionTabs: OptionTab[];
+ selectedTab: string;
+ setSelectedTab(name: string): void;
+}
+
+function DefaultEditorNavBar({
+ selectedTab,
+ setSelectedTab,
+ optionTabs,
+}: DefaultEditorNavBarProps) {
+ return (
+
+ {optionTabs.map(({ name, title }) => (
+ setSelectedTab(name)}
+ >
+ {title}
+
+ ))}
+
+ );
+}
+
+export { DefaultEditorNavBar };
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx
new file mode 100644
index 00000000000000..bf35c46dbb7b53
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx
@@ -0,0 +1,226 @@
+/*
+ * 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, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect } from 'react';
+import { get, isEqual } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
+
+import { Vis } from 'ui/vis';
+import { PersistedState } from 'ui/persisted_state';
+import { DefaultEditorNavBar, OptionTab } from './navbar';
+import { DefaultEditorControls } from './controls';
+import { setStateParamValue, useEditorReducer, useEditorFormState } from './state';
+import { AggGroupNames } from '../../agg_groups';
+import { DefaultEditorAggCommonProps } from '../agg_common_props';
+
+interface DefaultEditorSideBarProps {
+ isCollapsed: boolean;
+ onClickCollapse: () => void;
+ optionTabs: OptionTab[];
+ uiState: PersistedState;
+ vis: Vis;
+}
+
+function DefaultEditorSideBar({
+ isCollapsed,
+ onClickCollapse,
+ optionTabs,
+ uiState,
+ vis,
+}: DefaultEditorSideBarProps) {
+ const [selectedTab, setSelectedTab] = useState(optionTabs[0].name);
+ const [isDirty, setDirty] = useState(false);
+ const [state, dispatch] = useEditorReducer(vis);
+ const { formState, setTouched, setValidity, resetValidity } = useEditorFormState();
+
+ const responseAggs = useMemo(() => state.aggs.getResponseAggs(), [state.aggs]);
+ const metricAggs = useMemo(
+ () => responseAggs.filter(agg => get(agg, 'schema.group') === AggGroupNames.Metrics),
+ [responseAggs]
+ );
+ const hasHistogramAgg = useMemo(() => responseAggs.some(agg => agg.type.name === 'histogram'), [
+ responseAggs,
+ ]);
+
+ const setStateValidity = useCallback(
+ (value: boolean) => {
+ setValidity('visOptions', value);
+ },
+ [setValidity]
+ );
+
+ const setStateValue: DefaultEditorAggCommonProps['setStateParamValue'] = useCallback(
+ (paramName, value) => {
+ const shouldUpdate = !isEqual(state.params[paramName], value);
+
+ if (shouldUpdate) {
+ dispatch(setStateParamValue(paramName, value));
+ }
+ },
+ [dispatch, state.params]
+ );
+
+ const applyChanges = useCallback(() => {
+ if (formState.invalid || !isDirty) {
+ setTouched(true);
+
+ return;
+ }
+
+ vis.setCurrentState(state);
+ vis.updateState();
+ vis.emit('dirtyStateChange', {
+ isDirty: false,
+ });
+ setTouched(false);
+ }, [vis, state, formState.invalid, setDirty, setTouched, isDirty]);
+
+ const onSubmit: KeyboardEventHandler = useCallback(
+ event => {
+ if (event.ctrlKey && event.keyCode === keyCodes.ENTER) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ applyChanges();
+ }
+ },
+ [applyChanges]
+ );
+
+ useEffect(() => {
+ vis.on('dirtyStateChange', ({ isDirty: dirty }: { isDirty: boolean }) => {
+ setDirty(dirty);
+
+ if (!dirty) {
+ resetValidity();
+ }
+ });
+ }, [resetValidity, vis]);
+
+ const dataTabProps = {
+ dispatch,
+ formIsTouched: formState.touched,
+ metricAggs,
+ state,
+ schemas: vis.type.schemas,
+ setValidity,
+ setTouched,
+ setStateValue,
+ };
+
+ const optionTabProps = {
+ aggs: state.aggs,
+ hasHistogramAgg,
+ stateParams: state.params,
+ vis,
+ uiState,
+ setValue: setStateValue,
+ setValidity: setStateValidity,
+ setTouched,
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export { DefaultEditorSideBar };
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts
new file mode 100644
index 00000000000000..ab1d65c626ae39
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts
@@ -0,0 +1,171 @@
+/*
+ * 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, Vis, VisParams } from 'ui/vis';
+import { EditorStateActionTypes } from './constants';
+import { Schema } from '../../../schemas';
+
+export interface ActionType {
+ type: T;
+ payload: P;
+}
+
+type AggId = AggConfig['id'];
+type AggParams = AggConfig['params'];
+
+type AddNewAgg = ActionType;
+type DiscardChanges = ActionType;
+type ChangeAggType = ActionType<
+ EditorStateActionTypes.CHANGE_AGG_TYPE,
+ { aggId: AggId; value: AggConfig['type'] }
+>;
+type SetAggParamValue = ActionType<
+ EditorStateActionTypes.SET_AGG_PARAM_VALUE,
+ {
+ aggId: AggId;
+ paramName: T;
+ value: AggParams[T];
+ }
+>;
+type SetStateParamValue = ActionType<
+ EditorStateActionTypes.SET_STATE_PARAM_VALUE,
+ { paramName: T; value: AggParams[T] }
+>;
+type RemoveAgg = ActionType;
+type ReorderAggs = ActionType<
+ EditorStateActionTypes.REORDER_AGGS,
+ { sourceAgg: AggConfig; destinationAgg: AggConfig }
+>;
+type ToggleEnabledAgg = ActionType<
+ EditorStateActionTypes.TOGGLE_ENABLED_AGG,
+ { aggId: AggId; enabled: AggConfig['enabled'] }
+>;
+type UpdateStateParams = ActionType<
+ EditorStateActionTypes.UPDATE_STATE_PARAMS,
+ { params: VisParams }
+>;
+
+export type EditorAction =
+ | AddNewAgg
+ | DiscardChanges
+ | ChangeAggType
+ | SetAggParamValue
+ | SetStateParamValue
+ | RemoveAgg
+ | ReorderAggs
+ | ToggleEnabledAgg
+ | UpdateStateParams;
+
+export interface EditorActions {
+ addNewAgg(schema: Schema): AddNewAgg;
+ discardChanges(vis: Vis): DiscardChanges;
+ changeAggType(aggId: AggId, value: AggConfig['type']): ChangeAggType;
+ setAggParamValue(
+ aggId: AggId,
+ paramName: T,
+ value: AggParams[T]
+ ): SetAggParamValue;
+ setStateParamValue(
+ paramName: T,
+ value: AggParams[T]
+ ): SetStateParamValue;
+ removeAgg(aggId: AggId): RemoveAgg;
+ reorderAggs(sourceAgg: AggConfig, destinationAgg: AggConfig): ReorderAggs;
+ toggleEnabledAgg(aggId: AggId, enabled: AggConfig['enabled']): ToggleEnabledAgg;
+ updateStateParams(params: VisParams): UpdateStateParams;
+}
+
+const addNewAgg: EditorActions['addNewAgg'] = schema => ({
+ type: EditorStateActionTypes.ADD_NEW_AGG,
+ payload: {
+ schema,
+ },
+});
+
+const discardChanges: EditorActions['discardChanges'] = vis => ({
+ type: EditorStateActionTypes.DISCARD_CHANGES,
+ payload: vis,
+});
+
+const changeAggType: EditorActions['changeAggType'] = (aggId, value) => ({
+ type: EditorStateActionTypes.CHANGE_AGG_TYPE,
+ payload: {
+ aggId,
+ value,
+ },
+});
+
+const setAggParamValue: EditorActions['setAggParamValue'] = (aggId, paramName, value) => ({
+ type: EditorStateActionTypes.SET_AGG_PARAM_VALUE,
+ payload: {
+ aggId,
+ paramName,
+ value,
+ },
+});
+
+const setStateParamValue: EditorActions['setStateParamValue'] = (paramName, value) => ({
+ type: EditorStateActionTypes.SET_STATE_PARAM_VALUE,
+ payload: {
+ paramName,
+ value,
+ },
+});
+
+const removeAgg: EditorActions['removeAgg'] = aggId => ({
+ type: EditorStateActionTypes.REMOVE_AGG,
+ payload: {
+ aggId,
+ },
+});
+
+const reorderAggs: EditorActions['reorderAggs'] = (sourceAgg, destinationAgg) => ({
+ type: EditorStateActionTypes.REORDER_AGGS,
+ payload: {
+ sourceAgg,
+ destinationAgg,
+ },
+});
+
+const toggleEnabledAgg: EditorActions['toggleEnabledAgg'] = (aggId, enabled) => ({
+ type: EditorStateActionTypes.TOGGLE_ENABLED_AGG,
+ payload: {
+ aggId,
+ enabled,
+ },
+});
+
+const updateStateParams: EditorActions['updateStateParams'] = params => ({
+ type: EditorStateActionTypes.UPDATE_STATE_PARAMS,
+ payload: {
+ params,
+ },
+});
+
+export {
+ addNewAgg,
+ discardChanges,
+ changeAggType,
+ setAggParamValue,
+ setStateParamValue,
+ removeAgg,
+ reorderAggs,
+ toggleEnabledAgg,
+ updateStateParams,
+};
diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts
similarity index 66%
rename from src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx
rename to src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts
index d214abecb9c0c7..2c5f5f13848582 100644
--- a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts
@@ -17,15 +17,14 @@
* under the License.
*/
-import React from 'react';
-import { VisOptionsProps } from './vis_options_props';
-
-interface VisOptionsReactWrapperProps extends VisOptionsProps {
- component: React.ComponentType;
-}
-
-function VisOptionsReactWrapper({ component: Component, ...rest }: VisOptionsReactWrapperProps) {
- return ;
+export enum EditorStateActionTypes {
+ ADD_NEW_AGG = 'ADD_NEW_AGG',
+ DISCARD_CHANGES = 'DISCARD_CHANGES',
+ CHANGE_AGG_TYPE = 'CHANGE_AGG_TYPE',
+ SET_AGG_PARAM_VALUE = 'SET_AGG_PARAM_VALUE',
+ SET_STATE_PARAM_VALUE = 'SET_STATE_PARAM_VALUE',
+ TOGGLE_ENABLED_AGG = 'TOGGLE_ENABLED_AGG',
+ REMOVE_AGG = 'REMOVE_AGG',
+ REORDER_AGGS = 'REORDER_AGGS',
+ UPDATE_STATE_PARAMS = 'UPDATE_STATE_PARAMS',
}
-
-export { VisOptionsReactWrapper };
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts
new file mode 100644
index 00000000000000..1f98a5f7fa7df0
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { useState, useCallback } from 'react';
+
+export type SetValidity = (modelName: string, value: boolean) => void;
+export type SetTouched = (value: boolean) => void;
+
+const initialFormState = {
+ validity: {},
+ touched: false,
+ invalid: false,
+};
+
+function useEditorFormState() {
+ const [formState, setFormState] = useState(initialFormState);
+
+ const setValidity: SetValidity = useCallback((modelName, value) => {
+ setFormState(model => {
+ const validity = {
+ ...model.validity,
+ [modelName]: value,
+ };
+
+ return {
+ ...model,
+ validity,
+ invalid: Object.values(validity).some(valid => !valid),
+ };
+ });
+ }, []);
+
+ const resetValidity = useCallback(() => {
+ setFormState(initialFormState);
+ }, []);
+
+ const setTouched = useCallback((touched: boolean) => {
+ setFormState(model => ({
+ ...model,
+ touched,
+ }));
+ }, []);
+
+ return {
+ formState,
+ setValidity,
+ setTouched,
+ resetValidity,
+ };
+}
+
+export { useEditorFormState };
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts
new file mode 100644
index 00000000000000..6dbd9a69d82c04
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { useEffect, useReducer, useCallback } from 'react';
+import { isEqual } from 'lodash';
+
+import { Vis, VisState, VisParams } from 'ui/vis';
+import { editorStateReducer, initEditorState } from './reducers';
+import { EditorStateActionTypes } from './constants';
+import { EditorAction, updateStateParams } from './actions';
+
+export * from './editor_form_state';
+export * from './actions';
+
+export function useEditorReducer(vis: Vis): [VisState, React.Dispatch] {
+ const [state, dispatch] = useReducer(editorStateReducer, vis, initEditorState);
+
+ useEffect(() => {
+ const handleVisUpdate = (params: VisParams) => {
+ if (!isEqual(params, state.params)) {
+ dispatch(updateStateParams(params));
+ }
+ };
+
+ // fires when visualization state changes, and we need to copy changes to editorState
+ vis.on('updateEditorStateParams', handleVisUpdate);
+
+ return () => vis.off('updateEditorStateParams', handleVisUpdate);
+ }, [vis, state.params]);
+
+ const wrappedDispatch = useCallback(
+ (action: EditorAction) => {
+ dispatch(action);
+
+ vis.emit('dirtyStateChange', {
+ isDirty: action.type !== EditorStateActionTypes.DISCARD_CHANGES,
+ });
+ },
+ [vis]
+ );
+
+ return [state, wrappedDispatch];
+}
diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts
new file mode 100644
index 00000000000000..db52291c823e76
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts
@@ -0,0 +1,182 @@
+/*
+ * 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 { cloneDeep } from 'lodash';
+
+import { AggConfigs, AggConfig } from 'ui/agg_types';
+import { Vis, VisState } from 'ui/vis';
+import { move } from 'ui/utils/collection';
+import { EditorStateActionTypes } from './constants';
+import { AggGroupNames } from '../../../agg_groups';
+import { getEnabledMetricAggsCount } from '../../agg_group_helper';
+import { EditorAction } from './actions';
+
+function initEditorState(vis: Vis) {
+ return vis.copyCurrentState(true);
+}
+
+function editorStateReducer(state: VisState, action: EditorAction): VisState {
+ switch (action.type) {
+ case EditorStateActionTypes.ADD_NEW_AGG: {
+ const aggConfig = state.aggs.createAggConfig(action.payload as AggConfig, {
+ addToAggConfigs: false,
+ });
+ aggConfig.brandNew = true;
+ const newAggs = [...state.aggs.aggs, aggConfig];
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.DISCARD_CHANGES: {
+ return initEditorState(action.payload);
+ }
+
+ case EditorStateActionTypes.CHANGE_AGG_TYPE: {
+ const { aggId, value } = action.payload;
+
+ const newAggs = state.aggs.aggs.map(agg => {
+ if (agg.id === aggId) {
+ agg.type = value;
+
+ return agg.toJSON();
+ }
+
+ return agg;
+ });
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.SET_AGG_PARAM_VALUE: {
+ const { aggId, paramName, value } = action.payload;
+
+ const newAggs = state.aggs.aggs.map(agg => {
+ if (agg.id === aggId) {
+ const parsedAgg = agg.toJSON();
+
+ return {
+ ...parsedAgg,
+ params: {
+ ...parsedAgg.params,
+ [paramName]: value,
+ },
+ };
+ }
+
+ return agg;
+ });
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.SET_STATE_PARAM_VALUE: {
+ const { paramName, value } = action.payload;
+
+ return {
+ ...state,
+ params: {
+ ...state.params,
+ [paramName]: value,
+ },
+ };
+ }
+
+ case EditorStateActionTypes.REMOVE_AGG: {
+ let isMetric = false;
+
+ const newAggs = state.aggs.aggs.filter(({ id, schema }) => {
+ if (id === action.payload.aggId) {
+ if (schema.group === AggGroupNames.Metrics) {
+ isMetric = true;
+ }
+
+ return false;
+ }
+
+ return true;
+ });
+
+ if (isMetric && getEnabledMetricAggsCount(newAggs) === 0) {
+ const aggToEnable = newAggs.find(agg => agg.schema.name === 'metric');
+
+ if (aggToEnable) {
+ aggToEnable.enabled = true;
+ }
+ }
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.REORDER_AGGS: {
+ const { sourceAgg, destinationAgg } = action.payload;
+ const destinationIndex = state.aggs.aggs.indexOf(destinationAgg);
+ const newAggs = move([...state.aggs.aggs], sourceAgg, destinationIndex);
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.TOGGLE_ENABLED_AGG: {
+ const { aggId, enabled } = action.payload;
+
+ const newAggs = state.aggs.aggs.map(agg => {
+ if (agg.id === aggId) {
+ const parsedAgg = agg.toJSON();
+
+ return {
+ ...parsedAgg,
+ enabled,
+ };
+ }
+
+ return agg;
+ });
+
+ return {
+ ...state,
+ aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas),
+ };
+ }
+
+ case EditorStateActionTypes.UPDATE_STATE_PARAMS: {
+ const { params } = action.payload;
+
+ return {
+ ...state,
+ params: cloneDeep(params),
+ };
+ }
+ }
+}
+
+export { editorStateReducer, initEditorState };
diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx
index f215cf755886d9..55cd237a56689d 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx
@@ -17,12 +17,12 @@
* under the License.
*/
-import { VisParams } from '../../..';
-import { AggParams } from '../agg_params';
-import { OnAggParamsChange } from '../components/agg_common_props';
+import { AggConfig, VisParams } from 'ui/vis';
+import { DefaultEditorAggCommonProps } from '../components/agg_common_props';
export interface AggControlProps {
- aggParams: AggParams;
+ agg: AggConfig;
editorStateParams: VisParams;
- setValue: OnAggParamsChange;
+ setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue'];
+ setStateParamValue: DefaultEditorAggCommonProps['setStateParamValue'];
}
diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts
index 6491ef2e460544..98e4931b23ea3e 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts
+++ b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts
@@ -55,7 +55,7 @@ function useFallbackMetric(
.filter(isCompatibleAgg)
.find(aggregation => aggregation.id === value);
- if (!respAgg) {
+ if (!respAgg && value !== fallbackValue) {
setValue(fallbackValue);
}
}
diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx
index 4d15ac8e80e637..67ce3ba6d50722 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx
@@ -22,7 +22,7 @@ import { act } from 'react-dom/test-utils';
import { mount, shallow, ReactWrapper } from 'enzyme';
import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui';
import { Field } from '../../../../../../../plugins/data/public';
-import { ComboBoxGroupedOptions, SubAggParamsProp } from '..';
+import { ComboBoxGroupedOptions } from '..';
import { FieldParamEditor, FieldParamEditorProps } from './field';
import { AggConfig, VisState } from '../../..';
@@ -69,6 +69,7 @@ describe('FieldParamEditor component', () => {
editorComponent: () => null,
onChange,
} as any,
+ formIsTouched: false,
value: undefined,
editorConfig: {},
indexedFields,
@@ -78,7 +79,6 @@ describe('FieldParamEditor component', () => {
setTouched,
state: {} as VisState,
metricAggs: [] as AggConfig[],
- subAggParams: {} as SubAggParamsProp,
};
});
@@ -130,15 +130,6 @@ describe('FieldParamEditor component', () => {
expect(setValidity).toHaveBeenCalledWith(false);
});
- it('should call setTouched when the control is invalid', () => {
- defaultProps.value = field;
- const comp = mount();
- expect(setTouched).not.toHaveBeenCalled();
- comp.setProps({ customError: 'customError' });
-
- expect(setTouched).toHaveBeenCalled();
- });
-
it('should call onChange when a field selected', () => {
const comp = mount();
act(() => {
diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.tsx
index 75a9e24cd0dee1..b8cd0d630a019a 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/field.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/field.tsx
@@ -26,6 +26,7 @@ import { AggConfig } from '../../..';
import { Field } from '../../../../../../../plugins/data/public';
import { formatListAsProse, parseCommaSeparatedList } from '../../../../../../utils';
import { AggParam, FieldParamType } from '../../../../agg_types';
+import { useValidation } from './agg_utils';
import { AggParamEditorProps, ComboBoxGroupedOptions } from '..';
const label = i18n.translate('common.ui.aggTypes.field.fieldLabel', { defaultMessage: 'Field' });
@@ -78,13 +79,7 @@ function FieldParamEditor({
const isValid = !!value && !errors.length;
- useEffect(() => {
- setValidity(isValid);
-
- if (!!errors.length) {
- setTouched();
- }
- }, [isValid]);
+ useValidation(setValidity, isValid);
useEffect(() => {
// set field if only one available
diff --git a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx
index 9d337035f87348..efa8366ec550b6 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx
@@ -17,41 +17,43 @@
* under the License.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { AggParamType } from '../../../../agg_types/param_types/agg';
import { AggConfig } from '../../..';
+import { useSubAggParamsHandlers } from './utils';
+import { AggGroupNames } from '../agg_groups';
import { AggParamEditorProps, DefaultEditorAggParams } from '..';
function OrderAggParamEditor({
agg,
+ aggParam,
+ formIsTouched,
value,
metricAggs,
state,
setValue,
setValidity,
setTouched,
- subAggParams,
-}: AggParamEditorProps) {
+}: AggParamEditorProps) {
+ const orderBy = agg.params.orderBy;
+
useEffect(() => {
- if (metricAggs) {
- const orderBy = agg.params.orderBy;
+ if (orderBy === 'custom' && !value) {
+ setValue(aggParam.makeAgg(agg));
+ }
- // we aren't creating a custom aggConfig
- if (!orderBy || orderBy !== 'custom') {
- setValue(undefined);
- } else if (value) {
- setValue(value);
- } else {
- const paramDef = agg.type.paramByName('orderAgg');
- if (paramDef) {
- setValue((paramDef as AggParamType).makeAgg(agg));
- }
- }
+ if (orderBy !== 'custom' && value) {
+ setValue(undefined);
}
- }, [agg.params.orderBy, metricAggs]);
+ }, [orderBy]);
- const [innerState, setInnerState] = useState(true);
+ const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers(
+ agg,
+ aggParam,
+ value as AggConfig,
+ setValue
+ );
if (!agg.params.orderAgg) {
return null;
@@ -62,18 +64,14 @@ function OrderAggParamEditor({
{
- // to force update when sub-agg params are changed
- setInnerState(!innerState);
- subAggParams.onAggParamsChange(...rest);
- }}
- onAggTypeChange={subAggParams.onAggTypeChange}
+ setAggParamValue={setAggParamValue}
+ onAggTypeChange={onAggTypeChange}
setValidity={setValidity}
setTouched={setTouched}
/>
diff --git a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx
index d9e0abc0cce599..4d481bd74e8a31 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useCallback } from 'react';
import { EuiFormRow, EuiIconTip, EuiRange, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -26,7 +26,7 @@ import { AggControlProps } from './agg_control_props';
const DEFAULT_VALUE = 50;
const PARAM_NAME = 'radiusRatio';
-function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps) {
+function RadiusRatioOptionControl({ editorStateParams, setStateParamValue }: AggControlProps) {
const label = (
<>
{
if (!editorStateParams.radiusRatio) {
- setValue(editorStateParams, PARAM_NAME, DEFAULT_VALUE);
+ setStateParamValue(PARAM_NAME, DEFAULT_VALUE);
}
}, []);
+ const onChange = useCallback(
+ (e: React.ChangeEvent | React.MouseEvent) =>
+ setStateParamValue(PARAM_NAME, parseFloat(e.currentTarget.value)),
+ [setStateParamValue]
+ );
+
return (
<>
@@ -58,9 +64,7 @@ function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlPro
min={1}
max={100}
value={editorStateParams.radiusRatio || DEFAULT_VALUE}
- onChange={(
- e: React.ChangeEvent | React.MouseEvent
- ) => setValue(editorStateParams, PARAM_NAME, parseFloat(e.currentTarget.value))}
+ onChange={onChange}
showRange
showValue
valueAppend="%"
diff --git a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx
index e6e4d34aea8367..3ba4279a370dd5 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import { EuiButtonGroup, EuiFormRow, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggControlProps } from './agg_control_props';
@@ -28,8 +28,8 @@ const PARAMS = {
COLUMNS: 'visEditorSplitBy__false',
};
-function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) {
- const idSelected = `visEditorSplitBy__${aggParams.row}`;
+function RowsOrColumnsControl({ agg, setAggParamValue }: AggControlProps) {
+ const idSelected = `visEditorSplitBy__${agg.params.row}`;
const options = [
{
id: PARAMS.ROWS,
@@ -44,6 +44,10 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) {
}),
},
];
+ const onChange = useCallback(
+ optionId => setAggParamValue(agg.id, PARAMS.NAME, optionId === PARAMS.ROWS),
+ [setAggParamValue]
+ );
return (
<>
@@ -56,7 +60,7 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) {
options={options}
isFullWidth={true}
idSelected={idSelected}
- onChange={optionId => setValue(aggParams, PARAMS.NAME, optionId === PARAMS.ROWS)}
+ onChange={onChange}
/>
diff --git a/src/legacy/ui/public/vis/editors/default/controls/string.tsx b/src/legacy/ui/public/vis/editors/default/controls/string.tsx
index 63827205afdb33..dbfd0a7db33fbf 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/string.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/string.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, useCallback } from 'react';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { AggParamEditorProps } from '..';
@@ -37,6 +37,8 @@ function StringParamEditor({
setValidity(isValid);
}, [isValid]);
+ const onChange = useCallback(ev => setValue(ev.target.value), [setValue]);
+
return (
setValue(ev.target.value)}
+ onChange={onChange}
fullWidth={true}
compressed
onBlur={setTouched}
diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx
index 559a3f47db5630..b233480cb35ba0 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx
@@ -17,35 +17,40 @@
* under the License.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import { EuiSpacer } from '@elastic/eui';
-import { AggParamType } from '../../../../agg_types/param_types/agg';
+
+import { AggParamType } from 'ui/agg_types/param_types/agg';
import { AggConfig } from '../../..';
-import { AggParamEditorProps, DefaultEditorAggParams } from '..';
+import { useSubAggParamsHandlers } from './utils';
+import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..';
function SubAggParamEditor({
agg,
+ aggParam,
+ formIsTouched,
value,
metricAggs,
state,
setValue,
setValidity,
setTouched,
- subAggParams,
-}: AggParamEditorProps) {
+}: AggParamEditorProps) {
useEffect(() => {
// we aren't creating a custom aggConfig
if (agg.params.metricAgg !== 'custom') {
setValue(undefined);
} else if (!agg.params.customMetric) {
- const customMetric = agg.type.paramByName('customMetric');
- if (customMetric) {
- setValue((customMetric as AggParamType).makeAgg(agg));
- }
+ setValue(aggParam.makeAgg(agg));
}
}, [value, metricAggs]);
- const [innerState, setInnerState] = useState(true);
+ const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers(
+ agg,
+ aggParam,
+ agg.params.customMetric,
+ setValue
+ );
if (agg.params.metricAgg !== 'custom' || !agg.params.customMetric) {
return null;
@@ -56,18 +61,14 @@ function SubAggParamEditor({
{
- // to force update when sub-agg params are changed
- setInnerState(!innerState);
- subAggParams.onAggParamsChange(...rest);
- }}
- onAggTypeChange={subAggParams.onAggTypeChange}
+ setAggParamValue={setAggParamValue}
+ onAggTypeChange={onAggTypeChange}
setValidity={setValidity}
setTouched={setTouched}
/>
diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx
index df1640273135e4..d0a44d1d35d1c3 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx
@@ -17,23 +17,25 @@
* under the License.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import { EuiFormLabel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { AggParamType } from '../../../../agg_types/param_types/agg';
+
+import { AggParamType } from 'ui/agg_types/param_types/agg';
import { AggConfig } from '../../../../agg_types/agg_config';
+import { useSubAggParamsHandlers } from './utils';
import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..';
function SubMetricParamEditor({
agg,
aggParam,
+ formIsTouched,
metricAggs,
state,
setValue,
setValidity,
setTouched,
- subAggParams,
-}: AggParamEditorProps) {
+}: AggParamEditorProps) {
const metricTitle = i18n.translate('common.ui.aggTypes.metrics.metricTitle', {
defaultMessage: 'Metric',
});
@@ -49,14 +51,16 @@ function SubMetricParamEditor({
if (agg.params[type]) {
setValue(agg.params[type]);
} else {
- const param = agg.type.paramByName(type);
- if (param) {
- setValue((param as AggParamType).makeAgg(agg));
- }
+ setValue(aggParam.makeAgg(agg));
}
}, []);
- const [innerState, setInnerState] = useState(true);
+ const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers(
+ agg,
+ aggParam,
+ agg.params[type],
+ setValue
+ );
if (!agg.params[type]) {
return null;
@@ -71,16 +75,12 @@ function SubMetricParamEditor({
agg={agg.params[type]}
groupName={aggGroup}
className="visEditorAgg__subAgg"
- formIsTouched={subAggParams.formIsTouched}
+ formIsTouched={formIsTouched}
indexPattern={agg.getIndexPattern()}
metricAggs={metricAggs}
state={state}
- onAggParamsChange={(...rest) => {
- // to force update when sub-agg params are changed
- setInnerState(!innerState);
- subAggParams.onAggParamsChange(...rest);
- }}
- onAggTypeChange={subAggParams.onAggTypeChange}
+ setAggParamValue={setAggParamValue}
+ onAggTypeChange={onAggTypeChange}
setValidity={setValidity}
setTouched={setTouched}
/>
diff --git a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts
index 2e00b62d9b7fda..c5abf31a3cd8fb 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts
+++ b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts
@@ -20,14 +20,13 @@
import { AggConfig, VisState } from '../../..';
import { EditorConfig } from '../../config/types';
import { AggParam } from '../../../../agg_types';
-import { SubAggParamsProp } from '..';
export const aggParamCommonPropsMock = {
agg: {} as AggConfig,
aggParam: {} as AggParam,
editorConfig: {} as EditorConfig,
+ formIsTouched: false,
metricAggs: [] as AggConfig[],
- subAggParams: {} as SubAggParamsProp,
state: {} as VisState,
showValidation: false,
};
diff --git a/src/legacy/ui/public/vis/editors/default/controls/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/utils.ts
new file mode 100644
index 00000000000000..5fd7c284fa23d2
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/controls/utils.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { useCallback } from 'react';
+import { AggConfig } from 'ui/vis';
+import { AggParamType } from 'ui/agg_types/param_types/agg';
+
+type SetValue = (value?: AggConfig) => void;
+
+function useSubAggParamsHandlers(
+ agg: AggConfig,
+ aggParam: AggParamType,
+ subAgg: AggConfig,
+ setValue: SetValue
+) {
+ const setAggParamValue = useCallback(
+ (aggId, paramName, val) => {
+ const parsedParams = subAgg.toJSON();
+ const params = {
+ ...parsedParams,
+ params: {
+ ...parsedParams.params,
+ [paramName]: val,
+ },
+ };
+
+ setValue(aggParam.makeAgg(agg, params));
+ },
+ [agg, aggParam, setValue, subAgg]
+ );
+
+ const onAggTypeChange = useCallback(
+ (aggId, aggType) => {
+ const parsedAgg = subAgg.toJSON();
+
+ const params = {
+ ...parsedAgg,
+ type: aggType,
+ };
+
+ setValue(aggParam.makeAgg(agg, params));
+ },
+ [agg, aggParam, setValue, subAgg]
+ );
+
+ return { onAggTypeChange, setAggParamValue };
+}
+
+export { useSubAggParamsHandlers };
diff --git a/src/legacy/ui/public/vis/editors/default/default.html b/src/legacy/ui/public/vis/editors/default/default.html
deleted file mode 100644
index 60fcbafdb88f59..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/default.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js
deleted file mode 100644
index 66f52ea84398fb..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/default.js
+++ /dev/null
@@ -1,213 +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 'ui/angular-bootstrap';
-import './fancy_forms';
-import './sidebar';
-import { i18n } from '@kbn/i18n';
-import './vis_options';
-import './vis_editor_resizer';
-import './vis_type_agg_filter';
-import $ from 'jquery';
-
-import _ from 'lodash';
-import angular from 'angular';
-import defaultEditorTemplate from './default.html';
-import { keyCodes } from '@elastic/eui';
-import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper';
-import { DefaultEditorSize } from '../../editor_size';
-
-import { AggGroupNames } from './agg_groups';
-
-import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy';
-
-const defaultEditor = function($rootScope, $compile) {
- return class DefaultEditor {
- static key = 'default';
-
- constructor(el, savedObj) {
- this.el = $(el);
- this.savedObj = savedObj;
- this.vis = savedObj.vis;
-
- if (!this.vis.type.editorConfig.optionTabs && this.vis.type.editorConfig.optionsTemplate) {
- this.vis.type.editorConfig.optionTabs = [
- {
- name: 'options',
- title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', {
- defaultMessage: 'Options',
- }),
- editor: this.vis.type.editorConfig.optionsTemplate,
- },
- ];
- }
- }
-
- render({ uiState, timeRange, filters, query, appState }) {
- let $scope;
-
- const updateScope = () => {
- $scope.vis = this.vis;
- $scope.uiState = uiState;
- //$scope.$apply();
- };
-
- return new Promise(async resolve => {
- if (!this.$scope) {
- this.$scope = $scope = $rootScope.$new();
-
- updateScope();
-
- $scope.state = $scope.vis.copyCurrentState(true);
- $scope.oldState = $scope.vis.getSerializableState($scope.state);
-
- $scope.toggleSidebar = () => {
- $scope.$broadcast('render');
- };
-
- this.el.one('renderComplete', resolve);
- // track state of editable vis vs. "actual" vis
- $scope.stageEditableVis = () => {
- $scope.oldState = $scope.vis.getSerializableState($scope.state);
- $scope.vis.setCurrentState($scope.state);
- $scope.vis.updateState();
- $scope.vis.dirty = false;
- };
- $scope.resetEditableVis = () => {
- $scope.state = $scope.vis.copyCurrentState(true);
- $scope.vis.dirty = false;
- };
-
- $scope.autoApplyEnabled = false;
- if ($scope.vis.type.editorConfig.enableAutoApply) {
- $scope.toggleAutoApply = () => {
- $scope.autoApplyEnabled = !$scope.autoApplyEnabled;
- };
-
- $scope.$watch(
- 'vis.dirty',
- _.debounce(() => {
- if (!$scope.autoApplyEnabled || !$scope.vis.dirty) return;
- $scope.stageEditableVis();
- }, 800)
- );
- }
-
- $scope.submitEditorWithKeyboard = event => {
- if (event.ctrlKey && event.keyCode === keyCodes.ENTER) {
- event.preventDefault();
- event.stopPropagation();
- $scope.stageEditableVis();
- }
- };
-
- $scope.getSidebarClass = () => {
- if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.SMALL) {
- return 'visEditor__collapsibleSidebar--small';
- } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.MEDIUM) {
- return 'visEditor__collapsibleSidebar--medium';
- } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.LARGE) {
- return 'visEditor__collapsibleSidebar--large';
- }
- };
-
- $scope.$watch(
- () => {
- return $scope.vis.getSerializableState($scope.state);
- },
- function(newState) {
- $scope.vis.dirty = !angular.equals(newState, $scope.oldState);
- const responseAggs = $scope.state.aggs.getResponseAggs();
- $scope.hasHistogramAgg = responseAggs.some(agg => agg.type.name === 'histogram');
- $scope.metricAggs = responseAggs.filter(
- agg => _.get(agg, 'schema.group') === AggGroupNames.Metrics
- );
- const lastParentPipelineAgg = _.findLast(
- $scope.metricAggs,
- ({ type }) => type.subtype === parentPipelineAggHelper.subtype
- );
- $scope.lastParentPipelineAggTitle =
- lastParentPipelineAgg && lastParentPipelineAgg.type.title;
- },
- true
- );
-
- // fires when visualization state changes, and we need to copy changes to editorState
- $scope.$watch(
- () => {
- return $scope.vis.getCurrentState(false);
- },
- newState => {
- if (!_.isEqual(newState, $scope.oldState)) {
- $scope.state = $scope.vis.copyCurrentState(true);
- $scope.oldState = newState;
- }
- },
- true
- );
-
- // Load the default editor template, attach it to the DOM and compile it.
- // It should be added to the DOM before compiling, to prevent some resize
- // listener issues.
- const template = $(defaultEditorTemplate);
- this.el.html(template);
- $compile(template)($scope);
- } else {
- $scope = this.$scope;
- updateScope();
- }
-
- if (!this._handler) {
- const visualizationEl = this.el.find('.visEditor__canvas')[0];
-
- this._handler = await embeddables
- .getEmbeddableFactory('visualization')
- .createFromObject(this.savedObj, {
- uiState: uiState,
- appState,
- timeRange: timeRange,
- filters: filters || [],
- query: query,
- });
- this._handler.render(visualizationEl);
- } else {
- this._handler.updateInput({
- timeRange: timeRange,
- filters: filters || [],
- query: query,
- });
- }
- });
- }
-
- resize() {}
-
- destroy() {
- if (this.$scope) {
- this.$scope.$destroy();
- this.$scope = null;
- }
- if (this._handler) {
- this._handler.destroy();
- }
- }
- };
-};
-
-export { defaultEditor };
diff --git a/src/legacy/ui/public/vis/editors/default/default_editor.tsx b/src/legacy/ui/public/vis/editors/default/default_editor.tsx
new file mode 100644
index 00000000000000..3e99bb83d224f6
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/default_editor.tsx
@@ -0,0 +1,127 @@
+/*
+ * 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, useRef, useState, useCallback } from 'react';
+
+import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy';
+import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types';
+import { VisualizeEmbeddable } from '../../../../../core_plugins/visualizations/public/embeddable';
+import { VisualizeEmbeddableFactory } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable_factory';
+import {
+ PanelsContainer,
+ Panel,
+} from '../../../../../core_plugins/console/public/np_ready/application/components/split_panel';
+
+import './vis_type_agg_filter';
+import { DefaultEditorSideBar } from './components/sidebar';
+import { DefaultEditorControllerState } from './default_editor_controller';
+import { getInitialWidth } from '../../editor_size';
+
+function DefaultEditor({
+ savedObj,
+ uiState,
+ timeRange,
+ filters,
+ appState,
+ optionTabs,
+ query,
+}: DefaultEditorControllerState & EditorRenderProps) {
+ const visRef = useRef(null);
+ const visHandler = useRef(null);
+ const [isCollapsed, setIsCollapsed] = useState(false);
+ const [factory, setFactory] = useState(null);
+ const { vis } = savedObj;
+
+ const onClickCollapse = useCallback(() => {
+ setIsCollapsed(value => !value);
+ }, []);
+
+ useEffect(() => {
+ async function visualize() {
+ if (!visRef.current || (!visHandler.current && factory)) {
+ return;
+ }
+
+ if (!visHandler.current) {
+ const embeddableFactory = embeddables.getEmbeddableFactory(
+ 'visualization'
+ ) as VisualizeEmbeddableFactory;
+ setFactory(embeddableFactory);
+
+ visHandler.current = (await embeddableFactory.createFromObject(savedObj, {
+ // should be look through createFromObject interface again because of "id" param
+ id: '',
+ uiState,
+ appState,
+ timeRange,
+ filters,
+ query,
+ })) as VisualizeEmbeddable;
+
+ visHandler.current.render(visRef.current);
+ } else {
+ visHandler.current.updateInput({
+ timeRange,
+ filters,
+ query,
+ });
+ }
+ }
+
+ visualize();
+ }, [uiState, savedObj, timeRange, filters, appState, query, factory]);
+
+ useEffect(() => {
+ return () => {
+ if (visHandler.current) {
+ visHandler.current.destroy();
+ }
+ };
+ }, []);
+
+ const editorInitialWidth = getInitialWidth(vis.type.editorConfig.defaultSize);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export { DefaultEditor };
diff --git a/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx
new file mode 100644
index 00000000000000..bf843a98deaa50
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx
@@ -0,0 +1,89 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { i18n } from '@kbn/i18n';
+import { I18nProvider } from '@kbn/i18n/react';
+
+import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types';
+import { VisSavedObject } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable';
+import { DefaultEditor } from './default_editor';
+import { DefaultEditorDataTab, OptionTab } from './components/sidebar';
+
+export interface DefaultEditorControllerState {
+ savedObj: VisSavedObject;
+ optionTabs: OptionTab[];
+}
+
+class DefaultEditorController {
+ private el: HTMLElement;
+ private state: DefaultEditorControllerState;
+
+ constructor(el: HTMLElement, savedObj: VisSavedObject) {
+ this.el = el;
+ const { type: visType } = savedObj.vis;
+
+ const optionTabs = [
+ ...(visType.schemas.buckets || visType.schemas.metrics
+ ? [
+ {
+ name: 'data',
+ title: i18n.translate('common.ui.vis.editors.sidebar.tabs.dataLabel', {
+ defaultMessage: 'Data',
+ }),
+ editor: DefaultEditorDataTab,
+ },
+ ]
+ : []),
+
+ ...(!visType.editorConfig.optionTabs && visType.editorConfig.optionsTemplate
+ ? [
+ {
+ name: 'options',
+ title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', {
+ defaultMessage: 'Options',
+ }),
+ editor: visType.editorConfig.optionsTemplate,
+ },
+ ]
+ : visType.editorConfig.optionTabs),
+ ];
+
+ this.state = {
+ savedObj,
+ optionTabs,
+ };
+ }
+
+ render(props: EditorRenderProps) {
+ render(
+
+
+ ,
+ this.el
+ );
+ }
+
+ destroy() {
+ unmountComponentAtNode(this.el);
+ }
+}
+
+export { DefaultEditorController };
diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js
deleted file mode 100644
index e9fda6bb9efabd..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js
+++ /dev/null
@@ -1,62 +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 ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import $ from 'jquery';
-
-describe('fancy forms', function() {
- let $el;
- let $scope;
- let $compile;
- let $rootScope;
- let ngForm;
-
- function generateEl() {
- return $('
-`;
-
-describe('fancy forms', function() {
- let setup;
- const trash = [];
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(
- ngMock.inject($injector => {
- const $rootScope = $injector.get('$rootScope');
- const $compile = $injector.get('$compile');
-
- setup = function(options = {}) {
- const { name = 'person1', tasks = [], onSubmit = () => {} } = options;
-
- const $el = $(template).appendTo('body');
- trash.push(() => $el.remove());
- const $scope = $rootScope.$new();
-
- $scope.name = name;
- $scope.tasks = tasks;
- $scope.onSubmit = onSubmit;
-
- $compile($el)($scope);
- $scope.$apply();
-
- return {
- $el,
- $scope,
- };
- };
- })
- );
-
- afterEach(() => trash.splice(0).forEach(fn => fn()));
-
- describe('nested forms', function() {
- it('treats new fields as "soft" errors', function() {
- const { $scope } = setup({ name: '' });
- expect($scope.person.errorCount()).to.be(1);
- expect($scope.person.softErrorCount()).to.be(0);
- });
-
- it('upgrades fields to regular errors on attempted submit', function() {
- const { $scope, $el } = setup({ name: '' });
-
- expect($scope.person.errorCount()).to.be(1);
- expect($scope.person.softErrorCount()).to.be(0);
- $el.find(testSubjSelector('submit')).click();
- expect($scope.person.errorCount()).to.be(1);
- expect($scope.person.softErrorCount()).to.be(1);
- });
-
- it('prevents submit when there are errors', function() {
- const onSubmit = sinon.stub();
- const { $scope, $el } = setup({ name: '', onSubmit });
-
- expect($scope.person.errorCount()).to.be(1);
- sinon.assert.notCalled(onSubmit);
- $el.find(testSubjSelector('submit')).click();
- expect($scope.person.errorCount()).to.be(1);
- sinon.assert.notCalled(onSubmit);
-
- $scope.$apply(() => {
- $scope.name = 'foo';
- });
-
- expect($scope.person.errorCount()).to.be(0);
- sinon.assert.notCalled(onSubmit);
- $el.find(testSubjSelector('submit')).click();
- expect($scope.person.errorCount()).to.be(0);
- sinon.assert.calledOnce(onSubmit);
- });
-
- it('new fields are no longer soft after blur', function() {
- const { $scope, $el } = setup({ name: '' });
- expect($scope.person.softErrorCount()).to.be(0);
- $el.find(testSubjSelector('name')).blur();
- expect($scope.person.softErrorCount()).to.be(1);
- });
-
- it('counts errors/softErrors in sub forms', function() {
- const { $scope, $el } = setup();
-
- expect($scope.person.errorCount()).to.be(0);
-
- $scope.$apply(() => {
- $scope.tasks = [
- {
- name: 'foo',
- description: '',
- },
- {
- name: 'foo',
- description: '',
- },
- ];
- });
-
- expect($scope.person.errorCount()).to.be(2);
- expect($scope.person.softErrorCount()).to.be(0);
-
- $el
- .find(testSubjSelector('taskDesc'))
- .first()
- .blur();
-
- expect($scope.person.errorCount()).to.be(2);
- expect($scope.person.softErrorCount()).to.be(1);
- });
-
- it('only counts down', function() {
- const { $scope, $el } = setup({
- tasks: [
- {
- name: 'foo',
- description: '',
- },
- {
- name: 'bar',
- description: '',
- },
- {
- name: 'baz',
- description: '',
- },
- ],
- });
-
- // top level form sees 3 errors
- expect($scope.person.errorCount()).to.be(3);
- expect($scope.person.softErrorCount()).to.be(0);
-
- $el
- .find('ng-form')
- .toArray()
- .forEach((el, i) => {
- const $task = $(el);
- const $taskScope = $task.scope();
- const form = $task.controller('form');
-
- // sub forms only see one error
- expect(form.errorCount()).to.be(1);
- expect(form.softErrorCount()).to.be(0);
-
- // blurs only count locally
- $task.find(testSubjSelector('taskDesc')).blur();
- expect(form.softErrorCount()).to.be(1);
-
- // but parent form see them
- expect($scope.person.softErrorCount()).to.be(1);
-
- $taskScope.$apply(() => {
- $taskScope.task.description = 'valid';
- });
-
- expect(form.errorCount()).to.be(0);
- expect(form.softErrorCount()).to.be(0);
- expect($scope.person.errorCount()).to.be(2 - i);
- expect($scope.person.softErrorCount()).to.be(0);
- });
- });
- });
-});
diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js
deleted file mode 100644
index 1f0788cf74d1d0..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js
+++ /dev/null
@@ -1,29 +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';
-
-import { decorateFormController } from './kbn_form_controller';
-import { decorateModelController } from './kbn_model_controller';
-
-uiModules.get('kibana').config(function($provide) {
- $provide.decorator('formDirective', decorateFormController);
- $provide.decorator('ngFormDirective', decorateFormController);
- $provide.decorator('ngModelDirective', decorateModelController);
-});
diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js
deleted file mode 100644
index 927e6d69e3c8a3..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js
+++ /dev/null
@@ -1,20 +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 './fancy_forms';
diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js
deleted file mode 100644
index 90971140482f7c..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js
+++ /dev/null
@@ -1,87 +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.
- */
-
-export function decorateFormController($delegate, $injector) {
- const [directive] = $delegate;
- const FormController = directive.controller;
-
- class KbnFormController extends FormController {
- // prevent inheriting FormController's static $inject property
- // which is angular's cache of the DI arguments for a function
- static $inject = ['$scope', '$element'];
-
- constructor($scope, $element, ...superArgs) {
- super(...superArgs);
-
- const onSubmit = event => {
- this._markInvalidTouched(event);
- };
-
- $element.on('submit', onSubmit);
- $scope.$on('$destroy', () => {
- $element.off('submit', onSubmit);
- });
- }
-
- errorCount() {
- return this._getInvalidModels().length;
- }
-
- // same as error count, but filters out untouched and pristine models
- softErrorCount() {
- return this._getInvalidModels().filter(model => model.$touched || model.$dirty).length;
- }
-
- $setTouched() {
- this._getInvalidModels().forEach(model => model.$setTouched());
- }
-
- _markInvalidTouched(event) {
- if (this.errorCount()) {
- event.preventDefault();
- event.stopImmediatePropagation();
- this.$setTouched();
- }
- }
-
- _getInvalidModels() {
- return this.$$controls.reduce((acc, control) => {
- // recurse into sub-form
- if (typeof control._getInvalidModels === 'function') {
- return [...acc, ...control._getInvalidModels()];
- }
-
- if (control.$invalid) {
- return [...acc, control];
- }
-
- return acc;
- }, []);
- }
- }
-
- // replace controller with our wrapper
- directive.controller = [
- ...$injector.annotate(KbnFormController),
- ...$injector.annotate(FormController),
- (...args) => new KbnFormController(...args),
- ];
-
- return $delegate;
-}
diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js
deleted file mode 100644
index bb4d026aa18106..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js
+++ /dev/null
@@ -1,54 +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.
- */
-
-export function decorateModelController($delegate, $injector) {
- const [directive] = $delegate;
- const ModelController = directive.controller;
-
- class KbnModelController extends ModelController {
- // prevent inheriting ModelController's static $inject property
- // which is angular's cache of the DI arguments for a function
- static $inject = ['$scope', '$element'];
-
- constructor($scope, $element, ...superArgs) {
- super(...superArgs);
-
- const onInvalid = () => {
- this.$setTouched();
- };
-
- // the browser emits an "invalid" event when browser supplied
- // validation fails, which implies that the user has indirectly
- // interacted with the control and it should be treated as "touched"
- $element.on('invalid', onInvalid);
- $scope.$on('$destroy', () => {
- $element.off('invalid', onInvalid);
- });
- }
- }
-
- // replace controller with our wrapper
- directive.controller = [
- ...$injector.annotate(KbnModelController),
- ...$injector.annotate(ModelController),
- (...args) => new KbnModelController(...args),
- ];
-
- return $delegate;
-}
diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts
index 7079ba23afb5c0..fada4e5d2266ff 100644
--- a/src/legacy/ui/public/vis/editors/default/index.ts
+++ b/src/legacy/ui/public/vis/editors/default/index.ts
@@ -18,7 +18,7 @@
*/
export { AggParamEditorProps } from './components/agg_param_props';
-export { DefaultEditorAggParams, SubAggParamsProp } from './components/agg_params';
+export { DefaultEditorAggParams } from './components/agg_params';
export { ComboBoxGroupedOptions } from './utils';
export * from './vis_options_props';
export * from './utils';
diff --git a/src/legacy/ui/public/vis/editors/default/schemas.ts b/src/legacy/ui/public/vis/editors/default/schemas.ts
index e86a73732c3f4c..3cacd1cfbe68f7 100644
--- a/src/legacy/ui/public/vis/editors/default/schemas.ts
+++ b/src/legacy/ui/public/vis/editors/default/schemas.ts
@@ -28,6 +28,11 @@ import { RadiusRatioOptionControl } from './controls/radius_ratio_option';
import { AggGroupNames } from './agg_groups';
import { AggControlProps } from './controls/agg_control_props';
+export interface ISchemas {
+ [AggGroupNames.Buckets]: Schema[];
+ [AggGroupNames.Metrics]: Schema[];
+}
+
export interface Schema {
aggFilter: string | string[];
editor: boolean | string;
diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.html b/src/legacy/ui/public/vis/editors/default/sidebar.html
deleted file mode 100644
index b0a03e461fc1ce..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/sidebar.html
+++ /dev/null
@@ -1,191 +0,0 @@
-
diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js
deleted file mode 100644
index 195ae68c0e9599..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/sidebar.js
+++ /dev/null
@@ -1,105 +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 'lodash';
-import './agg_group';
-import './vis_options';
-import 'ui/directives/css_truncate';
-import { uiModules } from '../../../modules';
-import sidebarTemplate from './sidebar.html';
-import { move } from '../../../utils/collection';
-import { AggGroupNames } from './agg_groups';
-import { getEnabledMetricAggsCount } from './components/agg_group_helper';
-
-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'));
- }
- });
-
- $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 = $scope.state.aggs.createAggConfig({ schema });
- aggConfig.brandNew = true;
- };
-
- $scope.removeAgg = function(agg) {
- const aggs = $scope.state.aggs.aggs;
- const index = aggs.indexOf(agg);
-
- if (index === -1) {
- return;
- }
-
- aggs.splice(index, 1);
-
- if (agg.schema.group === AggGroupNames.Metrics) {
- const metrics = $scope.state.aggs.bySchemaGroup(AggGroupNames.Metrics);
-
- if (getEnabledMetricAggsCount(metrics) === 0) {
- metrics.find(aggregation => aggregation.schema.name === 'metric').enabled = true;
- }
- }
- };
-
- $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.aggs.indexOf(group[0]);
- _.forEach(group, (agg, index) => {
- move($scope.state.aggs.aggs, agg, indexOffset + index);
- });
- };
- },
- };
-});
diff --git a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js b/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js
deleted file mode 100644
index 3cbc94a0293268..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js
+++ /dev/null
@@ -1,62 +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';
-import { keyCodes } from '@elastic/eui';
-
-uiModules.get('kibana').directive('visEditorResizer', function() {
- return {
- restrict: 'E',
- link: function($scope, $el) {
- const $left = $el.parent();
-
- $el.on('mousedown', function(event) {
- $el.addClass('active');
- const startWidth = $left.width();
- const startX = event.pageX;
-
- function onMove(event) {
- const newWidth = startWidth + event.pageX - startX;
- $left.width(newWidth);
- }
-
- $(document.body)
- .on('mousemove', onMove)
- .one('mouseup', () => {
- $el.removeClass('active');
- $(document.body).off('mousemove', onMove);
- $scope.$broadcast('render');
- });
- });
-
- $el.on('keydown', event => {
- const { keyCode } = event;
-
- if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) {
- event.preventDefault();
- const startWidth = $left.width();
- const newWidth = startWidth + (keyCode === keyCodes.LEFT ? -15 : 15);
- $left.width(newWidth);
- $scope.$broadcast('render');
- }
- });
- },
- };
-});
diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js
deleted file mode 100644
index 9c9b0353cee270..00000000000000
--- a/src/legacy/ui/public/vis/editors/default/vis_options.js
+++ /dev/null
@@ -1,121 +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 { VisOptionsReactWrapper } from './vis_options_react_wrapper';
-import { safeMakeLabel } from './controls/agg_utils';
-
-/**
- * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute.
- * This lets you specify a full-screen UI for editing a vis type, instead of using the regular
- * sidebar.
- */
-
-uiModules
- .get('app/visualize')
- .directive('visOptionsReactWrapper', reactDirective =>
- reactDirective(wrapInI18nContext(VisOptionsReactWrapper), [
- ['component', { wrapApply: false }],
- ['aggs', { watchDepth: 'collection' }],
- ['stateParams', { watchDepth: 'collection' }],
- ['vis', { watchDepth: 'collection' }],
- ['uiState', { watchDepth: 'collection' }],
- ['setValue', { watchDepth: 'reference' }],
- ['setValidity', { watchDepth: 'reference' }],
- ['setVisType', { watchDepth: 'reference' }],
- ['setTouched', { watchDepth: 'reference' }],
- 'hasHistogramAgg',
- 'currentTab',
- 'aggsLabels',
- ])
- )
- .directive('visEditorVisOptions', function($compile) {
- return {
- restrict: 'E',
- require: '?^ngModel',
- scope: {
- vis: '=',
- visData: '=',
- uiState: '=',
- editor: '=',
- visualizeEditor: '=',
- editorState: '=',
- onAggParamsChange: '=',
- hasHistogramAgg: '=',
- currentTab: '=',
- },
- link: function($scope, $el, attrs, ngModelCtrl) {
- $scope.setValue = (paramName, value) =>
- $scope.onAggParamsChange($scope.editorState.params, paramName, value);
-
- $scope.setValidity = isValid => {
- ngModelCtrl.$setValidity(`visOptions`, isValid);
- };
-
- $scope.setTouched = isTouched => {
- if (isTouched) {
- ngModelCtrl.$setTouched();
- } else {
- ngModelCtrl.$setUntouched();
- }
- };
-
- $scope.setVisType = type => {
- $scope.vis.type.type = type;
- };
-
- // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it
- $scope.aggsLabels = '';
-
- $scope.$watch(
- () => {
- return $scope.editorState.aggs.aggs
- .map(agg => {
- return safeMakeLabel(agg);
- })
- .join();
- },
- value => {
- $scope.aggsLabels = value;
- }
- );
-
- const comp =
- typeof $scope.editor === 'string'
- ? $scope.editor
- : `
- `;
- const $editor = $compile(comp)($scope);
- $el.append($editor);
- },
- };
- });
diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx
index 3f9fa9f5f352f4..5b4badc1036457 100644
--- a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx
+++ b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx
@@ -23,13 +23,12 @@ import { Vis } from './../..';
export interface VisOptionsProps {
aggs: AggConfigs;
- aggsLabels: string;
hasHistogramAgg: boolean;
+ isTabSelected: boolean;
stateParams: VisParamType;
vis: Vis;
uiState: PersistedState;
setValue(paramName: T, value: VisParamType[T]): void;
setValidity(isValid: boolean): void;
- setVisType(type: string): void;
setTouched(isTouched: boolean): void;
}
diff --git a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js
index 88412c76d0d366..c34294d45548c5 100644
--- a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js
+++ b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js
@@ -18,6 +18,7 @@
*/
import $ from 'jquery';
+import { isEqual } from 'lodash';
import chrome from 'ui/chrome';
export class AngularVisController {
@@ -37,6 +38,11 @@ export class AngularVisController {
this.$scope.vis = this.vis;
this.$scope.visState = this.vis.getState();
this.$scope.esResponse = esResponse;
+
+ if (!isEqual(this.$scope.visParams, visParams)) {
+ this.vis.emit('updateEditorStateParams', visParams);
+ }
+
this.$scope.visParams = visParams;
this.$scope.renderComplete = resolve;
this.$scope.renderFailed = reject;
diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js
index 84f955d9c78792..d989f8e2539a00 100644
--- a/test/functional/apps/visualize/_inspector.js
+++ b/test/functional/apps/visualize/_inspector.js
@@ -37,6 +37,7 @@ export default function({ getService, getPageObjects }) {
it('should update table header when columns change', async function() {
await inspector.open();
await inspector.expectTableHeaders(['Count']);
+ await inspector.close();
log.debug('Add Average Metric on machine.ram field');
await PageObjects.visEditor.clickBucket('Y-axis', 'metrics');
@@ -45,6 +46,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.visEditor.clickGo();
await inspector.open();
await inspector.expectTableHeaders(['Count', 'Average machine.ram']);
+ await inspector.close();
});
describe('filtering on inspector table values', function() {
diff --git a/test/functional/apps/visualize/_markdown_vis.js b/test/functional/apps/visualize/_markdown_vis.js
index 51c03c90f507b8..fee6c074af5d25 100644
--- a/test/functional/apps/visualize/_markdown_vis.js
+++ b/test/functional/apps/visualize/_markdown_vis.js
@@ -63,7 +63,7 @@ export default function({ getPageObjects, getService }) {
});
it('should resize the editor', async function() {
- const editorSidebar = await find.byCssSelector('.visEditor__sidebar');
+ const editorSidebar = await find.byCssSelector('.visEditor__collapsibleSidebar');
const initialSize = await editorSidebar.getSize();
await PageObjects.visEditor.sizeUpEditor();
const afterSize = await editorSidebar.getSize();
diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js
index e7ce5808554b47..d0f7810b6f8bb3 100644
--- a/test/functional/apps/visualize/_point_series_options.js
+++ b/test/functional/apps/visualize/_point_series_options.js
@@ -57,7 +57,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.visEditor.selectField('machine.ram', 'metrics');
// go to options page
log.debug('Going to axis options');
- await pointSeriesVis.clickAxisOptions();
+ await PageObjects.visEditor.clickMetricsAndAxes();
// add another value axis
log.debug('adding axis');
await pointSeriesVis.clickAddAxis();
diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js
index 10cbd9913c70c2..2467a540616430 100644
--- a/test/functional/apps/visualize/_region_map.js
+++ b/test/functional/apps/visualize/_region_map.js
@@ -57,6 +57,7 @@ export default function({ getService, getPageObjects }) {
];
await inspector.open();
await inspector.expectTableData(expectedData);
+ await inspector.close();
});
it('should change results after changing layer to world', async function() {
@@ -94,6 +95,8 @@ export default function({ getService, getPageObjects }) {
['BR', '415'],
];
expect(actualData).to.eql(expectedData);
+
+ await inspector.close();
});
it('should contain a dropdown with the default road_map base layer as an option', async () => {
diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js
index 4f921cec1fdf10..a527e9bcad42ff 100644
--- a/test/functional/apps/visualize/_tag_cloud.js
+++ b/test/functional/apps/visualize/_tag_cloud.js
@@ -77,12 +77,12 @@ export default function({ getService, getPageObjects }) {
});
it('should collapse the sidebar', async function() {
- const editorSidebar = await find.byCssSelector('.collapsible-sidebar');
+ const editorSidebar = await find.byCssSelector('.visEditorSidebar');
await PageObjects.visEditor.clickEditorSidebarCollapse();
// Give d3 tag cloud some time to rearrange tags
await PageObjects.common.sleep(1000);
- const afterSize = await editorSidebar.getSize();
- expect(afterSize.width).to.be(0);
+ const isDisplayed = await editorSidebar.isDisplayed();
+ expect(isDisplayed).to.be(false);
await PageObjects.visEditor.clickEditorSidebarCollapse();
});
diff --git a/test/functional/page_objects/point_series_page.js b/test/functional/page_objects/point_series_page.js
index 74bf07b59bc381..594facb8b74b5b 100644
--- a/test/functional/page_objects/point_series_page.js
+++ b/test/functional/page_objects/point_series_page.js
@@ -23,10 +23,6 @@ export function PointSeriesPageProvider({ getService }) {
const find = getService('find');
class PointSeriesVis {
- async clickAxisOptions() {
- return await testSubjects.click('visEditorTabadvanced');
- }
-
async clickAddAxis() {
return await testSubjects.click('visualizeAddYAxisButton');
}
diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts
index 30e13d551fa28d..1e098e86216e36 100644
--- a/test/functional/page_objects/visualize_editor_page.ts
+++ b/test/functional/page_objects/visualize_editor_page.ts
@@ -37,19 +37,19 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
class VisualizeEditorPage {
public async clickDataTab() {
- await testSubjects.click('visualizeEditDataLink');
+ await testSubjects.click('visEditorTab__data');
}
public async clickOptionsTab() {
- await testSubjects.click('visEditorTaboptions');
+ await testSubjects.click('visEditorTab__options');
}
public async clickMetricsAndAxes() {
- await testSubjects.click('visEditorTabadvanced');
+ await testSubjects.click('visEditorTab__advanced');
}
public async clickVisEditorTab(tabName: string) {
- await testSubjects.click('visEditorTab' + tabName);
+ await testSubjects.click(`visEditorTab__${tabName}`);
await header.waitUntilLoadingHasFinished();
}
@@ -134,7 +134,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
public async getBucketErrorMessage() {
const error = await find.byCssSelector(
- '[group-name="buckets"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText'
+ '[data-test-subj="bucketsAggGroup"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText'
);
const errorMessage = await error.getAttribute('innerText');
log.debug(errorMessage);
@@ -152,7 +152,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
) {
log.debug(`selectField ${fieldValue}`);
const selector = `
- [group-name="${groupName}"]
+ [data-test-subj="${groupName}AggGroup"]
[data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen
[data-test-subj="visAggEditorParams"]
${childAggregationType ? '.visEditorAgg__subAgg' : ''}
@@ -180,7 +180,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
childAggregationType = false
) {
const comboBoxElement = await find.byCssSelector(`
- [group-name="${groupName}"]
+ [data-test-subj="${groupName}AggGroup"]
[data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen
${childAggregationType ? '.visEditorAgg__subAgg' : ''}
[data-test-subj="defaultEditorAggSelect"]
@@ -291,8 +291,9 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
}
public async sizeUpEditor() {
- await testSubjects.click('visualizeEditorResizer');
- await browser.pressKeys(browser.keys.ARROW_RIGHT);
+ const resizerPanel = await testSubjects.find('splitPanelResizer');
+ // Drag panel 100 px left
+ await browser.dragAndDrop({ location: resizerPanel }, { location: { x: -100, y: 0 } });
}
public async toggleDisabledAgg(agg: string) {
@@ -320,7 +321,10 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
}
public async toggleAutoMode() {
- await testSubjects.click('visualizeEditorAutoButton');
+ // this is a temporary solution, should be replaced with initial after fixing the EuiToggleButton
+ // passing the data-test-subj attribute to a checkbox
+ await find.clickByCssSelector('.visEditorSidebar__controls input[type="checkbox"]');
+ // await testSubjects.click('visualizeEditorAutoButton');
}
public async isApplyEnabled() {
@@ -428,7 +432,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP
}
public async clickMetricEditor() {
- await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button');
+ await find.clickByCssSelector('[data-test-subj="metricsAggGroup"] .euiAccordion__button');
}
public async clickMetricByIndex(index: number) {
diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts
index 0071b8d993f706..e54e3d1d011540 100644
--- a/test/functional/page_objects/visualize_page.ts
+++ b/test/functional/page_objects/visualize_page.ts
@@ -200,7 +200,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide
}
public async getSideEditorExists() {
- return await find.existsByCssSelector('.collapsible-sidebar');
+ return await find.existsByCssSelector('.visEditor__collapsibleSidebar');
}
public async clickSavedSearch(savedSearchName: string) {
diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts
index 6a689e85de214c..2d799b7daca732 100644
--- a/test/functional/services/browser.ts
+++ b/test/functional/services/browser.ts
@@ -240,8 +240,8 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
* @return {Promise}
*/
public async dragAndDrop(
- from: { offset: { x: any; y: any }; location: any },
- to: { offset: { x: any; y: any }; location: any }
+ from: { offset?: { x: any; y: any }; location: any },
+ to: { offset?: { x: any; y: any }; location: any }
) {
if (this.isW3CEnabled) {
// The offset should be specified in pixels relative to the center of the element's bounding box
diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
index ab88e4780936ea..2c203e507260fa 100644
--- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
+++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
@@ -53,9 +53,10 @@ discover-app .discover-table-footer {
* Visualize Editor Tweaks
*/
-/* hide unusable controls */
-visualization-editor .visEditor--default > :not(.visEditor__canvas) {
- display: none;
+/* hide unusable controls
+* !important is required to override resizable panel inline display */
+visualization-editor .visEditor--default > :not(.visEditor__visualization) {
+ display: none !important;
}
/** THIS IS FOR TSVB UNTIL REFACTOR **/
diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
index 8aca042144b3b4..b5c9861208b7bd 100644
--- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
+++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
@@ -52,9 +52,10 @@ discover-app .discover-table-footer {
* Visualize Editor Tweaks
*/
-/* hide unusable controls */
-visualization-editor .visEditor--default > :not(.visEditor__canvas) {
- display: none;
+/* hide unusable controls
+* !important is required to override resizable panel inline display */
+visualization-editor .visEditor--default > :not(.visEditor__visualization) {
+ display: none !important;
}
/** THIS IS FOR TSVB UNTIL REFACTOR **/
.tvbEditorVisualization {
diff --git a/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js b/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js
index 590f3dc85740ed..897caa07fd8739 100644
--- a/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js
+++ b/x-pack/legacy/plugins/rollup/public/visualize/editor_config.js
@@ -9,7 +9,7 @@ import { editorConfigProviders } from 'ui/vis/editors/config/editor_config_provi
export function initEditorConfig() {
// Limit agg params based on rollup capabilities
- editorConfigProviders.register((aggType, indexPattern, aggConfig) => {
+ editorConfigProviders.register((indexPattern, aggConfig) => {
if (indexPattern.type !== 'rollup') {
return {};
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 84020926955906..c8a58d90595c4e 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -503,15 +503,7 @@
"common.ui.vis.editors.aggGroups.bucketsText": "バケット",
"common.ui.vis.editors.aggGroups.metricsText": "メトリック",
"common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "「{schema}」集約は他のバケットの前に実行する必要があります!",
- "common.ui.vis.editors.resizeAriaLabels": "左右のキーでエディターのサイズを変更します",
- "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "ビジュアライゼーションを変更と共に更新します",
- "common.ui.vis.editors.sidebar.applyChangesTooltip": "変更を適用",
"common.ui.vis.editors.sidebar.autoApplyChangesAriaLabel": "変更されるごとにビジュアライゼーションを自動的に更新します",
- "common.ui.vis.editors.sidebar.autoApplyChangesLabel": "自動適用",
- "common.ui.vis.editors.sidebar.autoApplyChangesTooltip": "変更を自動適用",
- "common.ui.vis.editors.sidebar.discardChangesAriaLabel": "ビジュアライゼーションをリセット",
- "common.ui.vis.editors.sidebar.discardChangesTooltip": "変更を破棄",
- "common.ui.vis.editors.sidebar.errorButtonAriaLabel": "ハイライトされたフィールドのエラーを解決する必要があります。",
"common.ui.vis.editors.sidebar.errorButtonTooltip": "ハイライトされたフィールドのエラーを解決する必要があります。",
"common.ui.vis.editors.sidebar.tabs.dataLabel": "データ",
"common.ui.vis.editors.sidebar.tabs.optionsLabel": "オプション",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index f3af5ec10338cd..583495972c4c1e 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -503,15 +503,7 @@
"common.ui.vis.editors.aggGroups.bucketsText": "存储桶",
"common.ui.vis.editors.aggGroups.metricsText": "指标",
"common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "“{schema}” 聚合必须在所有其他存储桶之前运行!",
- "common.ui.vis.editors.resizeAriaLabels": "按向左/向右键以调整编辑器的大小",
- "common.ui.vis.editors.sidebar.applyChangesAriaLabel": "使用您的更改更新可视化",
- "common.ui.vis.editors.sidebar.applyChangesTooltip": "应用更改",
"common.ui.vis.editors.sidebar.autoApplyChangesAriaLabel": "每次更改时自动更新可视化",
- "common.ui.vis.editors.sidebar.autoApplyChangesLabel": "自动应用",
- "common.ui.vis.editors.sidebar.autoApplyChangesTooltip": "自动应用更改",
- "common.ui.vis.editors.sidebar.discardChangesAriaLabel": "重置可视化",
- "common.ui.vis.editors.sidebar.discardChangesTooltip": "放弃更改",
- "common.ui.vis.editors.sidebar.errorButtonAriaLabel": "需要解决突出显示的字段中的错误。",
"common.ui.vis.editors.sidebar.errorButtonTooltip": "需要解决突出显示的字段中的错误。",
"common.ui.vis.editors.sidebar.tabs.dataLabel": "数据",
"common.ui.vis.editors.sidebar.tabs.optionsLabel": "选项",