diff --git a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx index 9b40f14eccc34..9d7017845d41e 100644 --- a/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx +++ b/src/plugins/dashboard/public/dashboard_app/hooks/use_dashboard_outcome_validation.tsx @@ -12,6 +12,7 @@ import { pluginServices } from '../../services/plugin_services'; import { createDashboardEditUrl } from '../../dashboard_constants'; import { useDashboardMountContext } from './dashboard_mount_context'; import { LoadDashboardReturn } from '../../services/dashboard_content_management/types'; +import { DashboardCreationOptions } from '../..'; export const useDashboardOutcomeValidation = () => { const [aliasId, setAliasId] = useState(); @@ -26,10 +27,10 @@ export const useDashboardOutcomeValidation = () => { */ const { screenshotMode, spaces } = pluginServices.getServices(); - const validateOutcome = useCallback( + const validateOutcome: DashboardCreationOptions['validateLoadedSavedObject'] = useCallback( ({ dashboardFound, resolveMeta, dashboardId }: LoadDashboardReturn) => { if (!dashboardFound) { - return false; // redirected. Stop loading dashboard. + return 'invalid'; } if (resolveMeta && dashboardId) { @@ -40,17 +41,17 @@ export const useDashboardOutcomeValidation = () => { if (loadOutcome === 'aliasMatch' && dashboardId && alias) { const path = scopedHistory.location.hash.replace(dashboardId, alias); if (screenshotMode.isScreenshotMode()) { - scopedHistory.replace(path); + scopedHistory.replace(path); // redirect without the toast when in screenshot mode. } else { spaces.redirectLegacyUrl?.({ path, aliasPurpose }); - return false; // redirected. Stop loading dashboard. } + return 'redirected'; // redirected. Stop loading dashboard. } setAliasId(alias); setOutcome(loadOutcome); setSavedObjectId(dashboardId); } - return true; + return 'valid'; }, [scopedHistory, screenshotMode, spaces] ); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index c2499a2168350..5c1165dc68c5d 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -44,13 +44,21 @@ test('throws error when no data views are available', async () => { test('throws error when provided validation function returns invalid', async () => { const creationOptions: DashboardCreationOptions = { - validateLoadedSavedObject: jest.fn().mockImplementation(() => false), + validateLoadedSavedObject: jest.fn().mockImplementation(() => 'invalid'), }; await expect(async () => { await createDashboard(creationOptions, 0, 'test-id'); }).rejects.toThrow('Dashboard failed saved object result validation'); }); +test('returns undefined when provided validation function returns redireted', async () => { + const creationOptions: DashboardCreationOptions = { + validateLoadedSavedObject: jest.fn().mockImplementation(() => 'redirected'), + }; + const dashboard = await createDashboard(creationOptions, 0, 'test-id'); + expect(dashboard).toBeUndefined(); +}); + test('pulls state from dashboard saved object when given a saved object id', async () => { pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest .fn() @@ -64,7 +72,8 @@ test('pulls state from dashboard saved object when given a saved object id', asy expect( pluginServices.getServices().dashboardContentManagement.loadDashboardState ).toHaveBeenCalledWith({ id: 'wow-such-id' }); - expect(dashboard.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`); + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.description).toBe(`wow would you look at that? Wow.`); }); test('pulls state from session storage which overrides state from saved object', async () => { @@ -80,7 +89,8 @@ test('pulls state from session storage which overrides state from saved object', .fn() .mockReturnValue({ description: 'wow this description marginally better' }); const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id'); - expect(dashboard.getState().explicitInput.description).toBe( + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.description).toBe( 'wow this description marginally better' ); }); @@ -105,7 +115,8 @@ test('pulls state from creation options initial input which overrides all other 0, 'wow-such-id' ); - expect(dashboard.getState().explicitInput.description).toBe( + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.description).toBe( 'wow this description is a masterpiece' ); }); @@ -165,7 +176,8 @@ test('applies time range from query service to initial input if time restore is timeRange: savedTimeRange, }), }); - expect(dashboard.getState().explicitInput.timeRange).toEqual(urlTimeRange); + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.timeRange).toEqual(urlTimeRange); }); test('applies time range from query service to initial input if time restore is off', async () => { @@ -179,7 +191,8 @@ test('applies time range from query service to initial input if time restore is kbnUrlStateStorage: createKbnUrlStateStorage(), }, }); - expect(dashboard.getState().explicitInput.timeRange).toEqual(timeRange); + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.timeRange).toEqual(timeRange); }); test('replaces panel with incoming embeddable if id matches existing panel', async () => { @@ -205,7 +218,8 @@ test('replaces panel with incoming embeddable if id matches existing panel', asy }, }), }); - expect(dashboard.getState().explicitInput.panels.i_match.explicitInput).toStrictEqual( + expect(dashboard).toBeDefined(); + expect(dashboard!.getState().explicitInput.panels.i_match.explicitInput).toStrictEqual( expect.objectContaining({ id: 'i_match', firstName: 'wow look at this replacement wow', @@ -323,7 +337,8 @@ test('searchSessionId is updated prior to child embeddable parent subscription e createSessionRestorationDataProvider: () => {}, } as unknown as DashboardCreationOptions['searchSessionSettings'], }); - const embeddable = await dashboard.addNewEmbeddable< + expect(dashboard).toBeDefined(); + const embeddable = await dashboard!.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, ContactCardEmbeddable @@ -333,7 +348,7 @@ test('searchSessionId is updated prior to child embeddable parent subscription e expect(embeddable.getInput().searchSessionId).toBe('searchSessionId1'); - dashboard.updateInput({ + dashboard!.updateInput({ timeRange: { to: 'now', from: 'now-7d', diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index e8f9020ba3fcb..a595bca8c1841 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -37,7 +37,7 @@ export const createDashboard = async ( creationOptions?: DashboardCreationOptions, dashboardCreationStartTime?: number, savedObjectId?: string -): Promise => { +): Promise => { const { data: { dataViews }, dashboardContentManagement: { loadDashboardState }, @@ -75,11 +75,13 @@ export const createDashboard = async ( // -------------------------------------------------------------------------------------- // Initialize Dashboard integrations // -------------------------------------------------------------------------------------- - const { input, searchSessionId } = await initializeDashboard({ + const initializeResult = await initializeDashboard({ loadDashboardReturn: savedObjectResult, untilDashboardReady, creationOptions, }); + if (!initializeResult) return; + const { input, searchSessionId } = initializeResult; // -------------------------------------------------------------------------------------- // Build and return the dashboard container. @@ -140,13 +142,12 @@ export const initializeDashboard = async ({ // -------------------------------------------------------------------------------------- // Run validation. // -------------------------------------------------------------------------------------- - if ( - loadDashboardReturn && - validateLoadedSavedObject && - !validateLoadedSavedObject(loadDashboardReturn) - ) { + const validationResult = loadDashboardReturn && validateLoadedSavedObject?.(loadDashboardReturn); + if (validationResult === 'invalid') { // throw error to stop the rest of Dashboard loading and make the factory return an ErrorEmbeddable. throw new Error('Dashboard failed saved object result validation'); + } else if (validationResult === 'redirected') { + return; } // -------------------------------------------------------------------------------------- diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 40b67faf67ed6..d1f3c7d57bb40 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -380,12 +380,14 @@ export class DashboardContainer extends Container boolean; + validateLoadedSavedObject?: (result: LoadDashboardReturn) => 'valid' | 'invalid' | 'redirected'; isEmbeddedExternally?: boolean; } @@ -95,7 +95,7 @@ export class DashboardContainerFactoryDefinition parent?: Container, creationOptions?: DashboardCreationOptions, savedObjectId?: string - ): Promise => { + ): Promise => { const dashboardCreationStartTime = performance.now(); const { createDashboard } = await import('./create/create_dashboard'); try { diff --git a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx index 2a10b4dedb79e..c36f7fd30ba38 100644 --- a/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx +++ b/src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx @@ -96,8 +96,8 @@ export const DashboardRenderer = forwardRef { const creationOptions = await getCreationOptions?.(); @@ -115,14 +115,14 @@ export const DashboardRenderer = forwardRef