diff --git a/.github/workflows/pr-project-assigner.yml b/.github/workflows/pr-project-assigner.yml index aea8a9cad6b1f..e2e5ce1a4d963 100644 --- a/.github/workflows/pr-project-assigner.yml +++ b/.github/workflows/pr-project-assigner.yml @@ -11,5 +11,5 @@ jobs: uses: elastic/github-actions/project-assigner@v1.0.0 id: project_assigner with: - issue-mappings: '[{"label": "Team:AppAch", "projectName": "kibana-app-arch", "columnId": 6173897}]' - ghToken: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173897}, {"label": "Feature:Lens", "projectName": "Lens", "columnId": 6219362}]' + ghToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/project-assigner.yml b/.github/workflows/project-assigner.yml index c7f17993249eb..6fd6fd645b5ee 100644 --- a/.github/workflows/project-assigner.yml +++ b/.github/workflows/project-assigner.yml @@ -11,7 +11,7 @@ jobs: uses: elastic/github-actions/project-assigner@v1.0.0 id: project_assigner with: - issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173895}]' + issue-mappings: '[{"label": "Team:AppArch", "projectName": "kibana-app-arch", "columnId": 6173895}, {"label": "Feature:Lens", "projectName": "Lens", "columnId": 6219363}]' ghToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index a771a130d08b1..1cec7aaacbbc9 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "17.0.0", + "@elastic/eui": "17.1.2", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index b7fbe629246ec..bb28c1460c9c2 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index ccdddc87dbc18..0764451c74575 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,9 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE + ? [] + : ['--source-maps', 'inline']), ], wait: true, env: { diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 495b5fb374b43..36bbc8cc82873 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -18,6 +18,7 @@ */ import { PromiseType } from 'utility-types'; +export { $Values, Required, Optional, Class } from 'utility-types'; /** * Returns wrapped type of a promise. diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index a79d08677020b..a999eb41eb781 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -13,7 +13,7 @@ "clean": "del target" }, "dependencies": { - "utility-types": "^3.7.0" + "utility-types": "^3.10.0" }, "devDependencies": { "del-cli": "^3.0.0", diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap index 626c91b6a9668..9bd686776138f 100644 --- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -31,7 +31,7 @@ Array [ ] `; -exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` Array [ @@ -74,4 +74,4 @@ Array [ ] `; -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap index 3928c54f90179..131ec836f5252 100644 --- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -29,7 +29,7 @@ Array [ ] `; -exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; +exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` Array [ diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js index 801580f4e158c..b3999c76493c0 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js @@ -61,7 +61,6 @@ export function ScriptHighlightRules() { }, { token: 'script.keyword.operator', - regex: '\\?\\.|\\*\\.|=~|==~|!|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|->|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|instanceof|new|typeof|void)', }, diff --git a/src/legacy/core_plugins/input_control_vis/index.ts b/src/legacy/core_plugins/input_control_vis/index.ts new file mode 100644 index 0000000000000..8f6178e26126b --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const inputControlVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'input_control_vis', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default inputControlVisPluginInitializer; diff --git a/src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap similarity index 73% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap index 809214f756713..632fe63e9e148 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap @@ -16,9 +16,33 @@ exports[`renders ControlsTab 1`] = ` "size": 5, "type": "terms", }, + "parent": "parent", "type": "list", } } + deps={ + Object { + "core": Object { + "getStartServices": [MockFunction], + "injectedMetadata": Object { + "getInjectedVar": [MockFunction], + }, + }, + "data": Object { + "query": Object { + "filterManager": Object { + "fieldName": "myField", + "getAppFilters": [MockFunction], + "getGlobalFilters": [MockFunction], + "getIndexPattern": [Function], + }, + "timefilter": Object { + "timefilter": Object {}, + }, + }, + }, + } + } getIndexPattern={[Function]} handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} @@ -49,9 +73,33 @@ exports[`renders ControlsTab 1`] = ` "options": Object { "step": 1, }, + "parent": "parent", "type": "range", } } + deps={ + Object { + "core": Object { + "getStartServices": [MockFunction], + "injectedMetadata": Object { + "getInjectedVar": [MockFunction], + }, + }, + "data": Object { + "query": Object { + "filterManager": Object { + "fieldName": "myField", + "getAppFilters": [MockFunction], + "getGlobalFilters": [MockFunction], + "getIndexPattern": [Function], + }, + "timefilter": Object { + "timefilter": Object {}, + }, + }, + }, + } + } getIndexPattern={[Function]} handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap index ff3d1ffc146e3..9bc8b1b9ac5cd 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/list_control_editor.test.tsx.snap @@ -3,6 +3,7 @@ exports[`renders dynamic options should display disabled dynamic options with tooltip for non-string fields 1`] = ` { + return fields.find(({ name: n }) => n === name); +}; + +export const getDepsMock = (): InputControlVisDependencies => + ({ + core: { + getStartServices: jest.fn().mockReturnValue([ + null, + { + data: { + ui: { + IndexPatternSelect: () => (
) as any, + }, + indexPatterns: { + get: () => ({ + fields, + }), + }, + }, + }, + ]), + injectedMetadata: { + getInjectedVar: jest.fn().mockImplementation(key => { + switch (key) { + case 'autocompleteTimeout': + return 1000; + case 'autocompleteTerminateAfter': + return 100000; + default: + return ''; + } + }), + }, + }, + data: { + query: { + filterManager: { + fieldName: 'myField', + getIndexPattern: () => ({ + fields, + }), + getAppFilters: jest.fn().mockImplementation(() => []), + getGlobalFilters: jest.fn().mockImplementation(() => []), + }, + timefilter: { + timefilter: {}, + }, + }, + }, + } as any); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts similarity index 82% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts index c693bf100e265..638dd7170cb8d 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts @@ -17,7 +17,12 @@ * under the License. */ -export const getIndexPatternMock = () => { +import { IIndexPattern } from '../../../../../../../plugins/data/public'; + +/** + * Returns forced **Partial** IndexPattern for use in tests + */ +export const getIndexPatternMock = (): Promise => { return Promise.resolve({ id: 'mockIndexPattern', title: 'mockIndexPattern', @@ -26,5 +31,5 @@ export const getIndexPatternMock = () => { { name: 'textField', type: 'string', aggregatable: false }, { name: 'numberField', type: 'number', aggregatable: true }, ], - }); + } as IIndexPattern); }; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts new file mode 100644 index 0000000000000..9da47bedcc784 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts @@ -0,0 +1,46 @@ +/* + * 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 { SearchSource } from '../../../legacy_imports'; + +export const getSearchSourceMock = (esSearchResponse?: any): SearchSource => + jest.fn().mockImplementation(() => ({ + setParent: jest.fn(), + setField: jest.fn(), + fetch: jest.fn().mockResolvedValue( + esSearchResponse + ? esSearchResponse + : { + aggregations: { + termsAgg: { + buckets: [ + { + key: 'Zurich Airport', + doc_count: 691, + }, + { + key: 'Xi an Xianyang International Airport', + doc_count: 526, + }, + ], + }, + }, + } + ), + })); diff --git a/src/legacy/core_plugins/input_control_vis/index.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts similarity index 65% rename from src/legacy/core_plugins/input_control_vis/index.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts index a21b79e28fb7f..881412a7c56fd 100644 --- a/src/legacy/core_plugins/input_control_vis/index.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts @@ -17,14 +17,15 @@ * under the License. */ -import { resolve } from 'path'; +import { ShallowWrapper, ReactWrapper } from 'enzyme'; -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - visTypes: ['plugins/input_control_vis/register_vis'], - interpreter: ['plugins/input_control_vis/input_control_fn'], - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, - }); -} +export const updateComponent = async ( + component: + | ShallowWrapper, React.Component<{}, {}, any>> + | ReactWrapper, React.Component<{}, {}, any>> +) => { + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); +}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx similarity index 72% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx index cc31b8d238dbe..dbac5d9d94371 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -17,13 +17,10 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { RangeControlEditor } from './range_control_editor'; -import { ListControlEditor } from './list_control_editor'; -import { getTitle } from '../../editor_utils'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import React, { PureComponent, ChangeEvent } from 'react'; +import { InjectedIntlProps } from 'react-intl'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiAccordion, EuiButtonIcon, @@ -32,11 +29,45 @@ import { EuiFormRow, EuiPanel, EuiSpacer, + EuiSwitchEvent, } from '@elastic/eui'; -class ControlEditorUi extends Component { - changeLabel = evt => { - this.props.handleLabelChange(this.props.controlIndex, evt); +import { RangeControlEditor } from './range_control_editor'; +import { ListControlEditor } from './list_control_editor'; +import { getTitle, ControlParams, CONTROL_TYPES, ControlParamsOptions } from '../../editor_utils'; +import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; + +interface ControlEditorUiProps { + controlIndex: number; + controlParams: ControlParams; + handleLabelChange: (controlIndex: number, event: ChangeEvent) => 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: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + parentCandidates: Array<{ + value: string; + text: string; + }>; + handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + deps: InputControlVisDependencies; +} + +class ControlEditorUi extends PureComponent { + changeLabel = (event: ChangeEvent) => { + this.props.handleLabelChange(this.props.controlIndex, event); }; removeControl = () => { @@ -51,18 +82,18 @@ class ControlEditorUi extends Component { this.props.moveControl(this.props.controlIndex, 1); }; - changeIndexPattern = evt => { - this.props.handleIndexPatternChange(this.props.controlIndex, evt); + changeIndexPattern = (indexPatternId: string) => { + this.props.handleIndexPatternChange(this.props.controlIndex, indexPatternId); }; - changeFieldName = evt => { - this.props.handleFieldNameChange(this.props.controlIndex, evt); + changeFieldName = (fieldName: string) => { + this.props.handleFieldNameChange(this.props.controlIndex, fieldName); }; renderEditor() { let controlEditor = null; switch (this.props.controlParams.type) { - case 'list': + case CONTROL_TYPES.LIST: controlEditor = ( ); break; - case 'range': + case CONTROL_TYPES.RANGE: controlEditor = ( ); break; @@ -167,24 +200,4 @@ class ControlEditorUi extends Component { } } -ControlEditorUi.propTypes = { - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleLabelChange: PropTypes.func.isRequired, - moveControl: PropTypes.func.isRequired, - handleRemoveControl: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - getIndexPattern: PropTypes.func.isRequired, - handleCheckboxOptionChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, - parentCandidates: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - }) - ).isRequired, - handleParentChange: PropTypes.func.isRequired, -}; - export const ControlEditor = injectI18n(ControlEditorUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx similarity index 87% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index 28f435c27ea8f..4e7c9bafbf510 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -17,45 +17,36 @@ * under the License. */ -jest.mock('../../../../../core_plugins/data/public/legacy', () => ({ - indexPatterns: { - indexPatterns: { - get: jest.fn(), - }, - }, -})); - -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - import React from 'react'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; +import { getDepsMock } from './__tests__/get_deps_mock'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; -import { ControlsTab } from './controls_tab'; +import { ControlsTab, ControlsTabUiProps } from './controls_tab'; const indexPatternsMock = { get: getIndexPatternMock, }; -let props; +let props: ControlsTabUiProps; beforeEach(() => { props = { + deps: getDepsMock(), vis: { API: { indexPatterns: indexPatternsMock, }, + type: { + name: 'test', + title: 'test', + visualization: null, + requestHandler: 'test', + responseHandler: 'test', + stage: 'beta', + requiresSearch: false, + hidden: false, + }, }, stateParams: { controls: [ @@ -71,6 +62,7 @@ beforeEach(() => { size: 5, order: 'desc', }, + parent: 'parent', }, { id: '2', @@ -81,10 +73,12 @@ beforeEach(() => { options: { step: 1, }, + parent: 'parent', }, ], }, setValue: jest.fn(), + intl: null as any, }; }); @@ -105,7 +99,7 @@ describe('behavior', () => { 'controls', expect.arrayContaining(props.stateParams.controls) ); - expect(props.setValue.mock.calls[0][1].length).toEqual(3); + expect((props.setValue as jest.Mock).mock.calls[0][1].length).toEqual(3); }); test('remove control button', () => { @@ -120,6 +114,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -142,6 +137,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -152,6 +148,7 @@ describe('behavior', () => { fieldName: 'keywordField', label: 'custom label', type: 'list', + parent: 'parent', options: { type: 'terms', multiselect: true, @@ -177,6 +174,7 @@ describe('behavior', () => { fieldName: 'numberField', label: '', type: 'range', + parent: 'parent', options: { step: 1, }, @@ -187,6 +185,7 @@ describe('behavior', () => { fieldName: 'keywordField', label: 'custom label', type: 'list', + parent: 'parent', options: { type: 'terms', multiselect: true, diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx index 97036d7b0f5df..56381ef7d1570 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -17,14 +17,10 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { ControlEditor } from './control_editor'; -import { addControl, moveControl, newControl, removeControl, setControl } from '../../editor_utils'; -import { getLineageMap, getParentCandidates } from '../../lineage'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { npStart } from 'ui/new_platform'; +import React, { PureComponent, ChangeEvent } from 'react'; +import { InjectedIntlProps } from 'react-intl'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiFlexGroup, @@ -32,55 +28,97 @@ import { EuiFormRow, EuiPanel, EuiSelect, + EuiSwitchEvent, } from '@elastic/eui'; -class ControlsTabUi extends Component { +import { ControlEditor } from './control_editor'; +import { + addControl, + moveControl, + newControl, + removeControl, + setControl, + ControlParams, + CONTROL_TYPES, + ControlParamsOptions, +} from '../../editor_utils'; +import { getLineageMap, getParentCandidates } from '../../lineage'; +import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { VisOptionsProps } from '../../legacy_imports'; +import { InputControlVisDependencies } from '../../plugin'; + +interface ControlsTabUiState { + type: CONTROL_TYPES; +} + +interface ControlsTabUiParams { + controls: ControlParams[]; +} +type ControlsTabUiInjectedProps = InjectedIntlProps & + Pick, 'vis' | 'stateParams' | 'setValue'> & { + deps: InputControlVisDependencies; + }; + +export type ControlsTabUiProps = ControlsTabUiInjectedProps; + +class ControlsTabUi extends PureComponent { state = { - type: 'list', + type: CONTROL_TYPES.LIST, }; - getIndexPattern = async indexPatternId => { - return await npStart.plugins.data.indexPatterns.get(indexPatternId); + getIndexPattern = async (indexPatternId: string): Promise => { + const [, startDeps] = await this.props.deps.core.getStartServices(); + return await startDeps.data.indexPatterns.get(indexPatternId); }; - onChange = value => this.props.setValue('controls', value); + onChange = (value: ControlParams[]) => this.props.setValue('controls', value); - handleLabelChange = (controlIndex, evt) => { + handleLabelChange = (controlIndex: number, event: ChangeEvent) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.label = evt.target.value; + updatedControl.label = event.target.value; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleIndexPatternChange = (controlIndex, indexPatternId) => { + handleIndexPatternChange = (controlIndex: number, indexPatternId: string) => { const updatedControl = this.props.stateParams.controls[controlIndex]; updatedControl.indexPattern = indexPatternId; updatedControl.fieldName = ''; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleFieldNameChange = (controlIndex, fieldName) => { + handleFieldNameChange = (controlIndex: number, fieldName: string) => { const updatedControl = this.props.stateParams.controls[controlIndex]; updatedControl.fieldName = fieldName; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleCheckboxOptionChange = (controlIndex, optionName, evt) => { + handleCheckboxOptionChange = ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.options[optionName] = evt.target.checked; + // @ts-ignore + updatedControl.options[optionName] = event.target.checked; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleNumberOptionChange = (controlIndex, optionName, evt) => { + handleNumberOptionChange = ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.options[optionName] = parseFloat(evt.target.value); + // @ts-ignore + updatedControl.options[optionName] = parseFloat(event.target.value); this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleRemoveControl = controlIndex => { + handleRemoveControl = (controlIndex: number) => { this.onChange(removeControl(this.props.stateParams.controls, controlIndex)); }; - moveControl = (controlIndex, direction) => { + moveControl = (controlIndex: number, direction: number) => { this.onChange(moveControl(this.props.stateParams.controls, controlIndex, direction)); }; @@ -88,9 +126,9 @@ class ControlsTabUi extends Component { this.onChange(addControl(this.props.stateParams.controls, newControl(this.state.type))); }; - handleParentChange = (controlIndex, evt) => { + handleParentChange = (controlIndex: number, event: ChangeEvent) => { const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.parent = evt.target.value; + updatedControl.parent = event.target.value; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -117,6 +155,7 @@ class ControlsTabUi extends Component { handleNumberOptionChange={this.handleNumberOptionChange} parentCandidates={parentCandidates} handleParentChange={this.handleParentChange} + deps={this.props.deps} /> ); }); @@ -137,14 +176,14 @@ class ControlsTabUi extends Component { data-test-subj="selectControlType" options={[ { - value: 'range', + value: CONTROL_TYPES.RANGE, text: intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.rangeDropDownOptionLabel', defaultMessage: 'Range slider', }), }, { - value: 'list', + value: CONTROL_TYPES.LIST, text: intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.listDropDownOptionLabel', defaultMessage: 'Options list', @@ -152,7 +191,7 @@ class ControlsTabUi extends Component { }, ]} value={this.state.type} - onChange={evt => this.setState({ type: evt.target.value })} + onChange={event => this.setState({ type: event.target.value as CONTROL_TYPES })} aria-label={intl.formatMessage({ id: 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', defaultMessage: 'Select control type', @@ -186,9 +225,8 @@ class ControlsTabUi extends Component { } } -ControlsTabUi.propTypes = { - vis: PropTypes.object.isRequired, - setValue: PropTypes.func.isRequired, -}; - export const ControlsTab = injectI18n(ControlsTabUi); + +export const getControlsTab = (deps: InputControlVisDependencies) => ( + props: Omit +) => ; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx index 456ff17a316a1..bde2f09ab0a47 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.tsx @@ -18,43 +18,59 @@ */ import _ from 'lodash'; -import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { InjectedIntlProps } from 'react-intl'; + import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; + +import { IIndexPattern, IFieldType } from '../../../../../../plugins/data/public'; + +interface FieldSelectUiState { + isLoading: boolean; + fields: Array>; + indexPatternId: string; +} + +export type FieldSelectUiProps = InjectedIntlProps & { + getIndexPattern: (indexPatternId: string) => Promise; + indexPatternId: string; + onChange: (value: any) => void; + fieldName?: string; + filterField?: (field: IFieldType) => boolean; + controlIndex: number; +}; -import { EuiFormRow, EuiComboBox } from '@elastic/eui'; +class FieldSelectUi extends Component { + private hasUnmounted: boolean; -class FieldSelectUi extends Component { - constructor(props) { + constructor(props: FieldSelectUiProps) { super(props); - this._hasUnmounted = false; + this.hasUnmounted = false; this.state = { isLoading: false, fields: [], indexPatternId: props.indexPatternId, }; - this.filterField = _.get(props, 'filterField', () => { - return true; - }); } componentWillUnmount() { - this._hasUnmounted = true; + this.hasUnmounted = true; } componentDidMount() { this.loadFields(this.state.indexPatternId); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: FieldSelectUiProps) { if (this.props.indexPatternId !== nextProps.indexPatternId) { - this.loadFields(nextProps.indexPatternId); + this.loadFields(nextProps.indexPatternId ?? ''); } } - loadFields = indexPatternId => { + loadFields = (indexPatternId: string) => { this.setState( { isLoading: true, @@ -65,12 +81,12 @@ class FieldSelectUi extends Component { ); }; - debouncedLoad = _.debounce(async indexPatternId => { + debouncedLoad = _.debounce(async (indexPatternId: string) => { if (!indexPatternId || indexPatternId.length === 0) { return; } - let indexPattern; + let indexPattern: IIndexPattern; try { indexPattern = await this.props.getIndexPattern(indexPatternId); } catch (err) { @@ -78,7 +94,7 @@ class FieldSelectUi extends Component { return; } - if (this._hasUnmounted) { + if (this.hasUnmounted) { return; } @@ -88,17 +104,15 @@ class FieldSelectUi extends Component { return; } - const fieldsByTypeMap = new Map(); - const fields = []; - indexPattern.fields.filter(this.filterField).forEach(field => { - if (fieldsByTypeMap.has(field.type)) { - const fieldsList = fieldsByTypeMap.get(field.type); + const fieldsByTypeMap = new Map(); + const fields: Array> = []; + indexPattern.fields + .filter(this.props.filterField ?? (() => true)) + .forEach((field: IFieldType) => { + const fieldsList = fieldsByTypeMap.get(field.type) ?? []; fieldsList.push(field.name); fieldsByTypeMap.set(field.type, fieldsList); - } else { - fieldsByTypeMap.set(field.type, [field.name]); - } - }); + }); fieldsByTypeMap.forEach((fieldsList, fieldType) => { fields.push({ @@ -117,11 +131,11 @@ class FieldSelectUi extends Component { this.setState({ isLoading: false, - fields: fields, + fields, }); }, 300); - onChange = selectedOptions => { + onChange = (selectedOptions: Array>) => { this.props.onChange(_.get(selectedOptions, '0.value')); }; @@ -165,13 +179,4 @@ class FieldSelectUi extends Component { } } -FieldSelectUi.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - indexPatternId: PropTypes.string, - onChange: PropTypes.func.isRequired, - fieldName: PropTypes.string, - filterField: PropTypes.func, - controlIndex: PropTypes.number.isRequired, -}; - export const FieldSelect = injectI18n(FieldSelectUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx similarity index 73% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx index 7d7fbc0539de0..66fdbca64f053 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.tsx @@ -17,15 +17,20 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { ComponentType } from 'react'; import { injectI18n } from '@kbn/i18n/react'; import { EuiFormRow } from '@elastic/eui'; +import { InjectedIntlProps } from 'react-intl'; +import { IndexPatternSelect } from 'src/plugins/data/public'; -import { npStart } from 'ui/new_platform'; -const { IndexPatternSelect } = npStart.plugins.data.ui; +export type IndexPatternSelectFormRowUiProps = InjectedIntlProps & { + onChange: (opt: any) => void; + indexPatternId: string; + controlIndex: number; + IndexPatternSelect: ComponentType; +}; -function IndexPatternSelectFormRowUi(props) { +function IndexPatternSelectFormRowUi(props: IndexPatternSelectFormRowUiProps) { const { controlIndex, indexPatternId, intl, onChange } = props; const selectId = `indexPatternSelect-${controlIndex}`; @@ -37,7 +42,7 @@ function IndexPatternSelectFormRowUi(props) { defaultMessage: 'Index Pattern', })} > - ); } -IndexPatternSelectFormRowUi.propTypes = { - onChange: PropTypes.func.isRequired, - indexPatternId: PropTypes.string, - controlIndex: PropTypes.number.isRequired, -}; - export const IndexPatternSelectFormRow = injectI18n(IndexPatternSelectFormRowUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx similarity index 80% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx index 24b14943c8bb3..de0187f87212f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx @@ -17,31 +17,21 @@ * under the License. */ -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { getDepsMock } from './__tests__/get_deps_mock'; +import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { ListControlEditor } from './list_control_editor'; +import { ControlParams } from '../../editor_utils'; +import { updateComponent } from './__tests__/update_component'; -const controlParams = { +const controlParamsBase: ControlParams = { id: '1', indexPattern: 'indexPattern1', fieldName: 'keywordField', @@ -53,11 +43,13 @@ const controlParams = { dynamicOptions: false, size: 10, }, + parent: '', }; -let handleFieldNameChange; -let handleIndexPatternChange; -let handleCheckboxOptionChange; -let handleNumberOptionChange; +const deps = getDepsMock(); +let handleFieldNameChange: sinon.SinonSpy; +let handleIndexPatternChange: sinon.SinonSpy; +let handleCheckboxOptionChange: sinon.SinonSpy; +let handleNumberOptionChange: sinon.SinonSpy; beforeEach(() => { handleFieldNameChange = sinon.spy(); @@ -68,8 +60,9 @@ beforeEach(() => { describe('renders', () => { test('should not display any options until field is selected', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: '', type: 'list', @@ -79,9 +72,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); @@ -109,9 +101,10 @@ describe('renders', () => { ]; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); describe('dynamic options', () => { test('should display dynamic options for string fields', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'keywordField', type: 'list', @@ -142,9 +133,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); test('should display size field when dynamic options is disabled', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'keywordField', type: 'list', @@ -177,9 +168,11 @@ describe('renders', () => { dynamicOptions: false, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); test('should display disabled dynamic options with tooltip for non-string fields', async () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', + label: 'mock', indexPattern: 'mockIndexPattern', fieldName: 'numberField', type: 'list', @@ -212,9 +203,11 @@ describe('renders', () => { dynamicOptions: true, size: 5, }, + parent: '', }; const component = shallow( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); expect(component).toMatchSnapshot(); }); @@ -240,9 +230,10 @@ describe('renders', () => { test('handleCheckboxOptionChange - multiselect', async () => { const component = mountWithIntl( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); const checkbox = findTestSubject(component, 'listControlMultiselectInput'); checkbox.simulate('click'); @@ -268,10 +256,10 @@ test('handleCheckboxOptionChange - multiselect', async () => { handleCheckboxOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - // Synthetic `evt.target.checked` does not get altered by EuiSwitch, + sinon.match(event => { + // Synthetic `event.target.checked` does not get altered by EuiSwitch, // but its aria attribute is correctly updated - if (evt.target.getAttribute('aria-checked') === 'true') { + if (event.target.getAttribute('aria-checked') === 'true') { return true; } return false; @@ -282,9 +270,10 @@ test('handleCheckboxOptionChange - multiselect', async () => { test('handleNumberOptionChange - size', async () => { const component = mountWithIntl( { /> ); - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + await updateComponent(component); const input = findTestSubject(component, 'listControlSizeInput'); input.simulate('change', { target: { value: 7 } }); @@ -310,8 +296,8 @@ test('handleNumberOptionChange - size', async () => { handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 7) { + sinon.match(event => { + if (event.target.value === 7) { return true; } return false; @@ -322,9 +308,10 @@ test('handleNumberOptionChange - size', async () => { test('field name change', async () => { const component = shallowWithIntl( { /> ); - const update = async () => { - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - }; - // ensure that after async loading is complete the DynamicOptionsSwitch is not disabled expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(1); component.setProps({ controlParams: { - ...controlParams, + ...controlParamsBase, fieldName: 'numberField', }, }); @@ -361,20 +341,20 @@ test('field name change', async () => { expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=true]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=true]') ).toHaveLength(1); component.setProps({ - controlParams, + controlParams: controlParamsBase, }); // ensure that after async loading is complete the DynamicOptionsSwitch is not disabled again, because we switched to original "string" field expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(0); - await update(); + await updateComponent(component); expect( component.find('[data-test-subj="listControlDynamicOptionsSwitch"][disabled=false]') ).toHaveLength(1); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx index 2ee225475b0fe..ff74d30a6e1a8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx @@ -17,35 +17,90 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; +import React, { PureComponent, ChangeEvent, ComponentType } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFormRow, + EuiFieldNumber, + EuiSwitch, + EuiSelect, + EuiSelectProps, + EuiSwitchEvent, +} from '@elastic/eui'; + import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; import { FieldSelect } from './field_select'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { ControlParams, ControlParamsOptions } from '../../editor_utils'; +import { + IIndexPattern, + IFieldType, + IndexPatternSelect, +} from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; -import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect } from '@elastic/eui'; +interface ListControlEditorState { + isLoadingFieldType: boolean; + isStringField: boolean; + prevFieldName: string; + IndexPatternSelect: ComponentType | null; +} -function filterField(field) { - return field.aggregatable && ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type); +interface ListControlEditorProps { + getIndexPattern: (indexPatternId: string) => Promise; + controlIndex: number; + controlParams: ControlParams; + handleFieldNameChange: (fieldName: string) => void; + handleIndexPatternChange: (indexPatternId: string) => void; + handleCheckboxOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: EuiSwitchEvent + ) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + parentCandidates: EuiSelectProps['options']; + deps: InputControlVisDependencies; } -export class ListControlEditor extends Component { - state = { +function filterField(field: IFieldType) { + return ( + Boolean(field.aggregatable) && + ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) + ); +} + +export class ListControlEditor extends PureComponent< + ListControlEditorProps, + ListControlEditorState +> { + private isMounted: boolean = false; + + state: ListControlEditorState = { isLoadingFieldType: true, isStringField: false, prevFieldName: this.props.controlParams.fieldName, + IndexPatternSelect: null, }; componentDidMount() { - this._isMounted = true; + this.isMounted = true; this.loadIsStringField(); + this.getIndexPatternSelect(); } componentWillUnmount() { - this._isMounted = false; + this.isMounted = false; } - static getDerivedStateFromProps = (nextProps, prevState) => { + static getDerivedStateFromProps = ( + nextProps: ListControlEditorProps, + prevState: ListControlEditorState + ) => { const isNewFieldName = prevState.prevFieldName !== nextProps.controlParams.fieldName; if (!prevState.isLoadingFieldType && isNewFieldName) { return { @@ -63,13 +118,20 @@ export class ListControlEditor extends Component { } }; + async getIndexPatternSelect() { + const [, { data }] = await this.props.deps.core.getStartServices(); + this.setState({ + IndexPatternSelect: data.ui.IndexPatternSelect, + }); + } + loadIsStringField = async () => { if (!this.props.controlParams.indexPattern || !this.props.controlParams.fieldName) { this.setState({ isLoadingFieldType: false }); return; } - let indexPattern; + let indexPattern: IIndexPattern; try { indexPattern = await this.props.getIndexPattern(this.props.controlParams.indexPattern); } catch (err) { @@ -77,13 +139,13 @@ export class ListControlEditor extends Component { return; } - if (!this._isMounted) { + if (!this.isMounted) { return; } - const field = indexPattern.fields.find(field => { - return field.name === this.props.controlParams.fieldName; - }); + const field = (indexPattern.fields as IFieldType[]).find( + ({ name }) => name === this.props.controlParams.fieldName + ); if (!field) { return; } @@ -121,8 +183,8 @@ export class ListControlEditor extends Component { { - this.props.handleParentChange(this.props.controlIndex, evt); + onChange={event => { + this.props.handleParentChange(this.props.controlIndex, event); }} /> @@ -147,9 +209,9 @@ export class ListControlEditor extends Component { defaultMessage="Multiselect" /> } - checked={this.props.controlParams.options.multiselect} - onChange={evt => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', evt); + checked={this.props.controlParams.options.multiselect ?? true} + onChange={event => { + this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', event); }} data-test-subj="listControlMultiselectInput" /> @@ -180,9 +242,9 @@ export class ListControlEditor extends Component { defaultMessage="Dynamic Options" /> } - checked={this.props.controlParams.options.dynamicOptions} - onChange={evt => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', evt); + checked={this.props.controlParams.options.dynamicOptions ?? false} + onChange={event => { + this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', event); }} disabled={this.state.isStringField ? false : true} data-test-subj="listControlDynamicOptionsSwitch" @@ -212,8 +274,8 @@ export class ListControlEditor extends Component { { - this.props.handleNumberOptionChange(this.props.controlIndex, 'size', evt); + onChange={event => { + this.props.handleNumberOptionChange(this.props.controlIndex, 'size', event); }} data-test-subj="listControlSizeInput" /> @@ -225,12 +287,17 @@ export class ListControlEditor extends Component { }; render() { + if (this.state.IndexPatternSelect === null) { + return null; + } + return ( - + <> {this.renderOptions()} - + ); } } - -ListControlEditor.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleCheckboxOptionChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, - parentCandidates: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - }) - ).isRequired, - handleParentChange: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx similarity index 93% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx index ef84d37ca8de5..36ec4d4446fd6 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -21,17 +21,19 @@ import React from 'react'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { OptionsTab } from './options_tab'; +import { OptionsTab, OptionsTabProps } from './options_tab'; +import { Vis } from '../../legacy_imports'; describe('OptionsTab', () => { - let props; + let props: OptionsTabProps; beforeEach(() => { props = { - vis: {}, + vis: {} as Vis, stateParams: { updateFiltersOnChange: false, useTimeFilter: false, + pinFilters: false, }, setValue: jest.fn(), }; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx similarity index 74% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx index 236624b11118c..43f9e15302e51 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -17,24 +17,37 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiSwitchEvent } from '@elastic/eui'; + +import { VisOptionsProps } from '../../legacy_imports'; + +interface OptionsTabParams { + updateFiltersOnChange: boolean; + useTimeFilter: boolean; + pinFilters: boolean; +} +type OptionsTabInjectedProps = Pick< + VisOptionsProps, + 'vis' | 'setValue' | 'stateParams' +>; + +export type OptionsTabProps = OptionsTabInjectedProps; -export class OptionsTab extends Component { - handleUpdateFiltersChange = evt => { - this.props.setValue('updateFiltersOnChange', evt.target.checked); +export class OptionsTab extends PureComponent { + handleUpdateFiltersChange = (event: EuiSwitchEvent) => { + this.props.setValue('updateFiltersOnChange', event.target.checked); }; - handleUseTimeFilter = evt => { - this.props.setValue('useTimeFilter', evt.target.checked); + handleUseTimeFilter = (event: EuiSwitchEvent) => { + this.props.setValue('useTimeFilter', event.target.checked); }; - handlePinFilters = evt => { - this.props.setValue('pinFilters', evt.target.checked); + handlePinFilters = (event: EuiSwitchEvent) => { + this.props.setValue('pinFilters', event.target.checked); }; render() { @@ -85,8 +98,3 @@ export class OptionsTab extends Component { ); } } - -OptionsTab.propTypes = { - vis: PropTypes.object.isRequired, - setValue: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js deleted file mode 100644 index 6e1754b28647f..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.js +++ /dev/null @@ -1,102 +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 PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; -import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; -import { FieldSelect } from './field_select'; - -import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -function filterField(field) { - return field.type === 'number'; -} - -export function RangeControlEditor(props) { - const stepSizeId = `stepSize-${props.controlIndex}`; - const decimalPlacesId = `decimalPlaces-${props.controlIndex}`; - const handleDecimalPlacesChange = evt => { - props.handleNumberOptionChange(props.controlIndex, 'decimalPlaces', evt); - }; - const handleStepChange = evt => { - props.handleNumberOptionChange(props.controlIndex, 'step', evt); - }; - return ( - - - - - - - } - > - - - - - } - > - - - - ); -} - -RangeControlEditor.propTypes = { - getIndexPattern: PropTypes.func.isRequired, - controlIndex: PropTypes.number.isRequired, - controlParams: PropTypes.object.isRequired, - handleFieldNameChange: PropTypes.func.isRequired, - handleIndexPatternChange: PropTypes.func.isRequired, - handleNumberOptionChange: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx similarity index 71% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index 145b18a42dc15..e7f9b6083890c 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -18,30 +18,20 @@ */ import React from 'react'; -import sinon from 'sinon'; import { shallow } from 'enzyme'; +import { SinonSpy, spy, assert, match } from 'sinon'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - ui: { - IndexPatternSelect: () => { - return
; - }, - }, - }, - }, - }, -})); - +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { RangeControlEditor } from './range_control_editor'; +import { ControlParams } from '../../editor_utils'; +import { getDepsMock } from './__tests__/get_deps_mock'; +import { updateComponent } from './__tests__/update_component'; -const controlParams = { +const controlParams: ControlParams = { id: '1', indexPattern: 'indexPattern1', fieldName: 'numberField', @@ -51,20 +41,23 @@ const controlParams = { decimalPlaces: 0, step: 1, }, + parent: '', }; -let handleFieldNameChange; -let handleIndexPatternChange; -let handleNumberOptionChange; +const deps = getDepsMock(); +let handleFieldNameChange: SinonSpy; +let handleIndexPatternChange: SinonSpy; +let handleNumberOptionChange: SinonSpy; beforeEach(() => { - handleFieldNameChange = sinon.spy(); - handleIndexPatternChange = sinon.spy(); - handleNumberOptionChange = sinon.spy(); + handleFieldNameChange = spy(); + handleIndexPatternChange = spy(); + handleNumberOptionChange = spy(); }); -test('renders RangeControlEditor', () => { +test('renders RangeControlEditor', async () => { const component = shallow( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + expect(component).toMatchSnapshot(); // eslint-disable-line }); -test('handleNumberOptionChange - step', () => { +test('handleNumberOptionChange - step', async () => { const component = mountWithIntl( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + findTestSubject(component, 'rangeControlSizeInput0').simulate('change', { target: { value: 0.5 }, }); - sinon.assert.notCalled(handleFieldNameChange); - sinon.assert.notCalled(handleIndexPatternChange); + assert.notCalled(handleFieldNameChange); + assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'step'; - sinon.assert.calledWith( + assert.calledWith( handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 0.5) { + match(event => { + if (event.target.value === 0.5) { return true; } return false; @@ -107,9 +107,10 @@ test('handleNumberOptionChange - step', () => { ); }); -test('handleNumberOptionChange - decimalPlaces', () => { +test('handleNumberOptionChange - decimalPlaces', async () => { const component = mountWithIntl( { handleNumberOptionChange={handleNumberOptionChange} /> ); + + await updateComponent(component); + findTestSubject(component, 'rangeControlDecimalPlacesInput0').simulate('change', { target: { value: 2 }, }); - sinon.assert.notCalled(handleFieldNameChange); - sinon.assert.notCalled(handleIndexPatternChange); + assert.notCalled(handleFieldNameChange); + assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'decimalPlaces'; - sinon.assert.calledWith( + assert.calledWith( handleNumberOptionChange, expectedControlIndex, expectedOptionName, - sinon.match(evt => { - if (evt.target.value === 2) { + match(event => { + if (event.target.value === 2) { return true; } return false; 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 new file mode 100644 index 0000000000000..44477eafda6b1 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx @@ -0,0 +1,139 @@ +/* + * 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, { Component, Fragment, ChangeEvent, ComponentType } from 'react'; + +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; +import { FieldSelect } from './field_select'; +import { ControlParams, ControlParamsOptions } from '../../editor_utils'; +import { + IIndexPattern, + IFieldType, + IndexPatternSelect, +} from '../../../../../../plugins/data/public'; +import { InputControlVisDependencies } from '../../plugin'; + +interface RangeControlEditorProps { + controlIndex: number; + controlParams: ControlParams; + getIndexPattern: (indexPatternId: string) => Promise; + handleFieldNameChange: (fieldName: string) => void; + handleIndexPatternChange: (indexPatternId: string) => void; + handleNumberOptionChange: ( + controlIndex: number, + optionName: keyof ControlParamsOptions, + event: ChangeEvent + ) => void; + deps: InputControlVisDependencies; +} + +interface RangeControlEditorState { + IndexPatternSelect: ComponentType | null; +} + +function filterField(field: IFieldType) { + return field.type === 'number'; +} + +export class RangeControlEditor extends Component< + RangeControlEditorProps, + RangeControlEditorState +> { + state: RangeControlEditorState = { + IndexPatternSelect: null, + }; + + componentDidMount() { + this.getIndexPatternSelect(); + } + + async getIndexPatternSelect() { + const [, { data }] = await this.props.deps.core.getStartServices(); + this.setState({ + IndexPatternSelect: data.ui.IndexPatternSelect, + }); + } + + render() { + const stepSizeId = `stepSize-${this.props.controlIndex}`; + const decimalPlacesId = `decimalPlaces-${this.props.controlIndex}`; + if (this.state.IndexPatternSelect === null) { + return null; + } + + return ( + + + + + + + } + > + { + this.props.handleNumberOptionChange(this.props.controlIndex, 'step', event); + }} + data-test-subj={`rangeControlSizeInput${this.props.controlIndex}`} + /> + + + + } + > + { + this.props.handleNumberOptionChange(this.props.controlIndex, 'decimalPlaces', event); + }} + data-test-subj={`rangeControlDecimalPlacesInput${this.props.controlIndex}`} + /> + + + ); + } +} diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap index 6437cb19aef97..ba183cc40b126 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/form_row.test.tsx.snap @@ -47,7 +47,6 @@ exports[`renders disabled control with tooltip 1`] = ` anchorClassName="eui-displayBlock" content="I am disabled for testing purposes" delay="regular" - placement="top" position="top" >
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap similarity index 99% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap rename to src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap index 841421474e7b1..5a76967c71fbb 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap @@ -18,7 +18,6 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = ` > diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.tsx similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.test.tsx diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx similarity index 75% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx index fdd4fcb6e26ae..29385582924e7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/form_row.tsx @@ -17,16 +17,24 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { ReactElement } from 'react'; import { EuiFormRow, EuiToolTip, EuiIcon } from '@elastic/eui'; -export function FormRow(props) { +export interface FormRowProps { + label: string; + warningMsg?: string; + id: string; + children: ReactElement; + controlIndex: number; + disableMsg?: string; +} + +export function FormRow(props: FormRowProps) { let control = props.children; if (props.disableMsg) { control = ( - + {control} ); @@ -49,12 +57,3 @@ export function FormRow(props) { ); } - -FormRow.propTypes = { - label: PropTypes.string.isRequired, - warningMsg: PropTypes.string, - id: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, - controlIndex: PropTypes.number.isRequired, - disableMsg: PropTypes.string, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx similarity index 87% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx index a835078ab4dc0..1712f024f5b7b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx @@ -21,11 +21,16 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { InputControlVis } from './input_control_vis'; +import { ListControl } from '../../control/list_control_factory'; +import { RangeControl } from '../../control/range_control_factory'; -const mockListControl = { +jest.mock('ui/new_platform'); + +const mockListControl: ListControl = { id: 'mock-list-control', isEnabled: () => { return true; @@ -38,11 +43,9 @@ const mockListControl = { label: 'list control', value: [], selectOptions: ['choice1', 'choice2'], - format: value => { - return value; - }, -}; -const mockRangeControl = { + format: (value: any) => value, +} as ListControl; +const mockRangeControl: RangeControl = { id: 'mock-range-control', isEnabled: () => { return true; @@ -56,16 +59,16 @@ const mockRangeControl = { value: { min: 0, max: 0 }, min: 0, max: 100, - format: value => { - return value; - }, -}; + format: (value: any) => value, +} as RangeControl; const updateFiltersOnChange = false; -let stageFilter; -let submitFilters; -let resetControls; -let clearControls; +const refreshControlMock = () => Promise.resolve(); + +let stageFilter: sinon.SinonSpy; +let submitFilters: sinon.SinonSpy; +let resetControls: sinon.SinonSpy; +let clearControls: sinon.SinonSpy; beforeEach(() => { stageFilter = sinon.spy(); @@ -89,7 +92,7 @@ test('Renders list control', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -110,7 +113,7 @@ test('Renders range control', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -131,7 +134,7 @@ test('Apply and Cancel change btns enabled when there are changes', () => { hasValues={() => { return false; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -152,7 +155,7 @@ test('Clear btns enabled when there are values', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -173,7 +176,7 @@ test('clearControls', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlClearBtn').simulate('click'); @@ -198,7 +201,7 @@ test('submitFilters', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlSubmitBtn').simulate('click'); @@ -223,7 +226,7 @@ test('resetControls', () => { hasValues={() => { return true; }} - refreshControl={() => {}} + refreshControl={refreshControlMock} /> ); findTestSubject(component, 'inputControlCancelBtn').simulate('click'); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx similarity index 60% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx index 9e140155698f0..e2497287f35d0 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -17,16 +17,37 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { RangeControl } from './range_control'; -import { ListControl } from './list_control'; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - import { FormattedMessage } from '@kbn/i18n/react'; +import { CONTROL_TYPES } from '../../editor_utils'; +import { ListControl } from '../../control/list_control_factory'; +import { RangeControl } from '../../control/range_control_factory'; +import { ListControl as ListControlComponent } from '../vis/list_control'; +import { RangeControl as RangeControlComponent } from '../vis/range_control'; + +function isListControl(control: RangeControl | ListControl): control is ListControl { + return control.type === CONTROL_TYPES.LIST; +} + +function isRangeControl(control: RangeControl | ListControl): control is RangeControl { + return control.type === CONTROL_TYPES.RANGE; +} + +interface InputControlVisProps { + stageFilter: (controlIndex: number, newValue: any) => void; + submitFilters: () => void; + resetControls: () => void; + clearControls: () => void; + controls: Array; + updateFiltersOnChange?: boolean; + hasChanges: () => boolean; + hasValues: () => boolean; + refreshControl: (controlIndex: number, query: any) => Promise; +} -export class InputControlVis extends Component { - constructor(props) { +export class InputControlVis extends Component { + constructor(props: InputControlVisProps) { super(props); this.handleSubmit = this.handleSubmit.bind(this); @@ -49,39 +70,38 @@ export class InputControlVis extends Component { renderControls() { return this.props.controls.map((control, index) => { let controlComponent = null; - switch (control.type) { - case 'list': - controlComponent = ( - { - this.props.refreshControl(index, query); - }} - /> - ); - break; - case 'range': - controlComponent = ( - - ); - break; - default: - throw new Error(`Unhandled control type ${control.type}`); + + if (isListControl(control)) { + controlComponent = ( + { + this.props.refreshControl(index, query); + }} + /> + ); + } else if (isRangeControl(control)) { + controlComponent = ( + + ); + } else { + throw new Error(`Unhandled control type ${control!.type}`); } + return ( { +const formatOptionLabel = (value: any) => { return `${value} + formatting`; }; -let stageFilter; +let stageFilter: sinon.SinonSpy; beforeEach(() => { stageFilter = sinon.spy(); @@ -46,6 +46,7 @@ test('renders ListControl', () => { controlIndex={0} stageFilter={stageFilter} formatOptionLabel={formatOptionLabel} + intl={{} as any} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -56,11 +57,13 @@ test('disableMsg', () => { ); expect(component).toMatchSnapshot(); // eslint-disable-line diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx similarity index 76% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx index 7e92545e817e0..d62adfdce56b4 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/list_control.tsx @@ -17,46 +17,76 @@ * under the License. */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import _ from 'lodash'; -import { FormRow } from './form_row'; import { injectI18n } from '@kbn/i18n/react'; +import { InjectedIntlProps } from 'react-intl'; import { EuiFieldText, EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormRow } from './form_row'; + +interface ListControlUiState { + isLoading: boolean; +} + +export type ListControlUiProps = InjectedIntlProps & { + id: string; + label: string; + selectedOptions: any[]; + options?: any[]; + formatOptionLabel: (option: any) => any; + disableMsg?: string; + multiselect?: boolean; + dynamicOptions?: boolean; + partialResults?: boolean; + controlIndex: number; + stageFilter: (controlIndex: number, value: any) => void; + fetchOptions?: (searchValue: string) => void; +}; + +class ListControlUi extends PureComponent { + static defaultProps = { + dynamicOptions: false, + multiselect: true, + selectedOptions: [], + options: [], + }; + + private isMounted: boolean = false; -class ListControlUi extends Component { state = { isLoading: false, }; componentDidMount = () => { - this._isMounted = true; + this.isMounted = true; }; componentWillUnmount = () => { - this._isMounted = false; + this.isMounted = false; }; - handleOnChange = selectedOptions => { + handleOnChange = (selectedOptions: any[]) => { const selectedValues = selectedOptions.map(({ value }) => { return value; }); this.props.stageFilter(this.props.controlIndex, selectedValues); }; - debouncedFetch = _.debounce(async searchValue => { - await this.props.fetchOptions(searchValue); + debouncedFetch = _.debounce(async (searchValue: string) => { + if (this.props.fetchOptions) { + await this.props.fetchOptions(searchValue); + } - if (this._isMounted) { + if (this.isMounted) { this.setState({ isLoading: false, }); } }, 300); - onSearchChange = searchValue => { + onSearchChange = (searchValue: string) => { this.setState( { isLoading: true, @@ -81,7 +111,7 @@ class ListControlUi extends Component { } const options = this.props.options - .map(option => { + ?.map(option => { return { label: this.props.formatOptionLabel(option).toString(), value: option, @@ -141,29 +171,4 @@ class ListControlUi extends Component { } } -ListControlUi.propTypes = { - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - selectedOptions: PropTypes.array.isRequired, - options: PropTypes.array, - formatOptionLabel: PropTypes.func.isRequired, - disableMsg: PropTypes.string, - multiselect: PropTypes.bool, - dynamicOptions: PropTypes.bool, - partialResults: PropTypes.bool, - controlIndex: PropTypes.number.isRequired, - stageFilter: PropTypes.func.isRequired, - fetchOptions: PropTypes.func, -}; - -ListControlUi.defaultProps = { - dynamicOptions: false, - multiselect: true, -}; - -ListControlUi.defaultProps = { - selectedOptions: [], - options: [], -}; - export const ListControl = injectI18n(ListControlUi); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx similarity index 89% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx index 8b72def2f2698..639616151a395 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.test.tsx @@ -21,8 +21,11 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { RangeControl, ceilWithPrecision, floorWithPrecision } from './range_control'; +import { RangeControl as RangeControlClass } from '../../control/range_control_factory'; -const control = { +jest.mock('ui/new_platform'); + +const control: RangeControlClass = { id: 'mock-range-control', isEnabled: () => { return true; @@ -39,7 +42,7 @@ const control = { hasValue: () => { return false; }, -}; +} as RangeControlClass; test('renders RangeControl', () => { const component = shallowWithIntl( @@ -49,7 +52,7 @@ test('renders RangeControl', () => { }); test('disabled', () => { - const disabledRangeControl = { + const disabledRangeControl: RangeControlClass = { id: 'mock-range-control', isEnabled: () => { return false; @@ -64,7 +67,7 @@ test('disabled', () => { hasValue: () => { return false; }, - }; + } as RangeControlClass; const component = shallowWithIntl( {}} /> ); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx similarity index 72% rename from src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js rename to src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx index ee3e3c8fe4788..cd3982afd9afd 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/range_control.tsx @@ -18,12 +18,17 @@ */ import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; + +import { ValidatedDualRange } from '../../legacy_imports'; import { FormRow } from './form_row'; -import { ValidatedDualRange } from 'ui/validated_range'; +import { RangeControl as RangeControlClass } from '../../control/range_control_factory'; -function roundWithPrecision(value, decimalPlaces, roundFunction) { +function roundWithPrecision( + value: number, + decimalPlaces: number, + roundFunction: (n: number) => number +) { if (decimalPlaces <= 0) { return roundFunction(value); } @@ -35,18 +40,29 @@ function roundWithPrecision(value, decimalPlaces, roundFunction) { return results; } -export function ceilWithPrecision(value, decimalPlaces) { +export function ceilWithPrecision(value: number, decimalPlaces: number) { return roundWithPrecision(value, decimalPlaces, Math.ceil); } -export function floorWithPrecision(value, decimalPlaces) { +export function floorWithPrecision(value: number, decimalPlaces: number) { return roundWithPrecision(value, decimalPlaces, Math.floor); } -export class RangeControl extends Component { - state = {}; +export interface RangeControlState { + value?: [string, string]; + prevValue?: [string, string]; +} + +export interface RangeControlProps { + control: RangeControlClass; + controlIndex: number; + stageFilter: (controlIndex: number, value: any) => void; +} + +export class RangeControl extends PureComponent { + state: RangeControlState = {}; - static getDerivedStateFromProps(nextProps, prevState) { + static getDerivedStateFromProps(nextProps: RangeControlProps, prevState: RangeControlState) { const nextValue = nextProps.control.hasValue() ? [nextProps.control.value.min, nextProps.control.value.max] : ['', '']; @@ -68,7 +84,7 @@ export class RangeControl extends Component { return null; } - onChangeComplete = _.debounce(value => { + onChangeComplete = _.debounce((value: [string, string]) => { const controlValue = { min: value[0], max: value[1], @@ -111,16 +127,10 @@ export class RangeControl extends Component { id={this.props.control.id} label={this.props.control.label} controlIndex={this.props.controlIndex} - disableMsg={this.props.control.isEnabled() ? null : this.props.control.disabledReason} + disableMsg={this.props.control.isEnabled() ? undefined : this.props.control.disabledReason} > {this.renderControl()} ); } } - -RangeControl.propTypes = { - control: PropTypes.object.isRequired, - controlIndex: PropTypes.number.isRequired, - stageFilter: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.test.js b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts similarity index 78% rename from src/legacy/core_plugins/input_control_vis/public/control/control.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/control.test.ts index aa9bed44d031d..e76b199a0262c 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts @@ -19,34 +19,50 @@ import expect from '@kbn/expect'; import { Control } from './control'; +import { ControlParams } from '../editor_utils'; +import { FilterManager as BaseFilterManager } from './filter_manager/filter_manager'; +import { SearchSource } from '../legacy_imports'; -function createControlParams(id, label) { +function createControlParams(id: string, label: string): ControlParams { return { - id: id, + id, options: {}, - label: label, - }; + label, + } as ControlParams; } -let valueFromFilterBar; -const mockFilterManager = { +let valueFromFilterBar: any; +const mockFilterManager: BaseFilterManager = { getValueFromFilterBar: () => { return valueFromFilterBar; }, - createFilter: value => { - return `mockKbnFilter:${value}`; + createFilter: (value: any) => { + return `mockKbnFilter:${value}` as any; }, getIndexPattern: () => { return 'mockIndexPattern'; }, -}; -const mockKbnApi = {}; +} as any; + +class ControlMock extends Control { + fetch() { + return Promise.resolve(); + } + + destroy() {} +} +const mockKbnApi: SearchSource = {} as SearchSource; describe('hasChanged', () => { - let control; + let control: ControlMock; beforeEach(() => { - control = new Control(createControlParams(3, 'control'), mockFilterManager, mockKbnApi); + control = new ControlMock( + createControlParams('3', 'control'), + mockFilterManager, + false, + mockKbnApi + ); }); afterEach(() => { @@ -70,23 +86,26 @@ describe('hasChanged', () => { }); describe('ancestors', () => { - let grandParentControl; - let parentControl; - let childControl; + let grandParentControl: any; + let parentControl: any; + let childControl: any; beforeEach(() => { - grandParentControl = new Control( - createControlParams(1, 'grandparent control'), + grandParentControl = new ControlMock( + createControlParams('1', 'grandparent control'), mockFilterManager, + false, mockKbnApi ); - parentControl = new Control( - createControlParams(2, 'parent control'), + parentControl = new ControlMock( + createControlParams('2', 'parent control'), mockFilterManager, + false, mockKbnApi ); - childControl = new Control( - createControlParams(3, 'child control'), + childControl = new ControlMock( + createControlParams('3', 'child control'), mockFilterManager, + false, mockKbnApi ); }); @@ -122,7 +141,7 @@ describe('ancestors', () => { }); describe('getAncestorValues', () => { - let lastAncestorValues; + let lastAncestorValues: any[]; beforeEach(() => { grandParentControl.set('myGrandParentValue'); parentControl.set('myParentValue'); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.js b/src/legacy/core_plugins/input_control_vis/public/control/control.ts similarity index 66% rename from src/legacy/core_plugins/input_control_vis/public/control/control.js rename to src/legacy/core_plugins/input_control_vis/public/control/control.ts index 4035dc1adefe8..9dc03ecc23452 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.ts @@ -22,32 +22,53 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -export function noValuesDisableMsg(fieldName, indexPatternName) { +import { esFilters } from '../../../../../plugins/data/public'; +import { SearchSource as SearchSourceClass } from '../legacy_imports'; +import { ControlParams, ControlParamsOptions, CONTROL_TYPES } from '../editor_utils'; +import { RangeFilterManager } from './filter_manager/range_filter_manager'; +import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; +import { FilterManager as BaseFilterManager } from './filter_manager/filter_manager'; + +export function noValuesDisableMsg(fieldName: string, indexPatternName: string) { return i18n.translate('inputControl.control.noValuesDisableTooltip', { defaultMessage: 'Filtering occurs on the "{fieldName}" field, which doesn\'t exist on any documents in the "{indexPatternName}" \ index pattern. Choose a different field or index documents that contain values for this field.', - values: { fieldName: fieldName, indexPatternName: indexPatternName }, + values: { fieldName, indexPatternName }, }); } -export function noIndexPatternMsg(indexPatternId) { +export function noIndexPatternMsg(indexPatternId: string) { return i18n.translate('inputControl.control.noIndexPatternTooltip', { defaultMessage: 'Could not locate index-pattern id: {indexPatternId}.', values: { indexPatternId }, }); } -export class Control { - constructor(controlParams, filterManager, useTimeFilter, SearchSource) { +export abstract class Control { + private kbnFilter: esFilters.Filter | null = null; + + enable: boolean = false; + disabledReason: string = ''; + value: any; + + id: string; + options: ControlParamsOptions; + type: CONTROL_TYPES; + label: string; + ancestors: Array> = []; + + constructor( + public controlParams: ControlParams, + public filterManager: FilterManager, + public useTimeFilter: boolean, + public SearchSource: SearchSourceClass + ) { this.id = controlParams.id; this.controlParams = controlParams; this.options = controlParams.options; this.type = controlParams.type; this.label = controlParams.label ? controlParams.label : controlParams.fieldName; - this.useTimeFilter = useTimeFilter; - this.filterManager = filterManager; - this.SearchSource = SearchSource; // restore state from kibana filter context this.reset(); @@ -59,28 +80,20 @@ export class Control { ); } - async fetch() { - throw new Error('fetch method not defined, subclass are required to implement'); - } + abstract fetch(query: string): Promise; - destroy() { - throw new Error('destroy method not defined, subclass are required to implement'); - } + abstract destroy(): void; - format = value => { + format = (value: any) => { const field = this.filterManager.getField(); - if (field) { + if (field?.format?.convert) { return field.format.convert(value); } return value; }; - /** - * - * @param ancestors {array of Controls} - */ - setAncestors(ancestors) { + setAncestors(ancestors: Array>) { this.ancestors = ancestors; } @@ -110,17 +123,17 @@ export class Control { return this.enable; } - disable(reason) { + disable(reason: string) { this.enable = false; this.disabledReason = reason; } - set(newValue) { + set(newValue: any) { this.value = newValue; if (this.hasValue()) { - this._kbnFilter = this.filterManager.createFilter(this.value); + this.kbnFilter = this.filterManager.createFilter(this.value); } else { - this._kbnFilter = null; + this.kbnFilter = null; } } @@ -128,7 +141,7 @@ export class Control { * Remove any user changes to value by resetting value to that as provided by Kibana filter pills */ reset() { - this._kbnFilter = null; + this.kbnFilter = null; this.value = this.filterManager.getValueFromFilterBar(); } @@ -144,17 +157,17 @@ export class Control { } hasKbnFilter() { - if (this._kbnFilter) { + if (this.kbnFilter) { return true; } return false; } getKbnFilter() { - return this._kbnFilter; + return this.kbnFilter; } - hasValue() { + hasValue(): boolean { return this.value !== undefined; } } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts similarity index 86% rename from src/legacy/core_plugins/input_control_vis/public/control/control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts index 6d3c7756f72aa..3dcc1d53d4211 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/control_factory.ts @@ -19,14 +19,15 @@ import { rangeControlFactory } from './range_control_factory'; import { listControlFactory } from './list_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -export function controlFactory(controlParams) { +export function getControlFactory(controlParams: ControlParams) { let factory = null; switch (controlParams.type) { - case 'range': + case CONTROL_TYPES.RANGE: factory = rangeControlFactory; break; - case 'list': + case CONTROL_TYPES.LIST: factory = listControlFactory; break; default: diff --git a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts similarity index 71% rename from src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js rename to src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts index 2917dda5e96a7..c8fa5af5e052b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts @@ -16,15 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -import { timefilter } from 'ui/timefilter'; + +import { esFilters, IndexPattern, TimefilterSetup } from '../../../../../plugins/data/public'; +import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; export function createSearchSource( - SearchSource, - initialState, - indexPattern, - aggs, - useTimeFilter, - filters = [] + SearchSource: SearchSourceClass, + initialState: SearchSourceFields | null, + indexPattern: IndexPattern, + aggs: any, + useTimeFilter: boolean, + filters: esFilters.PhraseFilter[] = [], + timefilter: TimefilterSetup['timefilter'] ) { const searchSource = initialState ? new SearchSource(initialState) : new SearchSource(); // Do not not inherit from rootSearchSource to avoid picking up time and globals @@ -32,7 +35,10 @@ export function createSearchSource( searchSource.setField('filter', () => { const activeFilters = [...filters]; if (useTimeFilter) { - activeFilters.push(timefilter.createFilter(indexPattern)); + const filter = timefilter.createFilter(indexPattern); + if (filter) { + activeFilters.push(filter); + } } return activeFilters; }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts similarity index 66% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts index 95277ac073d75..fd2cbae121b7e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.test.ts @@ -18,30 +18,45 @@ */ import expect from '@kbn/expect'; + import { FilterManager } from './filter_manager'; +import { coreMock } from '../../../../../../core/public/mocks'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; + +const setupMock = coreMock.createSetup(); + +class FilterManagerTest extends FilterManager { + createFilter() { + return {} as esFilters.Filter; + } + + getValueFromFilterBar() { + return null; + } +} describe('FilterManager', function() { const controlId = 'control1'; describe('findFilters', function() { - const indexPatternMock = {}; - let kbnFilters; - const queryFilterMock = { - getAppFilters: () => { - return kbnFilters; - }, - getGlobalFilters: () => { - return []; - }, - }; - let filterManager; + const indexPatternMock = {} as IndexPattern; + let kbnFilters: esFilters.Filter[]; + const queryFilterMock = new QueryFilterManager(setupMock.uiSettings); + queryFilterMock.getAppFilters = () => kbnFilters; + queryFilterMock.getGlobalFilters = () => []; + + let filterManager: FilterManagerTest; beforeEach(() => { kbnFilters = []; - filterManager = new FilterManager(controlId, 'field1', indexPatternMock, queryFilterMock); + filterManager = new FilterManagerTest(controlId, 'field1', indexPatternMock, queryFilterMock); }); test('should not find filters that are not controlled by any visualization', function() { - kbnFilters.push({}); + kbnFilters.push({} as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(0); }); @@ -51,7 +66,7 @@ describe('FilterManager', function() { meta: { controlledBy: 'anotherControl', }, - }); + } as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(0); }); @@ -61,7 +76,7 @@ describe('FilterManager', function() { meta: { controlledBy: controlId, }, - }); + } as esFilters.Filter); const foundFilters = filterManager.findFilters(); expect(foundFilters.length).to.be(1); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts similarity index 62% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts index 672f56746cf80..d80a74ed46eae 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/filter_manager.ts @@ -19,15 +19,33 @@ import _ from 'lodash'; -export class FilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { - this.controlId = controlId; - this.fieldName = fieldName; - this.indexPattern = indexPattern; - this.queryFilter = queryFilter; - } +import { + FilterManager as QueryFilterManager, + IndexPattern, + esFilters, +} from '../../../../../../plugins/data/public'; + +export abstract class FilterManager { + constructor( + public controlId: string, + public fieldName: string, + public indexPattern: IndexPattern, + public queryFilter: QueryFilterManager + ) {} + + /** + * Convert phrases into filter + * + * @param {any[]} phrases + * @returns PhraseFilter + * single phrase: match query + * multiple phrases: bool query with should containing list of match_phrase queries + */ + abstract createFilter(phrases: any): esFilters.Filter; + + abstract getValueFromFilterBar(): any; - getIndexPattern() { + getIndexPattern(): IndexPattern { return this.indexPattern; } @@ -35,11 +53,7 @@ export class FilterManager { return this.indexPattern.fields.getByName(this.fieldName); } - createFilter() { - throw new Error('Must implement createFilter.'); - } - - findFilters() { + findFilters(): esFilters.Filter[] { const kbnFilters = _.flatten([ this.queryFilter.getAppFilters(), this.queryFilter.getGlobalFilters(), @@ -48,8 +62,4 @@ export class FilterManager { return _.get(kbnFilter, 'meta.controlledBy') === this.controlId; }); } - - getValueFromFilterBar() { - throw new Error('Must implement getValueFromFilterBar.'); - } } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts similarity index 79% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts index 7aa1ec6632043..dc577ca7168d1 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.ts @@ -18,6 +18,12 @@ */ import expect from '@kbn/expect'; + +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; import { PhraseFilterManager } from './phrase_filter_manager'; describe('PhraseFilterManager', function() { @@ -28,22 +34,20 @@ describe('PhraseFilterManager', function() { const fieldMock = { name: 'field1', format: { - convert: val => { - return val; - }, + convert: (value: any) => value, }, }; - const indexPatternMock = { + const indexPatternMock: IndexPattern = { id: indexPatternId, fields: { - getByName: name => { - const fields = { field1: fieldMock }; + getByName: (name: string) => { + const fields: any = { field1: fieldMock }; return fields[name]; }, }, - }; - const queryFilterMock = {}; - let filterManager; + } as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: PhraseFilterManager; beforeEach(() => { filterManager = new PhraseFilterManager( controlId, @@ -83,22 +87,32 @@ describe('PhraseFilterManager', function() { }); describe('getValueFromFilterBar', function() { - const indexPatternMock = {}; - const queryFilterMock = {}; - let filterManager; - beforeEach(() => { - class MockFindFiltersPhraseFilterManager extends PhraseFilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter, delimiter) { - super(controlId, fieldName, indexPattern, queryFilter, delimiter); - this.mockFilters = []; - } - findFilters() { - return this.mockFilters; - } - setMockFilters(mockFilters) { - this.mockFilters = mockFilters; - } + class MockFindFiltersPhraseFilterManager extends PhraseFilterManager { + mockFilters: esFilters.Filter[]; + + constructor( + id: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { + super(id, fieldName, indexPattern, queryFilter); + this.mockFilters = []; } + + findFilters() { + return this.mockFilters; + } + + setMockFilters(mockFilters: esFilters.Filter[]) { + this.mockFilters = mockFilters; + } + } + + const indexPatternMock: IndexPattern = {} as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: MockFindFiltersPhraseFilterManager; + beforeEach(() => { filterManager = new MockFindFiltersPhraseFilterManager( controlId, 'field1', @@ -119,7 +133,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios']); }); @@ -145,7 +159,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); @@ -169,7 +183,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); @@ -185,7 +199,7 @@ describe('PhraseFilterManager', function() { }, }, }, - ]); + ] as esFilters.Filter[]); expect(filterManager.getValueFromFilterBar()).to.eql(undefined); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts index 1e60f8c4ebb67..b0b46be86f1e8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.ts @@ -18,37 +18,38 @@ */ import _ from 'lodash'; -import { FilterManager } from './filter_manager.js'; -import { esFilters } from '../../../../../../plugins/data/public'; + +import { FilterManager } from './filter_manager'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; export class PhraseFilterManager extends FilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { + constructor( + controlId: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { super(controlId, fieldName, indexPattern, queryFilter); } - /** - * Convert phrases into filter - * - * @param {array} phrases - * @return {object} query filter - * single phrase: match query - * multiple phrases: bool query with should containing list of match_phrase queries - */ - createFilter(phrases) { - let newFilter; + createFilter(phrases: any): esFilters.PhraseFilter { + let newFilter: esFilters.PhraseFilter; + const value = this.indexPattern.fields.getByName(this.fieldName); + + if (!value) { + throw new Error(`Unable to find field with name: ${this.fieldName} on indexPattern`); + } + if (phrases.length === 1) { - newFilter = esFilters.buildPhraseFilter( - this.indexPattern.fields.getByName(this.fieldName), - phrases[0], - this.indexPattern - ); + newFilter = esFilters.buildPhraseFilter(value, phrases[0], this.indexPattern); } else { - newFilter = esFilters.buildPhrasesFilter( - this.indexPattern.fields.getByName(this.fieldName), - phrases, - this.indexPattern - ); + newFilter = esFilters.buildPhrasesFilter(value, phrases, this.indexPattern); } + newFilter.meta.key = this.fieldName; newFilter.meta.controlledBy = this.controlId; return newFilter; @@ -62,7 +63,7 @@ export class PhraseFilterManager extends FilterManager { const values = kbnFilters .map(kbnFilter => { - return this._getValueFromFilter(kbnFilter); + return this.getValueFromFilter(kbnFilter); }) .filter(value => value != null); @@ -78,15 +79,15 @@ export class PhraseFilterManager extends FilterManager { /** * Extract filtering value from kibana filters * - * @param {object} kbnFilter + * @param {esFilters.PhraseFilter} kbnFilter * @return {Array.} array of values pulled from filter */ - _getValueFromFilter(kbnFilter) { + private getValueFromFilter(kbnFilter: esFilters.PhraseFilter): any { // bool filter - multiple phrase filters if (_.has(kbnFilter, 'query.bool.should')) { - return _.get(kbnFilter, 'query.bool.should') - .map(kbnFilter => { - return this._getValueFromFilter(kbnFilter); + return _.get(kbnFilter, 'query.bool.should') + .map(kbnQueryFilter => { + return this.getValueFromFilter(kbnQueryFilter); }) .filter(value => { if (value) { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts similarity index 68% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts index ffe2ebdad53bc..f4993a60c5b39 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.test.ts @@ -18,7 +18,13 @@ */ import expect from '@kbn/expect'; + import { RangeFilterManager } from './range_filter_manager'; +import { + esFilters, + IndexPattern, + FilterManager as QueryFilterManager, +} from '../../../../../../plugins/data/public'; describe('RangeFilterManager', function() { const controlId = 'control1'; @@ -28,19 +34,19 @@ describe('RangeFilterManager', function() { const fieldMock = { name: 'field1', }; - const indexPatternMock = { + const indexPatternMock: IndexPattern = { id: indexPatternId, fields: { - getByName: name => { - const fields = { + getByName: (name: any) => { + const fields: any = { field1: fieldMock, }; return fields[name]; }, }, - }; - const queryFilterMock = {}; - let filterManager; + } as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: RangeFilterManager; beforeEach(() => { filterManager = new RangeFilterManager( controlId, @@ -62,22 +68,32 @@ describe('RangeFilterManager', function() { }); describe('getValueFromFilterBar', function() { - const indexPatternMock = {}; - const queryFilterMock = {}; - let filterManager; - beforeEach(() => { - class MockFindFiltersRangeFilterManager extends RangeFilterManager { - constructor(controlId, fieldName, indexPattern, queryFilter) { - super(controlId, fieldName, indexPattern, queryFilter); - this.mockFilters = []; - } - findFilters() { - return this.mockFilters; - } - setMockFilters(mockFilters) { - this.mockFilters = mockFilters; - } + class MockFindFiltersRangeFilterManager extends RangeFilterManager { + mockFilters: esFilters.RangeFilter[]; + + constructor( + id: string, + fieldName: string, + indexPattern: IndexPattern, + queryFilter: QueryFilterManager + ) { + super(id, fieldName, indexPattern, queryFilter); + this.mockFilters = []; + } + + findFilters() { + return this.mockFilters; + } + + setMockFilters(mockFilters: esFilters.RangeFilter[]) { + this.mockFilters = mockFilters; } + } + + const indexPatternMock: IndexPattern = {} as IndexPattern; + const queryFilterMock: QueryFilterManager = {} as QueryFilterManager; + let filterManager: MockFindFiltersRangeFilterManager; + beforeEach(() => { filterManager = new MockFindFiltersRangeFilterManager( controlId, 'field1', @@ -95,14 +111,15 @@ describe('RangeFilterManager', function() { lt: 3, }, }, + meta: {} as esFilters.RangeFilterMeta, }, - ]); + ] as esFilters.RangeFilter[]); const value = filterManager.getValueFromFilterBar(); expect(value).to.be.a('object'); expect(value).to.have.property('min'); - expect(value.min).to.be(1); + expect(value?.min).to.be(1); expect(value).to.have.property('max'); - expect(value.max).to.be(3); + expect(value?.max).to.be(3); }); test('should return undefined when filter value can not be extracted from Kibana filter', function() { @@ -114,8 +131,9 @@ describe('RangeFilterManager', function() { lte: 3, }, }, + meta: {} as esFilters.RangeFilterMeta, }, - ]); + ] as esFilters.RangeFilter[]); expect(filterManager.getValueFromFilterBar()).to.eql(undefined); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts similarity index 77% rename from src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js rename to src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts index 5a2e7b7d779bc..0a6819bd68e6f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.ts @@ -18,11 +18,17 @@ */ import _ from 'lodash'; -import { FilterManager } from './filter_manager.js'; -import { esFilters } from '../../../../../../plugins/data/public'; + +import { FilterManager } from './filter_manager'; +import { esFilters, IFieldType } from '../../../../../../plugins/data/public'; + +interface SliderValue { + min?: string | number; + max?: string | number; +} // Convert slider value into ES range filter -function toRange(sliderValue) { +function toRange(sliderValue: SliderValue) { return { gte: sliderValue.min, lte: sliderValue.max, @@ -30,8 +36,8 @@ function toRange(sliderValue) { } // Convert ES range filter into slider value -function fromRange(range) { - const sliderValue = {}; +function fromRange(range: esFilters.RangeFilterParams): SliderValue { + const sliderValue: SliderValue = {}; if (_.has(range, 'gte')) { sliderValue.min = _.get(range, 'gte'); } @@ -54,9 +60,10 @@ export class RangeFilterManager extends FilterManager { * @param {object} react-input-range value - POJO with `min` and `max` properties * @return {object} range filter */ - createFilter(value) { + createFilter(value: SliderValue): esFilters.RangeFilter { const newFilter = esFilters.buildRangeFilter( - this.indexPattern.fields.getByName(this.fieldName), + // TODO: Fix type to be required + this.indexPattern.fields.getByName(this.fieldName) as IFieldType, toRange(value), this.indexPattern ); @@ -65,13 +72,13 @@ export class RangeFilterManager extends FilterManager { return newFilter; } - getValueFromFilterBar() { + getValueFromFilterBar(): SliderValue | undefined { const kbnFilters = this.findFilters(); if (kbnFilters.length === 0) { return; } - let range; + let range: esFilters.RangeFilterParams; if (_.has(kbnFilters[0], 'script')) { range = _.get(kbnFilters[0], 'script.script.params'); } else { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts similarity index 57% rename from src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts index 3b5ef7372bc1f..2420907727638 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts @@ -17,92 +17,33 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { listControlFactory } from './list_control_factory'; +import { listControlFactory, ListControl } from './list_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; +import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; +import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; -jest.mock('ui/timefilter', () => ({ - createFilter: jest.fn(), -})); +const MockSearchSource = getSearchSourceMock(); +const deps = getDepsMock(); -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - query: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { - getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - }, - }, - }), - getAppFilters: jest.fn().mockImplementation(() => []), - getGlobalFilters: jest.fn().mockImplementation(() => []), - }, - }, - indexPatterns: { - get: () => ({ - fields: { - getByName: name => { - const fields = { myField: { name: 'myField' } }; - return fields[name]; - }, - }, - }), - }, - }, - }, - }, +jest.doMock('./create_search_source.ts', () => ({ + createSearchSource: MockSearchSource, })); -chrome.getInjected.mockImplementation(key => { - switch (key) { - case 'autocompleteTimeout': - return 1000; - case 'autocompleteTerminateAfter': - return 100000; - } -}); - -function MockSearchSource() { - return { - setParent: () => {}, - setField: () => {}, - fetch: async () => { - return { - aggregations: { - termsAgg: { - buckets: [ - { - key: 'Zurich Airport', - doc_count: 691, - }, - { - key: 'Xi an Xianyang International Airport', - doc_count: 526, - }, - ], - }, - }, - }; - }, - }; -} - describe('hasValue', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - let listControl; + let listControl: ListControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); }); test('should be false when control has no value', () => { @@ -121,22 +62,25 @@ describe('hasValue', () => { }); describe('fetch', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - const SearchSource = jest.fn(MockSearchSource); - let listControl; + let listControl: ListControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, SearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); }); test('should pass in timeout parameters from injected vars', async () => { await listControl.fetch(); - expect(SearchSource).toHaveBeenCalledWith({ + expect(MockSearchSource).toHaveBeenCalledWith({ timeout: `1000ms`, terminate_after: 100000, }); @@ -152,24 +96,37 @@ describe('fetch', () => { }); describe('fetch with ancestors', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; const useTimeFilter = false; - let listControl; + let listControl: ListControl; let parentControl; beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource); + listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); - const parentControlParams = { + const parentControlParams: ControlParams = { id: 'parent', fieldName: 'myField', - options: {}, + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', }; - parentControl = await listControlFactory(parentControlParams, useTimeFilter, MockSearchSource); + parentControl = await listControlFactory( + parentControlParams, + useTimeFilter, + MockSearchSource, + deps + ); parentControl.clear(); listControl.setAncestors([parentControl]); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts similarity index 63% rename from src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts index d90b21eead5c6..56b42f295ce15 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts @@ -18,20 +18,30 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; import { createSearchSource } from './create_search_source'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; -import chrome from 'ui/chrome'; +import { ControlParams } from '../editor_utils'; +import { InputControlVisDependencies } from '../plugin'; +import { IFieldType, TimefilterSetup } from '../../../../../plugins/data/public'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators return query.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, match => `\\${match}`); } -const termsAgg = ({ field, size, direction, query }) => { - const terms = { +interface TermsAggArgs { + field?: IFieldType; + size: number | null; + direction: string; + query?: string; +} + +const termsAgg = ({ field, size, direction, query }: TermsAggArgs) => { + const terms: any = { order: { _count: direction, }, @@ -41,14 +51,14 @@ const termsAgg = ({ field, size, direction, query }) => { terms.size = size < 1 ? 1 : size; } - if (field.scripted) { + if (field?.scripted) { terms.script = { source: field.script, lang: field.lang, }; terms.value_type = field.type === 'number' ? 'float' : field.type; } else { - terms.field = field.name; + terms.field = field?.name; } if (query) { @@ -57,13 +67,34 @@ const termsAgg = ({ field, size, direction, query }) => { return { termsAgg: { - terms: terms, + terms, }, }; }; -class ListControl extends Control { - fetch = async query => { +export class ListControl extends Control { + private getInjectedVar: InputControlVisDependencies['core']['injectedMetadata']['getInjectedVar']; + private timefilter: TimefilterSetup['timefilter']; + + abortController?: AbortController; + lastAncestorValues: any; + lastQuery?: string; + partialResults?: boolean; + selectOptions?: string[]; + + constructor( + controlParams: ControlParams, + filterManager: PhraseFilterManager, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies + ) { + super(controlParams, filterManager, useTimeFilter, SearchSource); + this.getInjectedVar = deps.core.injectedMetadata.getInjectedVar; + this.timefilter = deps.data.query.timefilter.timefilter; + } + + fetch = async (query?: string) => { // Abort any in-progress fetch if (this.abortController) { this.abortController.abort(); @@ -101,9 +132,9 @@ class ListControl extends Control { } const fieldName = this.filterManager.fieldName; - const initialSearchSourceState = { - timeout: `${chrome.getInjected('autocompleteTimeout')}ms`, - terminate_after: chrome.getInjected('autocompleteTerminateAfter'), + const initialSearchSourceState: SearchSourceFields = { + timeout: `${this.getInjectedVar('autocompleteTimeout')}ms`, + terminate_after: Number(this.getInjectedVar('autocompleteTerminateAfter')), }; const aggs = termsAgg({ field: indexPattern.fields.getByName(fieldName), @@ -117,7 +148,8 @@ class ListControl extends Control { indexPattern, aggs, this.useTimeFilter, - ancestorFilters + ancestorFilters, + this.timefilter ); const abortSignal = this.abortController.signal; @@ -143,8 +175,8 @@ class ListControl extends Control { return; } - const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map(bucket => { - return bucket.key; + const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket: any) => { + return bucket?.key; }); if (selectOptions.length === 0 && !query) { @@ -167,29 +199,34 @@ class ListControl extends Control { } } -export async function listControlFactory(controlParams, useTimeFilter, SearchSource) { - let indexPattern; - try { - indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); - - // dynamic options are only allowed on String fields but the setting defaults to true so it could - // be enabled for non-string fields (since UI input is hidden for non-string fields). - // If field is not string, then disable dynamic options. - const field = indexPattern.fields.find(field => { - return field.name === controlParams.fieldName; - }); - if (field && field.type !== 'string') { - controlParams.options.dynamicOptions = false; - } - } catch (err) { - // ignore not found error and return control so it can be displayed in disabled state. +export async function listControlFactory( + controlParams: ControlParams, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies +) { + const [, { data: dataPluginStart }] = await deps.core.getStartServices(); + const indexPattern = await dataPluginStart.indexPatterns.get(controlParams.indexPattern); + + // dynamic options are only allowed on String fields but the setting defaults to true so it could + // be enabled for non-string fields (since UI input is hidden for non-string fields). + // If field is not string, then disable dynamic options. + const field = indexPattern.fields.find(({ name }) => name === controlParams.fieldName); + if (field && field.type !== 'string') { + controlParams.options.dynamicOptions = false; } - const { filterManager } = npStart.plugins.data.query; - return new ListControl( + const listControl = new ListControl( controlParams, - new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), + new PhraseFilterManager( + controlParams.id, + controlParams.fieldName, + indexPattern, + deps.data.query.filterManager + ), useTimeFilter, - SearchSource + SearchSource, + deps ); + return listControl; } diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts similarity index 59% rename from src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js rename to src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts index b545c6e2834f3..5328aeb6c6a47 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts @@ -18,74 +18,37 @@ */ import { rangeControlFactory } from './range_control_factory'; +import { ControlParams, CONTROL_TYPES } from '../editor_utils'; +import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; +import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; -let esSearchResponse; -class MockSearchSource { - setParent() {} - setField() {} - async fetch() { - return esSearchResponse; - } -} - -jest.mock('ui/timefilter', () => ({ - createFilter: jest.fn(), -})); - -jest.mock('ui/new_platform', () => ({ - npStart: { - plugins: { - data: { - query: { - filterManager: { - fieldName: 'myNumberField', - getIndexPattern: () => ({ - fields: { - getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - }, - }, - }), - getAppFilters: jest.fn().mockImplementation(() => []), - getGlobalFilters: jest.fn().mockImplementation(() => []), - }, - }, - indexPatterns: { - get: () => ({ - fields: { - getByName: name => { - const fields = { myNumberField: { name: 'myNumberField' } }; - return fields[name]; - }, - }, - }), - }, - }, - }, - }, -})); +const deps = getDepsMock(); describe('fetch', () => { - const controlParams = { + const controlParams: ControlParams = { id: '1', fieldName: 'myNumberField', options: {}, + type: CONTROL_TYPES.RANGE, + label: 'test', + indexPattern: {} as any, + parent: {} as any, }; const useTimeFilter = false; - let rangeControl; - beforeEach(async () => { - rangeControl = await rangeControlFactory(controlParams, useTimeFilter, MockSearchSource); - }); - test('should set min and max from aggregation results', async () => { - esSearchResponse = { + const esSearchResponse = { aggregations: { maxAgg: { value: 100 }, minAgg: { value: 10 }, }, }; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(true); @@ -95,12 +58,18 @@ describe('fetch', () => { test('should disable control when there are 0 hits', async () => { // ES response when the query does not match any documents - esSearchResponse = { + const esSearchResponse = { aggregations: { maxAgg: { value: null }, minAgg: { value: null }, }, }; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(false); @@ -109,7 +78,13 @@ describe('fetch', () => { test('should disable control when response is empty', async () => { // ES response for dashboardonly user who does not have read permissions on index is 200 (which is weird) // and there is not aggregations key - esSearchResponse = {}; + const esSearchResponse = {}; + const rangeControl = await rangeControlFactory( + controlParams, + useTimeFilter, + getSearchSourceMock(esSearchResponse), + deps + ); await rangeControl.fetch(); expect(rangeControl.isEnabled()).toBe(false); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts similarity index 63% rename from src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js rename to src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts index c99c794c1fcd5..b9191436b5968 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts @@ -18,22 +18,29 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { SearchSource as SearchSourceClass } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; -import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; +import { ControlParams } from '../editor_utils'; +import { InputControlVisDependencies } from '../plugin'; +import { IFieldType, TimefilterSetup } from '../.../../../../../../plugins/data/public'; -const minMaxAgg = field => { - const aggBody = {}; - if (field.scripted) { - aggBody.script = { - source: field.script, - lang: field.lang, - }; - } else { - aggBody.field = field.name; +const minMaxAgg = (field?: IFieldType) => { + const aggBody: any = {}; + if (field) { + if (field.scripted) { + aggBody.script = { + source: field.script, + lang: field.lang, + }; + } else { + aggBody.field = field.name; + } } + return { maxAgg: { max: aggBody, @@ -44,7 +51,23 @@ const minMaxAgg = field => { }; }; -class RangeControl extends Control { +export class RangeControl extends Control { + timefilter: TimefilterSetup['timefilter']; + abortController: any; + min: any; + max: any; + + constructor( + controlParams: ControlParams, + filterManager: RangeFilterManager, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies + ) { + super(controlParams, filterManager, useTimeFilter, SearchSource); + this.timefilter = deps.data.query.timefilter.timefilter; + } + async fetch() { // Abort any in-progress fetch if (this.abortController) { @@ -58,14 +81,15 @@ class RangeControl extends Control { } const fieldName = this.filterManager.fieldName; - const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); const searchSource = createSearchSource( this.SearchSource, null, indexPattern, aggs, - this.useTimeFilter + this.useTimeFilter, + [], + this.timefilter ); const abortSignal = this.abortController.signal; @@ -102,18 +126,25 @@ class RangeControl extends Control { } } -export async function rangeControlFactory(controlParams, useTimeFilter, SearchSource) { - let indexPattern; - try { - indexPattern = await npStart.plugins.data.indexPatterns.get(controlParams.indexPattern); - } catch (err) { - // ignore not found error and return control so it can be displayed in disabled state. - } - const { filterManager } = npStart.plugins.data.query; +export async function rangeControlFactory( + controlParams: ControlParams, + useTimeFilter: boolean, + SearchSource: SearchSourceClass, + deps: InputControlVisDependencies +): Promise { + const [, { data: dataPluginStart }] = await deps.core.getStartServices(); + const indexPattern = await dataPluginStart.indexPatterns.get(controlParams.indexPattern); + return new RangeControl( controlParams, - new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager), + new RangeFilterManager( + controlParams.id, + controlParams.fieldName, + indexPattern, + deps.data.query.filterManager + ), useTimeFilter, - SearchSource + SearchSource, + deps ); } diff --git a/src/legacy/core_plugins/input_control_vis/public/editor_utils.js b/src/legacy/core_plugins/input_control_vis/public/editor_utils.ts similarity index 64% rename from src/legacy/core_plugins/input_control_vis/public/editor_utils.js rename to src/legacy/core_plugins/input_control_vis/public/editor_utils.ts index f5b4390342a0f..74def0a8d86f4 100644 --- a/src/legacy/core_plugins/input_control_vis/public/editor_utils.js +++ b/src/legacy/core_plugins/input_control_vis/public/editor_utils.ts @@ -16,21 +16,54 @@ * specific language governing permissions and limitations * under the License. */ +import { $Values } from '@kbn/utility-types'; export const CONTROL_TYPES = { - LIST: 'list', - RANGE: 'range', + LIST: 'list' as 'list', + RANGE: 'range' as 'range', }; +export type CONTROL_TYPES = $Values; -export const setControl = (controls, controlIndex, control) => [ +export interface ControlParamsOptions { + decimalPlaces?: number; + step?: number; + type?: string; + multiselect?: boolean; + dynamicOptions?: boolean; + size?: number; + order?: string; +} + +export interface ControlParams { + id: string; + type: CONTROL_TYPES; + label: string; + fieldName: string; + indexPattern: string; + parent: string; + options: ControlParamsOptions; +} + +export const setControl = ( + controls: ControlParams[], + controlIndex: number, + control: ControlParams +): ControlParams[] => [ ...controls.slice(0, controlIndex), control, ...controls.slice(controlIndex + 1), ]; -export const addControl = (controls, control) => [...controls, control]; +export const addControl = (controls: ControlParams[], control: ControlParams): ControlParams[] => [ + ...controls, + control, +]; -export const moveControl = (controls, controlIndex, direction) => { +export const moveControl = ( + controls: ControlParams[], + controlIndex: number, + direction: number +): ControlParams[] => { let newIndex; if (direction >= 0) { newIndex = controlIndex + 1; @@ -54,13 +87,13 @@ export const moveControl = (controls, controlIndex, direction) => { } }; -export const removeControl = (controls, controlIndex) => [ +export const removeControl = (controls: ControlParams[], controlIndex: number): ControlParams[] => [ ...controls.slice(0, controlIndex), ...controls.slice(controlIndex + 1), ]; -export const getDefaultOptions = type => { - const defaultOptions = {}; +export const getDefaultOptions = (type: CONTROL_TYPES): ControlParamsOptions => { + const defaultOptions: ControlParamsOptions = {}; switch (type) { case CONTROL_TYPES.RANGE: defaultOptions.decimalPlaces = 0; @@ -77,17 +110,17 @@ export const getDefaultOptions = type => { return defaultOptions; }; -export const newControl = type => ({ +export const newControl = (type: CONTROL_TYPES): ControlParams => ({ id: new Date().getTime().toString(), indexPattern: '', fieldName: '', parent: '', label: '', - type: type, + type, options: getDefaultOptions(type), }); -export const getTitle = (controlParams, controlIndex) => { +export const getTitle = (controlParams: ControlParams, controlIndex: number): string => { let title = `${controlParams.type}: ${controlIndex}`; if (controlParams.label) { title = `${controlParams.label}`; diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.js b/src/legacy/core_plugins/input_control_vis/public/index.ts similarity index 76% rename from src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.js rename to src/legacy/core_plugins/input_control_vis/public/index.ts index c50cda56c7151..e14c2cc4b69b6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.js +++ b/src/legacy/core_plugins/input_control_vis/public/index.ts @@ -17,9 +17,9 @@ * under the License. */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import './saved_visualizations'; +import { PluginInitializerContext } from '../../../../core/public'; +import { InputControlVisPlugin as Plugin } from './plugin'; -SavedObjectRegistryProvider.register(savedVisualizations => { - return savedVisualizations; -}); +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts similarity index 83% rename from src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js rename to src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts index 09c6749bcab94..aa1383587ea68 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts @@ -17,14 +17,15 @@ * under the License. */ -jest.mock('ui/new_platform'); +import { createInputControlVisFn } from './input_control_fn'; // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -import { inputControlVis } from './input_control_fn'; + +jest.mock('./legacy_imports.ts'); describe('interpreter/functions#input_control_vis', () => { - const fn = functionWrapper(inputControlVis); + const fn = functionWrapper(createInputControlVisFn); const visConfig = { controls: [ { @@ -47,8 +48,8 @@ describe('interpreter/functions#input_control_vis', () => { pinFilters: false, }; - it('returns an object with the correct structure', () => { - const actual = fn(undefined, { visConfig: JSON.stringify(visConfig) }); + it('returns an object with the correct structure', async () => { + const actual = await fn(null, { visConfig: JSON.stringify(visConfig) }); expect(actual).toMatchSnapshot(); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.js b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts similarity index 70% rename from src/legacy/core_plugins/input_control_vis/public/input_control_fn.js rename to src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts index 0bd435f502a5d..0482c0d2cbff3 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.js +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.ts @@ -17,10 +17,37 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { i18n } from '@kbn/i18n'; -export const inputControlVis = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; + +const name = 'input_control_vis'; + +type Context = KibanaDatatable; + +interface Arguments { + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visType: 'input_control_vis'; + visConfig: VisParams; +} + +type Return = Promise>; + +export const createInputControlVisFn = (): ExpressionFunction< + typeof name, + Context, + Arguments, + Return +> => ({ name: 'input_control_vis', type: 'render', context: { @@ -33,9 +60,10 @@ export const inputControlVis = () => ({ visConfig: { types: ['string'], default: '"{}"', + help: '', }, }, - fn(context, args) { + async fn(context, args) { const params = JSON.parse(args.visConfig); return { type: 'render', @@ -47,5 +75,3 @@ export const inputControlVis = () => ({ }; }, }); - -functionsRegistry.register(inputControlVis); 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 new file mode 100644 index 0000000000000..b6774aa87b43c --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +import { createInputControlVisController } from './vis_controller'; +import { getControlsTab } from './components/editor/controls_tab'; +import { OptionsTab } from './components/editor/options_tab'; +import { Status, defaultFeedbackMessage } from '../../visualizations/public'; +import { InputControlVisDependencies } from './plugin'; + +export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { + const InputControlVisController = createInputControlVisController(deps); + const ControlsTab = getControlsTab(deps); + + return { + name: 'input_control_vis', + title: i18n.translate('inputControl.register.controlsTitle', { + defaultMessage: 'Controls', + }), + icon: 'visControls', + description: i18n.translate('inputControl.register.controlsDescription', { + defaultMessage: 'Create interactive controls for easy dashboard manipulation.', + }), + stage: 'experimental', + requiresUpdateStatus: [Status.PARAMS, Status.TIME], + feedbackMessage: defaultFeedbackMessage, + visualization: InputControlVisController, + visConfig: { + defaults: { + controls: [], + updateFiltersOnChange: false, + useTimeFilter: false, + pinFilters: false, + }, + }, + editor: 'default', + editorConfig: { + optionTabs: [ + { + name: 'controls', + title: i18n.translate('inputControl.register.tabs.controlsTitle', { + defaultMessage: 'Controls', + }), + editor: ControlsTab, + }, + { + name: 'options', + title: i18n.translate('inputControl.register.tabs.optionsTitle', { + defaultMessage: 'Options', + }), + editor: OptionsTab, + }, + ], + }, + requestHandler: 'none', + responseHandler: 'none', + }; +} diff --git a/src/legacy/core_plugins/input_control_vis/public/legacy.ts b/src/legacy/core_plugins/input_control_vis/public/legacy.ts new file mode 100644 index 0000000000000..438cdffdb323a --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/legacy.ts @@ -0,0 +1,49 @@ +/* + * 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 { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from '.'; + +import { + InputControlVisPluginSetupDependencies, + InputControlVisPluginStartDependencies, +} from './plugin'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const startPlugins: Readonly = { + expressions: npStart.plugins.expressions, + data: npStart.plugins.data, + visualizations: visualizationsStart, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts new file mode 100644 index 0000000000000..864ce3b146689 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts @@ -0,0 +1,29 @@ +/* + * 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 { SearchSource as SearchSourceClass } from 'ui/courier'; +import { Class } from '@kbn/utility-types'; + +export { Vis, VisParams } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { ValidatedDualRange } from 'ui/validated_range'; +export { SearchSourceFields } from 'ui/courier/types'; + +export type SearchSource = Class; +export const SearchSource = SearchSourceClass; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/index.js b/src/legacy/core_plugins/input_control_vis/public/lineage/index.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/lineage/index.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/index.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts similarity index 94% rename from src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts index de1b589b7dfa9..a0cd648007ecc 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.test.ts @@ -23,11 +23,11 @@ import { CONTROL_TYPES, newControl } from '../editor_utils'; test('creates lineage map', () => { const control1 = newControl(CONTROL_TYPES.LIST); - control1.id = 1; + control1.id = '1'; const control2 = newControl(CONTROL_TYPES.LIST); - control2.id = 2; + control2.id = '2'; const control3 = newControl(CONTROL_TYPES.LIST); - control3.id = 3; + control3.id = '3'; control2.parent = control1.id; control3.parent = control2.id; @@ -40,9 +40,9 @@ test('creates lineage map', () => { test('safely handles circular graph', () => { const control1 = newControl(CONTROL_TYPES.LIST); - control1.id = 1; + control1.id = '1'; const control2 = newControl(CONTROL_TYPES.LIST); - control2.id = 2; + control2.id = '2'; control1.parent = control2.id; control2.parent = control1.id; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts similarity index 80% rename from src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts index a08c5d1670a09..d74782c373942 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/lineage_map.ts @@ -18,18 +18,19 @@ */ import _ from 'lodash'; +import { ControlParams } from '../editor_utils'; -export function getLineageMap(controlParamsList) { - function getControlParamsById(controlId) { +export function getLineageMap(controlParamsList: ControlParams[]) { + function getControlParamsById(controlId: string) { return controlParamsList.find(controlParams => { return controlParams.id === controlId; }); } - const lineageMap = new Map(); + const lineageMap = new Map(); controlParamsList.forEach(rootControlParams => { const lineage = [rootControlParams.id]; - const getLineage = controlParams => { + const getLineage = (controlParams: ControlParams) => { if ( _.has(controlParams, 'parent') && controlParams.parent !== '' && @@ -37,7 +38,10 @@ export function getLineageMap(controlParamsList) { ) { lineage.push(controlParams.parent); const parent = getControlParamsById(controlParams.parent); - getLineage(parent); + + if (parent) { + getLineage(parent); + } } }; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts similarity index 98% rename from src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts index fe180357067a9..af6e2444b486f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.test.ts @@ -22,7 +22,7 @@ import { getLineageMap } from './lineage_map'; import { getParentCandidates } from './parent_candidates'; import { CONTROL_TYPES, newControl } from '../editor_utils'; -function createControlParams(id) { +function createControlParams(id: any) { const controlParams = newControl(CONTROL_TYPES.LIST); controlParams.id = id; controlParams.indexPattern = 'indexPatternId'; diff --git a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts similarity index 85% rename from src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js rename to src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts index 17005c24dd41d..af4fddef19001 100644 --- a/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.js +++ b/src/legacy/core_plugins/input_control_vis/public/lineage/parent_candidates.ts @@ -17,9 +17,13 @@ * under the License. */ -import { getTitle } from '../editor_utils'; +import { getTitle, ControlParams } from '../editor_utils'; -export function getParentCandidates(controlParamsList, controlId, lineageMap) { +export function getParentCandidates( + controlParamsList: ControlParams[], + controlId: string, + lineageMap: Map +) { return controlParamsList .filter(controlParams => { // Ignore controls that do not have index pattern and field set @@ -28,7 +32,7 @@ export function getParentCandidates(controlParamsList, controlId, lineageMap) { } // Ignore controls that would create a circular graph const lineage = lineageMap.get(controlParams.id); - if (lineage.includes(controlId)) { + if (lineage?.includes(controlId)) { return false; } return true; diff --git a/src/legacy/core_plugins/input_control_vis/public/plugin.ts b/src/legacy/core_plugins/input_control_vis/public/plugin.ts new file mode 100644 index 0000000000000..e9ffad8b35f21 --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/plugin.ts @@ -0,0 +1,70 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; + +import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { createInputControlVisFn } from './input_control_fn'; +import { createInputControlVisTypeDefinition } from './input_control_vis_type'; + +type InputControlVisCoreSetup = CoreSetup; + +export interface InputControlVisDependencies { + core: InputControlVisCoreSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export interface InputControlVisPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export interface InputControlVisPluginStartDependencies { + expressions: ReturnType; + visualizations: VisualizationsStart; + data: DataPublicPluginStart; +} + +/** @internal */ +export class InputControlVisPlugin implements Plugin, void> { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: InputControlVisCoreSetup, + { expressions, visualizations, data }: InputControlVisPluginSetupDependencies + ) { + const visualizationDependencies: Readonly = { + core, + data, + }; + + expressions.registerFunction(createInputControlVisFn); + visualizations.types.createBaseVisualization( + createInputControlVisTypeDefinition(visualizationDependencies) + ); + } + + public start(core: CoreStart, deps: InputControlVisPluginStartDependencies) { + // nothing to do here + } +} diff --git a/src/legacy/core_plugins/input_control_vis/public/register_vis.js b/src/legacy/core_plugins/input_control_vis/public/register_vis.js deleted file mode 100644 index 09993be3614f2..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/register_vis.js +++ /dev/null @@ -1,72 +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 { VisController } from './vis_controller'; -import { ControlsTab } from './components/editor/controls_tab'; -import { OptionsTab } from './components/editor/options_tab'; -import { i18n } from '@kbn/i18n'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; -import { Status, defaultFeedbackMessage } from '../../visualizations/public'; - -export const inputControlVisDefinition = { - name: 'input_control_vis', - title: i18n.translate('inputControl.register.controlsTitle', { - defaultMessage: 'Controls', - }), - icon: 'visControls', - description: i18n.translate('inputControl.register.controlsDescription', { - defaultMessage: 'Create interactive controls for easy dashboard manipulation.', - }), - stage: 'experimental', - requiresUpdateStatus: [Status.PARAMS, Status.TIME], - feedbackMessage: defaultFeedbackMessage, - visualization: VisController, - visConfig: { - defaults: { - controls: [], - updateFiltersOnChange: false, - useTimeFilter: false, - pinFilters: false, - }, - }, - editor: 'default', - editorConfig: { - optionTabs: [ - { - name: 'controls', - title: i18n.translate('inputControl.register.tabs.controlsTitle', { - defaultMessage: 'Controls', - }), - editor: ControlsTab, - }, - { - name: 'options', - title: i18n.translate('inputControl.register.tabs.optionsTitle', { - defaultMessage: 'Options', - }), - editor: OptionsTab, - }, - ], - }, - requestHandler: 'none', - responseHandler: 'none', -}; - -// register the provider with the visTypes registry -visualizations.types.createBaseVisualization(inputControlVisDefinition); diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js deleted file mode 100644 index 6a1e23769e28c..0000000000000 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js +++ /dev/null @@ -1,207 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { InputControlVis } from './components/vis/input_control_vis'; -import { controlFactory } from './control/control_factory'; -import { getLineageMap } from './lineage'; -import { npStart } from 'ui/new_platform'; -import { SearchSource } from '../../../ui/public/courier/search_source/search_source'; - -class VisController { - constructor(el, vis) { - this.el = el; - this.vis = vis; - this.controls = []; - - this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); - - this.filterManager = npStart.plugins.data.query.filterManager; - this.updateSubsciption = this.filterManager.getUpdates$().subscribe(this.queryBarUpdateHandler); - } - - async render(visData, visParams, status) { - if (status.params || (visParams.useTimeFilter && status.time)) { - this.visParams = visParams; - this.controls = []; - this.controls = await this.initControls(); - this.drawVis(); - } - } - - destroy() { - this.updateSubsciption.unsubscribe(); - unmountComponentAtNode(this.el); - this.controls.forEach(control => control.destroy()); - } - - drawVis = () => { - render( - - - , - this.el - ); - }; - - async initControls() { - const controlParamsList = this.visParams.controls.filter(controlParams => { - // ignore controls that do not have indexPattern or field - return controlParams.indexPattern && controlParams.fieldName; - }); - - const controlFactoryPromises = controlParamsList.map(controlParams => { - const factory = controlFactory(controlParams); - return factory(controlParams, this.visParams.useTimeFilter, SearchSource); - }); - const controls = await Promise.all(controlFactoryPromises); - - const getControl = id => { - return controls.find(control => { - return id === control.id; - }); - }; - - const controlInitPromises = []; - getLineageMap(controlParamsList).forEach((lineage, controlId) => { - // first lineage item is the control. remove it - lineage.shift(); - const ancestors = []; - lineage.forEach(ancestorId => { - ancestors.push(getControl(ancestorId)); - }); - const control = getControl(controlId); - control.setAncestors(ancestors); - controlInitPromises.push(control.fetch()); - }); - - await Promise.all(controlInitPromises); - return controls; - } - - stageFilter = async (controlIndex, newValue) => { - this.controls[controlIndex].set(newValue); - if (this.visParams.updateFiltersOnChange) { - // submit filters on each control change - this.submitFilters(); - } else { - // Do not submit filters, just update vis so controls are updated with latest value - await this.updateNestedControls(); - this.drawVis(); - } - }; - - submitFilters = () => { - const stagedControls = this.controls.filter(control => { - return control.hasChanged(); - }); - - const newFilters = stagedControls - .filter(control => { - return control.hasKbnFilter(); - }) - .map(control => { - return control.getKbnFilter(); - }); - - stagedControls.forEach(control => { - // to avoid duplicate filters, remove any old filters for control - control.filterManager.findFilters().forEach(existingFilter => { - this.filterManager.removeFilter(existingFilter); - }); - }); - - // Clean up filter pills for nested controls that are now disabled because ancestors are not set. - // This has to be done after looking up the staged controls because otherwise removing a filter - // will re-sync the controls of all other filters. - this.controls.map(control => { - if (control.hasAncestors() && control.hasUnsetAncestor()) { - control.filterManager.findFilters().forEach(existingFilter => { - this.filterManager.removeFilter(existingFilter); - }); - } - }); - - this.filterManager.addFilters(newFilters, this.visParams.pinFilters); - }; - - clearControls = async () => { - this.controls.forEach(control => { - control.clear(); - }); - await this.updateNestedControls(); - this.drawVis(); - }; - - updateControlsFromKbn = async () => { - this.controls.forEach(control => { - control.reset(); - }); - await this.updateNestedControls(); - this.drawVis(); - }; - - async updateNestedControls() { - const fetchPromises = this.controls.map(async control => { - if (control.hasAncestors()) { - await control.fetch(); - } - }); - return await Promise.all(fetchPromises); - } - - hasChanges = () => { - return this.controls - .map(control => { - return control.hasChanged(); - }) - .reduce((a, b) => { - return a || b; - }); - }; - - hasValues = () => { - return this.controls - .map(control => { - return control.hasValue(); - }) - .reduce((a, b) => { - return a || b; - }); - }; - - refreshControl = async (controlIndex, query) => { - await this.controls[controlIndex].fetch(query); - this.drawVis(); - }; -} - -export { VisController }; diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx new file mode 100644 index 0000000000000..849b58b8ee2da --- /dev/null +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.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 from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { I18nStart } from 'kibana/public'; +import { Vis, VisParams, SearchSource } from './legacy_imports'; + +import { InputControlVis } from './components/vis/input_control_vis'; +import { getControlFactory } from './control/control_factory'; +import { getLineageMap } from './lineage'; +import { ControlParams } from './editor_utils'; +import { RangeControl } from './control/range_control_factory'; +import { ListControl } from './control/list_control_factory'; +import { InputControlVisDependencies } from './plugin'; +import { FilterManager, esFilters } from '../../../../plugins/data/public'; + +export const createInputControlVisController = (deps: InputControlVisDependencies) => { + return class InputControlVisController { + private I18nContext?: I18nStart['Context']; + + controls: Array; + queryBarUpdateHandler: () => void; + filterManager: FilterManager; + updateSubsciption: any; + visParams?: VisParams; + + constructor(public el: Element, public vis: Vis) { + this.controls = []; + + this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); + + this.filterManager = deps.data.query.filterManager; + this.updateSubsciption = this.filterManager + .getUpdates$() + .subscribe(this.queryBarUpdateHandler); + } + + async render(visData: any, visParams: VisParams, status: any) { + if (status.params || (visParams.useTimeFilter && status.time)) { + this.visParams = visParams; + this.controls = []; + this.controls = await this.initControls(); + const [{ i18n }] = await deps.core.getStartServices(); + this.I18nContext = i18n.Context; + this.drawVis(); + } + } + + destroy() { + this.updateSubsciption.unsubscribe(); + unmountComponentAtNode(this.el); + this.controls.forEach(control => control.destroy()); + } + + drawVis = () => { + if (!this.I18nContext) { + throw new Error('no i18n context found'); + } + + render( + + + , + this.el + ); + }; + + async initControls() { + const controlParamsList = (this.visParams?.controls as ControlParams[])?.filter( + controlParams => { + // ignore controls that do not have indexPattern or field + return controlParams.indexPattern && controlParams.fieldName; + } + ); + + const controlFactoryPromises = controlParamsList.map(controlParams => { + const factory = getControlFactory(controlParams); + return factory(controlParams, this.visParams?.useTimeFilter, SearchSource, deps); + }); + const controls = await Promise.all(controlFactoryPromises); + + const getControl = (controlId: string) => { + return controls.find(({ id }) => id === controlId); + }; + + const controlInitPromises: Array> = []; + getLineageMap(controlParamsList).forEach((lineage, controlId) => { + // first lineage item is the control. remove it + lineage.shift(); + const ancestors: Array = []; + lineage.forEach(ancestorId => { + const control = getControl(ancestorId); + + if (control) { + ancestors.push(control); + } + }); + const control = getControl(controlId); + + if (control) { + control.setAncestors(ancestors); + controlInitPromises.push(control.fetch()); + } + }); + + await Promise.all(controlInitPromises); + return controls; + } + + stageFilter = async (controlIndex: number, newValue: any) => { + this.controls[controlIndex].set(newValue); + if (this.visParams?.updateFiltersOnChange) { + // submit filters on each control change + this.submitFilters(); + } else { + // Do not submit filters, just update vis so controls are updated with latest value + await this.updateNestedControls(); + this.drawVis(); + } + }; + + submitFilters = () => { + const stagedControls = this.controls.filter(control => { + return control.hasChanged(); + }); + + const newFilters = stagedControls + .map(control => control.getKbnFilter()) + .filter((filter): filter is esFilters.Filter => { + return filter !== null; + }); + + stagedControls.forEach(control => { + // to avoid duplicate filters, remove any old filters for control + control.filterManager.findFilters().forEach(existingFilter => { + this.filterManager.removeFilter(existingFilter); + }); + }); + + // Clean up filter pills for nested controls that are now disabled because ancestors are not set. + // This has to be done after looking up the staged controls because otherwise removing a filter + // will re-sync the controls of all other filters. + this.controls.map(control => { + if (control.hasAncestors() && control.hasUnsetAncestor()) { + control.filterManager.findFilters().forEach(existingFilter => { + this.filterManager.removeFilter(existingFilter); + }); + } + }); + + this.filterManager.addFilters(newFilters, this.visParams?.pinFilters); + }; + + clearControls = async () => { + this.controls.forEach(control => { + control.clear(); + }); + await this.updateNestedControls(); + this.drawVis(); + }; + + updateControlsFromKbn = async () => { + this.controls.forEach(control => { + control.reset(); + }); + await this.updateNestedControls(); + this.drawVis(); + }; + + async updateNestedControls() { + const fetchPromises = this.controls.map(async control => { + if (control.hasAncestors()) { + await control.fetch(); + } + }); + return await Promise.all(fetchPromises); + } + + hasChanges = () => { + return this.controls.map(control => control.hasChanged()).some(control => control); + }; + + hasValues = () => { + return this.controls + .map(control => { + return control.hasValue(); + }) + .reduce((a, b) => { + return a || b; + }); + }; + + refreshControl = async (controlIndex: number, query: any) => { + await this.controls[controlIndex].fetch(query); + this.drawVis(); + }; + }; +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 4ea658bcd03ef..178014a691be3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -230,14 +230,18 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` type="dashboardApp" > + +
+
+ +`; + +exports[`should render a Welcome screen with the telemetry disclaimer when optIn is false 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+`; + +exports[`should render a Welcome screen with the telemetry disclaimer when optIn is true 1`] = ` + +
+
+
+ + + + + +

+ +

+
+ +

+ +

+
+ +
+
+
+ + + diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index c87ceb9777c74..d552dd070c86d 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -51,10 +51,7 @@ export class Home extends Component { getServices().getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' ); - const showTelemetryDisclaimer = getServices().getInjected( - 'telemetryNotifyUserAboutOptInDefault' - ); - + const currentOptInStatus = this.props.getOptInStatus(); this.state = { // If welcome is enabled, we wait for loading to complete // before rendering. This prevents an annoying flickering @@ -63,7 +60,7 @@ export class Home extends Component { isLoading: isWelcomeEnabled, isNewKibanaInstance: false, isWelcomeEnabled, - showTelemetryDisclaimer, + currentOptInStatus, }; } @@ -222,14 +219,13 @@ export class Home extends Component { renderLoading() { return ''; } - renderWelcome() { return ( ); } @@ -269,4 +265,5 @@ Home.propTypes = { urlBasePath: PropTypes.string.isRequired, mlEnabled: PropTypes.bool.isRequired, onOptInSeen: PropTypes.func.isRequired, + getOptInStatus: PropTypes.func.isRequired, }; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index 780e2af695381..1f46cf2875fee 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -63,6 +63,10 @@ describe('home', () => { setItem: sinon.mock(), }, urlBasePath: 'goober', + onOptInSeen() { + return false; + }, + getOptInStatus: jest.fn(), }; }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 5a12eb0a66cf1..29f24f5b841a3 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -29,14 +29,13 @@ import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../kibana_services'; import { npSetup } from 'ui/new_platform'; - export function HomeApp({ directories }) { const { getInjected, savedObjectsClient, getBasePath, addBasePath, - telemetryOptInProvider: { setOptInNoticeSeen }, + telemetryOptInProvider: { setOptInNoticeSeen, getOptIn }, } = getServices(); const { cloud } = npSetup.plugins; const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); @@ -87,6 +86,7 @@ export function HomeApp({ directories }) { localStorage={localStorage} urlBasePath={getBasePath()} onOptInSeen={setOptInNoticeSeen} + getOptInStatus={getOptIn} /> diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap index 07744dd0f6f4b..161061868dab2 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap @@ -508,6 +508,7 @@ exports[`bulkCreate should display success message when bulkCreate is successful aria-label="complete" className="euiIcon euiIcon--medium euiIcon-isLoaded euiStepNumber__icon" focusable="false" + role="img" style={null} > + diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx index 21dcfd9ef15de..42c6e6ff6056a 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.test.tsx @@ -35,7 +35,25 @@ jest.mock('../kibana_services', () => ({ test('should render a Welcome screen with the telemetry disclaimer', () => { const component = shallow( // @ts-ignore - {}} showTelemetryDisclaimer={true} onOptInSeen={() => {}} /> + {}} onOptInSeen={() => {}} /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render a Welcome screen with the telemetry disclaimer when optIn is true', () => { + const component = shallow( + // @ts-ignore + {}} onOptInSeen={() => {}} currentOptInStatus={true} /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render a Welcome screen with the telemetry disclaimer when optIn is false', () => { + const component = shallow( + // @ts-ignore + {}} onOptInSeen={() => {}} currentOptInStatus={false} /> ); expect(component).toMatchSnapshot(); @@ -45,7 +63,7 @@ test('should render a Welcome screen with no telemetry disclaimer', () => { // @ts-ignore const component = shallow( // @ts-ignore - {}} showTelemetryDisclaimer={false} onOptInSeen={() => {}} /> + {}} onOptInSeen={() => {}} /> ); expect(component).toMatchSnapshot(); @@ -56,7 +74,7 @@ test('fires opt-in seen when mounted', () => { shallow( // @ts-ignore - {}} showTelemetryDisclaimer={true} onOptInSeen={seen} /> + {}} onOptInSeen={seen} /> ); expect(seen).toHaveBeenCalled(); diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index c8de0bf7bb936..435bf98ca7840 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -23,7 +23,7 @@ * in Elasticsearch. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { EuiLink, EuiTextColor, @@ -39,12 +39,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { getServices } from '../kibana_services'; import { SampleDataCard } from './sample_data'; - interface Props { urlBasePath: string; onSkip: () => void; onOptInSeen: () => any; - showTelemetryDisclaimer: boolean; + currentOptInStatus: boolean; } /** @@ -84,9 +83,42 @@ export class Welcome extends React.Component { document.removeEventListener('keydown', this.hideOnEsc); } - render() { - const { urlBasePath, showTelemetryDisclaimer } = this.props; + private renderTelemetryEnabledOrDisabledText = () => { + if (this.props.currentOptInStatus) { + return ( + + + + + + + ); + } else { + return ( + + + + + + + ); + } + }; + render() { + const { urlBasePath } = this.props; return (
@@ -121,34 +153,23 @@ export class Welcome extends React.Component { onDecline={this.onSampleDataDecline} /> - {showTelemetryDisclaimer && ( - - - - - + + + - - - - - )} + + {this.renderTelemetryEnabledOrDisabledText()} + diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap index ca04ac8fcfaab..f758511990d6f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -93,7 +93,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -136,9 +135,11 @@ exports[`Table should render the boolean template (false) 1`] = ``; exports[`Table should render the boolean template (true) 1`] = ` `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap index f2a55649fe4d7..4716fb8f77633 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -55,7 +55,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap index 415bae7389e97..7856572373e79 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -78,7 +78,6 @@ exports[`Table should render normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx index f54afc4a5e359..f254a6bc22a0d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index_pattern_table/index_pattern_table.tsx @@ -59,7 +59,7 @@ const columns = [ ))} ), - dataType: 'string', + dataType: 'string' as const, sortable: ({ sort }: { sort: string }) => sort, }, ]; @@ -72,7 +72,7 @@ const pagination = { const sorting = { sort: { field: 'title', - direction: 'asc', + direction: 'asc' as const, }, }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap index 843c8207c88c3..731a3379491c1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap @@ -54,7 +54,6 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap index a9175e7b2a63e..ace06e0420a7c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap @@ -90,7 +90,6 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -116,7 +115,6 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` } } responsive={true} - sorting={false} /> @@ -411,7 +409,6 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -448,7 +445,6 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` } } responsive={true} - sorting={false} /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap index 6060e96f3cfb6..941a0ffded820 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap @@ -83,7 +83,6 @@ exports[`Relationships should render dashboards normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -155,7 +154,6 @@ exports[`Relationships should render dashboards normally 1`] = ` ], } } - sorting={false} />
@@ -294,7 +292,6 @@ exports[`Relationships should render index patterns normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -371,7 +368,6 @@ exports[`Relationships should render index patterns normally 1`] = ` ], } } - sorting={false} />
@@ -461,7 +457,6 @@ exports[`Relationships should render searches normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -538,7 +533,6 @@ exports[`Relationships should render searches normally 1`] = ` ], } } - sorting={false} />
@@ -628,7 +622,6 @@ exports[`Relationships should render visualizations normally 1`] = ` }, ] } - executeQueryOptions={Object {}} items={ Array [ Object { @@ -700,7 +693,6 @@ exports[`Relationships should render visualizations normally 1`] = ` ], } } - sorting={false} />
diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 8e414984a0c08..45cc1dc5fb9dd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -29,7 +29,7 @@ import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { AppState } from 'ui/state_management/app_state'; import { npStart } from 'ui/new_platform'; import { IExpressionLoaderParams } from 'src/plugins/expressions/public'; -import { SearchSourceContract } from '../../../../../ui/public/courier'; +import { SearchSourceContract } from 'ui/courier'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { IIndexPattern, @@ -47,6 +47,7 @@ import { APPLY_FILTER_TRIGGER, } from '../../../../../../plugins/embeddable/public'; import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public'; +import { SavedSearch } from '../../discover/types'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -57,6 +58,10 @@ export interface VisSavedObject extends SavedObject { title: string; uiStateJSON?: string; destroy: () => void; + savedSearchRefName?: string; + savedSearchId?: string; + savedSearch?: SavedSearch; + visState: any; } export interface VisualizeEmbeddableConfiguration { diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js deleted file mode 100644 index fd643115e9084..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ /dev/null @@ -1,150 +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. - */ - -/** - * @name SavedVis - * - * @extends SavedObject. - * - * NOTE: It's a type of SavedObject, but specific to visualizations. - */ - -import { Vis } from 'ui/vis'; -import { uiModules } from 'ui/modules'; -import { updateOldState } from '../../../../visualizations/public'; -import { VisualizeConstants } from '../visualize_constants'; -import { createLegacyClass } from 'ui/utils/legacy_class'; -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { extractReferences, injectReferences } from './saved_visualization_references'; - -uiModules.get('app/visualize').factory('SavedVis', function(savedSearches, Private) { - const SavedObject = Private(SavedObjectProvider); - createLegacyClass(SavedVis).inherits(SavedObject); - function SavedVis(opts) { - const self = this; - opts = opts || {}; - if (typeof opts !== 'object') opts = { id: opts }; - - SavedVis.Super.call(self, { - type: SavedVis.type, - mapping: SavedVis.mapping, - searchSource: SavedVis.searchSource, - extractReferences: extractReferences, - injectReferences: injectReferences, - - id: opts.id, - indexPattern: opts.indexPattern, - defaults: { - title: '', - visState: (function() { - if (!opts.type) return null; - const def = {}; - def.type = opts.type; - return def; - })(), - uiStateJSON: '{}', - description: '', - savedSearchId: opts.savedSearchId, - version: 1, - }, - - afterESResp: this._afterEsResp, - }); - - this.showInRecentlyAccessed = true; - } - - SavedVis.type = 'visualization'; - - SavedVis.mapping = { - title: 'text', - visState: 'json', - uiStateJSON: 'text', - description: 'text', - savedSearchId: 'keyword', - version: 'integer', - }; - - // Order these fields to the top, the rest are alphabetical - SavedVis.fieldOrder = ['title', 'description']; - - SavedVis.searchSource = true; - - SavedVis.prototype.getFullPath = function() { - return `/app/kibana#${VisualizeConstants.EDIT_PATH}/${this.id}`; - }; - - SavedVis.prototype._afterEsResp = async function() { - const self = this; - - await self._getLinkedSavedSearch(); - self.searchSource.setField('size', 0); - return self.vis ? self._updateVis() : self._createVis(); - }; - - SavedVis.prototype._getLinkedSavedSearch = async function() { - const self = this; - const linkedSearch = !!self.savedSearchId; - const current = self.savedSearch; - - if (linkedSearch && current && current.id === self.savedSearchId) { - return; - } - - if (self.savedSearch) { - self.searchSource.setParent(self.savedSearch.searchSource.getParent()); - self.savedSearch.destroy(); - self.savedSearch = null; - } - - if (linkedSearch) { - self.savedSearch = await savedSearches.get(self.savedSearchId); - self.searchSource.setParent(self.savedSearch.searchSource); - } - }; - - SavedVis.prototype._createVis = function() { - const self = this; - - self.visState = updateOldState(self.visState); - - // visState doesn't yet exist when importing a visualization, so we can't - // assume that exists at this point. If it does exist, then we're not - // importing a visualization, so we want to sync the title. - if (self.visState) { - self.visState.title = self.title; - } - self.vis = new Vis(self.searchSource.getField('index'), self.visState); - - self.vis.savedSearchId = self.savedSearchId; - - return self.vis; - }; - - SavedVis.prototype._updateVis = function() { - const self = this; - - self.vis.indexPattern = self.searchSource.getField('index'); - self.visState.title = self.title; - self.vis.setState(self.visState); - self.vis.savedSearchId = self.savedSearchId; - }; - - return SavedVis; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts new file mode 100644 index 0000000000000..3490e0ab127ed --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts @@ -0,0 +1,146 @@ +/* + * 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. + */ + +/** + * @name SavedVis + * + * @extends SavedObject. + * + * NOTE: It's a type of SavedObject, but specific to visualizations. + */ +// @ts-ignore +import { Vis } from 'ui/vis'; +import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types'; +import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; +import { updateOldState } from '../../../../visualizations/public'; +import { VisualizeConstants } from '../visualize_constants'; +import { extractReferences, injectReferences } from './saved_visualization_references'; +import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { VisSavedObject } from '../legacy_imports'; + +import { createSavedSearchesService } from '../../discover'; + +async function _afterEsResp(savedVis: VisSavedObject, services: any) { + await _getLinkedSavedSearch(savedVis, services); + savedVis.searchSource!.setField('size', 0); + savedVis.vis = savedVis.vis ? _updateVis(savedVis) : await _createVis(savedVis); + return savedVis; +} + +async function _getLinkedSavedSearch(savedVis: VisSavedObject, services: any) { + const linkedSearch = !!savedVis.savedSearchId; + const current = savedVis.savedSearch; + + if (linkedSearch && current && current.id === savedVis.savedSearchId) { + return; + } + + if (savedVis.savedSearch) { + savedVis.searchSource!.setParent(savedVis.savedSearch.searchSource.getParent()); + savedVis.savedSearch.destroy(); + delete savedVis.savedSearch; + } + const savedSearches = createSavedSearchesService(services); + + if (linkedSearch) { + savedVis.savedSearch = await savedSearches.get(savedVis.savedSearchId!); + savedVis.searchSource!.setParent(savedVis.savedSearch!.searchSource); + } +} + +async function _createVis(savedVis: VisSavedObject) { + savedVis.visState = updateOldState(savedVis.visState); + + // visState doesn't yet exist when importing a visualization, so we can't + // assume that exists at this point. If it does exist, then we're not + // importing a visualization, so we want to sync the title. + if (savedVis.visState) { + savedVis.visState.title = savedVis.title; + } + // the typescript compiler is wrong here, will be right when vis.js -> vis.ts + // @ts-ignore + savedVis.vis = new Vis(savedVis.searchSource!.getField('index'), savedVis.visState); + + savedVis.vis!.savedSearchId = savedVis.savedSearchId; + + return savedVis.vis; +} + +function _updateVis(savedVis: VisSavedObject) { + if (savedVis.vis && savedVis.searchSource) { + savedVis.vis.indexPattern = savedVis.searchSource.getField('index'); + savedVis.visState.title = savedVis.title; + savedVis.vis.setState(savedVis.visState); + savedVis.vis.savedSearchId = savedVis.savedSearchId; + } + return savedVis.vis; +} + +export function createSavedVisClass(services: SavedObjectKibanaServices) { + const SavedObjectClass = createSavedObjectClass(services); + + class SavedVis extends SavedObjectClass { + public static type: string = 'visualization'; + public static mapping: Record = { + title: 'text', + visState: 'json', + uiStateJSON: 'text', + description: 'text', + savedSearchId: 'keyword', + version: 'integer', + }; + // Order these fields to the top, the rest are alphabetical + public static fieldOrder = ['title', 'description']; + public static searchSource = true; + + constructor(opts: Record | string = {}) { + if (typeof opts !== 'object') { + opts = { id: opts }; + } + const visState = !opts.type ? null : { type: opts.type }; + // Gives our SavedWorkspace the properties of a SavedObject + super({ + type: SavedVis.type, + mapping: SavedVis.mapping, + searchSource: SavedVis.searchSource, + extractReferences, + injectReferences, + id: (opts.id as string) || '', + indexPattern: opts.indexPattern as IIndexPattern, + defaults: { + title: '', + visState, + uiStateJSON: '{}', + description: '', + savedSearchId: opts.savedSearchId, + version: 1, + }, + afterESResp: (savedObject: SavedObject) => { + return _afterEsResp(savedObject as VisSavedObject, services) as Promise; + }, + }); + this.showInRecentlyAccessed = true; + this.getFullPath = () => { + return `/app/kibana#${VisualizeConstants.EDIT_PATH}/${this.id}`; + }; + } + } + + return SavedVis; +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/index.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/index.js rename to src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js rename to src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts index cdc232b06cf51..6549b317d1634 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts @@ -18,6 +18,7 @@ */ import { extractReferences, injectReferences } from './saved_visualization_references'; +import { VisSavedObject } from '../embeddable/visualize_embeddable'; describe('extractReferences', () => { test('extracts nothing if savedSearchId is empty', () => { @@ -26,6 +27,7 @@ describe('extractReferences', () => { attributes: { foo: true, }, + references: [], }; const updatedDoc = extractReferences(doc); expect(updatedDoc).toMatchInlineSnapshot(` @@ -45,6 +47,7 @@ Object { foo: true, savedSearchId: '123', }, + references: [], }; const updatedDoc = extractReferences(doc); expect(updatedDoc).toMatchInlineSnapshot(` @@ -83,6 +86,7 @@ Object { }, }), }, + references: [], }; const updatedDoc = extractReferences(doc); @@ -108,13 +112,13 @@ describe('injectReferences', () => { test('injects nothing when savedSearchRefName is null', () => { const context = { id: '1', - foo: true, - }; + title: 'test', + } as VisSavedObject; injectReferences(context, []); expect(context).toMatchInlineSnapshot(` Object { - "foo": true, "id": "1", + "title": "test", } `); }); @@ -122,7 +126,7 @@ Object { test('injects references into context', () => { const context = { id: '1', - foo: true, + title: 'test', savedSearchRefName: 'search_0', visState: { params: { @@ -137,7 +141,7 @@ Object { ], }, }, - }; + } as VisSavedObject; const references = [ { name: 'search_0', @@ -153,9 +157,9 @@ Object { injectReferences(context, references); expect(context).toMatchInlineSnapshot(` Object { - "foo": true, "id": "1", "savedSearchId": "123", + "title": "test", "visState": Object { "params": Object { "controls": Array [ @@ -176,9 +180,9 @@ Object { test(`fails when it can't find the saved search reference in the array`, () => { const context = { id: '1', - foo: true, savedSearchRefName: 'search_0', - }; + title: 'test', + } as VisSavedObject; expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( `"Could not find saved search reference \\"search_0\\""` ); @@ -187,6 +191,7 @@ Object { test(`fails when it can't find the index pattern reference in the array`, () => { const context = { id: '1', + title: 'test', visState: { params: { controls: [ @@ -197,7 +202,7 @@ Object { ], }, }, - }; + } as VisSavedObject; expect(() => injectReferences(context, [])).toThrowErrorMatchingInlineSnapshot( `"Could not find index pattern reference \\"control_0_index_pattern\\""` ); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts similarity index 77% rename from src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js rename to src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts index 7f451076e239c..dd8c2e9d2b74f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts @@ -16,8 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +import { SavedObjectAttributes, SavedObjectReference } from 'kibana/server'; +import { VisSavedObject } from '../embeddable/visualize_embeddable'; -export function extractReferences({ attributes, references = [] }) { +export function extractReferences({ + attributes, + references = [], +}: { + attributes: SavedObjectAttributes; + references: SavedObjectReference[]; +}) { const updatedAttributes = { ...attributes }; const updatedReferences = [...references]; @@ -26,7 +34,7 @@ export function extractReferences({ attributes, references = [] }) { updatedReferences.push({ name: 'search_0', type: 'search', - id: updatedAttributes.savedSearchId, + id: String(updatedAttributes.savedSearchId), }); delete updatedAttributes.savedSearchId; updatedAttributes.savedSearchRefName = 'search_0'; @@ -34,9 +42,9 @@ export function extractReferences({ attributes, references = [] }) { // Extract index patterns from controls if (updatedAttributes.visState) { - const visState = JSON.parse(updatedAttributes.visState); + const visState = JSON.parse(String(updatedAttributes.visState)); const controls = (visState.params && visState.params.controls) || []; - controls.forEach((control, i) => { + controls.forEach((control: Record, i: number) => { if (!control.indexPattern) { return; } @@ -57,7 +65,7 @@ export function extractReferences({ attributes, references = [] }) { }; } -export function injectReferences(savedObject, references) { +export function injectReferences(savedObject: VisSavedObject, references: SavedObjectReference[]) { if (savedObject.savedSearchRefName) { const savedSearchReference = references.find( reference => reference.name === savedObject.savedSearchRefName @@ -70,13 +78,11 @@ export function injectReferences(savedObject, references) { } if (savedObject.visState) { const controls = (savedObject.visState.params && savedObject.visState.params.controls) || []; - controls.forEach(control => { + controls.forEach((control: Record) => { if (!control.indexPatternRefName) { return; } - const reference = references.find( - reference => reference.name === control.indexPatternRefName - ); + const reference = references.find(ref => ref.name === control.indexPatternRefName); if (!reference) { throw new Error(`Could not find index pattern reference "${control.indexPatternRefName}"`); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts new file mode 100644 index 0000000000000..803474b1f7b3f --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +// @ts-ignore +import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; +import './saved_visualizations'; + +SavedObjectRegistryProvider.register((savedVisualizations: any) => { + return savedVisualizations; +}); + +// Register this service with the saved object registry so it can be +// edited by the object editor. +savedObjectManagementRegistry.register({ + service: 'savedVisualizations', + title: 'visualizations', +}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js deleted file mode 100644 index 784f45436e3a3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js +++ /dev/null @@ -1,92 +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 './_saved_vis'; -import { uiModules } from 'ui/modules'; -import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; -import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; -import { createVisualizeEditUrl } from '../visualize_constants'; -import { findListItems } from './find_list_items'; -import { npStart } from '../../../../../ui/public/new_platform'; - -const app = uiModules.get('app/visualize'); - -// Register this service with the saved object registry so it can be -// edited by the object editor. -savedObjectManagementRegistry.register({ - service: 'savedVisualizations', - title: 'visualizations', -}); - -app.service('savedVisualizations', function(SavedVis, Private) { - const visTypes = visualizations.types; - const savedObjectClient = Private(SavedObjectsClientProvider); - const saveVisualizationLoader = new SavedObjectLoader( - SavedVis, - savedObjectClient, - npStart.core.chrome - ); - - saveVisualizationLoader.mapHitSource = function(source, id) { - source.id = id; - source.url = this.urlFor(id); - - let typeName = source.typeName; - if (source.visState) { - try { - typeName = JSON.parse(source.visState).type; - } catch (e) { - /* missing typename handled below */ - } // eslint-disable-line no-empty - } - - if (!typeName || !visTypes.get(typeName)) { - source.error = 'Unknown visualization type'; - return source; - } - - source.type = visTypes.get(typeName); - source.savedObjectType = 'visualization'; - source.icon = source.type.icon; - source.image = source.type.image; - source.typeTitle = source.type.title; - source.editUrl = `#${createVisualizeEditUrl(id)}`; - - return source; - }; - - saveVisualizationLoader.urlFor = function(id) { - return `#/visualize/edit/${encodeURIComponent(id)}`; - }; - - // This behaves similarly to find, except it returns visualizations that are - // defined as appExtensions and which may not conform to type: visualization - saveVisualizationLoader.findListItems = function(search = '', size = 100) { - return findListItems({ - search, - size, - mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this), - savedObjectsClient: this.savedObjectsClient, - visTypes: visualizations.types.getAliases(), - }); - }; - - return saveVisualizationLoader; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts new file mode 100644 index 0000000000000..7425250bffe1a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts @@ -0,0 +1,86 @@ +/* + * 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 { npStart } from 'ui/new_platform'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { SavedObjectLoader } from 'ui/saved_objects'; + +import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; +import { createVisualizeEditUrl } from '../visualize_constants'; +// @ts-ignore +import { findListItems } from './find_list_items'; +import { createSavedVisClass } from './_saved_vis'; +const app = uiModules.get('app/visualize'); + +app.service('savedVisualizations', function() { + const savedObjectsClient = npStart.core.savedObjects.client; + const services = { + savedObjectsClient, + indexPatterns: npStart.plugins.data.indexPatterns, + chrome: npStart.core.chrome, + overlays: npStart.core.overlays, + }; + class SavedObjectLoaderVisualize extends SavedObjectLoader { + mapHitSource = (source: Record, id: string) => { + const visTypes = visualizations.types; + source.id = id; + source.url = this.urlFor(id); + + let typeName = source.typeName; + if (source.visState) { + try { + typeName = JSON.parse(String(source.visState)).type; + } catch (e) { + /* missing typename handled below */ + } // eslint-disable-line no-empty + } + + if (!typeName || !visTypes.get(typeName)) { + source.error = 'Unknown visualization type'; + return source; + } + + source.type = visTypes.get(typeName); + source.savedObjectType = 'visualization'; + source.icon = source.type.icon; + source.image = source.type.image; + source.typeTitle = source.type.title; + source.editUrl = `#${createVisualizeEditUrl(id)}`; + + return source; + }; + urlFor(id: string) { + return `#/visualize/edit/${encodeURIComponent(id)}`; + } + // This behaves similarly to find, except it returns visualizations that are + // defined as appExtensions and which may not conform to type: visualization + findListItems(search: string = '', size: number = 100) { + return findListItems({ + search, + size, + mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this), + savedObjectsClient, + visTypes: visualizations.types.getAliases(), + }); + } + } + const SavedVis = createSavedVisClass(services); + + return new SavedObjectLoaderVisualize(SavedVis, savedObjectsClient, npStart.core.chrome); +}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index ca6b872c73f8f..0b44c7dc4e860 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -169,6 +169,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -226,6 +227,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -275,6 +277,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -290,6 +293,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -325,6 +329,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -361,6 +366,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -494,6 +500,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -551,6 +558,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -600,6 +608,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -615,6 +624,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -650,6 +660,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -686,6 +697,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -758,6 +770,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -815,6 +828,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -864,6 +878,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--medium euiIcon-isLoading euiBetaBadge__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -879,6 +894,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -914,6 +930,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -950,6 +967,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` class="euiIcon euiIcon--large euiIcon--secondary euiIcon-isLoading" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" @@ -1031,16 +1049,18 @@ exports[`NewVisModal filter for visualization types should render as expected 1` type="cross" >
-
-
- -
-
-
- -
-
- -
`; @@ -472,10 +452,11 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` className="euiButtonEmpty__content" >
- +
+ + + +
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index 489d4f2908cbe..9b2a2c8f2490a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -111,12 +111,14 @@ NodeList [ class="euiIcon euiIcon--medium euiIcon-isLoaded euiButton__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" > + <path - d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 0 0-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 0 1 0 8.373zM8 15A6.956 6.956 0 0 1 3.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 0 0 2.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 0 1 8 15zm-5.601-2.813a6.963 6.963 0 0 1 0-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 0 0 3 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 1 1 8 4a4 4 0 0 1 0 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 0 0 8 3a4.979 4.979 0 0 0-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 0 1 8 1zm0-1a8.001 8.001 0 1 0 .003 16.002A8.001 8.001 0 0 0 8 0z" + d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 00-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 010 8.373zM8 15A6.956 6.956 0 013.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 002.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 018 15zm-5.601-2.813a6.963 6.963 0 010-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 003 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 118 4a4 4 0 010 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 008 3a4.979 4.979 0 00-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 018 1zm0-1a8.001 8.001 0 10.003 16.002A8.001 8.001 0 008 0z" fill-rule="evenodd" /> </svg> diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx index 53c66e04c468a..c8404b02afe70 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { sortByOrder } from 'lodash'; import React, { useMemo, useCallback, ReactNode } from 'react'; import { useUrlParams } from '../../../hooks/useUrlParams'; @@ -69,8 +69,8 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { const sort = useMemo(() => { return { sort: { - field: sortField, - direction: sortDirection + field: sortField as keyof T, + direction: sortDirection as 'asc' | 'desc' } }; }, [sortField, sortDirection]); @@ -78,7 +78,7 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { const onTableChange = useCallback( (options: { page: { index: number; size: number }; - sort: { field: string; direction: 'asc' | 'desc' }; + sort?: { field: keyof T; direction: 'asc' | 'desc' }; }) => { history.push({ ...history.location, @@ -86,8 +86,8 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { ...toQuery(history.location.search), page: options.page.index, pageSize: options.page.size, - sortField: options.sort.field, - sortDirection: options.sort.direction + sortField: options.sort!.field, + sortDirection: options.sort!.direction }) }); }, @@ -107,7 +107,7 @@ function UnoptimizedManagedTable<T>(props: Props<T>) { <EuiBasicTable noItemsMessage={noItemsMessage} items={renderedItems} - columns={columns} + columns={(columns as unknown) as Array<EuiBasicTableColumn<T>>} // EuiBasicTableColumn is stricter than ITableColumn pagination={pagination} sorting={sort} onChange={onTableChange} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 5b17b124a321d..ea1b825c856ad 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -185,14 +185,18 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] type="arrowRight" > <EuiIconEmpty + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiAccordion__icon" focusable="false" + role="img" style={null} > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiAccordion__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap index 63f56b8db5c50..48e442ce734cf 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap @@ -21,12 +21,14 @@ exports[`TransactionActionMenu component should match the snapshot 1`] = ` class="euiIcon euiIcon--medium euiIcon-isLoaded euiButtonEmpty__icon" focusable="false" height="16" + role="img" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" > + <title /> <path - d="M13.069 5.157L8.384 9.768a.546.546 0 0 1-.768 0L2.93 5.158a.552.552 0 0 0-.771 0 .53.53 0 0 0 0 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 0 0 0-.76.552.552 0 0 0-.771 0z" + d="M13.069 5.157L8.384 9.768a.546.546 0 01-.768 0L2.93 5.158a.552.552 0 00-.771 0 .53.53 0 000 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 000-.76.552.552 0 00-.771 0z" fill-rule="non-zero" /> </svg> diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index 0f928fe626bd3..6b21f08b7695e 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -31,11 +31,8 @@ _Docker Compose is required_ ### Setup default APM users -APM behaves differently depending on which the role and permissions a logged in user has. -For testing purposes APM has invented 4 custom users: - - -**elastic**: Apps: read/write. Indices: read/write (all) +APM behaves differently depending on which the role and permissions a logged in user has. +For testing purposes APM uses 3 custom users: **apm_read_user**: Apps: read. Indices: read (`apm-*`) @@ -44,10 +41,10 @@ For testing purposes APM has invented 4 custom users: **kibana_write_user** Apps: read/write. Indices: None -To create the 4 users with the correct roles run the following script: +To create the users with the correct roles run the following script: ```sh -node x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js --username <github-username> +node x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js --role-suffix <github-username-or-something-unique> ``` The users will be created with the password specified in kibana.dev.yml for `elasticsearch.password` diff --git a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts index 66f2a8d1ac79f..85aa43f78f7dd 100644 --- a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts +++ b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts @@ -20,13 +20,13 @@ const config = yaml.safeLoad( ) ); -const GITHUB_USERNAME = argv.username as string; const KIBANA_INDEX = config['kibana.index'] as string; const TASK_MANAGER_INDEX = config['xpack.task_manager.index'] as string; -const ELASTICSEARCH_USERNAME = (argv.esUsername as string) || 'elastic'; -const ELASTICSEARCH_PASSWORD = (argv.esPassword || +const KIBANA_ROLE_SUFFIX = argv.roleSuffix as string; +const ELASTICSEARCH_USERNAME = (argv.username as string) || 'elastic'; +const ELASTICSEARCH_PASSWORD = (argv.password || config['elasticsearch.password']) as string; -const KIBANA_BASE_URL = (argv.baseUrl as string) || 'http://localhost:5601'; +const KIBANA_BASE_URL = (argv.kibanaUrl as string) || 'http://localhost:5601'; interface User { username: string; @@ -40,51 +40,76 @@ const getKibanaBasePath = once(async () => { try { await axios.request({ url: KIBANA_BASE_URL, maxRedirects: 0 }); } catch (e) { - const err = e as AxiosError; - const { location } = err.response?.headers; - const isBasePath = RegExp(/^\/\w{3}$/).test(location); - return isBasePath ? location : ''; + if (isAxiosError(e)) { + const location = e.response?.headers?.location; + const isBasePath = RegExp(/^\/\w{3}$/).test(location); + return isBasePath ? location : ''; + } + + throw e; } return ''; }); init().catch(e => { - if (e.response) { - console.log( - JSON.stringify({ request: e.config, response: e.response.data }, null, 2) + if (e instanceof AbortError) { + console.error(e.message); + } else if (isAxiosError(e)) { + console.error( + `${e.config.method?.toUpperCase() || 'GET'} ${e.config.url} (Code: ${ + e.response?.status + })` ); - return; - } - console.log(e); + if (e.response) { + console.error( + JSON.stringify( + { request: e.config, response: e.response.data }, + null, + 2 + ) + ); + } + } else { + console.error(e); + } }); async function init() { + const version = await getKibanaVersion(); + console.log(`Connected to Kibana ${version}`); + + const isKibanaLocal = KIBANA_BASE_URL.startsWith('http://localhost'); + // kibana.index must be different from `.kibana` - if (KIBANA_INDEX === '.kibana') { + if (isKibanaLocal && KIBANA_INDEX === '.kibana') { console.log( 'kibana.dev.yml: Please use a custom "kibana.index". Example: "kibana.index: .kibana-john"' ); return; } - if (!KIBANA_INDEX.startsWith('.kibana')) { + if (isKibanaLocal && !KIBANA_INDEX.startsWith('.kibana')) { console.log( 'kibana.dev.yml: "kibana.index" must be prefixed with `.kibana`. Example: "kibana.index: .kibana-john"' ); return; } - if (TASK_MANAGER_INDEX && !TASK_MANAGER_INDEX.startsWith('.kibana')) { + if ( + isKibanaLocal && + TASK_MANAGER_INDEX && + !TASK_MANAGER_INDEX.startsWith('.kibana') + ) { console.log( 'kibana.dev.yml: "xpack.task_manager.index" must be prefixed with `.kibana`. Example: "xpack.task_manager.index: .kibana-task-manager-john"' ); return; } - if (!GITHUB_USERNAME) { + if (!KIBANA_ROLE_SUFFIX) { console.log( - 'Please specify your github username with `--username <username>` ' + 'Please specify a unique suffix that will be added to your roles with `--role-suffix <suffix>` ' ); return; } @@ -95,8 +120,8 @@ async function init() { return; } - const KIBANA_READ_ROLE = `kibana_read_${GITHUB_USERNAME}`; - const KIBANA_WRITE_ROLE = `kibana_write_${GITHUB_USERNAME}`; + const KIBANA_READ_ROLE = `kibana_read_${KIBANA_ROLE_SUFFIX}`; + const KIBANA_WRITE_ROLE = `kibana_write_${KIBANA_ROLE_SUFFIX}`; // create roles await createRole({ roleName: KIBANA_READ_ROLE, privilege: 'read' }); @@ -132,16 +157,18 @@ async function isSecurityEnabled() { } async function callKibana<T>(options: AxiosRequestConfig): Promise<T> { - const basePath = await getKibanaBasePath(); - const { data } = await axios.request({ + const kibanaBasePath = await getKibanaBasePath(); + const reqOptions = { ...options, - baseURL: KIBANA_BASE_URL + basePath, + baseURL: KIBANA_BASE_URL + kibanaBasePath, auth: { username: ELASTICSEARCH_USERNAME, password: ELASTICSEARCH_PASSWORD }, headers: { 'kbn-xsrf': 'true', ...options.headers } - }); + }; + + const { data } = await axios.request(reqOptions); return data; } @@ -222,10 +249,8 @@ async function getUser(username: string) { url: `/internal/security/users/${username}` }); } catch (e) { - const err = e as AxiosError; - // return empty if user doesn't exist - if (err.response?.status === 404) { + if (isAxiosError(e) && e.response?.status === 404) { return null; } @@ -240,13 +265,51 @@ async function getRole(roleName: string) { url: `/api/security/role/${roleName}` }); } catch (e) { - const err = e as AxiosError; - // return empty if role doesn't exist - if (err.response?.status === 404) { + if (isAxiosError(e) && e.response?.status === 404) { return null; } throw e; } } + +async function getKibanaVersion() { + try { + const res: { version: { number: number } } = await callKibana({ + method: 'GET', + url: `/api/status` + }); + return res.version.number; + } catch (e) { + if (isAxiosError(e)) { + switch (e.response?.status) { + case 401: + throw new AbortError( + `Could not access Kibana with the provided credentials. Username: "${e.config.auth?.username}". Password: "${e.config.auth?.password}"` + ); + + case 404: + throw new AbortError( + `Could not get version on ${e.config.url} (Code: 404)` + ); + + default: + throw new AbortError( + `Cannot access Kibana on ${e.config.baseURL}. Please specify Kibana with: "--kibana-url <url>"` + ); + } + } + throw e; + } +} + +function isAxiosError(e: AxiosError | Error): e is AxiosError { + return 'isAxiosError' in e; +} + +class AbortError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js index a3dc3d66f56a0..825c1a526fcc5 100644 --- a/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js +++ b/x-pack/legacy/plugins/apm/scripts/setup-kibana-security.js @@ -12,7 +12,7 @@ * The two roles will be assigned to the already existing users: `apm_read_user`, `apm_write_user`, `kibana_write_user` * * This makes it possible to use the existing cloud users locally - * Usage: node setup-kibana-security.js --username YOUR-GITHUB-USERNAME + * Usage: node setup-kibana-security.js --role-suffix <YOUR-GITHUB-USERNAME-OR-SOMETHING-UNIQUE> ******************************/ // compile typescript on the fly diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx index 8b101196d21ee..26ddd682405cb 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx @@ -93,7 +93,7 @@ export class Table extends React.Component<TableProps, TableState> { }; const selectionOptions = hideTableControls - ? null + ? undefined : { onSelectionChange: this.setSelection, selectable: () => true, @@ -148,7 +148,7 @@ export class Table extends React.Component<TableProps, TableState> { ); } - private onTableChange = (page: { index: number; size: number } = { index: 0, size: 50 }) => { + private onTableChange = ({ page }: { page: { index: number; size: number } }) => { if (this.props.onTableChange) { this.props.onTableChange(page.index, page.size); } diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx index 6f03f884563e1..6fff7574c39e6 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -14,7 +14,7 @@ import { ConnectedLink } from '../navigation/connected_link'; import { TagBadge } from '../tag'; export interface ColumnDefinition { - align?: string; + align?: 'left' | 'right' | 'center' | undefined; field: string; name: string; sortable?: boolean; diff --git a/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx index 3eaf550cb8c77..3952b44f82561 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx @@ -13,6 +13,7 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiBasicTableColumn, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -98,7 +99,7 @@ class BeatDetailPageUi extends React.PureComponent<PageProps, PageState> { ), })); - const columns = [ + const columns: Array<EuiBasicTableColumn<ConfigurationBlock>> = [ { field: 'displayValue', name: intl.formatMessage({ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot index 7d37bf184b49b..b9c6d258821f2 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/component/__examples__/__snapshots__/dropdown_filter.examples.storyshot @@ -16,9 +16,11 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -61,9 +63,11 @@ exports[`Storyshots renderers/DropdownFilter with choices 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -107,9 +111,11 @@ exports[`Storyshots renderers/DropdownFilter with choices and new value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -153,9 +159,11 @@ exports[`Storyshots renderers/DropdownFilter with choices and value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -181,9 +189,11 @@ exports[`Storyshots renderers/DropdownFilter with new value 1`] = ` </option> </select> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading canvasDropdownFilter__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot index 6c9d4ce4459b3..be4db66a84d83 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot @@ -60,10 +60,11 @@ exports[`Storyshots arguments/AxisConfig extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -141,10 +142,11 @@ exports[`Storyshots arguments/AxisConfig/components extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot index 4bed74ddb6c60..014c365055fa3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot @@ -43,10 +43,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -120,10 +121,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -196,10 +198,11 @@ exports[`Storyshots arguments/DateFormat with preset format 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot index 6de7df3454dde..e93ad3be31f1d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot @@ -53,10 +53,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -140,10 +141,11 @@ Array [ className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -226,10 +228,11 @@ exports[`Storyshots arguments/NumberFormat with preset format 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot index 89ed05a1eea93..454ef0a79d10a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot @@ -80,10 +80,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -113,10 +114,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -147,10 +149,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -177,10 +180,11 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -275,10 +279,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -308,10 +313,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -342,10 +348,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -372,10 +379,11 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot index 9ffc95c82531e..77359957b8443 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot @@ -127,9 +127,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#000", @@ -156,9 +158,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", @@ -185,9 +189,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", @@ -214,9 +220,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#fff", diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot index 25198ac2e79f3..5da0ea8738c37 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot @@ -389,10 +389,11 @@ exports[`Storyshots components/Color/ColorManager interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -407,10 +408,11 @@ exports[`Storyshots components/Color/ColorManager interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -807,10 +809,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -825,10 +828,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -894,10 +898,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -912,10 +917,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -981,10 +987,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -999,10 +1006,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot index 05dd67960db05..badbf96029f12 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot @@ -364,9 +364,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -713,9 +715,11 @@ exports[`Storyshots components/Color/ColorPalette six colors, wrap at 4 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -985,9 +989,11 @@ Array [ } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot index 1af31bdef47fe..6e25ce3d4b0d4 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot @@ -220,10 +220,11 @@ exports[`Storyshots components/Color/ColorPicker interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -238,10 +239,11 @@ exports[`Storyshots components/Color/ColorPicker interactive 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -307,9 +309,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -511,10 +515,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -529,10 +534,11 @@ exports[`Storyshots components/Color/ColorPicker six colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -767,10 +773,11 @@ exports[`Storyshots components/Color/ColorPicker six colors, value missing 1`] = type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -785,10 +792,11 @@ exports[`Storyshots components/Color/ColorPicker six colors, value missing 1`] = type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -833,9 +841,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading selected-color" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -959,10 +969,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -977,10 +988,11 @@ exports[`Storyshots components/Color/ColorPicker three colors 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot index ec282e9a44fde..efaa34001971e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/custom_element_modal/__examples__/__snapshots__/custom_element_modal.examples.storyshot @@ -55,10 +55,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -209,10 +210,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -255,9 +257,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -412,10 +416,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -566,10 +571,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -766,10 +772,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -920,10 +927,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -966,9 +974,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1122,10 +1132,11 @@ Array [ type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1276,10 +1287,11 @@ Array [ className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1322,9 +1334,11 @@ Array [ className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot index f09921607ef46..5eedf32020e4c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/__snapshots__/element_card.examples.storyshot @@ -16,9 +16,11 @@ exports[`Storyshots components/Elements/ElementCard with click handler 1`] = ` className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -118,9 +120,11 @@ exports[`Storyshots components/Elements/ElementCard with tags 1`] = ` className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -289,9 +293,11 @@ exports[`Storyshots components/Elements/ElementCard with title and description 1 className="euiCard__top" > <svg + aria-hidden={true} className="euiIcon euiIcon--xxLarge euiIcon--app euiIcon-isLoading euiCard__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot index c88869ea22f1a..27a2a60180930 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot @@ -28,10 +28,11 @@ exports[`Storyshots components/Elements/ElementControls has two buttons 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -57,10 +58,11 @@ exports[`Storyshots components/Elements/ElementControls has two buttons 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot index 01774f849dfe7..c9fb77061572d 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot @@ -75,10 +75,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -104,10 +105,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -182,10 +184,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -211,10 +214,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -289,10 +293,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -318,10 +323,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -411,10 +417,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls and filter 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -440,10 +447,11 @@ exports[`Storyshots components/Elements/ElementGrid with controls and filter 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot index 085662eeb65c7..7f18c2dc85215 100644 --- a/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot @@ -20,10 +20,11 @@ exports[`Storyshots components/FileUpload default 1`] = ` className="euiFilePicker__prompt" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--large euiIcon-isLoading euiFilePicker__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot index 20996df68747c..c1cb45123f04b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot @@ -47,10 +47,11 @@ exports[`Storyshots components/FontPicker default 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -129,10 +130,11 @@ exports[`Storyshots components/FontPicker with value 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot index 79b547102b089..5ee984885bbc0 100644 --- a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot @@ -71,9 +71,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#333", @@ -100,9 +102,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#FFF", @@ -129,9 +133,11 @@ exports[`Storyshots components/ItemGrid complex grid 1`] = ` } > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#FFF", @@ -151,27 +157,33 @@ exports[`Storyshots components/ItemGrid icon grid 1`] = ` className="item-grid-row" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} xmlns="http://www.w3.org/2000/svg" /> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} xmlns="http://www.w3.org/2000/svg" /> <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot index e02d64e3e0647..9397a67402e51 100644 --- a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot @@ -55,10 +55,11 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot index 5a5472e422963..09fc81c382d11 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot @@ -43,10 +43,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -85,10 +86,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -148,10 +150,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader without layer controls 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -190,10 +193,11 @@ exports[`Storyshots components/Sidebar/SidebarHeader without layer controls 1`] type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot index ec6d0cbe4d1ec..562feb8111e41 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot @@ -55,9 +55,11 @@ exports[`Storyshots components/Tags/Tag as health 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#666666", @@ -88,9 +90,11 @@ exports[`Storyshots components/Tags/Tag as health with color 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#9b3067", diff --git a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot index 1e41a56b82f9e..9dcf55642c66f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot @@ -76,9 +76,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#cc3b54", @@ -106,9 +108,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#9b3067", @@ -136,9 +140,11 @@ Array [ className="euiFlexItem euiFlexItem--flexGrowZero" > <svg + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading" focusable="false" height={16} + role="img" style={ Object { "fill": "#d41e93", diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot index a94f593661cf3..1888d01a3ac93 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot @@ -71,10 +71,11 @@ exports[`Storyshots components/Export/PDFPanel default 1`] = ` className="euiButton__content" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButton__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot index c9f5cf67f734a..9c7fca6d78190 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot @@ -28,10 +28,11 @@ exports[`Storyshots components/Export/WorkpadExport disabled 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -72,10 +73,11 @@ exports[`Storyshots components/Export/WorkpadExport enabled 1`] = ` type="button" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot index 9e3bebbd71273..2915d3bfef57b 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -139,10 +139,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -208,10 +209,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -367,10 +369,11 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -626,10 +629,11 @@ exports[`Storyshots arguments/ContainerStyle/components appearance form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -695,10 +699,11 @@ exports[`Storyshots arguments/ContainerStyle/components appearance form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -853,10 +858,11 @@ exports[`Storyshots arguments/ContainerStyle/components border form 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1123,10 +1129,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1192,10 +1199,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -1351,10 +1359,11 @@ exports[`Storyshots arguments/ContainerStyle/components extended template 1`] = className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot index 3b41da4cd3d54..8fa2d406831a4 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -66,10 +66,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -163,10 +164,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -252,10 +254,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} @@ -341,10 +344,11 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` className="euiFormControlLayoutCustomIcon" > <svg - aria-hidden="true" + aria-hidden={true} className="euiIcon euiIcon--medium euiIcon-isLoading euiFormControlLayoutCustomIcon__icon" focusable="false" height={16} + role="img" style={null} viewBox="0 0 16 16" width={16} diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot index c843a62702b16..72477cec2603b 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot @@ -173,12 +173,14 @@ exports[`Storyshots arguments/SeriesStyle/components simple: no series 1`] = ` onMouseOver={[Function]} > <svg + aria-hidden={true} aria-label="Info" className="euiIcon euiIcon--medium euiIcon--warning euiIcon-isLoading" focusable="true" height={16} onBlur={[Function]} onFocus={[Function]} + role="img" style={null} tabIndex={0} viewBox="0 0 16 16" diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap index 782d5364de821..acd68622f1af0 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap @@ -9,7 +9,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with default propertie </style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div><div class=\\"root\\" style=\\"height: 48px;\\"><div class=\\"root\\"><div class=\\"slideContainer\\"><div class=\\"root\\" style=\\"height: 100px; width: 150px;\\"><div class=\\"preview\\" style=\\"height: 720px; width: 1080px;\\"><div id=\\"page-7186b301-f8a7-4c65-8b89-38d68d31cfc4\\" class=\\"root\\" style=\\"height: 720px; width: 1080px; background: rgb(119, 119, 119);\\"><div class=\\"canvasPositionable canvasInteractable\\" style=\\"width: 1082px; height: 205.37748344370857px; margin-left: -541px; margin-top: -102.68874172185429px; position: absolute;\\"><div class=\\"root\\"><div class=\\"container s2042575598\\" style=\\"overflow: hidden;\\"><style type=\\"text/css\\">.s2042575598 .canvasRenderEl h1 { font-size: 150px; text-align: center; color: #d3d3d3; } -</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"32\\" height=\\"32\\" viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoaded\\" focusable=\\"false\\"><g fill=\\"none\\"><path fill=\\"#FDD009\\" d=\\"M11.934 13.152l7.353 3.356 7.42-6.507c.107-.537.16-1.072.16-1.633 0-4.578-3.721-8.303-8.295-8.303a8.288 8.288 0 0 0-6.84 3.61l-1.234 6.409 1.436 3.068z\\"></path><path fill=\\"#23BAB1\\" d=\\"M4.322 20.947a8.461 8.461 0 0 0-.162 1.657c0 4.59 3.731 8.326 8.317 8.326a8.288 8.288 0 0 0 6.873-3.646l1.224-6.387-1.634-3.127-7.383-3.368-7.235 6.545z\\"></path><path fill=\\"#EE5097\\" d=\\"M4.276 8.208L9.315 9.4l1.104-5.736a3.976 3.976 0 0 0-2.413-.815 3.978 3.978 0 0 0-3.971 3.976c0 .484.08.948.24 1.383\\"></path><path fill=\\"#17A7E0\\" d=\\"M3.838 9.41c-2.251.747-3.817 2.907-3.817 5.284 0 2.314 1.43 4.38 3.576 5.198l7.07-6.398-1.298-2.776-5.53-1.308z\\"></path><path fill=\\"#92C73D\\" d=\\"M20.642 27.284a3.945 3.945 0 0 0 2.4.822 3.977 3.977 0 0 0 3.972-3.975c0-.484-.08-.948-.24-1.383l-5.036-1.18-1.096 5.716z\\"></path><path fill=\\"#0678A0\\" d=\\"M21.667 20.247l5.543 1.298c2.252-.745 3.818-2.907 3.818-5.284a5.553 5.553 0 0 0-3.583-5.19l-7.25 6.36 1.472 2.816z\\"></path></g></svg></a></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\" style=\\"min-width: 0; cursor: default;\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\"><div class=\\"eui-textTruncate\\">My Canvas Workpad</div></div></div></div></div></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\" style=\\"margin: 0px 12px;\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsPrevPage\\" aria-label=\\"Previous Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path fill-rule=\\"nonzero\\" d=\\"M10.843 13.069L6.232 8.384a.546.546 0 0 1 0-.768l4.61-4.685a.552.552 0 0 0 0-.771.53.53 0 0 0-.759 0l-4.61 4.684a1.65 1.65 0 0 0 0 2.312l4.61 4.684a.53.53 0 0 0 .76 0 .552.552 0 0 0 0-.771z\\"></path></svg></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button class=\\"euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small\\" type=\\"button\\" data-test-subj=\\"pageControlsCurrentPage\\"><span class=\\"euiButtonEmpty__content\\"><span class=\\"euiButtonEmpty__text\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\">Page 1</div></div></span></span></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsNextPage\\" aria-label=\\"Next Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path fill-rule=\\"nonzero\\" d=\\"M5.157 13.069l4.611-4.685a.546.546 0 0 0 0-.768L5.158 2.93a.552.552 0 0 1 0-.771.53.53 0 0 1 .759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 0 1-.76 0 .552.552 0 0 1 0-.771z\\"></path></svg></button></div></div><div class=\\"euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiPopover euiPopover--anchorUpRight euiPopover--withTitle\\" id=\\"settings\\"><div class=\\"euiPopover__anchor\\"><button class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" aria-label=\\"Settings\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"><path d=\\"M.164 10.329L1.87 8 .163 5.67c.18-.601.43-1.19.758-1.757a8.197 8.197 0 0 1 1.142-1.535l2.872.313L6.099.05a8.166 8.166 0 0 1 3.8-.003l1.166 2.644 2.872-.313a8.166 8.166 0 0 1 1.899 3.293L14.13 8l1.706 2.33c-.18.601-.43 1.19-.758 1.757a8.197 8.197 0 0 1-1.142 1.535l-2.872-.313-1.164 2.641a8.166 8.166 0 0 1-3.8.003l-1.166-2.644-2.872.313a8.166 8.166 0 0 1-1.899-3.293zm4.663 1.986a1 1 0 0 1 1.023.591l.957 2.17c.79.134 1.597.132 2.387-.001l.956-2.169a1 1 0 0 1 1.023-.59l2.358.256a7.23 7.23 0 0 0 1.194-2.068l-1.401-1.913a1 1 0 0 1 0-1.182l1.4-1.912a7.165 7.165 0 0 0-1.192-2.069l-2.359.257a1 1 0 0 1-1.023-.591L9.193.924a7.165 7.165 0 0 0-2.387.001L5.85 3.094a1 1 0 0 1-1.023.59l-2.358-.256a7.23 7.23 0 0 0-1.194 2.068l1.401 1.913a1 1 0 0 1 0 1.182l-1.4 1.912c.28.751.681 1.45 1.192 2.069l2.359-.257zM8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z\\"></path></svg></button></div></div></div></div></div></div></div></div></div></div></div>" +</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"32\\" height=\\"32\\" viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoaded\\" focusable=\\"false\\" role=\\"img\\" aria-hidden=\\"true\\"><title>
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot index c3352b52c591d..6a33dba76c126 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__examples__/__snapshots__/canvas.examples.storyshot @@ -1325,9 +1325,11 @@ exports[`Storyshots shareables/Canvas component 1`] = ` title="Powered by Elastic.co" >
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot index 6570016336d9e..7b3ac299f80ad 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/__examples__/__snapshots__/footer.examples.storyshot @@ -1278,9 +1278,11 @@ exports[`Storyshots shareables/Footer contextual: austin 1`] = ` title="Powered by Elastic.co" > can navigate Autoplay Settings 1`] = ` Auto Play @@ -95,13 +101,16 @@ exports[` can navigate Autoplay Settings 1`] = ` class="euiContextMenu__itemLayout" > can navigate Autoplay Settings 2`] = ` Auto Play @@ -237,13 +255,16 @@ exports[` can navigate Autoplay Settings 2`] = ` class="euiContextMenu__itemLayout" > can navigate Toolbar Settings, closes when activated 1`] = Auto Play @@ -558,13 +597,16 @@ exports[` can navigate Toolbar Settings, closes when activated 1`] = class="euiContextMenu__itemLayout" > can navigate Toolbar Settings, closes when activated 2`] = Auto Play @@ -700,13 +751,16 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] = class="euiContextMenu__itemLayout" >
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index a7cc8843140c8..bd42346e36135 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -263,16 +263,18 @@ exports[`ilm summary extension should return extension when index has lifecycle type="cross" >