diff --git a/.betterer.results b/.betterer.results
index 556d0c991c4..e18c7cf5220 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -2582,7 +2582,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
],
"public/app/features/dashboard-scene/pages/DashboardScenePage.tsx:5381": [
- [0, 0, 0, "Do not use any type assertions.", "0"]
+ [0, 0, 0, "Do not use any type assertions.", "0"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
+ [0, 0, 0, "Do not use any type assertions.", "2"]
],
"public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx
index dcd13745076..72107998d74 100644
--- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx
+++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx
@@ -1,5 +1,6 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { cloneDeep } from 'lodash';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
@@ -7,11 +8,12 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { PanelProps } from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { config, getPluginLinkExtensions, locationService, setPluginImportUtils } from '@grafana/runtime';
+import { Dashboard } from '@grafana/schema';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
-
-import { setupLoadDashboardMock } from '../utils/test-utils';
+import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
import { DashboardScenePage, Props } from './DashboardScenePage';
+import { getDashboardScenePageStateManager } from './DashboardScenePageStateManager';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
@@ -32,7 +34,7 @@ function setup() {
const props: Props = {
...getRouteComponentProps(),
};
- props.match.params.uid = 'd10';
+ props.match.params.uid = 'my-dash-uid';
const renderResult = render(
@@ -40,12 +42,22 @@ function setup() {
);
- return { renderResult, context };
+ const rerender = (newProps: Props) => {
+ renderResult.rerender(
+
+
+
+ );
+ };
+
+ return { rerender, context, props };
}
-const simpleDashboard = {
+const simpleDashboard: Dashboard = {
title: 'My cool dashboard',
- uid: '10d',
+ uid: 'my-dash-uid',
+ schemaVersion: 30,
+ version: 1,
panels: [
{
id: 1,
@@ -94,10 +106,20 @@ setPluginImportUtils({
getPanelPluginFromCache: (id: string) => undefined,
});
+const loadDashboardMock = jest.fn();
+
+setDashboardLoaderSrv({
+ loadDashboard: loadDashboardMock,
+ // disabling type checks since this is a test util
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+} as unknown as DashboardLoaderSrv);
+
describe('DashboardScenePage', () => {
beforeEach(() => {
locationService.push('/');
- setupLoadDashboardMock({ dashboard: simpleDashboard, meta: {} });
+ getDashboardScenePageStateManager().clearDashboardCache();
+ loadDashboardMock.mockClear();
+ loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: {} });
// hacky way because mocking autosizer does not work
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 1000 });
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 1000 });
@@ -117,6 +139,27 @@ describe('DashboardScenePage', () => {
expect(await screen.findByText('Content B')).toBeInTheDocument();
});
+ it('routeReloadCounter should trigger reload', async () => {
+ const { rerender, props } = setup();
+
+ await waitForDashbordToRender();
+
+ expect(await screen.findByTitle('Panel A')).toBeInTheDocument();
+
+ const updatedDashboard = cloneDeep(simpleDashboard);
+ updatedDashboard.version = 11;
+ updatedDashboard.panels![0].title = 'Updated title';
+
+ getDashboardScenePageStateManager().clearDashboardCache();
+ loadDashboardMock.mockResolvedValue({ dashboard: updatedDashboard, meta: {} });
+
+ props.history.location.state = { routeReloadCounter: 1 };
+
+ rerender(props);
+
+ expect(await screen.findByTitle('Updated title')).toBeInTheDocument();
+ });
+
it('Can inspect panel', async () => {
setup();
diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx
index 19251e72e38..97d683d92ab 100644
--- a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx
+++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx
@@ -12,9 +12,11 @@ import { getDashboardScenePageStateManager } from './DashboardScenePageStateMana
export interface Props extends GrafanaRouteComponentProps {}
-export function DashboardScenePage({ match, route, queryParams }: Props) {
+export function DashboardScenePage({ match, route, queryParams, history }: Props) {
const stateManager = getDashboardScenePageStateManager();
const { dashboard, isLoading, loadError } = stateManager.useState();
+ // After scene migration is complete and we get rid of old dashboard we should refactor dashboardWatcher so this route reload is not need
+ const routeReloadCounter = (history.location.state as any)?.routeReloadCounter;
useEffect(() => {
stateManager.loadDashboard({
@@ -26,7 +28,7 @@ export function DashboardScenePage({ match, route, queryParams }: Props) {
return () => {
stateManager.clearState();
};
- }, [stateManager, match.params.uid, route.routeName, queryParams.folderUid]);
+ }, [stateManager, match.params.uid, route.routeName, queryParams.folderUid, routeReloadCounter]);
if (!dashboard) {
return (
diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
index 56c0dee4992..3516e56a463 100644
--- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
+++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
@@ -80,7 +80,6 @@ export class DashboardScenePageStateManager extends StateManagerBase {
this.startTrackingChanges();
}
+ if (!this.state.meta.isEmbedded && this.state.uid) {
+ dashboardWatcher.watch(this.state.uid);
+ }
+
const clearKeyBindings = setupKeyboardShortcuts(this);
const oldDashboardWrapper = new DashboardModelCompatibilityWrapper(this);
@@ -149,6 +154,7 @@ export class DashboardScene extends SceneObjectBase {
this.stopTrackingChanges();
this.stopUrlSync();
oldDashboardWrapper.destroy();
+ dashboardWatcher.leave();
};
}
diff --git a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
index 81f3afa51b3..3bcf30bcc4d 100644
--- a/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
+++ b/public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts
@@ -228,6 +228,10 @@ export class DashboardModelCompatibilityWrapper {
this.events.removeAllListeners();
this._subs.unsubscribe();
}
+
+ public hasUnsavedChanges() {
+ return this._scene.state.isDirty;
+ }
}
class PanelCompatibilityWrapper {