From c2f9f0ac84a4219a31295c811ff145a1faf71fdd Mon Sep 17 00:00:00 2001 From: ymao1 Date: Mon, 12 Oct 2020 07:33:16 -0400 Subject: [PATCH 1/8] [Alerting] Selectively update threshold value on threshold comparator change (#79914) * Only updating threshold value on threshold comparator change when number of required values for comparator changes * Adding unit test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../expression_items/threshold.test.tsx | 45 ++++++++++++++++++- .../common/expression_items/threshold.tsx | 15 ++++--- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index 01791ef6147bf..73efba6929b71 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -5,6 +5,7 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ThresholdExpression } from './threshold'; describe('threshold expression', () => { @@ -52,7 +53,7 @@ describe('threshold expression', () => { `); }); - it('renders with treshold title', () => { + it('renders with threshold title', () => { const onChangeSelectedThreshold = jest.fn(); const onChangeSelectedThresholdComparator = jest.fn(); const wrapper = shallow( @@ -65,4 +66,46 @@ describe('threshold expression', () => { ); expect(wrapper.contains('Is between')).toBeTruthy(); }); + + it('fires onChangeSelectedThreshold only when threshold actually changed', async () => { + const onChangeSelectedThreshold = jest.fn(); + const onChangeSelectedThresholdComparator = jest.fn(); + + const wrapper = mountWithIntl( + '} + threshold={[10]} + errors={{ threshold0: [], threshold1: [] }} + onChangeSelectedThreshold={onChangeSelectedThreshold} + onChangeSelectedThresholdComparator={onChangeSelectedThresholdComparator} + /> + ); + + wrapper.find('[data-test-subj="thresholdPopover"]').first().simulate('click'); + expect(wrapper.find('[data-test-subj="comparatorOptionsComboBox"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alertThresholdInput"]').exists()).toBeTruthy(); + + wrapper + .find('[data-test-subj="alertThresholdInput"]') + .last() + .simulate('change', { target: { value: 1000 } }); + expect(onChangeSelectedThreshold).toHaveBeenCalled(); + expect(onChangeSelectedThresholdComparator).not.toHaveBeenCalled(); + + jest.clearAllMocks(); + wrapper + .find('[data-test-subj="comparatorOptionsComboBox"]') + .last() + .simulate('change', { target: { value: '<' } }); + expect(onChangeSelectedThreshold).not.toHaveBeenCalled(); + expect(onChangeSelectedThresholdComparator).toHaveBeenCalled(); + + jest.clearAllMocks(); + wrapper + .find('[data-test-subj="comparatorOptionsComboBox"]') + .last() + .simulate('change', { target: { value: 'between' } }); + expect(onChangeSelectedThreshold).toHaveBeenCalled(); + expect(onChangeSelectedThresholdComparator).toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index fe592aadb37a5..2b5cec98b16a1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -111,12 +111,17 @@ export const ThresholdExpression = ({ data-test-subj="comparatorOptionsComboBox" value={thresholdComparator} onChange={(e) => { + const updateThresholdValue = + comparators[thresholdComparator].requiredValues !== + comparators[e.target.value].requiredValues; onChangeSelectedThresholdComparator(e.target.value); - const thresholdValues = threshold.slice( - 0, - comparators[e.target.value].requiredValues - ); - onChangeSelectedThreshold(thresholdValues); + if (updateThresholdValue) { + const thresholdValues = threshold.slice( + 0, + comparators[e.target.value].requiredValues + ); + onChangeSelectedThreshold(thresholdValues); + } }} options={Object.values(comparators).map(({ text, value }) => { return { text, value }; From 34b2cf9aa177814c5c41c62d3e33810d718754a2 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 12 Oct 2020 13:53:47 +0200 Subject: [PATCH 2/8] fix URL field formatter images (#79876) * fix image preview urls * fix typescript. use new recommended way of accessing context Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../label_template_flyout.test.tsx.snap | 1 + .../url/__snapshots__/url.test.tsx.snap | 943 ++++++++---------- .../url_template_flyout.test.tsx.snap | 1 + .../editors/url/label_template_flyout.tsx | 2 +- .../editors/url/url.test.tsx | 88 +- .../field_format_editor/editors/url/url.tsx | 19 +- .../editors/url/url_template_flyout.tsx | 2 +- 7 files changed, 500 insertions(+), 556 deletions(-) diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap index 69b192a81d097..38f630358d064 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/label_template_flyout.test.tsx.snap @@ -4,6 +4,7 @@ exports[`LabelTemplateFlyout should not render if not visible 1`] = `""`; exports[`LabelTemplateFlyout should render normally 1`] = ` diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap index f862d0ebe8477..13be0353e1640 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.tsx.snap @@ -1,544 +1,431 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UrlFormatEditor should render label template help 1`] = ` - - - - - } - labelType="label" +exports[`UrlFormatEditor should render normally 1`] = ` +
+
- - - + - + Type + + +
+
+
- - - } - isInvalid={false} - label={ - - } - labelType="label" - > - - - - -`; - -exports[`UrlFormatEditor should render normally 1`] = ` - - - - - } - labelType="label" +
+ +
+ + +
+
+
+
+
- - - + - + Open in a new tab + + +
+
+
- - - } - isInvalid={false} - label={ - - } - labelType="label" + + + + Off + + +
+
+
+
- - - - -`; - -exports[`UrlFormatEditor should render url template help 1`] = ` - - - - - } - labelType="label" - > - - - + - + URL template + + +
+
+
- - - } - isInvalid={false} - label={ - - } - labelType="label" - > - - - - -`; - -exports[`UrlFormatEditor should render width and height fields if image 1`] = ` - - - - - } - labelType="label" - > - - - + +
+
+
- - - } - isInvalid={false} - label={ - - } - labelType="label" + +
+ + +
- - - + - - } - labelType="label" - > - - - - } - labelType="label" + + Label template + + +
+
+
+
+ +
+
+
+ +
+
+ +
- - - - +
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Input + +
+
+
+ + Output + +
+
+
+ Input +
+
+ john +
+
+
+ Output +
+
+
+ converted url for john +
+
+
+
+ Input +
+
+ /some/pathname/asset.png +
+
+
+ Output +
+
+
+ converted url for /some/pathname/asset.png +
+
+
+
+ Input +
+
+ 1234 +
+
+
+ Output +
+
+
+ converted url for 1234 +
+
+
+
+
+
+
+
`; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap index 14e5012e9a554..83e815dd72661 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.tsx.snap @@ -4,6 +4,7 @@ exports[`UrlTemplateFlyout should not render if not visible 1`] = `""`; exports[`UrlTemplateFlyout should render normally 1`] = ` diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx index d04ee58f26b0a..4dd3fb8f1b695 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/label_template_flyout.tsx @@ -63,7 +63,7 @@ const items: LabelTemplateExampleItem[] = [ export const LabelTemplateFlyout = ({ isVisible = false, onClose = () => {} }) => { return isVisible ? ( - +

diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx index a1a1655949432..eb5cac111928f 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.test.tsx @@ -18,13 +18,22 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; import { FieldFormat } from 'src/plugins/data/public'; - +import { IntlProvider } from 'react-intl'; import { UrlFormatEditor } from './url'; +import { coreMock } from '../../../../../../../../../core/public/mocks'; +import { createKibanaReactContext } from '../../../../../../../../kibana_react/public'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +jest.mock('@elastic/eui/lib/services/accessibility', () => { + return { + htmlIdGenerator: () => () => `generated-id`, + }; +}); const fieldType = 'string'; -const format = { +const format = ({ getConverterFor: jest .fn() .mockImplementation(() => (input: string) => `converted url for ${input}`), @@ -35,78 +44,115 @@ const format = { { kind: 'audio', text: 'Audio' }, ], }, -}; +} as unknown) as FieldFormat; const formatParams = { openLinkInCurrentTab: true, urlTemplate: '', labelTemplate: '', width: '', height: '', + type: 'a', }; + const onChange = jest.fn(); const onError = jest.fn(); +const renderWithContext = (Element: React.ReactElement) => + render( + + {Element} + + ); + +const MY_BASE_PATH = 'my-base-path'; +const KibanaReactContext = createKibanaReactContext( + coreMock.createStart({ basePath: 'my-base-path' }) +); + describe('UrlFormatEditor', () => { it('should have a formatId', () => { expect(UrlFormatEditor.formatId).toEqual('url'); }); it('should render normally', async () => { - const component = shallow( + const { container } = renderWithContext( ); - - expect(component).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); it('should render url template help', async () => { - const component = shallow( + const { getByText, getByTestId } = renderWithContext( ); - (component.instance() as UrlFormatEditor).showUrlTemplateHelp(); - component.update(); - expect(component).toMatchSnapshot(); + getByText('URL template help'); + userEvent.click(getByText('URL template help')); + expect(getByTestId('urlTemplateFlyoutTestSubj')).toBeVisible(); }); it('should render label template help', async () => { - const component = shallow( + const { getByText, getByTestId } = renderWithContext( ); - (component.instance() as UrlFormatEditor).showLabelTemplateHelp(); - component.update(); - expect(component).toMatchSnapshot(); + getByText('Label template help'); + userEvent.click(getByText('Label template help')); + expect(getByTestId('labelTemplateFlyoutTestSubj')).toBeVisible(); }); it('should render width and height fields if image', async () => { - const component = shallow( + const { getByLabelText } = renderWithContext( ); - expect(component).toMatchSnapshot(); + expect(getByLabelText('Width')).toBeInTheDocument(); + expect(getByLabelText('Height')).toBeInTheDocument(); + }); + + it('should append base path to preview images', async () => { + let sampleImageUrlTemplate = ''; + const { getByLabelText } = renderWithContext( + { + sampleImageUrlTemplate = urlTemplate; + }} + onError={onError} + /> + ); + + // TODO: sample image url emitted only during change event + // So can't just path `type: img` and check rendered value + userEvent.selectOptions(getByLabelText('Type'), 'img'); + expect(sampleImageUrlTemplate).toContain(MY_BASE_PATH); + expect(sampleImageUrlTemplate).toMatchInlineSnapshot( + `"my-base-path/plugins/indexPatternManagement/assets/icons/{{value}}.png"` + ); }); }); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx index 30acf09526f85..95b5fc3955280 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url.tsx @@ -36,6 +36,8 @@ import { FormatEditorSamples } from '../../samples'; import { LabelTemplateFlyout } from './label_template_flyout'; import { UrlTemplateFlyout } from './url_template_flyout'; +import type { IndexPatternManagmentContextValue } from '../../../../../../types'; +import { context as contextType } from '../../../../../../../../kibana_react/public'; interface OnChangeParam { type: string; @@ -66,14 +68,21 @@ export class UrlFormatEditor extends DefaultFormatEditor< UrlFormatEditorFormatParams, UrlFormatEditorFormatState > { + static contextType = contextType; static formatId = 'url'; - iconPattern: string; + // TODO: @kbn/optimizer can't compile this + // declare context: IndexPatternManagmentContextValue; + context: IndexPatternManagmentContextValue | undefined; + private get sampleIconPath() { + const sampleIconPath = `/plugins/indexPatternManagement/assets/icons/{{value}}.png`; + return this.context?.services.http + ? this.context.services.http.basePath.prepend(sampleIconPath) + : sampleIconPath; + } constructor(props: FormatEditorProps) { super(props); - this.iconPattern = `/plugins/indexPatternManagement/assets/icons/{{value}}.png`; - this.state = { ...this.state, sampleInputsByType: { @@ -104,9 +113,9 @@ export class UrlFormatEditor extends DefaultFormatEditor< params.width = width; params.height = height; if (!urlTemplate) { - params.urlTemplate = this.iconPattern; + params.urlTemplate = this.sampleIconPath; } - } else if (newType !== 'img' && urlTemplate === this.iconPattern) { + } else if (newType !== 'img' && urlTemplate === this.sampleIconPath) { params.urlTemplate = undefined; } this.onChange(params); diff --git a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx index c1b144b0d9eac..b1c66874d69cf 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/components/field_format_editor/editors/url/url_template_flyout.tsx @@ -26,7 +26,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; export const UrlTemplateFlyout = ({ isVisible = false, onClose = () => {} }) => { return isVisible ? ( - +

From 4c92f36db11a6e3bf4cc29d783c8175bdbdfeef2 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 12 Oct 2020 13:26:56 +0100 Subject: [PATCH 3/8] skip flaky suite (#77957) --- .../security_solution/cypress/integration/alerts.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index db841d2a732c4..07d0d63e57059 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -30,7 +30,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Alerts', () => { +// FLAKY: https://github.com/elastic/kibana/issues/77957 +describe.skip('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { esArchiverLoad('alerts'); From de6855b3ef75ff6d4ab7e69e05a4cc8a92acd454 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 12 Oct 2020 13:35:20 +0100 Subject: [PATCH 4/8] skip flaky suite (#79967) --- .../cypress/integration/timeline_template_creation.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts index e262d12770d3a..91255d6110d59 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_template_creation.spec.ts @@ -43,7 +43,8 @@ import { openTimeline } from '../tasks/timelines'; import { OVERVIEW_URL } from '../urls/navigation'; -describe('Timeline Templates', () => { +// FLAKY: https://github.com/elastic/kibana/issues/79967 +describe.skip('Timeline Templates', () => { before(() => { cy.server(); cy.route('PATCH', '**/api/timeline').as('timeline'); From 356c0175c2dc2b4fd147575c346506bae6b90291 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 12 Oct 2020 15:40:54 +0200 Subject: [PATCH 5/8] tiny embeddable state change performance optimization (#79646) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/lib/panel/embeddable_panel.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index a2da31773696c..137f8c24b1fae 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -20,6 +20,7 @@ import { EuiContextMenuPanelDescriptor, EuiPanel, htmlIdGenerator } from '@elast import classNames from 'classnames'; import React from 'react'; import { Subscription } from 'rxjs'; +import deepEqual from 'fast-deep-equal'; import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions'; import { CoreStart, OverlayStart } from '../../../../../core/public'; import { toMountPoint } from '../../../../kibana_react/public'; @@ -123,9 +124,11 @@ export class EmbeddablePanel extends React.Component { badges = badges.filter((badge) => disabledActions.indexOf(badge.id) === -1); } - this.setState({ - badges, - }); + if (!deepEqual(this.state.badges, badges)) { + this.setState({ + badges, + }); + } } private async refreshNotifications() { @@ -139,9 +142,11 @@ export class EmbeddablePanel extends React.Component { notifications = notifications.filter((badge) => disabledActions.indexOf(badge.id) === -1); } - this.setState({ - notifications, - }); + if (!deepEqual(this.state.notifications, notifications)) { + this.setState({ + notifications, + }); + } } public UNSAFE_componentWillMount() { From 13059bdd8fabc0c7ad63750bc2fc6af546a29d50 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Mon, 12 Oct 2020 14:51:39 +0100 Subject: [PATCH 6/8] [Refactor] Move AttributeService from Dashboard to Embeddable plugin (#79830) * [Refactor] Move AttributeService from Dashboard to Embeddable plugin * Fix wrong import * Fixing typescript errors * Make Dashboard a required dependency * Fixing circular dependency * Fix label namespace * Updating docs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...embeddable-public.attribute_service_key.md | 13 ++++++ ...e-public.attributeservice._constructor_.md | 25 +++++++++++ ...eservice.getexplicitinputfromembeddable.md | 22 +++++++++ ...blic.attributeservice.getinputasreftype.md | 16 +++++++ ...ic.attributeservice.getinputasvaluetype.md | 11 +++++ ...-public.attributeservice.inputisreftype.md | 11 +++++ ...gins-embeddable-public.attributeservice.md | 40 +++++++++++++++++ ...ublic.attributeservice.unwrapattributes.md | 22 +++++++++ ...-public.attributeservice.wrapattributes.md | 24 ++++++++++ ...lic.embeddablestart.getattributeservice.md | 17 +++++++ ...ugins-embeddable-public.embeddablestart.md | 1 + ...kibana-plugin-plugins-embeddable-public.md | 2 + ...ver.embeddablesetup.getattributeservice.md | 11 +++++ ...ugins-embeddable-server.embeddablesetup.md | 1 + examples/embeddable_examples/kibana.json | 2 +- .../public/book/book_embeddable.tsx | 2 +- .../public/book/book_embeddable_factory.tsx | 5 ++- .../public/book/edit_book_action.tsx | 9 ++-- examples/embeddable_examples/public/plugin.ts | 6 +-- src/plugins/dashboard/public/index.ts | 1 - src/plugins/dashboard/public/mocks.tsx | 5 +-- src/plugins/dashboard/public/plugin.tsx | 28 ------------ src/plugins/embeddable/public/index.ts | 2 + .../attribute_service.mock.tsx | 8 ++-- .../attribute_service.test.ts | 4 +- .../attribute_service/attribute_service.tsx | 12 ++--- .../public/lib}/attribute_service/index.ts | 0 src/plugins/embeddable/public/mocks.tsx | 2 + src/plugins/embeddable/public/plugin.tsx | 24 +++++++++- src/plugins/embeddable/public/public.api.md | 45 +++++++++++++++++++ src/plugins/embeddable/server/plugin.ts | 1 + src/plugins/embeddable/server/server.api.md | 2 + src/plugins/visualizations/kibana.json | 2 +- .../create_vis_embeddable_from_object.ts | 7 ++- .../public/embeddable/visualize_embeddable.ts | 2 +- .../visualize_embeddable_factory.tsx | 4 +- src/plugins/visualizations/public/plugin.ts | 2 +- x-pack/plugins/lens/kibana.json | 5 ++- .../lens/public/app_plugin/app.test.tsx | 2 +- .../embeddable/embeddable.test.tsx | 2 +- .../lens/public/lens_attribute_service.ts | 4 +- x-pack/plugins/lens/public/plugin.ts | 2 +- 42 files changed, 335 insertions(+), 71 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attribute_service_key.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md create mode 100644 docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md rename src/plugins/{dashboard/public => embeddable/public/lib}/attribute_service/attribute_service.mock.tsx (89%) rename src/plugins/{dashboard/public => embeddable/public/lib}/attribute_service/attribute_service.test.ts (98%) rename src/plugins/{dashboard/public => embeddable/public/lib}/attribute_service/attribute_service.tsx (95%) rename src/plugins/{dashboard/public => embeddable/public/lib}/attribute_service/index.ts (100%) diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attribute_service_key.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attribute_service_key.md new file mode 100644 index 0000000000000..9504d50cf92d3 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attribute_service_key.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [ATTRIBUTE\_SERVICE\_KEY](./kibana-plugin-plugins-embeddable-public.attribute_service_key.md) + +## ATTRIBUTE\_SERVICE\_KEY variable + +The attribute service is a shared, generic service that embeddables can use to provide the functionality required to fulfill the requirements of the ReferenceOrValueEmbeddable interface. The attribute\_service can also be used as a higher level wrapper to transform an embeddable input shape that references a saved object into an embeddable input shape that contains that saved object's attributes by value. + +Signature: + +```typescript +ATTRIBUTE_SERVICE_KEY = "attributes" +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md new file mode 100644 index 0000000000000..930250be2018b --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [(constructor)](./kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md) + +## AttributeService.(constructor) + +Constructs a new instance of the `AttributeService` class + +Signature: + +```typescript +constructor(type: string, showSaveModal: (saveModal: React.ReactElement, I18nContext: I18nStart['Context']) => void, i18nContext: I18nStart['Context'], toasts: NotificationsStart['toasts'], options: AttributeServiceOptions, getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| showSaveModal | (saveModal: React.ReactElement, I18nContext: I18nStart['Context']) => void | | +| i18nContext | I18nStart['Context'] | | +| toasts | NotificationsStart['toasts'] | | +| options | AttributeServiceOptions<SavedObjectAttributes> | | +| getEmbeddableFactory | (embeddableFactoryId: string) => EmbeddableFactory | | + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md new file mode 100644 index 0000000000000..e3f27723e1a70 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [getExplicitInputFromEmbeddable](./kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md) + +## AttributeService.getExplicitInputFromEmbeddable() method + +Signature: + +```typescript +getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| embeddable | IEmbeddable | | + +Returns: + +`ValType | RefType` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md new file mode 100644 index 0000000000000..7908327c594d8 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [getInputAsRefType](./kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md) + +## AttributeService.getInputAsRefType property + +Signature: + +```typescript +getInputAsRefType: (input: ValType | RefType, saveOptions?: { + showSaveModal: boolean; + saveModalTitle?: string | undefined; + } | { + title: string; + } | undefined) => Promise; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md new file mode 100644 index 0000000000000..939194575cbb7 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [getInputAsValueType](./kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md) + +## AttributeService.getInputAsValueType property + +Signature: + +```typescript +getInputAsValueType: (input: ValType | RefType) => Promise; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md new file mode 100644 index 0000000000000..c17ad97c3eeed --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [inputIsRefType](./kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md) + +## AttributeService.inputIsRefType property + +Signature: + +```typescript +inputIsRefType: (input: ValType | RefType) => input is RefType; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.md new file mode 100644 index 0000000000000..b63516c909d3c --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.md @@ -0,0 +1,40 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) + +## AttributeService class + +Signature: + +```typescript +export declare class AttributeService +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(type, showSaveModal, i18nContext, toasts, options, getEmbeddableFactory)](./kibana-plugin-plugins-embeddable-public.attributeservice._constructor_.md) | | Constructs a new instance of the AttributeService class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [getInputAsRefType](./kibana-plugin-plugins-embeddable-public.attributeservice.getinputasreftype.md) | | (input: ValType | RefType, saveOptions?: {
showSaveModal: boolean;
saveModalTitle?: string | undefined;
} | {
title: string;
} | undefined) => Promise<RefType> | | +| [getInputAsValueType](./kibana-plugin-plugins-embeddable-public.attributeservice.getinputasvaluetype.md) | | (input: ValType | RefType) => Promise<ValType> | | +| [inputIsRefType](./kibana-plugin-plugins-embeddable-public.attributeservice.inputisreftype.md) | | (input: ValType | RefType) => input is RefType | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [getExplicitInputFromEmbeddable(embeddable)](./kibana-plugin-plugins-embeddable-public.attributeservice.getexplicitinputfromembeddable.md) | | | +| [unwrapAttributes(input)](./kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md) | | | +| [wrapAttributes(newAttributes, useRefType, input)](./kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md) | | | + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md new file mode 100644 index 0000000000000..f08736a2240a3 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [unwrapAttributes](./kibana-plugin-plugins-embeddable-public.attributeservice.unwrapattributes.md) + +## AttributeService.unwrapAttributes() method + +Signature: + +```typescript +unwrapAttributes(input: RefType | ValType): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| input | RefType | ValType | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md new file mode 100644 index 0000000000000..e22a2ec3faeb4 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) > [wrapAttributes](./kibana-plugin-plugins-embeddable-public.attributeservice.wrapattributes.md) + +## AttributeService.wrapAttributes() method + +Signature: + +```typescript +wrapAttributes(newAttributes: SavedObjectAttributes, useRefType: boolean, input?: ValType | RefType): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newAttributes | SavedObjectAttributes | | +| useRefType | boolean | | +| input | ValType | RefType | | + +Returns: + +`Promise>` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md new file mode 100644 index 0000000000000..ca75b756d199e --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStart](./kibana-plugin-plugins-embeddable-public.embeddablestart.md) > [getAttributeService](./kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md) + +## EmbeddableStart.getAttributeService property + +Signature: + +```typescript +getAttributeService: (type: string, options: AttributeServiceOptions) => AttributeService; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md index f8e0028d8344b..541575566d3f7 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestart.md @@ -15,6 +15,7 @@ export interface EmbeddableStart extends PersistableState | Property | Type | Description | | --- | --- | --- | | [EmbeddablePanel](./kibana-plugin-plugins-embeddable-public.embeddablestart.embeddablepanel.md) | EmbeddablePanelHOC | | +| [getAttributeService](./kibana-plugin-plugins-embeddable-public.embeddablestart.getattributeservice.md) | <A extends {
title: string;
}, V extends EmbeddableInput & {
[ATTRIBUTE_SERVICE_KEY]: A;
} = EmbeddableInput & {
[ATTRIBUTE_SERVICE_KEY]: A;
}, R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput>(type: string, options: AttributeServiceOptions<A>) => AttributeService<A, V, R> | | | [getEmbeddableFactories](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablefactories.md) | () => IterableIterator<EmbeddableFactory> | | | [getEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablefactory.md) | <I extends EmbeddableInput = EmbeddableInput, O extends EmbeddableOutput = EmbeddableOutput, E extends IEmbeddable<I, O> = IEmbeddable<I, O>>(embeddableFactoryId: string) => EmbeddableFactory<I, O, E> | undefined | | | [getEmbeddablePanel](./kibana-plugin-plugins-embeddable-public.embeddablestart.getembeddablepanel.md) | (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index 64dfdd1c6dc22..df67eda5074b9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -9,6 +9,7 @@ | Class | Description | | --- | --- | | [AddPanelAction](./kibana-plugin-plugins-embeddable-public.addpanelaction.md) | | +| [AttributeService](./kibana-plugin-plugins-embeddable-public.attributeservice.md) | | | [Container](./kibana-plugin-plugins-embeddable-public.container.md) | | | [EditPanelAction](./kibana-plugin-plugins-embeddable-public.editpanelaction.md) | | | [Embeddable](./kibana-plugin-plugins-embeddable-public.embeddable.md) | | @@ -71,6 +72,7 @@ | --- | --- | | [ACTION\_ADD\_PANEL](./kibana-plugin-plugins-embeddable-public.action_add_panel.md) | | | [ACTION\_EDIT\_PANEL](./kibana-plugin-plugins-embeddable-public.action_edit_panel.md) | | +| [ATTRIBUTE\_SERVICE\_KEY](./kibana-plugin-plugins-embeddable-public.attribute_service_key.md) | The attribute service is a shared, generic service that embeddables can use to provide the functionality required to fulfill the requirements of the ReferenceOrValueEmbeddable interface. The attribute\_service can also be used as a higher level wrapper to transform an embeddable input shape that references a saved object into an embeddable input shape that contains that saved object's attributes by value. | | [CONTEXT\_MENU\_TRIGGER](./kibana-plugin-plugins-embeddable-public.context_menu_trigger.md) | | | [contextMenuTrigger](./kibana-plugin-plugins-embeddable-public.contextmenutrigger.md) | | | [defaultEmbeddableFactoryProvider](./kibana-plugin-plugins-embeddable-public.defaultembeddablefactoryprovider.md) | | diff --git a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md new file mode 100644 index 0000000000000..9cd77ca6e3a36 --- /dev/null +++ b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) > [EmbeddableSetup](./kibana-plugin-plugins-embeddable-server.embeddablesetup.md) > [getAttributeService](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md) + +## EmbeddableSetup.getAttributeService property + +Signature: + +```typescript +getAttributeService: any; +``` diff --git a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md index 59ca4a2bbca75..bd024095e80be 100644 --- a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md +++ b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.md @@ -14,6 +14,7 @@ export interface EmbeddableSetup | Property | Type | Description | | --- | --- | --- | +| [getAttributeService](./kibana-plugin-plugins-embeddable-server.embeddablesetup.getattributeservice.md) | any | | | [registerEmbeddableFactory](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerembeddablefactory.md) | (factory: EmbeddableRegistryDefinition) => void | | | [registerEnhancement](./kibana-plugin-plugins-embeddable-server.embeddablesetup.registerenhancement.md) | (enhancement: EnhancementRegistryDefinition) => void | | diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index 223b8c55a5fde..6025d24665901 100644 --- a/examples/embeddable_examples/kibana.json +++ b/examples/embeddable_examples/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["embeddable", "uiActions", "dashboard", "savedObjects"], + "requiredPlugins": ["embeddable", "uiActions", "savedObjects", "dashboard"], "optionalPlugins": [], "extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"], "requiredBundles": ["kibanaReact"] diff --git a/examples/embeddable_examples/public/book/book_embeddable.tsx b/examples/embeddable_examples/public/book/book_embeddable.tsx index 65ec22a2759e2..8d633a892ec6d 100644 --- a/examples/embeddable_examples/public/book/book_embeddable.tsx +++ b/examples/embeddable_examples/public/book/book_embeddable.tsx @@ -26,10 +26,10 @@ import { EmbeddableOutput, SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, + AttributeService, } from '../../../../src/plugins/embeddable/public'; import { BookSavedObjectAttributes } from '../../common'; import { BookEmbeddableComponent } from './book_component'; -import { AttributeService } from '../../../../src/plugins/dashboard/public'; export const BOOK_EMBEDDABLE = 'book'; export type BookEmbeddableInput = BookByValueInput | BookByReferenceInput; diff --git a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx index a535552282150..f569e2e8d154d 100644 --- a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx +++ b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx @@ -25,6 +25,8 @@ import { EmbeddableFactoryDefinition, IContainer, EmbeddableFactory, + EmbeddableStart, + AttributeService, } from '../../../../src/plugins/embeddable/public'; import { BookEmbeddable, @@ -38,11 +40,10 @@ import { SavedObjectsClientContract, SimpleSavedObject, } from '../../../../src/core/public'; -import { DashboardStart, AttributeService } from '../../../../src/plugins/dashboard/public'; import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public'; interface StartServices { - getAttributeService: DashboardStart['getAttributeService']; + getAttributeService: EmbeddableStart['getAttributeService']; openModal: OverlayStart['openModal']; savedObjectsClient: SavedObjectsClientContract; overlays: OverlayStart; diff --git a/examples/embeddable_examples/public/book/edit_book_action.tsx b/examples/embeddable_examples/public/book/edit_book_action.tsx index 77035b6887734..e2133a8d51ea2 100644 --- a/examples/embeddable_examples/public/book/edit_book_action.tsx +++ b/examples/embeddable_examples/public/book/edit_book_action.tsx @@ -22,7 +22,11 @@ import { i18n } from '@kbn/i18n'; import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common'; import { createAction } from '../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; -import { ViewMode, SavedObjectEmbeddableInput } from '../../../../src/plugins/embeddable/public'; +import { + ViewMode, + SavedObjectEmbeddableInput, + EmbeddableStart, +} from '../../../../src/plugins/embeddable/public'; import { BookEmbeddable, BOOK_EMBEDDABLE, @@ -30,13 +34,12 @@ import { BookByValueInput, } from './book_embeddable'; import { CreateEditBookComponent } from './create_edit_book_component'; -import { DashboardStart } from '../../../../src/plugins/dashboard/public'; import { OnSaveProps } from '../../../../src/plugins/saved_objects/public'; import { SavedObjectsClientContract } from '../../../../src/core/target/types/public/saved_objects'; interface StartServices { openModal: OverlayStart['openModal']; - getAttributeService: DashboardStart['getAttributeService']; + getAttributeService: EmbeddableStart['getAttributeService']; savedObjectsClient: SavedObjectsClientContract; } diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 6d1b119e741bd..9b9770e40611e 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -62,7 +62,6 @@ import { ACTION_ADD_BOOK_TO_LIBRARY, createAddBookToLibraryAction, } from './book/add_book_to_library_action'; -import { DashboardStart } from '../../../src/plugins/dashboard/public'; import { ACTION_UNLINK_BOOK_FROM_LIBRARY, createUnlinkBookFromLibraryAction, @@ -75,7 +74,6 @@ export interface EmbeddableExamplesSetupDependencies { export interface EmbeddableExamplesStartDependencies { embeddable: EmbeddableStart; - dashboard: DashboardStart; savedObjectsClient: SavedObjectsClient; } @@ -157,7 +155,7 @@ export class EmbeddableExamplesPlugin this.exampleEmbeddableFactories.getBookEmbeddableFactory = deps.embeddable.registerEmbeddableFactory( BOOK_EMBEDDABLE, new BookEmbeddableFactoryDefinition(async () => ({ - getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, + getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService, openModal: (await core.getStartServices())[0].overlays.openModal, savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, overlays: (await core.getStartServices())[0].overlays, @@ -165,7 +163,7 @@ export class EmbeddableExamplesPlugin ); const editBookAction = createEditBookAction(async () => ({ - getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, + getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService, openModal: (await core.getStartServices())[0].overlays.openModal, savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, })); diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index bf9a3b2b8a217..004b1a901bca9 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -45,7 +45,6 @@ export { export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; -export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/mocks.tsx b/src/plugins/dashboard/public/mocks.tsx index 07f29eca53042..be7460f3110a5 100644 --- a/src/plugins/dashboard/public/mocks.tsx +++ b/src/plugins/dashboard/public/mocks.tsx @@ -20,13 +20,10 @@ import { DashboardStart } from './plugin'; export type Start = jest.Mocked; -export { mockAttributeService } from './attribute_service/attribute_service.mock'; const createStartContract = (): DashboardStart => { // @ts-ignore - const startContract: DashboardStart = { - getAttributeService: jest.fn(), - }; + const startContract: DashboardStart = {}; return startContract; }; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 3325d193e56ed..2fda8008788e6 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -39,8 +39,6 @@ import { CONTEXT_MENU_TRIGGER, EmbeddableSetup, EmbeddableStart, - SavedObjectEmbeddableInput, - EmbeddableInput, PANEL_NOTIFICATION_TRIGGER, } from '../../embeddable/public'; import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from '../../data/public'; @@ -53,7 +51,6 @@ import { getSavedObjectFinder, SavedObjectLoader, SavedObjectsStart, - showSaveModal, } from '../../saved_objects/public'; import { ExitFullScreenButton as ExitFullScreenButtonUi, @@ -103,11 +100,6 @@ import { DashboardConstants } from './dashboard_constants'; import { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; import { UrlGeneratorState } from '../../share/public'; -import { AttributeService } from '.'; -import { - AttributeServiceOptions, - ATTRIBUTE_SERVICE_KEY, -} from './attribute_service/attribute_service'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -156,16 +148,6 @@ export interface DashboardStart { dashboardUrlGenerator?: DashboardUrlGenerator; dashboardFeatureFlagConfig: DashboardFeatureFlagConfig; DashboardContainerByValueRenderer: ReturnType; - getAttributeService: < - A extends { title: string }, - V extends EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: A } = EmbeddableInput & { - [ATTRIBUTE_SERVICE_KEY]: A; - }, - R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput - >( - type: string, - options: AttributeServiceOptions
- ) => AttributeService; } declare module '../../../plugins/ui_actions/public' { @@ -433,7 +415,6 @@ export class DashboardPlugin const { uiActions, data: { indexPatterns, search }, - embeddable, } = plugins; const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings); @@ -483,15 +464,6 @@ export class DashboardPlugin DashboardContainerByValueRenderer: createDashboardContainerByValueRenderer({ factory: dashboardContainerFactory, }), - getAttributeService: (type: string, options) => - new AttributeService( - type, - showSaveModal, - core.i18n.Context, - core.notifications.toasts, - options, - embeddable.getEmbeddableFactory - ), }; } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 7609f07d660bc..789353ca4abd7 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -77,6 +77,8 @@ export { EmbeddableRendererProps, } from './lib'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; + export { EnhancementRegistryDefinition } from './types'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.mock.tsx similarity index 89% rename from src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx rename to src/plugins/embeddable/public/lib/attribute_service/attribute_service.mock.tsx index 09d6f5b4f1e0d..9b08d52ed517c 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx +++ b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.mock.tsx @@ -17,11 +17,11 @@ * under the License. */ -import { EmbeddableInput, SavedObjectEmbeddableInput } from '../embeddable_plugin'; -import { coreMock } from '../../../../core/public/mocks'; +import { EmbeddableInput, SavedObjectEmbeddableInput } from '../index'; +import { coreMock } from '../../../../../core/public/mocks'; import { AttributeServiceOptions } from './attribute_service'; -import { CoreStart } from '../../../../core/public'; -import { AttributeService, ATTRIBUTE_SERVICE_KEY } from '..'; +import { CoreStart } from 'src/core/public'; +import { AttributeService, ATTRIBUTE_SERVICE_KEY } from './index'; export const mockAttributeService = < A extends { title: string }, diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.test.ts similarity index 98% rename from src/plugins/dashboard/public/attribute_service/attribute_service.test.ts rename to src/plugins/embeddable/public/lib/attribute_service/attribute_service.test.ts index d7368b299c411..868501adb9687 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts +++ b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.test.ts @@ -19,8 +19,8 @@ import { ATTRIBUTE_SERVICE_KEY } from './attribute_service'; import { mockAttributeService } from './attribute_service.mock'; -import { coreMock } from '../../../../core/public/mocks'; -import { OnSaveProps } from '../../../saved_objects/public/save_modal'; +import { coreMock } from '../../../../../core/public/mocks'; +import { OnSaveProps } from 'src/plugins/saved_objects/public/save_modal'; interface TestAttributes { title: string; diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.tsx similarity index 95% rename from src/plugins/dashboard/public/attribute_service/attribute_service.tsx rename to src/plugins/embeddable/public/lib/attribute_service/attribute_service.tsx index b46226ec4ab02..c4628ab7fbdba 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/embeddable/public/lib/attribute_service/attribute_service.tsx @@ -20,17 +20,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; +import { I18nStart, NotificationsStart } from 'src/core/public'; +import { SavedObjectSaveModal, OnSaveProps, SaveResult } from '../../../../saved_objects/public'; import { EmbeddableInput, SavedObjectEmbeddableInput, isSavedObjectEmbeddableInput, IEmbeddable, Container, - EmbeddableStart, EmbeddableFactoryNotFoundError, -} from '../embeddable_plugin'; -import { I18nStart, NotificationsStart } from '../../../../core/public'; -import { SavedObjectSaveModal, OnSaveProps, SaveResult } from '../../../saved_objects/public'; + EmbeddableFactory, +} from '../index'; /** * The attribute service is a shared, generic service that embeddables can use to provide the functionality @@ -66,7 +66,7 @@ export class AttributeService< private i18nContext: I18nStart['Context'], private toasts: NotificationsStart['toasts'], private options: AttributeServiceOptions, - getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'] + getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory ) { if (getEmbeddableFactory) { const factory = getEmbeddableFactory(this.type); @@ -113,7 +113,7 @@ export class AttributeService< return { ...originalInput } as RefType; } catch (error) { this.toasts.addDanger({ - title: i18n.translate('dashboard.attributeService.saveToLibraryError', { + title: i18n.translate('embeddableApi.attributeService.saveToLibraryError', { defaultMessage: `Panel was not saved to the library. Error: {errorMessage}`, values: { errorMessage: error.message, diff --git a/src/plugins/dashboard/public/attribute_service/index.ts b/src/plugins/embeddable/public/lib/attribute_service/index.ts similarity index 100% rename from src/plugins/dashboard/public/attribute_service/index.ts rename to src/plugins/embeddable/public/lib/attribute_service/index.ts diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index 26c10121adb3d..62063cb480338 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -39,6 +39,7 @@ import { dataPluginMock } from '../../data/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; +export { mockAttributeService } from './lib/attribute_service/attribute_service.mock'; export type Setup = jest.Mocked; export type Start = jest.Mocked; @@ -125,6 +126,7 @@ const createStartContract = (): Start => { EmbeddablePanel: jest.fn(), getEmbeddablePanel: jest.fn(), getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer), + getAttributeService: jest.fn(), }; return startContract; }; diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 00eb923c26662..aa4d66c43c9db 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { Subscription } from 'rxjs'; import { identity } from 'lodash'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../data/public'; -import { getSavedObjectFinder } from '../../saved_objects/public'; +import { getSavedObjectFinder, showSaveModal } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; import { @@ -47,6 +47,7 @@ import { defaultEmbeddableFactoryProvider, IEmbeddable, EmbeddablePanel, + SavedObjectEmbeddableInput, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; import { EmbeddableStateTransfer } from './lib/state_transfer'; @@ -56,6 +57,8 @@ import { telemetryBaseEmbeddableInput, } from '../common/lib/migrate_base_input'; import { PersistableState, SerializableState } from '../../kibana_utils/common'; +import { ATTRIBUTE_SERVICE_KEY, AttributeService } from './lib/attribute_service'; +import { AttributeServiceOptions } from './lib/attribute_service/attribute_service'; export interface EmbeddableSetupDependencies { data: DataPublicPluginSetup; @@ -93,6 +96,16 @@ export interface EmbeddableStart extends PersistableState { EmbeddablePanel: EmbeddablePanelHOC; getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC; getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer; + getAttributeService: < + A extends { title: string }, + V extends EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: A } = EmbeddableInput & { + [ATTRIBUTE_SERVICE_KEY]: A; + }, + R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput + >( + type: string, + options: AttributeServiceOptions + ) => AttributeService; } export type EmbeddablePanelHOC = React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; @@ -178,6 +191,15 @@ export class EmbeddablePublicPlugin implements Plugin + new AttributeService( + type, + showSaveModal, + core.i18n.Context, + core.notifications.toasts, + options, + this.getEmbeddableFactory + ), getStateTransfer: (history?: ScopedHistory) => { return history ? new EmbeddableStateTransfer(core.application.navigateToApp, history, this.appList) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index b01995ccaab08..6280d3a2e4a50 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -31,6 +31,7 @@ import { ExclusiveUnion } from '@elastic/eui'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { History } from 'history'; import { Href } from 'history'; +import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; import { ISearchOptions } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; @@ -119,6 +120,42 @@ export class AddPanelAction implements Action_3 { readonly type = "ACTION_ADD_PANEL"; } +// Warning: (ae-missing-release-tag) "ATTRIBUTE_SERVICE_KEY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const ATTRIBUTE_SERVICE_KEY = "attributes"; + +// Warning: (ae-missing-release-tag) "AttributeService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class AttributeService { + // Warning: (ae-forgotten-export) The symbol "AttributeServiceOptions" needs to be exported by the entry point index.d.ts + constructor(type: string, showSaveModal: (saveModal: React.ReactElement, I18nContext: I18nStart_2['Context']) => void, i18nContext: I18nStart_2['Context'], toasts: NotificationsStart_2['toasts'], options: AttributeServiceOptions, getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory); + // (undocumented) + getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType; + // (undocumented) + getInputAsRefType: (input: ValType | RefType, saveOptions?: { + showSaveModal: boolean; + saveModalTitle?: string | undefined; + } | { + title: string; + } | undefined) => Promise; + // (undocumented) + getInputAsValueType: (input: ValType | RefType) => Promise; + // (undocumented) + inputIsRefType: (input: ValType | RefType) => input is RefType; + // (undocumented) + unwrapAttributes(input: RefType | ValType): Promise; + // (undocumented) + wrapAttributes(newAttributes: SavedObjectAttributes, useRefType: boolean, input?: ValType | RefType): Promise>; +} + // Warning: (ae-missing-release-tag) "ChartActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -527,6 +564,14 @@ export interface EmbeddableStart extends PersistableState { // (undocumented) EmbeddablePanel: EmbeddablePanelHOC; // (undocumented) + getAttributeService: (type: string, options: AttributeServiceOptions) => AttributeService; + // (undocumented) getEmbeddableFactories: () => IterableIterator; // (undocumented) getEmbeddableFactory: = IEmbeddable>(embeddableFactoryId: string) => EmbeddableFactory | undefined; diff --git a/src/plugins/embeddable/server/plugin.ts b/src/plugins/embeddable/server/plugin.ts index f79c4b7620110..1e93561e4d063 100644 --- a/src/plugins/embeddable/server/plugin.ts +++ b/src/plugins/embeddable/server/plugin.ts @@ -35,6 +35,7 @@ import { SerializableState } from '../../kibana_utils/common'; import { EmbeddableInput } from '../common/types'; export interface EmbeddableSetup { + getAttributeService: any; registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void; registerEnhancement: (enhancement: EnhancementRegistryDefinition) => void; } diff --git a/src/plugins/embeddable/server/server.api.md b/src/plugins/embeddable/server/server.api.md index c4fad2917343b..d051793382ab7 100644 --- a/src/plugins/embeddable/server/server.api.md +++ b/src/plugins/embeddable/server/server.api.md @@ -23,6 +23,8 @@ export interface EmbeddableRegistryDefinition

void; // (undocumented) diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index bf36bb35d0563..688987b1104a1 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "inspector", "dashboard"], + "requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "inspector" ], "optionalPlugins": ["usageCollection"], "requiredBundles": ["kibanaUtils", "discover", "savedObjects"] } diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts index b27d24d980e8d..36f3f7d6ed22e 100644 --- a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts +++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts @@ -25,7 +25,11 @@ import { VisualizeByReferenceInput, VisualizeSavedObjectAttributes, } from './visualize_embeddable'; -import { IContainer, ErrorEmbeddable } from '../../../../plugins/embeddable/public'; +import { + IContainer, + ErrorEmbeddable, + AttributeService, +} from '../../../../plugins/embeddable/public'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getSavedVisualizationsLoader, @@ -37,7 +41,6 @@ import { import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; import { SavedVisualizationsLoader } from '../saved_visualizations'; -import { AttributeService } from '../../../dashboard/public'; export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDeps) => async ( vis: Vis, diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index fe8a9adff4052..a810b4b65528f 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -38,6 +38,7 @@ import { Adapters, SavedObjectEmbeddableInput, ReferenceOrValueEmbeddable, + AttributeService, } from '../../../../plugins/embeddable/public'; import { IExpressionLoaderParams, @@ -51,7 +52,6 @@ import { VIS_EVENT_TO_TRIGGER } from './events'; import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; import { TriggerId } from '../../../ui_actions/public'; import { SavedObjectAttributes } from '../../../../core/types'; -import { AttributeService } from '../../../dashboard/public'; import { SavedVisualizationsLoader } from '../saved_visualizations'; import { VisSavedObject } from '../types'; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 87f78f5639ff0..4b851da6be70e 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -26,6 +26,7 @@ import { EmbeddableOutput, ErrorEmbeddable, IContainer, + AttributeService, } from '../../../embeddable/public'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { @@ -50,7 +51,6 @@ import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_obje import { StartServicesGetter } from '../../../kibana_utils/public'; import { VisualizationsStartDeps } from '../plugin'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; -import { AttributeService } from '../../../dashboard/public'; import { checkForDuplicateTitle } from '../../../saved_objects/public'; interface VisualizationAttributes extends SavedObjectAttributes { @@ -126,7 +126,7 @@ export class VisualizeEmbeddableFactory if (!this.attributeService) { this.attributeService = await this.deps .start() - .plugins.dashboard.getAttributeService< + .plugins.embeddable.getAttributeService< VisualizeSavedObjectAttributes, VisualizeByValueInput, VisualizeByReferenceInput diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 37a9972983421..be7629ef41145 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -112,7 +112,7 @@ export interface VisualizationsStartDeps { uiActions: UiActionsStart; application: ApplicationStart; dashboard: DashboardStart; - getAttributeService: DashboardStart['getAttributeService']; + getAttributeService: EmbeddableStart['getAttributeService']; savedObjectsClient: SavedObjectsClientContract; } diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 46a0b56a03ec5..100527accd1b9 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -12,9 +12,10 @@ "visualizations", "dashboard", "charts", - "uiActions" + "uiActions", + "embeddable" ], - "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "globalSearch"], + "optionalPlugins": ["usageCollection", "taskManager", "globalSearch"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 70f3f767930ec..e9e6bf43d9f1b 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -36,7 +36,7 @@ import { LensByReferenceInput, } from '../editor_frame_service/embeddable/embeddable'; import { SavedObjectReference } from '../../../../../src/core/types'; -import { mockAttributeService } from '../../../../../src/plugins/dashboard/public/mocks'; +import { mockAttributeService } from '../../../../../src/plugins/embeddable/public/mocks'; import { LensAttributeService } from '../lens_attribute_service'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 4966fce590542..d91865c21a2a6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -25,7 +25,7 @@ import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks' import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks'; import { IBasePath } from '../../../../../../src/core/public'; -import { AttributeService } from '../../../../../../src/plugins/dashboard/public'; +import { AttributeService } from '../../../../../../src/plugins/embeddable/public'; import { LensAttributeService } from '../../lens_attribute_service'; import { OnSaveProps } from '../../../../../../src/plugins/saved_objects/public/save_modal'; diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index 9e1ce535d6675..9f6feee2877a8 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -6,7 +6,7 @@ import { CoreStart } from '../../../../src/core/public'; import { LensPluginStartDependencies } from './plugin'; -import { AttributeService } from '../../../../src/plugins/dashboard/public'; +import { AttributeService } from '../../../../src/plugins/embeddable/public'; import { LensSavedObjectAttributes, LensByValueInput, @@ -26,7 +26,7 @@ export function getLensAttributeService( startDependencies: LensPluginStartDependencies ): LensAttributeService { const savedObjectStore = new SavedObjectIndexStore(core.savedObjects.client); - return startDependencies.dashboard.getAttributeService< + return startDependencies.embeddable.getAttributeService< LensSavedObjectAttributes, LensByValueInput, LensByReferenceInput diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 90b0f0a2bde84..ef84ca2698ee6 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -58,7 +58,7 @@ export interface LensPluginStartDependencies { navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; dashboard: DashboardStart; - embeddable?: EmbeddableStart; + embeddable: EmbeddableStart; } export class LensPlugin { private datatableVisualization: DatatableVisualization; From 3bd95d42e83962eea776b3415a8d14404f69515a Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 12 Oct 2020 14:56:02 +0100 Subject: [PATCH 7/8] sort alert instance by status in UI (#80103) Sorts Alert instances by their status to ensure Active come first. --- .../alerts/common/alert_instance_summary.ts | 4 +-- .../components/alert_instances.test.tsx | 16 ++++++++---- .../components/alert_instances.tsx | 25 +++++++++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/alerts/common/alert_instance_summary.ts b/x-pack/plugins/alerts/common/alert_instance_summary.ts index 333db3ccda963..08c3b2fc2c241 100644 --- a/x-pack/plugins/alerts/common/alert_instance_summary.ts +++ b/x-pack/plugins/alerts/common/alert_instance_summary.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -type AlertStatusValues = 'OK' | 'Active' | 'Error'; -type AlertInstanceStatusValues = 'OK' | 'Active'; +export type AlertStatusValues = 'OK' | 'Active' | 'Error'; +export type AlertInstanceStatusValues = 'OK' | 'Active'; export interface AlertInstanceSummary { id: string; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index 2c1020ff1d5b3..e1287d299b6e9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -41,24 +41,26 @@ describe('alert_instances', () => { muted: false, }, second_instance: { - status: 'OK', + status: 'Active', muted: false, }, }, }); const instances: AlertInstanceListItem[] = [ + // active first alertInstanceToListItem( fakeNow.getTime(), alert, - 'first_instance', - alertInstanceSummary.instances.first_instance + 'second_instance', + alertInstanceSummary.instances.second_instance ), + // ok second alertInstanceToListItem( fakeNow.getTime(), alert, - 'second_instance', - alertInstanceSummary.instances.second_instance + 'first_instance', + alertInstanceSummary.instances.first_instance ), ]; @@ -176,6 +178,7 @@ describe('alertInstanceToListItem', () => { instance: 'id', status: { label: 'Active', healthColor: 'primary' }, start, + sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), isMuted: false, }); @@ -196,6 +199,7 @@ describe('alertInstanceToListItem', () => { instance: 'id', status: { label: 'Active', healthColor: 'primary' }, start, + sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), isMuted: true, }); @@ -213,6 +217,7 @@ describe('alertInstanceToListItem', () => { status: { label: 'Active', healthColor: 'primary' }, start: undefined, duration: 0, + sortPriority: 0, isMuted: false, }); }); @@ -230,6 +235,7 @@ describe('alertInstanceToListItem', () => { status: { label: 'OK', healthColor: 'subdued' }, start: undefined, duration: 0, + sortPriority: 1, isMuted: true, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 44d65eafc2412..0648f34927db3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -11,6 +11,7 @@ import { EuiBasicTable, EuiHealth, EuiSpacer, EuiSwitch } from '@elastic/eui'; // @ts-ignore import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '@elastic/eui/lib/services'; import { padStart, chunk } from 'lodash'; +import { AlertInstanceStatusValues } from '../../../../../../alerts/common'; import { Alert, AlertInstanceSummary, AlertInstanceStatus, Pagination } from '../../../../types'; import { ComponentOpts as AlertApis, @@ -124,11 +125,12 @@ export function AlertInstances({ size: DEFAULT_SEARCH_PAGE_SIZE, }); - const alertInstances = Object.entries( - alertInstanceSummary.instances - ).map(([instanceId, instance]) => - alertInstanceToListItem(durationEpoch, alert, instanceId, instance) - ); + const alertInstances = Object.entries(alertInstanceSummary.instances) + .map(([instanceId, instance]) => + alertInstanceToListItem(durationEpoch, alert, instanceId, instance) + ) + .sort((leftInstance, rightInstance) => leftInstance.sortPriority - rightInstance.sortPriority); + const pageOfAlertInstances = getPage(alertInstances, pagination); const onMuteAction = async (instance: AlertInstanceListItem) => { @@ -185,6 +187,7 @@ export interface AlertInstanceListItem { start?: Date; duration: number; isMuted: boolean; + sortPriority: number; } const ACTIVE_LABEL = i18n.translate( @@ -210,11 +213,23 @@ export function alertInstanceToListItem( : { label: INACTIVE_LABEL, healthColor: 'subdued' }; const start = instance?.activeStartDate ? new Date(instance.activeStartDate) : undefined; const duration = start ? durationEpoch - start.valueOf() : 0; + const sortPriority = getSortPriorityByStatus(instance?.status); return { instance: instanceId, status, start, duration, isMuted, + sortPriority, }; } + +function getSortPriorityByStatus(status?: AlertInstanceStatusValues): number { + switch (status) { + case 'Active': + return 0; + case 'OK': + return 1; + } + return 2; +} From 0bba32e572f8be497c7baf6413c526dbb295af78 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 12 Oct 2020 08:25:22 -0600 Subject: [PATCH 8/8] [Security Solution] [Cases] Improve jest tests on connector switcher feature (#79922) --- .../public/cases/components/settings/card.tsx | 2 +- .../components/settings/jira/__mocks__/api.ts | 9 +- .../components/settings/jira/fields.test.tsx | 118 +++++++++++++++++- .../cases/components/settings/jira/fields.tsx | 1 + .../settings/jira/search_issues.tsx | 2 +- .../settings/jira/use_get_issues.test.tsx | 79 ++++++++++++ .../jira/use_get_single_issue.test.tsx | 79 ++++++++++++ .../public/cases/components/settings/mock.ts | 7 ++ .../settings/servicenow/fields.test.tsx | 64 ++++++++++ .../components/settings/servicenow/fields.tsx | 21 ++-- 10 files changed, 362 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_issues.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_single_issue.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx index 60b471b1a99c4..344ca88f5ab37 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx @@ -35,7 +35,7 @@ const ConnectorCardDisplay: React.FC = ({ {listItems.length > 0 && listItems.map((item, i) => ( - + {`${item.title}: `} {item.description} diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/__mocks__/api.ts b/x-pack/plugins/security_solution/public/cases/components/settings/jira/__mocks__/api.ts index f6d404b9b08b1..d6f18450e2130 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/jira/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/__mocks__/api.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GetIssueTypesProps, GetFieldsByIssueTypeProps } from '../api'; -import { IssueTypes, Fields } from '../types'; +import { GetIssueTypesProps, GetFieldsByIssueTypeProps, GetIssueTypeProps } from '../api'; +import { IssueTypes, Fields, Issues, Issue } from '../types'; +import { issues } from '../../mock'; const issueTypes = [ { @@ -31,6 +32,10 @@ const fieldsByIssueType = { }, }; +export const getIssue = async (props: GetIssueTypeProps): Promise<{ data: Issue }> => + Promise.resolve({ data: issues[0] }); +export const getIssues = async (props: GetIssueTypesProps): Promise<{ data: Issues }> => + Promise.resolve({ data: issues }); export const getIssueTypes = async (props: GetIssueTypesProps): Promise<{ data: IssueTypes }> => Promise.resolve({ data: issueTypes }); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.test.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.test.tsx index b476b88ea3db4..c4f67f860fecc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.test.tsx @@ -8,19 +8,27 @@ import React from 'react'; import { mount } from 'enzyme'; import { omit } from 'lodash/fp'; -import { connector } from '../mock'; +import { connector, issues } from '../mock'; import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; import Fields from './fields'; +import { waitFor } from '@testing-library/dom'; +import { useGetSingleIssue } from './use_get_single_issue'; +import { useGetIssues } from './use_get_issues'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; jest.mock('../../../../common/lib/kibana'); jest.mock('./use_get_issue_types'); jest.mock('./use_get_fields_by_issue_type'); +jest.mock('./use_get_single_issue'); +jest.mock('./use_get_issues'); const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; +const useGetSingleIssueMock = useGetSingleIssue as jest.Mock; +const useGetIssuesMock = useGetIssues as jest.Mock; -describe('JiraParamsFields renders', () => { +describe('Jira Fields', () => { const useGetIssueTypesResponse = { isLoading: false, issueTypes: [ @@ -57,21 +65,32 @@ describe('JiraParamsFields renders', () => { }, }; + const useGetSingleIssueResponse = { + isLoading: false, + issue: { title: 'Parent Task', key: 'parentId' }, + }; + const fields = { issueType: '10006', priority: 'High', parent: null, }; + const useGetIssuesResponse = { + isLoading: false, + issues, + }; + const onChange = jest.fn(); beforeEach(() => { useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); + useGetSingleIssueMock.mockReturnValue(useGetSingleIssueResponse); jest.clearAllMocks(); }); - test('all params fields are rendered', () => { + test('all params fields are rendered - isEdit: true', () => { const wrapper = mount(); expect(wrapper.find('[data-test-subj="issueTypeSelect"]').first().prop('value')).toStrictEqual( '10006' @@ -79,6 +98,71 @@ describe('JiraParamsFields renders', () => { expect(wrapper.find('[data-test-subj="prioritySelect"]').first().prop('value')).toStrictEqual( 'High' ); + expect(wrapper.find('[data-test-subj="search-parent-issues"]').first().exists()).toBeFalsy(); + }); + + test('all params fields are rendered - isEdit: false', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(0).text()).toEqual( + 'Issue type: Task' + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(1).text()).toEqual( + 'Parent issue: Parent Task' + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(2).text()).toEqual( + 'Priority: High' + ); + }); + + test('it sets parent correctly', async () => { + useGetFieldsByIssueTypeMock.mockReturnValue({ + ...useGetFieldsByIssueTypeResponse, + fields: { + ...useGetFieldsByIssueTypeResponse.fields, + parent: {}, + }, + }); + useGetIssuesMock.mockReturnValue(useGetIssuesResponse); + const wrapper = mount(); + + await waitFor(() => + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ label: 'parentId', value: 'parentId' }]) + ); + wrapper.update(); + expect(onChange).toHaveBeenCalledWith({ + issueType: '10006', + parent: 'parentId', + priority: 'High', + }); + }); + test('it searches parent correctly', async () => { + useGetFieldsByIssueTypeMock.mockReturnValue({ + ...useGetFieldsByIssueTypeResponse, + fields: { + ...useGetFieldsByIssueTypeResponse.fields, + parent: {}, + }, + }); + useGetSingleIssueMock.mockReturnValue({ useGetSingleIssueResponse, issue: null }); + useGetIssuesMock.mockReturnValue(useGetIssuesResponse); + const wrapper = mount(); + + await waitFor(() => + ((wrapper.find(EuiComboBox).props() as unknown) as { + onSearchChange: (a: string) => void; + }).onSearchChange('womanId') + ); + wrapper.update(); + expect(useGetIssuesMock.mock.calls[2][0].query).toEqual('womanId'); }); test('it disabled the fields when loading issue types', () => { @@ -116,7 +200,7 @@ describe('JiraParamsFields renders', () => { expect(wrapper.find('[data-test-subj="prioritySelect"]').first().exists()).toBeFalsy(); }); - test('it sets issue type correctly', async () => { + test('it sets issue type correctly', () => { const wrapper = mount(); wrapper @@ -129,7 +213,29 @@ describe('JiraParamsFields renders', () => { expect(onChange).toHaveBeenCalledWith({ issueType: '10007', parent: null, priority: null }); }); - test('it sets priority correctly', async () => { + test('it sets issue type when it comes as null', () => { + const wrapper = mount( + + ); + expect(wrapper.find('select[data-test-subj="issueTypeSelect"]').first().props().value).toEqual( + '10006' + ); + }); + + test('it sets issue type when it comes as unknown value', () => { + const wrapper = mount( + + ); + expect(wrapper.find('select[data-test-subj="issueTypeSelect"]').first().props().value).toEqual( + '10006' + ); + }); + + test('it sets priority correctly', () => { const wrapper = mount(); wrapper @@ -142,7 +248,7 @@ describe('JiraParamsFields renders', () => { expect(onChange).toHaveBeenCalledWith({ issueType: '10006', parent: null, priority: '2' }); }); - test('it resets priority when changing issue type', async () => { + test('it resets priority when changing issue type', () => { const wrapper = mount(); wrapper .find('select[data-test-subj="issueTypeSelect"]') diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.tsx index b19c1bfdd3f03..08d4da617ed03 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/fields.tsx @@ -136,6 +136,7 @@ const JiraSettingFieldsComponent: React.FunctionComponent diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/search_issues.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/jira/search_issues.tsx index 367ed2001bd4a..0024930a4c619 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/jira/search_issues.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/search_issues.tsx @@ -80,7 +80,7 @@ const SearchIssuesComponent: React.FC = ({ selectedValue, actionConnector singleSelection fullWidth placeholder={inputPlaceholder} - data-test-sub={'search-parent-issues'} + data-test-subj={'search-parent-issues'} aria-label={i18n.SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL} options={options} isLoading={isLoadingIssues || isLoadingSingleIssue} diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_issues.test.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_issues.test.tsx new file mode 100644 index 0000000000000..84b5fa16bb7db --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_issues.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; + +import { useKibana } from '../../../../common/lib/kibana'; +import { connector as actionConnector, issues } from '../mock'; +import { useGetIssues, UseGetIssues } from './use_get_issues'; +import * as api from './api'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('./api'); + +const useKibanaMock = useKibana as jest.Mocked; + +describe('useGetIssues', () => { + const { http, notifications } = useKibanaMock().services; + beforeEach(() => jest.clearAllMocks()); + + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetIssues({ + http, + toastNotifications: notifications.toasts, + actionConnector, + query: null, + }) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ isLoading: false, issues: [] }); + }); + }); + + test('fetch issues', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetIssues({ + http, + toastNotifications: notifications.toasts, + actionConnector, + query: 'Task', + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + issues, + }); + }); + }); + + test('unhappy path', async () => { + const spyOnGetCaseConfigure = jest.spyOn(api, 'getIssues'); + spyOnGetCaseConfigure.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetIssues({ + http, + toastNotifications: notifications.toasts, + actionConnector, + query: 'oh no', + }) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ isLoading: false, issues: [] }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_single_issue.test.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_single_issue.test.tsx new file mode 100644 index 0000000000000..ae8e5a3f0b652 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/settings/jira/use_get_single_issue.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; + +import { useKibana } from '../../../../common/lib/kibana'; +import { connector as actionConnector, issues } from '../mock'; +import { useGetSingleIssue, UseGetSingleIssue } from './use_get_single_issue'; +import * as api from './api'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('./api'); + +const useKibanaMock = useKibana as jest.Mocked; + +describe('useGetSingleIssue', () => { + const { http, notifications } = useKibanaMock().services; + beforeEach(() => jest.clearAllMocks()); + + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetSingleIssue({ + http, + toastNotifications: notifications.toasts, + actionConnector, + id: null, + }) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ isLoading: false, issue: null }); + }); + }); + + test('fetch issues', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetSingleIssue({ + http, + toastNotifications: notifications.toasts, + actionConnector, + id: '123', + }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + isLoading: false, + issue: issues[0], + }); + }); + }); + + test('unhappy path', async () => { + const spyOnGetCaseConfigure = jest.spyOn(api, 'getIssue'); + spyOnGetCaseConfigure.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetSingleIssue({ + http, + toastNotifications: notifications.toasts, + actionConnector, + id: '123', + }) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ isLoading: false, issue: null }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/mock.ts b/x-pack/plugins/security_solution/public/cases/components/settings/mock.ts index 938335146dd9f..9b94e287c9edd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/components/settings/mock.ts @@ -11,3 +11,10 @@ export const connector = { config: {}, isPreconfigured: false, }; +export const issues = [ + { id: 'personId', title: 'Person Task', key: 'personKey' }, + { id: 'womanId', title: 'Woman Task', key: 'womanKey' }, + { id: 'manId', title: 'Man Task', key: 'manKey' }, + { id: 'cameraId', title: 'Camera Task', key: 'cameraKey' }, + { id: 'tvId', title: 'TV Task', key: 'tvKey' }, +]; diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.test.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.test.tsx new file mode 100644 index 0000000000000..3a49f03155167 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import Fields from './fields'; +import { connector } from '../mock'; +import { waitFor } from '@testing-library/dom'; +import { EuiSelect } from '@elastic/eui'; + +describe('ServiceNow Fields', () => { + const fields = { severity: '1', urgency: '2', impact: '3' }; + const onChange = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + }); + it('all params fields are rendered - isEdit: true', () => { + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="severitySelect"]').first().prop('value')).toEqual('1'); + expect(wrapper.find('[data-test-subj="urgencySelect"]').first().prop('value')).toEqual('2'); + expect(wrapper.find('[data-test-subj="impactSelect"]').first().prop('value')).toEqual('3'); + }); + + test('all params fields are rendered - isEdit: false', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(0).text()).toEqual( + 'Urgency: Medium' + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(1).text()).toEqual( + 'Severity: High' + ); + expect(wrapper.find('[data-test-subj="card-list-item"]').at(2).text()).toEqual('Impact: Low'); + }); + + describe('onChange calls', () => { + const wrapper = mount(); + + expect(onChange).toHaveBeenCalledWith(fields); + + const testers = ['severity', 'urgency', 'impact']; + testers.forEach((subj) => + test(`${subj.toUpperCase()}`, async () => { + await waitFor(() => { + const select = wrapper.find(EuiSelect).filter(`[data-test-subj="${subj}Select"]`)!; + select.prop('onChange')!({ + target: { + value: '9', + }, + } as React.ChangeEvent); + }); + wrapper.update(); + expect(onChange).toHaveBeenCalledWith({ + ...fields, + [subj]: '9', + }); + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.tsx index 34e41a6cee060..8b2e24628a760 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/servicenow/fields.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { EuiFormRow, EuiSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import * as i18n from './translations'; @@ -68,6 +68,13 @@ const ServiceNowSettingFieldsComponent: React.FunctionComponent { + onChange({ ...fields, [key]: value }); + }, + [fields, onChange] + ); + return isEdit ? ( @@ -77,9 +84,7 @@ const ServiceNowSettingFieldsComponent: React.FunctionComponent { - onChange({ ...fields, urgency: e.target.value }); - }} + onChange={(e) => onChangeCb('urgency', e.target.value)} /> @@ -92,9 +97,7 @@ const ServiceNowSettingFieldsComponent: React.FunctionComponent { - onChange({ ...fields, severity: e.target.value }); - }} + onChange={(e) => onChangeCb('severity', e.target.value)} /> @@ -106,9 +109,7 @@ const ServiceNowSettingFieldsComponent: React.FunctionComponent { - onChange({ ...fields, impact: e.target.value }); - }} + onChange={(e) => onChangeCb('impact', e.target.value)} />