diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap new file mode 100644 index 000000000000..d433fdd10b52 --- /dev/null +++ b/src/plugins/dashboard/public/application/components/dashboard_listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -0,0 +1,5400 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`dashboard listing hideWriteControls 1`] = ` + + + + + + + + + } + /> + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + Object { + "data-test-subj": "updated-at", + "dataType": "date", + "description": "Last update of the saved object", + "field": "updated_at", + "name": "Last updated", + "render": [Function], + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + > + +
+ +
+ +
+
+
+
+
+
+
+
+`; + +exports[`dashboard listing render table listing with initial filters from URL 1`] = ` + + + + + + + } + createItem={[Function]} + deleteItems={[Function]} + editItem={[Function]} + entityName="dashboard" + entityNamePlural="dashboards" + findItems={[Function]} + headingId="dashboardListingHeading" + initialFilter="dashboard" + initialPageSize={10} + listingLimit={100} + noItemsFragment={ + + + + } + body={ + +

+ +

+

+ + + , + } + } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ +

+ } + /> + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + Object { + "data-test-subj": "updated-at", + "dataType": "date", + "description": "Last update of the saved object", + "field": "updated_at", + "name": "Last updated", + "render": [Function], + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + > + +
+ +
+ +
+
+
+
+
+
+
+
+`; + +exports[`dashboard listing renders call to action when no dashboards exist 1`] = ` + + + + + + + } + createItem={[Function]} + deleteItems={[Function]} + editItem={[Function]} + entityName="dashboard" + entityNamePlural="dashboards" + findItems={[Function]} + headingId="dashboardListingHeading" + initialFilter="" + initialPageSize={10} + listingLimit={100} + noItemsFragment={ + + + + } + body={ + +

+ +

+

+ + + , + } + } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ +

+ } + /> + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + Object { + "data-test-subj": "updated-at", + "dataType": "date", + "description": "Last update of the saved object", + "field": "updated_at", + "name": "Last updated", + "render": [Function], + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + > + +
+ +
+ +
+
+
+
+
+
+
+
+`; + +exports[`dashboard listing renders table rows 1`] = ` + + + + + + + } + createItem={[Function]} + deleteItems={[Function]} + editItem={[Function]} + entityName="dashboard" + entityNamePlural="dashboards" + findItems={[Function]} + headingId="dashboardListingHeading" + initialFilter="" + initialPageSize={10} + listingLimit={100} + noItemsFragment={ + + + + } + body={ + +

+ +

+

+ + + , + } + } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ +

+ } + /> + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + Object { + "data-test-subj": "updated-at", + "dataType": "date", + "description": "Last update of the saved object", + "field": "updated_at", + "name": "Last updated", + "render": [Function], + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + > + +
+ +
+ +
+
+
+
+
+
+
+
+`; + +exports[`dashboard listing renders warning when listingLimit is exceeded 1`] = ` + + + + + + + } + createItem={[Function]} + deleteItems={[Function]} + editItem={[Function]} + entityName="dashboard" + entityNamePlural="dashboards" + findItems={[Function]} + headingId="dashboardListingHeading" + initialFilter="" + initialPageSize={10} + listingLimit={1} + noItemsFragment={ + + + + } + body={ + +

+ +

+

+ + + , + } + } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ +

+ } + /> + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "type", + "name": "Type", + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + Object { + "data-test-subj": "updated-at", + "dataType": "date", + "description": "Last update of the saved object", + "field": "updated_at", + "name": "Last updated", + "render": [Function], + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } + } + > + +
+ +
+ +
+
+
+
+
+
+
+
+`; diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/components/dashboard_listing/dashboard_listing.test.tsx index 3d9c8404be5e..edbd0298876b 100644 --- a/src/plugins/dashboard/public/application/components/dashboard_listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/application/components/dashboard_listing/dashboard_listing.test.tsx @@ -9,28 +9,6 @@ * GitHub history for details. */ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// TODO: -// Rewrite the dashboard listing tests for the new component -// https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4051 jest.mock( 'lodash', () => ({ @@ -46,63 +24,85 @@ jest.mock( { virtual: true } ); +let mockURLsearch = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ + search: mockURLsearch, + pathname: '', + hash: '', + state: undefined, + }), +})); + import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { DashboardListing } from './dashboard_listing'; - -const find = (num: number) => { - const hits = []; - for (let i = 0; i < num; i++) { - hits.push({ - id: `dashboard${i}`, - title: `dashboard${i} title`, - description: `dashboard${i} desc`, - }); - } - return Promise.resolve({ - total: num, - hits, - }); -}; - -test.skip('renders empty page in before initial fetch to avoid flickering', () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - initialPageSize={10} - listingLimit={1000} - hideWriteControls={false} - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> +import { createDashboardServicesMock } from '../../utils/mocks'; +import { OpenSearchDashboardsContextProvider } from 'src/plugins/opensearch_dashboards_react/public'; +import { I18nProvider } from '@osd/i18n/react'; +import { IOsdUrlStateStorage } from 'src/plugins/opensearch_dashboards_utils/public'; + +function wrapDashboardListingInContext(mockServices: any) { + const osdUrlStateStorage = ({ + set: jest.fn(), + get: jest.fn(() => ({ linked: false })), + flush: jest.fn(), + } as unknown) as IOsdUrlStateStorage; + const services = { + ...mockServices, + osdUrlStateStorage, + dashboardProviders: () => { + return { + dashboard: { + appId: '1', + savedObjectsName: 'dashboardSavedObjects', + viewUrlPathFn: jest.fn(), + editUrlPathFn: jest.fn(), + }, + }; + }, + }; + + return ( + + + + + ); - expect(component).toMatchSnapshot(); -}); +} + +describe('dashboard listing', () => { + let mockServices: any; + + beforeEach(() => { + mockServices = createDashboardServicesMock(); + mockServices.savedObjectsClient.find = () => { + const hits: any[] = []; + for (let i = 0; i < 2; i++) { + hits.push({ + type: `dashboard`, + id: `dashboard${i}`, + attributes: { + title: `dashboard${i}`, + description: `dashboard${i} desc`, + }, + }); + } + return Promise.resolve({ + savedObjects: hits, + }); + }; + mockServices.dashboardConfig.getHideWriteControls = () => false; + mockServices.savedObjectsPublic.settings.getListingLimit = () => 100; + }); -describe.skip('after fetch', () => { - test('initialFilter', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - listingLimit={1000} - hideWriteControls={false} - initialPageSize={10} - initialFilter="my dashboard" - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> - ); + test('renders table rows', async () => { + const component = mount(wrapDashboardListingInContext(mockServices)); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -112,22 +112,16 @@ describe.skip('after fetch', () => { expect(component).toMatchSnapshot(); }); - test('renders table rows', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - listingLimit={1000} - initialPageSize={10} - hideWriteControls={false} - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> - ); + test('renders call to action when no dashboards exist', async () => { + // savedObjectsClient.find() needs to find no dashboard + mockServices.savedObjectsClient.find = () => { + const hits: any[] = []; + return Promise.resolve({ + total: 0, + hits, + }); + }; + const component = mount(wrapDashboardListingInContext(mockServices)); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -137,22 +131,12 @@ describe.skip('after fetch', () => { expect(component).toMatchSnapshot(); }); - test('renders call to action when no dashboards exist', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - listingLimit={1} - initialPageSize={10} - hideWriteControls={false} - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> - ); + test('hideWriteControls', async () => { + // dashboardConfig.getHideWriteControls() to true + mockServices.dashboardConfig.getHideWriteControls = () => { + return true; + }; + const component = mount(wrapDashboardListingInContext(mockServices)); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -162,22 +146,10 @@ describe.skip('after fetch', () => { expect(component).toMatchSnapshot(); }); - test('hideWriteControls', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - listingLimit={1} - initialPageSize={10} - hideWriteControls={true} - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> - ); + test('renders warning when listingLimit is exceeded', async () => { + mockServices.savedObjectsPublic.settings.getListingLimit = () => 1; + + const component = mount(wrapDashboardListingInContext(mockServices)); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -187,22 +159,11 @@ describe.skip('after fetch', () => { expect(component).toMatchSnapshot(); }); - test('renders warning when listingLimit is exceeded', async () => { - const component = shallow( - {}} - createItem={() => {}} - editItem={() => {}} - viewItem={() => {}} - dashboardItemCreatorClickHandler={() => {}} - dashboardItemCreators={() => []} - listingLimit={1} - initialPageSize={10} - hideWriteControls={false} - core={{ notifications: { toasts: {} }, uiSettings: { get: jest.fn(() => 10) } }} - /> - ); + test('render table listing with initial filters from URL', async () => { + mockURLsearch = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&filter=dashboard'; + + const component = mount(wrapDashboardListingInContext(mockServices)); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap new file mode 100644 index 000000000000..14247831d6fa --- /dev/null +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/__snapshots__/dashboard_top_nav.test.tsx.snap @@ -0,0 +1,5136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dashboard top nav render in embed mode 1`] = ` + + + + + + + + + + + +`; + +exports[`Dashboard top nav render in embed mode, and force hide filter bar 1`] = ` + + + + + + + + + + + +`; + +exports[`Dashboard top nav render in embed mode, components can be forced show by appending URL param 1`] = ` + + + + + + + + + + + +`; + +exports[`Dashboard top nav render in full screen mode with appended URL param but none of the componenets can be forced show 1`] = ` + + + + + + + + + + + +`; + +exports[`Dashboard top nav render in full screen mode, no componenets should be shown 1`] = ` + + + + + + + + + + + +`; + +exports[`Dashboard top nav render with all components 1`] = ` + + + + + + + + + + + +`; diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx new file mode 100644 index 000000000000..3dafdf447d45 --- /dev/null +++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav/dashboard_top_nav.test.tsx @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +jest.mock( + 'lodash', + () => ({ + ...jest.requireActual('lodash'), + // mock debounce to fire immediately with no internal timer + debounce: (func: any) => { + function debounced(this: any, ...args: any[]) { + return func.apply(this, args); + } + return debounced; + }, + }), + { virtual: true } +); + +import { I18nProvider } from '@osd/i18n/react'; +import { OpenSearchDashboardsContextProvider } from 'src/plugins/opensearch_dashboards_react/public'; +import { DashboardTopNav } from './dashboard_top_nav'; +import React from 'react'; +import { DashboardAppState, DashboardAppStateContainer } from '../../../types'; +import { Dashboard } from '../../../dashboard'; +import { DashboardContainer } from '../../embeddable'; +import { createDashboardServicesMock } from '../../utils/mocks'; +import { mount } from 'enzyme'; +import { TopNavMenu } from 'src/plugins/navigation/public'; +import { dashboardAppStateStub } from '../../utils/stubs'; + +let mockURL = '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ + search: mockURL, + pathname: '', + hash: '', + state: undefined, + }), +})); + +function wrapDashboardTopNavInContext(mockServices: any, currentState: DashboardAppState) { + const services = { + ...mockServices, + dashboardCapabilities: { + saveQuery: true, + }, + navigation: { + ui: { TopNavMenu }, + }, + }; + + const topNavProps = { + isChromeVisible: false, + savedDashboardInstance: {}, + appState: { + getState: () => currentState, + } as DashboardAppStateContainer, + dashboard: {} as Dashboard, + currentAppState: currentState, + isEmbeddableRendered: true, + currentContainer: {} as DashboardContainer, + indexPatterns: [], + dashboardIdFromUrl: '', + }; + + return ( + + + + + + ); +} + +describe('Dashboard top nav', () => { + let mockServices: any; + let currentState: DashboardAppState; + + beforeEach(() => { + mockServices = createDashboardServicesMock(); + currentState = dashboardAppStateStub; + }); + + test('render with all components', async () => { + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('render in full screen mode, no componenets should be shown', async () => { + currentState.fullScreenMode = true; + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('render in full screen mode with appended URL param but none of the componenets can be forced show', async () => { + currentState.fullScreenMode = true; + mockURL = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&show-time-filter=true'; + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('render in embed mode', async () => { + mockURL = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&embed=true'; + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('render in embed mode, components can be forced show by appending URL param', async () => { + mockURL = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&embed=true&show-time-filter=true'; + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('render in embed mode, and force hide filter bar', async () => { + mockURL = + '?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&embed=true&hide-filter-bar=true'; + const component = mount(wrapDashboardTopNavInContext(mockServices, currentState)); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/dashboard/public/application/utils/mocks.ts b/src/plugins/dashboard/public/application/utils/mocks.ts index 9c2dfc30a184..84720b0bcbc4 100644 --- a/src/plugins/dashboard/public/application/utils/mocks.ts +++ b/src/plugins/dashboard/public/application/utils/mocks.ts @@ -34,5 +34,14 @@ export const createDashboardServicesMock = () => { opensearchDashboardsVersion, usageCollection, embeddable, + savedObjectsClient: { + find: jest.fn(), + }, + savedObjectsPublic: { + settings: { + getPerPage: () => 10, + getListingLimit: jest.fn(), + }, + }, } as unknown) as jest.Mocked; }; diff --git a/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.test.ts b/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.test.ts index 1d2c661876e2..f1a83cf4d8a7 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.test.ts +++ b/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.test.ts @@ -87,7 +87,7 @@ describe('useDashboardAppAndGlobalState', () => { expect(createDashboardGlobalAndAppState).toHaveBeenCalledWith({ services: mockServices, stateDefaults: dashboardAppStateStub, - osdUrlStateStorage: undefined, + osdUrlStateStorage: mockServices.osdUrlStateStorage, savedDashboardInstance, }); expect(mockServices.data.query.filterManager.setAppFilters).toHaveBeenCalledWith( diff --git a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts index 47c5b44fe7e5..7cc0a46b2a55 100644 --- a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts +++ b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts @@ -146,6 +146,18 @@ export const useSavedDashboardInstance = ({ return; } + /* + * The else if block prevents error when user clicks on one dashboard, + * and before it loads the next screen, user clicks a different dashboard right after. + * In this situation, there can be two useSavedDashboardInstance() executing, one shortly after another. + * The first running instance might already set the dashboardId before the second running instance + * execute the following if else block. + * The second running will go into the else if block because dashboardId + * is already set but it is a different value than dashboardIdFromUrl and savedDashboardInstance?.savedDashboard?.id. + * Therefore, to avoid errors and to actually load the second dashboard correctly, + * we need to reset the state by calling setSavedDashboardInstance({}) + * and then called getSavedDashboardInstance() again using the current dashboardIdFromUrl value. + */ if (!dashboardId.current) { dashboardId.current = dashboardIdFromUrl || 'new'; getSavedDashboardInstance();