mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Reload when someone else saves changes (#81855)
* DashboardScene: Reload when someone else saves changes * Update public/app/features/dashboard-scene/pages/DashboardScenePage.tsx Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> * Fixes --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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(
|
||||
<TestProvider grafanaContext={context}>
|
||||
@@ -40,12 +42,22 @@ function setup() {
|
||||
</TestProvider>
|
||||
);
|
||||
|
||||
return { renderResult, context };
|
||||
const rerender = (newProps: Props) => {
|
||||
renderResult.rerender(
|
||||
<TestProvider grafanaContext={context}>
|
||||
<DashboardScenePage {...newProps} />
|
||||
</TestProvider>
|
||||
);
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import { getDashboardScenePageStateManager } from './DashboardScenePageStateMana
|
||||
|
||||
export interface Props extends GrafanaRouteComponentProps<DashboardPageRouteParams, DashboardPageRouteSearchParams> {}
|
||||
|
||||
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 (
|
||||
|
||||
@@ -80,7 +80,6 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||
break;
|
||||
default:
|
||||
rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid);
|
||||
|
||||
if (route === DashboardRoutes.Embedded) {
|
||||
rsp.meta.isEmbedded = true;
|
||||
}
|
||||
@@ -188,6 +187,10 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||
public setDashboardCache(cacheKey: string, dashboard: DashboardDTO) {
|
||||
this.dashboardCache = { dashboard, ts: Date.now(), cacheKey };
|
||||
}
|
||||
|
||||
public clearDashboardCache() {
|
||||
this.dashboardCache = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let stateManager: DashboardScenePageStateManager | null = null;
|
||||
|
||||
@@ -23,6 +23,7 @@ import appEvents from 'app/core/app_events';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
|
||||
|
||||
@@ -136,6 +137,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
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<DashboardSceneState> {
|
||||
this.stopTrackingChanges();
|
||||
this.stopUrlSync();
|
||||
oldDashboardWrapper.destroy();
|
||||
dashboardWatcher.leave();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -228,6 +228,10 @@ export class DashboardModelCompatibilityWrapper {
|
||||
this.events.removeAllListeners();
|
||||
this._subs.unsubscribe();
|
||||
}
|
||||
|
||||
public hasUnsavedChanges() {
|
||||
return this._scene.state.isDirty;
|
||||
}
|
||||
}
|
||||
|
||||
class PanelCompatibilityWrapper {
|
||||
|
||||
Reference in New Issue
Block a user