diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx index c1f526e24b8e2..54be43596a431 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx @@ -7,7 +7,6 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues } from '../../../../../__mocks__'; import { setMockValues, setMockActions } from '../../../../../__mocks__'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; @@ -25,11 +24,11 @@ import { DisplaySettings } from './display_settings'; import { FieldEditorModal } from './field_editor_modal'; describe('DisplaySettings', () => { - const { navigateToUrl } = mockKibanaValues; const { exampleDocuments, searchResultConfig } = exampleResult; const initializeDisplaySettings = jest.fn(); const setServerData = jest.fn(); const setColorField = jest.fn(); + const handleSelectedTabChanged = jest.fn(); const values = { isOrganization: true, @@ -46,6 +45,7 @@ describe('DisplaySettings', () => { initializeDisplaySettings, setServerData, setColorField, + handleSelectedTabChanged, }); setMockValues({ ...values }); }); @@ -83,7 +83,7 @@ describe('DisplaySettings', () => { const tabsEl = wrapper.find(EuiTabbedContent); tabsEl.prop('onTabClick')!(tabs[0]); - expect(navigateToUrl).toHaveBeenCalledWith('/sources/123/display_settings/'); + expect(handleSelectedTabChanged).toHaveBeenCalledWith('search_results'); }); it('handles second tab click', () => { @@ -91,7 +91,7 @@ describe('DisplaySettings', () => { const tabsEl = wrapper.find(EuiTabbedContent); tabsEl.prop('onTabClick')!(tabs[1]); - expect(navigateToUrl).toHaveBeenCalledWith('/sources/123/display_settings/result_detail'); + expect(handleSelectedTabChanged).toHaveBeenCalledWith('result_detail'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index e39a8d17e406c..3441e5fcbaf82 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -20,19 +20,11 @@ import { } from '@elastic/eui'; import { clearFlashMessages } from '../../../../../shared/flash_messages'; -import { KibanaLogic } from '../../../../../shared/kibana'; import { Loading } from '../../../../../shared/loading'; import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; -import { AppLogic } from '../../../../app_logic'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { SAVE_BUTTON } from '../../../../constants'; -import { - DISPLAY_SETTINGS_RESULT_DETAIL_PATH, - DISPLAY_SETTINGS_SEARCH_RESULT_PATH, - getContentSourcePath, -} from '../../../../routes'; - import { UNSAVED_MESSAGE, DISPLAY_SETTINGS_TITLE, @@ -42,7 +34,7 @@ import { SEARCH_RESULTS_LABEL, RESULT_DETAIL_LABEL, } from './constants'; -import { DisplaySettingsLogic } from './display_settings_logic'; +import { DisplaySettingsLogic, TabId } from './display_settings_logic'; import { FieldEditorModal } from './field_editor_modal'; import { ResultDetail } from './result_detail'; import { SearchResults } from './search_results'; @@ -52,19 +44,20 @@ interface DisplaySettingsProps { } export const DisplaySettings: React.FC = ({ tabId }) => { - const { initializeDisplaySettings, setServerData } = useActions(DisplaySettingsLogic); + const { initializeDisplaySettings, setServerData, handleSelectedTabChanged } = useActions( + DisplaySettingsLogic + ); const { dataLoading, - sourceId, addFieldModalVisible, unsavedChanges, exampleDocuments, + navigatingBetweenTabs, } = useValues(DisplaySettingsLogic); - const { isOrganization } = useValues(AppLogic); - const hasDocuments = exampleDocuments.length > 0; + const hasUnsavedChanges = hasDocuments && unsavedChanges; useEffect(() => { initializeDisplaySettings(); @@ -87,12 +80,7 @@ export const DisplaySettings: React.FC = ({ tabId }) => { ] as EuiTabbedContentTab[]; const onSelectedTabChanged = (tab: EuiTabbedContentTab) => { - const path = - tab.id === tabs[1].id - ? getContentSourcePath(DISPLAY_SETTINGS_RESULT_DETAIL_PATH, sourceId, isOrganization) - : getContentSourcePath(DISPLAY_SETTINGS_SEARCH_RESULT_PATH, sourceId, isOrganization); - - KibanaLogic.values.navigateToUrl(path); + handleSelectedTabChanged(tab.id as TabId); }; const handleFormSubmit = (e: FormEvent) => { @@ -103,7 +91,7 @@ export const DisplaySettings: React.FC = ({ tabId }) => { return ( <>
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts index 73df0298ecd19..5a6ef5ba5990f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../../../__mocks__'; +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, + mockKibanaValues, +} from '../../../../../__mocks__'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; @@ -25,6 +30,7 @@ import { DisplaySettingsLogic, defaultSearchResultConfig } from './display_setti describe('DisplaySettingsLogic', () => { const { http } = mockHttpValues; + const { navigateToUrl } = mockKibanaValues; const { clearFlashMessages, flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers; const { mount } = new LogicMounter(DisplaySettingsLogic); @@ -40,6 +46,7 @@ describe('DisplaySettingsLogic', () => { serverRoute: '', editFieldIndex: null, dataLoading: true, + navigatingBetweenTabs: false, addFieldModalVisible: false, titleFieldHover: false, urlFieldHover: false, @@ -203,6 +210,12 @@ describe('DisplaySettingsLogic', () => { }); }); + it('setNavigatingBetweenTabs', () => { + DisplaySettingsLogic.actions.setNavigatingBetweenTabs(true); + + expect(DisplaySettingsLogic.values.navigatingBetweenTabs).toEqual(true); + }); + it('addDetailField', () => { const newField = { label: 'Monkey', fieldName: 'primate' }; DisplaySettingsLogic.actions.setServerResponseData(serverProps); @@ -351,6 +364,31 @@ describe('DisplaySettingsLogic', () => { expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); + + describe('handleSelectedTabChanged', () => { + beforeEach(() => { + DisplaySettingsLogic.actions.onInitializeDisplaySettings(serverProps); + }); + + it('calls sets navigatingBetweenTabs', async () => { + const setNavigatingBetweenTabsSpy = jest.spyOn( + DisplaySettingsLogic.actions, + 'setNavigatingBetweenTabs' + ); + DisplaySettingsLogic.actions.handleSelectedTabChanged('search_results'); + await nextTick(); + + expect(setNavigatingBetweenTabsSpy).toHaveBeenCalledWith(true); + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources/123/display_settings/'); + }); + + it('calls calls correct route for "result_detail"', async () => { + DisplaySettingsLogic.actions.handleSelectedTabChanged('result_detail'); + await nextTick(); + + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources/123/display_settings/result_detail'); + }); + }); }); describe('selectors', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts index 62d959083af59..e8b419a31abb2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts @@ -16,7 +16,13 @@ import { flashAPIErrors, } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; +import { KibanaLogic } from '../../../../../shared/kibana'; import { AppLogic } from '../../../../app_logic'; +import { + DISPLAY_SETTINGS_RESULT_DETAIL_PATH, + DISPLAY_SETTINGS_SEARCH_RESULT_PATH, + getContentSourcePath, +} from '../../../../routes'; import { DetailField, SearchResultConfig, OptionValue, Result } from '../../../../types'; import { SourceLogic } from '../../source_logic'; @@ -34,6 +40,8 @@ export interface DisplaySettingsInitialData extends DisplaySettingsResponseProps serverRoute: string; } +export type TabId = 'search_results' | 'result_detail'; + interface DisplaySettingsActions { initializeDisplaySettings(): void; setServerData(): void; @@ -51,6 +59,8 @@ interface DisplaySettingsActions { setDetailFields(result: DropResult): { result: DropResult }; openEditDetailField(editFieldIndex: number | null): number | null; removeDetailField(index: number): number; + setNavigatingBetweenTabs(navigatingBetweenTabs: boolean): boolean; + handleSelectedTabChanged(tabId: TabId): TabId; addDetailField(newField: DetailField): DetailField; updateDetailField( updatedField: DetailField, @@ -73,6 +83,7 @@ interface DisplaySettingsValues { serverRoute: string; editFieldIndex: number | null; dataLoading: boolean; + navigatingBetweenTabs: boolean; addFieldModalVisible: boolean; titleFieldHover: boolean; urlFieldHover: boolean; @@ -109,6 +120,8 @@ export const DisplaySettingsLogic = kea< setDetailFields: (result: DropResult) => ({ result }), openEditDetailField: (editFieldIndex: number | null) => editFieldIndex, removeDetailField: (index: number) => index, + setNavigatingBetweenTabs: (navigatingBetweenTabs: boolean) => navigatingBetweenTabs, + handleSelectedTabChanged: (tabId: TabId) => tabId, addDetailField: (newField: DetailField) => newField, updateDetailField: (updatedField: DetailField, index: number) => ({ updatedField, index }), toggleFieldEditorModal: () => true, @@ -224,6 +237,12 @@ export const DisplaySettingsLogic = kea< onInitializeDisplaySettings: () => false, }, ], + navigatingBetweenTabs: [ + false, + { + setNavigatingBetweenTabs: (_, navigatingBetweenTabs) => navigatingBetweenTabs, + }, + ], addFieldModalVisible: [ false, { @@ -330,6 +349,26 @@ export const DisplaySettingsLogic = kea< toggleFieldEditorModal: () => { clearFlashMessages(); }, + + handleSelectedTabChanged: async (tabId, breakpoint) => { + const { isOrganization } = AppLogic.values; + const { sourceId } = values; + const path = + tabId === 'result_detail' + ? getContentSourcePath(DISPLAY_SETTINGS_RESULT_DETAIL_PATH, sourceId, isOrganization) + : getContentSourcePath(DISPLAY_SETTINGS_SEARCH_RESULT_PATH, sourceId, isOrganization); + + // This method is needed because the shared `UnsavedChangesPrompt` component is triggered + // when navigating between tabs. We set a boolean flag that tells the prompt there are no + // unsaved changes when navigating between the tabs and reset it one the transition is complete + // in order to restore the intended functionality when navigating away with unsaved changes. + actions.setNavigatingBetweenTabs(true); + + await breakpoint(); + + KibanaLogic.values.navigateToUrl(path); + actions.setNavigatingBetweenTabs(false); + }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx index bf0c5471f7b57..12e1506ec6efd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx @@ -45,7 +45,10 @@ export const SourceSubNav: React.FC = () => { {NAV.SCHEMA} - + {NAV.DISPLAY_SETTINGS}