From 5a0e61bc4e37ab40580aa2833185f84f8ee1646d Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+dimaanj@users.noreply.github.com> Date: Wed, 12 Oct 2022 15:46:44 +0300 Subject: [PATCH] [Discover] Update data view id on adhoc data view change (#142069) * [Discover] update id on adhoc data view change * [Discover] fix ci * [Discover] fix ci * Update src/plugins/discover/public/application/main/components/layout/discover_layout.tsx Co-authored-by: Matthias Wilhelm * [Discover] add filters validation * [Discover] fix ci * [Discover] fix checks * [Discover] fix ci * Update src/plugins/discover/public/application/main/hooks/use_filters_validation.ts Co-authored-by: Matthias Wilhelm * Update src/plugins/discover/public/application/main/hooks/use_filters_validation.ts Co-authored-by: Matthias Wilhelm * [Discover] apply suggestions * [Discover] update wording in tests * [Discover] try to fix functional * [Discover] change test impl to get rid of flakiness * [Discover] remove flaky test Co-authored-by: Matthias Wilhelm Co-authored-by: Joe Reuter --- .../discover/public/__mocks__/services.ts | 2 + .../components/layout/discover_layout.tsx | 8 +- .../layout/discover_main_content.tsx | 2 +- .../components/sidebar/discover_sidebar.tsx | 2 +- .../sidebar/discover_sidebar_responsive.tsx | 4 +- .../components/top_nav/discover_topnav.tsx | 4 +- .../main/hooks/use_adhoc_data_views.test.ts | 20 +++- .../main/hooks/use_adhoc_data_views.ts | 51 +++++---- ...te.test.ts => use_discover_state.test.tsx} | 0 .../main/hooks/use_discover_state.ts | 8 +- .../main/hooks/use_filters_validation.ts | 56 ++++++++++ .../discover_grid/discover_grid.tsx | 2 +- .../apps/discover/group1/_discover.ts | 14 ++- .../apps/discover/group2/_adhoc_data_views.ts | 102 +++++++++++++----- test/functional/page_objects/discover_page.ts | 32 ++++++ test/functional/services/data_grid.ts | 4 + test/functional/services/toasts.ts | 5 + .../apps/lens/group1/ad_hoc_data_view.ts | 13 ++- 18 files changed, 256 insertions(+), 73 deletions(-) rename src/plugins/discover/public/application/main/hooks/{use_discover_state.test.ts => use_discover_state.test.tsx} (100%) create mode 100644 src/plugins/discover/public/application/main/hooks/use_filters_validation.ts diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index 252aead2f76ed2..97de63d231a464 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -26,10 +26,12 @@ import { FORMATS_UI_SETTINGS } from '@kbn/field-formats-plugin/common'; import { LocalStorageMock } from './local_storage_mock'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { dataViewsMock } from './data_views'; +import { Observable, of } from 'rxjs'; const dataPlugin = dataPluginMock.createStartContract(); const expressionsPlugin = expressionsPluginMock.createStartContract(); dataPlugin.query.filterManager.getFilters = jest.fn(() => []); +dataPlugin.query.filterManager.getUpdates$ = jest.fn(() => of({}) as unknown as Observable); export const discoverServiceMock = { core: coreMock.createStart(), diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index fe6ac392c35a87..18d004c72c14f1 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -152,9 +152,13 @@ export function DiscoverLayout({ [filterManager, dataView, dataViews, trackUiMetric, capabilities] ); - const onFieldEdited = useCallback(() => { + const onFieldEdited = useCallback(async () => { + if (!dataView.isPersisted()) { + await updateAdHocDataViewId(dataView); + return; + } savedSearchRefetch$.next('reset'); - }, [savedSearchRefetch$]); + }, [dataView, savedSearchRefetch$, updateAdHocDataViewId]); const onDisableFilters = useCallback(() => { const disabledFilters = filterManager diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx index 7cfa8e06e3bbc2..4998e561f5dd7d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -52,7 +52,7 @@ export interface DiscoverMainContentProps { isTimeBased: boolean; viewMode: VIEW_MODE; onAddFilter: DocViewFilterFn | undefined; - onFieldEdited: () => void; + onFieldEdited: () => Promise; columns: string[]; resizeRef: RefObject; } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx index 436fdc36f4d322..0172c32af4e84f 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx @@ -283,7 +283,7 @@ export function DiscoverSidebarComponent({ }, fieldName, onDelete: async () => { - onFieldEdited(); + await onFieldEdited(); }, }); if (setFieldEditorRef) { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index 274cb85cc35354..1387f3a0c87913 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -93,7 +93,7 @@ export interface DiscoverSidebarResponsiveProps { /** * callback to execute on edit runtime field */ - onFieldEdited: () => void; + onFieldEdited: () => Promise; /** * callback to execute on create dataview */ @@ -220,7 +220,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) }, fieldName, onSave: async () => { - onFieldEdited(); + await onFieldEdited(); }, }); if (setFieldEditorRef) { diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 66f06169256abf..0aa132d1437d3d 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -34,7 +34,7 @@ export type DiscoverTopNavProps = Pick< onChangeDataView: (dataView: string) => void; isPlainRecord: boolean; textBasedLanguageModeErrors?: Error; - onFieldEdited: () => void; + onFieldEdited: () => Promise; persistDataView: (dataView: DataView) => Promise; updateAdHocDataViewId: (dataView: DataView) => Promise; adHocDataViewList: DataView[]; @@ -111,7 +111,7 @@ export const DiscoverTopNav = ({ }, fieldName, onSave: async () => { - onFieldEdited(); + await onFieldEdited(); }, }); } diff --git a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.ts b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.ts index d06521d3452707..e14defbdeb5850 100644 --- a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.test.ts @@ -75,12 +75,18 @@ describe('useAdHocDataViews', () => { const hook = renderHook((d: DataView) => useAdHocDataViews({ dataView: mockDataView, - dataViews: mockDiscoverServices.dataViews, savedSearch: savedSearchMock, stateContainer: { appStateContainer: { getState: jest.fn().mockReturnValue({}) }, + replaceUrlAppState: jest.fn(), + kbnUrlStateStorage: { + kbnUrlControls: { flush: jest.fn() }, + }, } as unknown as GetStateReturn, - onChangeDataView: jest.fn(), + setUrlTracking: jest.fn(), + dataViews: mockDiscoverServices.dataViews, + filterManager: mockDiscoverServices.filterManager, + toastNotifications: mockDiscoverServices.toastNotifications, }) ); @@ -101,12 +107,18 @@ describe('useAdHocDataViews', () => { const hook = renderHook((d: DataView) => useAdHocDataViews({ dataView: mockDataView, - dataViews: mockDiscoverServices.dataViews, savedSearch: savedSearchMock, stateContainer: { appStateContainer: { getState: jest.fn().mockReturnValue({}) }, + replaceUrlAppState: jest.fn(), + kbnUrlStateStorage: { + kbnUrlControls: { flush: jest.fn() }, + }, } as unknown as GetStateReturn, - onChangeDataView: jest.fn(), + setUrlTracking: jest.fn(), + dataViews: mockDiscoverServices.dataViews, + filterManager: mockDiscoverServices.filterManager, + toastNotifications: mockDiscoverServices.toastNotifications, }) ); diff --git a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts index ded1b90d698732..89fd4477da34c4 100644 --- a/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts +++ b/src/plugins/discover/public/application/main/hooks/use_adhoc_data_views.ts @@ -7,29 +7,36 @@ */ import { useCallback, useEffect, useState } from 'react'; -import { DataViewsContract, type DataView } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import { UPDATE_FILTER_REFERENCES_ACTION, UPDATE_FILTER_REFERENCES_TRIGGER, } from '@kbn/unified-search-plugin/public'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { FilterManager } from '@kbn/data-plugin/public'; +import type { ToastsStart } from '@kbn/core-notifications-browser'; +import { getUiActions } from '../../../kibana_services'; import { useConfirmPersistencePrompt } from '../../../hooks/use_confirm_persistence_prompt'; -import { getUiActions, getUrlTracker } from '../../../kibana_services'; import { GetStateReturn } from '../services/discover_state'; +import { useFiltersValidation } from './use_filters_validation'; export const useAdHocDataViews = ({ dataView, savedSearch, - dataViews, stateContainer, - onChangeDataView, + setUrlTracking, + filterManager, + dataViews, + toastNotifications, }: { dataView: DataView; savedSearch: SavedSearch; - dataViews: DataViewsContract; stateContainer: GetStateReturn; - onChangeDataView: (dataViewId: string) => Promise; + setUrlTracking: (dataView: DataView) => void; + dataViews: DataViewsContract; + filterManager: FilterManager; + toastNotifications: ToastsStart; }) => { const [adHocDataViewList, setAdHocDataViewList] = useState( !dataView.isPersisted() ? [dataView] : [] @@ -44,6 +51,11 @@ export const useAdHocDataViews = ({ } }, [dataView]); + /** + * Takes care of checking data view id references in filters + */ + useFiltersValidation({ savedSearch, filterManager, toastNotifications }); + /** * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view * This is to prevent duplicate ids messing with our system @@ -57,12 +69,11 @@ export const useAdHocDataViews = ({ prev.filter((d) => d.id && dataViewToUpdate.id && d.id !== dataViewToUpdate.id) ); - savedSearch.searchSource.setField('index', newDataView); - - // update filters references const uiActions = await getUiActions(); const trigger = uiActions.getTrigger(UPDATE_FILTER_REFERENCES_TRIGGER); const action = uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION); + + // execute shouldn't be awaited, this is important for pending history push cancellation action?.execute({ trigger, fromDataView: dataViewToUpdate.id, @@ -70,9 +81,11 @@ export const useAdHocDataViews = ({ usedDataViews: [], } as ActionExecutionContext); + stateContainer.replaceUrlAppState({ index: newDataView.id }); + setUrlTracking(newDataView); return newDataView; }, - [dataViews, savedSearch.searchSource] + [dataViews, setUrlTracking, stateContainer] ); const { openConfirmSavePrompt, updateSavedSearch } = @@ -81,22 +94,16 @@ export const useAdHocDataViews = ({ const currentDataView = savedSearch.searchSource.getField('index')!; if (currentDataView && !currentDataView.isPersisted()) { const createdDataView = await openConfirmSavePrompt(currentDataView); - if (createdDataView) { - savedSearch.searchSource.setField('index', createdDataView); - await onChangeDataView(createdDataView.id!); - // update saved search with saved data view - if (savedSearch.id) { - const currentState = stateContainer.appStateContainer.getState(); - await updateSavedSearch({ savedSearch, dataView: createdDataView, state: currentState }); - } - getUrlTracker().setTrackingEnabled(true); - return createdDataView; + // update saved search with saved data view + if (createdDataView && savedSearch.id) { + const currentState = stateContainer.appStateContainer.getState(); + await updateSavedSearch({ savedSearch, dataView: createdDataView, state: currentState }); } - return undefined; + return createdDataView; } return currentDataView; - }, [stateContainer, onChangeDataView, openConfirmSavePrompt, savedSearch, updateSavedSearch]); + }, [stateContainer, openConfirmSavePrompt, savedSearch, updateSavedSearch]); return { adHocDataViewList, persistDataView, updateAdHocDataViewId }; }; diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.test.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx similarity index 100% rename from src/plugins/discover/public/application/main/hooks/use_discover_state.test.ts rename to src/plugins/discover/public/application/main/hooks/use_discover_state.test.tsx diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts index 1c530f475f0b11..ff448e59917a80 100644 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts +++ b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts @@ -44,7 +44,7 @@ export function useDiscoverState({ setExpandedDoc: (doc?: DataTableRecord) => void; dataViewList: DataViewListItem[]; }) { - const { uiSettings, data, filterManager, dataViews } = services; + const { uiSettings, data, filterManager, dataViews, toastNotifications } = services; const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const { timefilter } = data.query.timefilter; @@ -126,10 +126,12 @@ export function useDiscoverState({ */ const { adHocDataViewList, persistDataView, updateAdHocDataViewId } = useAdHocDataViews({ dataView, - dataViews, stateContainer, savedSearch, - onChangeDataView, + setUrlTracking, + dataViews, + toastNotifications, + filterManager, }); /** diff --git a/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts b/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts new file mode 100644 index 00000000000000..f093edbd7c65b7 --- /dev/null +++ b/src/plugins/discover/public/application/main/hooks/use_filters_validation.ts @@ -0,0 +1,56 @@ +/* + * 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 { IToasts, ToastsStart } from '@kbn/core/public'; +import { FilterManager } from '@kbn/data-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { useEffect } from 'react'; +import { debounceTime } from 'rxjs'; + +const addInvalidFiltersWarn = (toastNotifications: IToasts) => { + const warningTitle = i18n.translate('discover.invalidFiltersWarnToast.title', { + defaultMessage: 'Different index references', + }); + toastNotifications.addWarning({ + title: warningTitle, + text: i18n.translate('discover.invalidFiltersWarnToast.description', { + defaultMessage: + 'Data view id references in some of the applied filters differ from the current data view.', + }), + 'data-test-subj': 'invalidFiltersWarnToast', + }); +}; + +export const useFiltersValidation = ({ + savedSearch, + filterManager, + toastNotifications, +}: { + savedSearch: SavedSearch; + filterManager: FilterManager; + toastNotifications: ToastsStart; +}) => { + useEffect(() => { + const subscription = filterManager + .getUpdates$() + .pipe(debounceTime(500)) + .subscribe(() => { + const currentFilters = filterManager.getFilters(); + const dataView = savedSearch.searchSource.getField('index'); + const areFiltersInvalid = + dataView && + !dataView.isPersisted() && + !currentFilters.every((current) => current.meta.index === dataView.id); + if (areFiltersInvalid) { + addInvalidFiltersWarn(toastNotifications); + } + }); + return () => subscription.unsubscribe(); + }, [filterManager, savedSearch.searchSource, toastNotifications]); +}; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 1987542931676a..2da63f982e3050 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -376,7 +376,7 @@ export const DiscoverGrid = ({ }, fieldName, onSave: async () => { - onFieldEdited(); + await onFieldEdited(); }, }); } diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index 9ac159fa39168d..1ac0ad6fe013f9 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -364,20 +364,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('URL state', () => { - const getCurrentDataViewId = (currentUrl: string) => { - const [indexSubstring] = currentUrl.match(/index:[^,]*/)!; - const dataViewId = indexSubstring.replace('index:', ''); - return dataViewId; - }; - it('should show a warning and fall back to the default data view when navigating to a URL with an invalid data view ID', async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.header.waitUntilLoadingHasFinished(); + const dataViewId = await PageObjects.discover.getCurrentDataViewId(); + const originalUrl = await browser.getCurrentUrl(); - const dataViewId = getCurrentDataViewId(originalUrl); const newUrl = originalUrl.replace(dataViewId, 'invalid-data-view-id'); await browser.get(newUrl); + await PageObjects.header.waitUntilLoadingHasFinished(); expect(await browser.getCurrentUrl()).to.be(originalUrl); expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); @@ -387,10 +383,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); const originalHash = await browser.execute<[], string>('return window.location.hash'); - const dataViewId = getCurrentDataViewId(originalHash); + const dataViewId = await PageObjects.discover.getCurrentDataViewId(); + const newHash = originalHash.replace(dataViewId, 'invalid-data-view-id'); await browser.execute(`window.location.hash = "${newHash}"`); await PageObjects.header.waitUntilLoadingHasFinished(); + const currentHash = await browser.execute<[], string>('return window.location.hash'); expect(currentHash).to.be(originalHash); expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index c7f17472250fdd..9a1f4dd84d3005 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -11,15 +11,16 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const dataGrid = getService('dataGrid'); + const toasts = getService('toasts'); const esArchiver = getService('esArchiver'); const filterBar = getService('filterBar'); const dashboardAddPanel = getService('dashboardAddPanel'); const fieldEditor = getService('fieldEditor'); const kibanaServer = getService('kibanaServer'); - const browser = getService('browser'); const retry = getService('retry'); const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); + const browser = getService('browser'); const PageObjects = getPageObjects([ 'common', 'unifiedSearch', @@ -33,22 +34,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); const security = getService('security'); - const getCurrentDataViewId = async () => { - const currentUrl = await browser.getCurrentUrl(); - const [indexSubstring] = currentUrl.match(/index:[^,]*/)!; - const dataViewId = indexSubstring.replace('index:', ''); - return dataViewId; - }; - - const addRuntimeField = async (name: string, script: string) => { - await PageObjects.discover.clickAddField(); - await fieldEditor.setName(name); - await fieldEditor.enableValue(); - await fieldEditor.typeScript(script); - await fieldEditor.save(); - await PageObjects.header.waitUntilLoadingHasFinished(); - }; - const addSearchToDashboard = async (name: string) => { await dashboardAddPanel.addSavedSearch(name); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -113,35 +98,35 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not update data view id when saving search first time', async () => { - const prevDataViewId = await getCurrentDataViewId(); + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); await PageObjects.discover.saveSearch('logstash*-ss'); await PageObjects.header.waitUntilLoadingHasFinished(); - const newDataViewId = await getCurrentDataViewId(); + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); expect(prevDataViewId).to.equal(newDataViewId); }); it('should update data view id when saving new search copy', async () => { - const prevDataViewId = await getCurrentDataViewId(); + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); await PageObjects.discover.saveSearch('logstash*-ss-new', true); await PageObjects.header.waitUntilLoadingHasFinished(); - const newDataViewId = await getCurrentDataViewId(); + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); expect(prevDataViewId).not.to.equal(newDataViewId); }); it('should update data view id when saving data view from hoc one', async () => { - const prevDataViewId = await getCurrentDataViewId(); + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); await testSubjects.click('discoverAlertsButton'); await testSubjects.click('confirmModalConfirmButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - const newDataViewId = await getCurrentDataViewId(); + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); expect(prevDataViewId).not.to.equal(newDataViewId); }); @@ -149,9 +134,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('search results should be different after data view update', async () => { await PageObjects.discover.createAdHocDataView('logst', true); await PageObjects.header.waitUntilLoadingHasFinished(); + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); - await addRuntimeField('_bytes-runtimefield', `emit(doc["bytes"].value.toString())`); + // trigger data view id update + await PageObjects.discover.addRuntimeField( + '_bytes-runtimefield', + `emit(doc["bytes"].value.toString())` + ); await PageObjects.discover.clickFieldListItemToggle('_bytes-runtimefield'); + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); + expect(newDataViewId).not.to.equal(prevDataViewId); // save first search await PageObjects.discover.saveSearch('logst*-ss-_bytes-runtimefield'); @@ -162,7 +154,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.removeField('_bytes-runtimefield'); await PageObjects.header.waitUntilLoadingHasFinished(); - await addRuntimeField('_bytes-runtimefield', `emit((doc["bytes"].value * 2).toString())`); + // trigger data view id update + await PageObjects.discover.addRuntimeField( + '_bytes-runtimefield', + `emit((doc["bytes"].value * 2).toString())` + ); await PageObjects.discover.clickFieldListItemToggle('_bytes-runtimefield'); // save second search @@ -185,5 +181,61 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(+second).to.equal(+first * 2); }); + + it('should update id after data view field edit', async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.loadSavedSearch('logst*-ss-_bytes-runtimefield'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); + + // trigger data view id update + await dataGrid.clickEditField('_bytes-runtimefield'); + await fieldEditor.setName('_bytes-runtimefield-edited', true); + await fieldEditor.save(); + await fieldEditor.confirmSave(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); + expect(prevDataViewId).not.to.equal(newDataViewId); + }); + + it('should notify about invalid filter reffs', async () => { + await PageObjects.discover.createAdHocDataView('logstas', true); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await filterBar.addFilter('nestedField.child', 'is', 'nestedValue'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await filterBar.addFilter('extension', 'is', 'jpg'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const first = await PageObjects.discover.getCurrentDataViewId(); + // trigger data view id update + await PageObjects.discover.addRuntimeField( + '_bytes-runtimefield', + `emit((doc["bytes"].value * 2).toString())` + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const second = await PageObjects.discover.getCurrentDataViewId(); + expect(first).not.equal(second); + + await toasts.dismissAllToasts(); + + await browser.goBack(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + const firstToast = await toasts.getToastContent(1); + expect(firstToast).to.equal( + `"${first}" is not a configured data view ID\nShowing the saved data view: "logstas*" (${second})` + ); + + const secondToast = await toasts.getToastContent(2); + expect(secondToast).to.equal( + `Different index references\nData view id references in some of the applied filters differ from the current data view.` + ); + }); }); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index cc398f8d67be6e..d9bc8c46a4acc7 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -683,4 +683,36 @@ export class DiscoverPageObject extends FtrService { const button = await this.testSubjects.find('discover-dataView-switch-link'); return button.getAttribute('title'); } + + public async getCurrentDataViewId() { + const currentUrl = await this.browser.getCurrentUrl(); + const matches = currentUrl.matchAll(/index:[^,]*/g); + const indexes = []; + for (const matchEntry of matches) { + const [index] = matchEntry; + indexes.push(decodeURIComponent(index).replace('index:', '').replaceAll("'", '')); + } + + const first = indexes[0]; + if (first) { + const allEqual = indexes.every((val) => val === first); + if (allEqual) { + return first; + } else { + throw new Error( + 'Discover URL state contains different index references. They should be all the same.' + ); + } + } + throw new Error("Discover URL state doesn't contain an index reference."); + } + + public async addRuntimeField(name: string, script: string) { + await this.clickAddField(); + await this.fieldEditor.setName(name); + await this.fieldEditor.enableValue(); + await this.fieldEditor.typeScript(script); + await this.fieldEditor.save(); + await this.header.waitUntilLoadingHasFinished(); + } } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index c3107188d7c563..0c3464e103eca9 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -296,6 +296,10 @@ export class DataGridService extends FtrService { await this.openColMenuByField(field); await this.find.clickByButtonText('Copy name'); } + public async clickEditField(field: string) { + await this.openColMenuByField(field); + await this.testSubjects.click('gridEditFieldButton'); + } public async getDetailsRow(): Promise { const detailRows = await this.getDetailsRows(); diff --git a/test/functional/services/toasts.ts b/test/functional/services/toasts.ts index d71d66e399785e..6866c2a8b2fd27 100644 --- a/test/functional/services/toasts.ts +++ b/test/functional/services/toasts.ts @@ -67,6 +67,11 @@ export class ToastsService extends FtrService { return await list.findByCssSelector(`.euiToast:nth-child(${index})`); } + public async getToastContent(index: number) { + const toast = await this.getToastElement(index); + return await toast.getVisibleText(); + } + private async getGlobalToastList() { return await this.testSubjects.find('globalToastList'); } diff --git a/x-pack/test/functional/apps/lens/group1/ad_hoc_data_view.ts b/x-pack/test/functional/apps/lens/group1/ad_hoc_data_view.ts index 48bcf11c0db072..e70e4b59dc31d5 100644 --- a/x-pack/test/functional/apps/lens/group1/ad_hoc_data_view.ts +++ b/x-pack/test/functional/apps/lens/group1/ad_hoc_data_view.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'dashboard', 'timeToVisualize', 'common', + 'discover', ]); const elasticChart = getService('elasticChart'); const fieldEditor = getService('fieldEditor'); @@ -156,8 +157,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.switchToWindow(discoverWindowHandle); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.sleep(15000); - const actualIndexPattern = await ( await testSubjects.find('discover-dataView-switch-link') ).getVisibleText(); @@ -165,6 +164,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const actualDiscoverQueryHits = await testSubjects.getVisibleText('discoverQueryHits'); expect(actualDiscoverQueryHits).to.be('14,005'); + + const prevDataViewId = await PageObjects.discover.getCurrentDataViewId(); + + await PageObjects.discover.addRuntimeField( + '_bytes-runtimefield', + `emit(doc["bytes"].value.toString())` + ); + await PageObjects.discover.clickFieldListItemToggle('_bytes-runtimefield'); + const newDataViewId = await PageObjects.discover.getCurrentDataViewId(); + expect(newDataViewId).not.to.equal(prevDataViewId); }); }); }