From dce3124adc312bca796c3ca1d5bac1540bf3ca22 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Mon, 5 Dec 2022 09:26:01 -0700 Subject: [PATCH 1/6] Convert filter badge to icon notification + show only in edit mode --- .../actions/filters_notification_badge.tsx | 30 +++++++++++++++---- .../public/application/actions/index.ts | 14 ++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx index 6dbe7d5dbe3c9..f19c911d386c0 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx @@ -8,13 +8,14 @@ import React from 'react'; -import { EditPanelAction, isFilterableEmbeddable } from '@kbn/embeddable-plugin/public'; -import { type AggregateQuery } from '@kbn/es-query'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import type { ApplicationStart } from '@kbn/core/public'; -import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { EditPanelAction, isFilterableEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; +import { reactToUiComponent, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import { type AggregateQuery } from '@kbn/es-query'; +import { EuiButtonIcon } from '@elastic/eui'; import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; @@ -46,6 +47,22 @@ export class FiltersNotificationBadge implements Action = ({ + context, + }: { + context: FiltersNotificationActionContext; + }) => { + const { embeddable } = context; + return ( + this.execute(context)} + /> + ); + }; + + public readonly MenuItem = reactToUiComponent(this.FiltersNotification); + public getDisplayName({ embeddable }: FiltersNotificationActionContext) { if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { throw new IncompatibleActionError(); @@ -65,6 +82,7 @@ export class FiltersNotificationBadge implements Action Date: Mon, 5 Dec 2022 10:33:26 -0700 Subject: [PATCH 2/6] Fix Jest tests --- .../filters_notification_badge.test.tsx | 40 +++++++++---------- .../actions/filters_notification_badge.tsx | 8 +--- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx index 3b3fb5dde0497..06b68f13a4edb 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import { - IContainer, - ErrorEmbeddable, - isErrorEmbeddable, - FilterableEmbeddable, -} from '@kbn/embeddable-plugin/public'; +import { ErrorEmbeddable, isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { ContactCardEmbeddable, CONTACT_CARD_EMBEDDABLE, @@ -32,9 +27,6 @@ pluginServices.getServices().embeddable.getEmbeddableFactory = jest .fn() .mockReturnValue(mockEmbeddableFactory); -let action: FiltersNotificationBadge; -let container: DashboardContainer; -let embeddable: ContactCardEmbeddable & FilterableEmbeddable; const mockGetFilters = jest.fn(async () => [] as Filter[]); const mockGetQuery = jest.fn(async () => undefined as Query | AggregateQuery | undefined); @@ -58,46 +50,54 @@ const getMockPhraseFilter = (key: string, value: string) => { }; }; -beforeEach(async () => { - container = new DashboardContainer(getSampleDashboardInput()); - +const buildEmbeddable = async (input?: Partial) => { + const container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, ContactCardEmbeddable >(CONTACT_CARD_EMBEDDABLE, { firstName: 'Kibanana', + viewMode: ViewMode.EDIT, + ...input, }); if (isErrorEmbeddable(contactCardEmbeddable)) { throw new Error('Failed to create embeddable'); } - action = new FiltersNotificationBadge(); - embeddable = embeddablePluginMock.mockFilterableEmbeddable(contactCardEmbeddable, { + const embeddable = embeddablePluginMock.mockFilterableEmbeddable(contactCardEmbeddable, { getFilters: () => mockGetFilters(), getQuery: () => mockGetQuery(), }); -}); + + return embeddable; +}; + +const action = new FiltersNotificationBadge(); test('Badge is incompatible with Error Embeddables', async () => { - const errorEmbeddable = new ErrorEmbeddable( - 'Wow what an awful error', - { id: ' 404' }, - embeddable.getRoot() as IContainer - ); + const errorEmbeddable = new ErrorEmbeddable('Wow what an awful error', { id: ' 404' }); expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false); }); test('Badge is not shown when panel has no app-level filters or queries', async () => { + const embeddable = await buildEmbeddable(); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Badge is shown when panel has at least one app-level filter', async () => { + const embeddable = await buildEmbeddable(); mockGetFilters.mockResolvedValue([getMockPhraseFilter('fieldName', 'someValue')] as Filter[]); expect(await action.isCompatible({ embeddable })).toBe(true); }); test('Badge is shown when panel has at least one app-level query', async () => { + const embeddable = await buildEmbeddable(); mockGetQuery.mockResolvedValue({ sql: 'SELECT * FROM test_dataview' } as AggregateQuery); expect(await action.isCompatible({ embeddable })).toBe(true); }); + +test('Badge is not shown in view mode', async () => { + const embeddable = await buildEmbeddable({ viewMode: ViewMode.VIEW }); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx index f19c911d386c0..1d63587f1c963 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx @@ -47,11 +47,7 @@ export class FiltersNotificationBadge implements Action = ({ - context, - }: { - context: FiltersNotificationActionContext; - }) => { + private FilterIconButton = ({ context }: { context: FiltersNotificationActionContext }) => { const { embeddable } = context; return ( Date: Mon, 5 Dec 2022 11:08:00 -0700 Subject: [PATCH 3/6] Fix extra axe failure --- .../public/application/actions/filters_notification_badge.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx index 1d63587f1c963..134b34f2949e3 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx @@ -53,6 +53,7 @@ export class FiltersNotificationBadge implements Action this.execute(context)} + aria-label={this.displayName} /> ); }; From 496b3443c1aff35fd1ffeb55ce65a95e471b5920 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Mon, 5 Dec 2022 11:52:21 -0700 Subject: [PATCH 4/6] Fix theme of icon --- .../actions/filters_notification_badge.tsx | 19 ++++--- .../actions/filters_notification_modal.tsx | 51 +++++++++---------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx index 134b34f2949e3..f2b8b36a11e81 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx @@ -10,7 +10,11 @@ import React from 'react'; import { EditPanelAction, isFilterableEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { reactToUiComponent, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { + KibanaThemeProvider, + reactToUiComponent, + toMountPoint, +} from '@kbn/kibana-react-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import type { ApplicationStart } from '@kbn/core/public'; @@ -50,11 +54,14 @@ export class FiltersNotificationBadge implements Action { const { embeddable } = context; return ( - this.execute(context)} - aria-label={this.displayName} - /> + + await this.execute(context)} + aria-label={this.displayName} + /> + ); }; diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx index b188674a85b69..e61a3d5a7cbaa 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx @@ -84,7 +84,6 @@ export function FiltersNotificationModal({ }); const dataViewList: DataView[] = (embeddable.getRoot() as DashboardContainer)?.getAllDataViews(); - const viewMode = embeddable.getInput().viewMode; return ( <> @@ -131,32 +130,30 @@ export function FiltersNotificationModal({ )} - {viewMode !== ViewMode.VIEW && ( - - - - - {dashboardFilterNotificationBadge.getCloseButtonTitle()} - - - - { - onClose(); - editPanelAction.execute(context); - }} - fill - > - {dashboardFilterNotificationBadge.getEditButtonTitle()} - - - - - )} + + + + + {dashboardFilterNotificationBadge.getCloseButtonTitle()} + + + + { + onClose(); + editPanelAction.execute(context); + }} + fill + > + {dashboardFilterNotificationBadge.getEditButtonTitle()} + + + + ); } From 80106ef9fb5ac2aec281b3984d9d8f9908c8600d Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Mon, 5 Dec 2022 15:06:54 -0700 Subject: [PATCH 5/6] Make filter action a popover instead of a modal --- ...x => filters_notification_action.test.tsx} | 4 +- ...ge.tsx => filters_notification_action.tsx} | 87 +++------- .../actions/filters_notification_modal.tsx | 159 ------------------ ... => filters_notification_popover.test.tsx} | 53 +++--- .../actions/filters_notification_popover.tsx | 83 +++++++++ .../filters_notification_popover_contents.tsx | 103 ++++++++++++ .../public/application/actions/index.ts | 8 +- .../dashboard/public/dashboard_strings.ts | 2 +- 8 files changed, 238 insertions(+), 261 deletions(-) rename src/plugins/dashboard/public/application/actions/{filters_notification_badge.test.tsx => filters_notification_action.test.tsx} (96%) rename src/plugins/dashboard/public/application/actions/{filters_notification_badge.tsx => filters_notification_action.tsx} (68%) delete mode 100644 src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx rename src/plugins/dashboard/public/application/actions/{filters_notification_modal.test.tsx => filters_notification_popover.test.tsx} (63%) create mode 100644 src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx create mode 100644 src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx similarity index 96% rename from src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx index 06b68f13a4edb..be9dc25f69fb9 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx @@ -20,7 +20,7 @@ import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { getSampleDashboardInput } from '../test_helpers'; import { pluginServices } from '../../services/plugin_services'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { FiltersNotificationBadge } from './filters_notification_badge'; +import { FiltersNotificationAction } from './filters_notification_action'; const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); pluginServices.getServices().embeddable.getEmbeddableFactory = jest @@ -73,7 +73,7 @@ const buildEmbeddable = async (input?: Partial) => { return embeddable; }; -const action = new FiltersNotificationBadge(); +const action = new FiltersNotificationAction(); test('Badge is incompatible with Error Embeddables', async () => { const errorEmbeddable = new ErrorEmbeddable('Wow what an awful error', { id: ' 404' }); diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx similarity index 68% rename from src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_action.tsx index f2b8b36a11e81..d2cba311994e3 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx @@ -10,18 +10,14 @@ import React from 'react'; import { EditPanelAction, isFilterableEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { - KibanaThemeProvider, - reactToUiComponent, - toMountPoint, -} from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import type { ApplicationStart } from '@kbn/core/public'; import { type AggregateQuery } from '@kbn/es-query'; -import { EuiButtonIcon } from '@elastic/eui'; -import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; +import { FiltersNotificationPopover } from './filters_notification_popover'; +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION'; @@ -30,37 +26,49 @@ export interface FiltersNotificationActionContext { embeddable: IEmbeddable; } -export class FiltersNotificationBadge implements Action { +export class FiltersNotificationAction implements Action { public readonly id = BADGE_FILTERS_NOTIFICATION; public readonly type = BADGE_FILTERS_NOTIFICATION; public readonly order = 2; - private displayName = dashboardFilterNotificationBadge.getDisplayName(); + private displayName = dashboardFilterNotificationAction.getDisplayName(); private icon = 'filter'; private applicationService; private embeddableService; private settingsService; - private openModal; constructor() { ({ application: this.applicationService, embeddable: this.embeddableService, - overlays: { openModal: this.openModal }, settings: this.settingsService, } = pluginServices.getServices()); } private FilterIconButton = ({ context }: { context: FiltersNotificationActionContext }) => { const { embeddable } = context; + + const editPanelAction = new EditPanelAction( + this.embeddableService.getEmbeddableFactory, + this.applicationService as unknown as ApplicationStart, + this.embeddableService.getStateTransfer() + ); + + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings: this.settingsService.uiSettings, + }); + return ( - await this.execute(context)} - aria-label={this.displayName} - /> + + + ); }; @@ -102,48 +110,5 @@ export class FiltersNotificationBadge implements Action { - const { embeddable } = context; - - const isCompatible = await this.isCompatible({ embeddable }); - if (!isCompatible || !isFilterableEmbeddable(embeddable)) { - throw new IncompatibleActionError(); - } - - const { - uiSettings, - theme: { theme$ }, - } = this.settingsService; - const { getEmbeddableFactory, getStateTransfer } = this.embeddableService; - - const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ - uiSettings, - }); - const editPanelAction = new EditPanelAction( - getEmbeddableFactory, - this.applicationService as unknown as ApplicationStart, - getStateTransfer() - ); - const FiltersNotificationModal = await import('./filters_notification_modal').then( - (m) => m.FiltersNotificationModal - ); - - const session = this.openModal( - toMountPoint( - - session.close()} - /> - , - { theme$ } - ), - { - 'data-test-subj': 'filtersNotificationModal', - } - ); - }; + public execute = async () => {}; } diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx deleted file mode 100644 index e61a3d5a7cbaa..0000000000000 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useState } from 'react'; -import useMount from 'react-use/lib/useMount'; - -import { - EuiButton, - EuiButtonEmpty, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiLoadingContent, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import { css } from '@emotion/react'; -import { DataView } from '@kbn/data-views-plugin/public'; -import { - EditPanelAction, - FilterableEmbeddable, - IEmbeddable, - ViewMode, -} from '@kbn/embeddable-plugin/public'; -import { - type AggregateQuery, - type Filter, - getAggregateQueryMode, - isOfQueryType, -} from '@kbn/es-query'; -import { FilterItems } from '@kbn/unified-search-plugin/public'; - -import { FiltersNotificationActionContext } from './filters_notification_badge'; -import { DashboardContainer } from '../embeddable'; -import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; - -export interface FiltersNotificationProps { - context: FiltersNotificationActionContext; - displayName: string; - id: string; - editPanelAction: EditPanelAction; - onClose: () => void; -} - -export function FiltersNotificationModal({ - context, - displayName, - id, - editPanelAction, - onClose, -}: FiltersNotificationProps) { - const { embeddable } = context; - const [isLoading, setIsLoading] = useState(true); - const [filters, setFilters] = useState([]); - const [queryString, setQueryString] = useState(''); - const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>(); - - useMount(() => { - Promise.all([ - (embeddable as IEmbeddable & FilterableEmbeddable).getFilters(), - (embeddable as IEmbeddable & FilterableEmbeddable).getQuery(), - ]).then(([embeddableFilters, embeddableQuery]) => { - setFilters(embeddableFilters); - if (embeddableQuery) { - if (isOfQueryType(embeddableQuery)) { - setQueryString(embeddableQuery.query as string); - } else { - const language = getAggregateQueryMode(embeddableQuery); - setQueryLanguage(language); - setQueryString(embeddableQuery[language as keyof AggregateQuery]); - } - } - setIsLoading(false); - }); - }); - - const dataViewList: DataView[] = (embeddable.getRoot() as DashboardContainer)?.getAllDataViews(); - - return ( - <> - - -

{displayName}

-
-
- - - {isLoading ? ( - - ) : ( - - {queryString !== '' && ( - - - {queryString} - - - )} - {filters && filters.length > 0 && ( - - - - - - )} - - )} - - - - - - - {dashboardFilterNotificationBadge.getCloseButtonTitle()} - - - - { - onClose(); - editPanelAction.execute(context); - }} - fill - > - {dashboardFilterNotificationBadge.getEditButtonTitle()} - - - - - - ); -} diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx similarity index 63% rename from src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx index 9a83bf52e1092..92608264125ef 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx @@ -7,14 +7,18 @@ */ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; import { FilterableEmbeddable, isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { getSampleDashboardInput } from '../test_helpers'; -import { EuiModalFooter } from '@elastic/eui'; -import { FiltersNotificationModal, FiltersNotificationProps } from './filters_notification_modal'; +import { EuiPopover } from '@elastic/eui'; +import { + FiltersNotificationPopover, + FiltersNotificationProps, +} from './filters_notification_popover'; import { ContactCardEmbeddable, ContactCardEmbeddableFactory, @@ -53,55 +57,36 @@ describe('LibraryNotificationPopover', () => { }); defaultProps = { + icon: 'test', context: { embeddable: contactCardEmbeddable }, displayName: 'test display', id: 'testId', editPanelAction: { execute: jest.fn(), } as unknown as FiltersNotificationProps['editPanelAction'], - onClose: jest.fn(), }; }); function mountComponent(props?: Partial) { - return mountWithIntl(); + return mountWithIntl(); } - test('show modal footer in edit mode', async () => { + test('clicking edit button executes edit panel action', async () => { embeddable.updateInput({ viewMode: ViewMode.EDIT }); - await act(async () => { - const component = mountComponent(); - const footer = component.find(EuiModalFooter); - expect(footer.exists()).toBe(true); - }); - }); + const component = mountComponent(); - test('hide modal footer in view mode', async () => { - embeddable.updateInput({ viewMode: ViewMode.VIEW }); await act(async () => { - const component = mountComponent(); - const footer = component.find(EuiModalFooter); - expect(footer.exists()).toBe(false); + findTestSubject(component, `embeddablePanelNotification-${defaultProps.id}`).simulate( + 'click' + ); }); - }); - - test('clicking edit button executes edit panel action', async () => { - embeddable.updateInput({ viewMode: ViewMode.EDIT }); await act(async () => { - const component = mountComponent(); - const editButton = findTestSubject(component, 'filtersNotificationModal__editButton'); - editButton.simulate('click'); - expect(defaultProps.editPanelAction.execute).toHaveBeenCalled(); + component.update(); }); - }); - test('clicking close button calls onClose', async () => { - embeddable.updateInput({ viewMode: ViewMode.EDIT }); - await act(async () => { - const component = mountComponent(); - const editButton = findTestSubject(component, 'filtersNotificationModal__closeButton'); - editButton.simulate('click'); - expect(defaultProps.onClose).toHaveBeenCalled(); - }); + const popover = component.find(EuiPopover); + const editButton = findTestSubject(popover, 'filtersNotificationModal__editButton'); + editButton.simulate('click'); + expect(defaultProps.editPanelAction.execute).toHaveBeenCalled(); }); }); diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx new file mode 100644 index 0000000000000..974c7280f8968 --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; + +import { + EuiButton, + EuiPopover, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, + EuiPopoverTitle, + EuiPopoverFooter, +} from '@elastic/eui'; +import { EditPanelAction } from '@kbn/embeddable-plugin/public'; + +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; +import { FiltersNotificationActionContext } from './filters_notification_action'; +import { FiltersNotificationPopoverContents } from './filters_notification_popover_contents'; + +export interface FiltersNotificationProps { + context: FiltersNotificationActionContext; + editPanelAction: EditPanelAction; + displayName: string; + icon: string; + id: string; +} + +export function FiltersNotificationPopover({ + editPanelAction, + displayName, + context, + icon, + id, +}: FiltersNotificationProps) { + const { embeddable } = context; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)} + data-test-subj={`embeddablePanelNotification-${id}`} + aria-label={displayName} + /> + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + anchorPosition="upCenter" + > + {displayName} + + + + + editPanelAction.execute({ embeddable })} + > + {dashboardFilterNotificationAction.getEditButtonTitle()} + + + + + + ); +} diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx new file mode 100644 index 0000000000000..dad82f4c1c45a --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, useState } from 'react'; +import useMount from 'react-use/lib/useMount'; + +import { EuiCodeBlock, EuiFlexGroup, EuiForm, EuiFormRow, EuiLoadingContent } from '@elastic/eui'; +import { FilterableEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { FilterItems } from '@kbn/unified-search-plugin/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { + type AggregateQuery, + type Filter, + getAggregateQueryMode, + isOfQueryType, +} from '@kbn/es-query'; + +import { FiltersNotificationActionContext } from './filters_notification_action'; +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; +import { DashboardContainer } from '../embeddable'; + +export interface FiltersNotificationProps { + context: FiltersNotificationActionContext; +} + +export function FiltersNotificationPopoverContents({ context }: FiltersNotificationProps) { + const { embeddable } = context; + const [isLoading, setIsLoading] = useState(true); + const [filters, setFilters] = useState([]); + const [queryString, setQueryString] = useState(''); + const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>(); + + const dataViews = useMemo( + () => (embeddable.getRoot() as DashboardContainer)?.getAllDataViews(), + [embeddable] + ); + + useMount(() => { + Promise.all([ + (embeddable as IEmbeddable & FilterableEmbeddable).getFilters(), + (embeddable as IEmbeddable & FilterableEmbeddable).getQuery(), + ]).then(([embeddableFilters, embeddableQuery]) => { + setFilters(embeddableFilters); + if (embeddableQuery) { + if (isOfQueryType(embeddableQuery)) { + setQueryString(embeddableQuery.query as string); + } else { + const language = getAggregateQueryMode(embeddableQuery); + setQueryLanguage(language); + setQueryString(embeddableQuery[language as keyof AggregateQuery]); + } + } + setIsLoading(false); + }); + }); + + return ( + <> + {isLoading ? ( + + ) : ( + + {queryString !== '' && ( + + + {queryString} + + + )} + {filters && filters.length > 0 && ( + + + + + + + + )} + + )} + + ); +} diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index bda2f6bf0b11e..7793c28037544 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -18,7 +18,7 @@ import { ReplacePanelAction } from './replace_panel_action'; import { AddToLibraryAction } from './add_to_library_action'; import { CopyToDashboardAction } from './copy_to_dashboard_action'; import { UnlinkFromLibraryAction } from './unlink_from_library_action'; -import { FiltersNotificationBadge } from './filters_notification_badge'; +import { FiltersNotificationAction } from './filters_notification_action'; import { LibraryNotificationAction } from './library_notification_action'; interface BuildAllDashboardActionsProps { @@ -48,9 +48,9 @@ export const buildAllDashboardActions = async ({ uiActions.registerAction(expandPanelAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); - const panelLevelFiltersNotification = new FiltersNotificationBadge(); - uiActions.registerAction(panelLevelFiltersNotification); - uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelLevelFiltersNotification.id); + const panelLevelFiltersNotificationAction = new FiltersNotificationAction(); + uiActions.registerAction(panelLevelFiltersNotificationAction); + uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelLevelFiltersNotificationAction.id); if (share) { const ExportCSVPlugin = new ExportCSVAction(); diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index fcb7f3cb2a7c1..73c1969ae1996 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -191,7 +191,7 @@ export const dashboardReplacePanelAction = { }), }; -export const dashboardFilterNotificationBadge = { +export const dashboardFilterNotificationAction = { getDisplayName: () => i18n.translate('dashboard.panel.filters', { defaultMessage: 'Panel filters', From bffd987c31a5b7c8d43de7126039a62db24a8c9e Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Tue, 6 Dec 2022 09:42:24 -0700 Subject: [PATCH 6/6] Move `i18n` provider up two levels --- .../actions/filters_notification_action.tsx | 25 +++++++++++-------- .../filters_notification_popover_contents.tsx | 5 +--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx index d2cba311994e3..b7ee2311ebd18 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx @@ -15,6 +15,7 @@ import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import type { ApplicationStart } from '@kbn/core/public'; import { type AggregateQuery } from '@kbn/es-query'; +import { I18nProvider } from '@kbn/i18n-react'; import { FiltersNotificationPopover } from './filters_notification_popover'; import { dashboardFilterNotificationAction } from '../../dashboard_strings'; @@ -59,17 +60,19 @@ export class FiltersNotificationAction implements Action - - - - + + + + + + + ); }; diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx index dad82f4c1c45a..b3c37f40d6c6c 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx @@ -12,7 +12,6 @@ import useMount from 'react-use/lib/useMount'; import { EuiCodeBlock, EuiFlexGroup, EuiForm, EuiFormRow, EuiLoadingContent } from '@elastic/eui'; import { FilterableEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { FilterItems } from '@kbn/unified-search-plugin/public'; -import { I18nProvider } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { type AggregateQuery, @@ -90,9 +89,7 @@ export function FiltersNotificationPopoverContents({ context }: FiltersNotificat {filters && filters.length > 0 && ( - - - + )}