DashboardReload: Refactor / move state diffing out of DashboardPageStateManager and into reload behavior, simplify (#106039)
* DashboardReload refactor * Update * revert accidental commit * fix * merge fix * fix cache issue * Update scenes * Update * Update * revert debug * Update scenes * Update tests * add back reload on activation, fix infinite reload bug * Update prevState * Remove time range subscription * restore iso time format * Update scenes * Mock console for reload tests (for now) * Only update scopes from URL when scopes in URL change * fix equality check inside ScopesSelectorService * branch fix drone * Update * revert drone verbosity * Update scenes --------- Co-authored-by: Victor Marin <victor.marin@grafana.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
co-authored by
Victor Marin
Dominik Prokop
parent
f34d372bd3
commit
1b9d062583
+2
-2
@@ -284,8 +284,8 @@
|
||||
"@grafana/plugin-ui": "0.10.6",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^6.12.0",
|
||||
"@grafana/scenes-react": "^6.12.0",
|
||||
"@grafana/scenes": "^6.18.0",
|
||||
"@grafana/scenes-react": "^6.18.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -112,7 +112,7 @@ describe('DashboardScenePageStateManager v1', () => {
|
||||
const loader = new DashboardScenePageStateManager({});
|
||||
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', undefined);
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash');
|
||||
|
||||
// should use cache second time
|
||||
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||
@@ -664,8 +664,8 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
getDashSpy
|
||||
);
|
||||
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await loader.reloadDashboard(options);
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await loader.reloadDashboard(params);
|
||||
|
||||
expect(getDashSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loader.state.dashboard?.state.version).toBe(2);
|
||||
@@ -695,28 +695,11 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
expect(getDashSpy).toHaveBeenCalledTimes(1);
|
||||
const initialDashboard = loader.state.dashboard;
|
||||
|
||||
const mockDashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
access: {},
|
||||
apiVersion: 'v2alpha1',
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
metadata: {
|
||||
name: 'fake-dash',
|
||||
creationTimestamp: '',
|
||||
resourceVersion: '1',
|
||||
generation: 1,
|
||||
},
|
||||
spec: { ...defaultDashboardV2Spec() },
|
||||
};
|
||||
const params = { version: 1, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await loader.reloadDashboard(params);
|
||||
|
||||
const fetchDashboardSpy = jest.spyOn(loader, 'fetchDashboard').mockResolvedValue(mockDashboard);
|
||||
|
||||
const options = { version: 1, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await loader.reloadDashboard(options);
|
||||
|
||||
expect(fetchDashboardSpy).toHaveBeenCalledTimes(1);
|
||||
expect(getDashSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loader.state.dashboard).toBe(initialDashboard);
|
||||
|
||||
fetchDashboardSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not use cache if cache version and current dashboard state version differ', async () => {
|
||||
@@ -742,32 +725,32 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
|
||||
expect(getDashSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const mockDashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
access: {},
|
||||
apiVersion: 'v2alpha1',
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
metadata: {
|
||||
name: 'fake-dash',
|
||||
creationTimestamp: '',
|
||||
resourceVersion: '1',
|
||||
generation: 2,
|
||||
setupDashboardAPI(
|
||||
{
|
||||
access: {},
|
||||
apiVersion: 'v2alpha1',
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
metadata: {
|
||||
name: 'fake-dash',
|
||||
creationTimestamp: '',
|
||||
resourceVersion: '1',
|
||||
generation: 2,
|
||||
},
|
||||
spec: { ...defaultDashboardV2Spec() },
|
||||
},
|
||||
spec: { ...defaultDashboardV2Spec() },
|
||||
};
|
||||
|
||||
const fetchDashboardSpy = jest.spyOn(loader, 'fetchDashboard').mockResolvedValue(mockDashboard);
|
||||
getDashSpy
|
||||
);
|
||||
|
||||
// mimic navigating from db1 to db2 and then back to db1, which maintains the cache. but on
|
||||
// db1 load the initial version will be 1. Since the cache is set we also need to verify against the
|
||||
// current dashboard state whether we should reload or not
|
||||
loader.setSceneCache('fake-dash', loader.state.dashboard!.clone({ version: 2 }));
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await loader.reloadDashboard(options);
|
||||
|
||||
expect(fetchDashboardSpy).toHaveBeenCalledTimes(1);
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await loader.reloadDashboard(params);
|
||||
|
||||
expect(getDashSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loader.state.dashboard?.state.version).toBe(2);
|
||||
|
||||
fetchDashboardSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle errors during reload', async () => {
|
||||
@@ -797,8 +780,8 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
|
||||
loader['dashboardLoader'] = mockLoader as unknown as DashboardLoaderSrvV2;
|
||||
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await loader.reloadDashboard(options);
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await loader.reloadDashboard(params);
|
||||
|
||||
expect(loader.state.loadError).toBeDefined();
|
||||
expect(loader.state.loadError?.message).toBe('Failed to load dashboard');
|
||||
@@ -831,9 +814,9 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
|
||||
loader['dashboardLoader'] = mockLoader as unknown as DashboardLoaderSrvV2;
|
||||
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
|
||||
await expect(loader.reloadDashboard(options)).rejects.toThrow(DashboardVersionError);
|
||||
await expect(loader.reloadDashboard(params)).rejects.toThrow(DashboardVersionError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -852,7 +835,7 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
const manager = new UnifiedDashboardScenePageStateManager({});
|
||||
await manager.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', undefined);
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash');
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager);
|
||||
});
|
||||
|
||||
@@ -892,7 +875,7 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2);
|
||||
|
||||
// Reload should now work with v2 manager
|
||||
const options = { version: 1, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
const params = { version: 1, scopes: [], from: 'now-1h', to: 'now' };
|
||||
|
||||
// Mock the fetchDashboard method to return a dashboard
|
||||
const v2Manager = manager['activeManager'] as DashboardScenePageStateManagerV2;
|
||||
@@ -910,7 +893,7 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
spec: { ...defaultDashboardV2Spec() },
|
||||
});
|
||||
|
||||
await manager.reloadDashboard(options);
|
||||
await manager.reloadDashboard(params);
|
||||
|
||||
// Restore the original method
|
||||
v2Manager.fetchDashboard = originalFetchDashboard;
|
||||
@@ -959,21 +942,16 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
|
||||
await manager.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', undefined);
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash');
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager);
|
||||
|
||||
loadDashboardMock.mockClear();
|
||||
const reloadMock = jest.fn();
|
||||
manager['activeManager'].reloadDashboard = reloadMock;
|
||||
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await manager.reloadDashboard(options);
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await manager.reloadDashboard(params);
|
||||
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager);
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
version: 2,
|
||||
scopes: [],
|
||||
});
|
||||
expect(reloadMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should reload v2 dashboard with v2 manager', async () => {
|
||||
@@ -984,9 +962,10 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2);
|
||||
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
|
||||
try {
|
||||
await manager.reloadDashboard(options);
|
||||
await manager.reloadDashboard(params);
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect((e as Error).message).toBe('Method not implemented.');
|
||||
@@ -1004,11 +983,9 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
const manager = new UnifiedDashboardScenePageStateManager({});
|
||||
await manager.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', undefined);
|
||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash');
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager);
|
||||
|
||||
loadDashboardMock.mockClear();
|
||||
|
||||
const mockDashboard: DashboardDTO = {
|
||||
dashboard: {
|
||||
uid: 'fake-dash',
|
||||
@@ -1018,19 +995,18 @@ describe('UnifiedDashboardScenePageStateManager', () => {
|
||||
meta: {},
|
||||
};
|
||||
|
||||
const fetchDashboardSpy = jest.spyOn(manager['activeManager'], 'fetchDashboard').mockResolvedValue(mockDashboard);
|
||||
loadDashboardMock.mockResolvedValue(mockDashboard);
|
||||
|
||||
// mimic navigating from db1 to db2 and then back to db1, which maintains the cache. but on
|
||||
// db1 load the initial version will be 1. Since the cache is set we also need to verify against the
|
||||
// current dashboard state whether we should reload or not
|
||||
manager.setSceneCache('fake-dash', manager.state.dashboard!.clone({ version: 2 }));
|
||||
const options = { version: 2, scopes: [], timeRange: { from: 'now-1h', to: 'now' }, variables: {} };
|
||||
await manager.reloadDashboard(options);
|
||||
|
||||
expect(fetchDashboardSpy).toHaveBeenCalledTimes(1);
|
||||
const params = { version: 2, scopes: [], from: 'now-1h', to: 'now' };
|
||||
await manager.reloadDashboard(params);
|
||||
|
||||
expect(loadDashboardMock).toHaveBeenCalledTimes(2);
|
||||
expect(manager.state.dashboard?.state.version).toBe(2);
|
||||
|
||||
fetchDashboardSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { locationUtil, UrlQueryMap } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n/internal';
|
||||
import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
@@ -48,7 +46,6 @@ export interface LoadError {
|
||||
|
||||
export interface DashboardScenePageState {
|
||||
dashboard?: DashboardScene;
|
||||
options?: LoadDashboardOptions;
|
||||
panelEditor?: PanelEditor;
|
||||
isLoading?: boolean;
|
||||
loadError?: LoadError;
|
||||
@@ -73,15 +70,6 @@ export interface LoadDashboardOptions {
|
||||
slug?: string;
|
||||
type?: string;
|
||||
urlFolderUid?: string;
|
||||
params?: {
|
||||
version: number;
|
||||
scopes: string[];
|
||||
timeRange: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
variables: UrlQueryMap;
|
||||
};
|
||||
}
|
||||
|
||||
export type HomeDashboardDTO = DashboardDTO & {
|
||||
@@ -93,7 +81,7 @@ interface DashboardScenePageStateManagerLike<T> {
|
||||
getDashboardFromCache(cacheKey: string): T | null;
|
||||
loadDashboard(options: LoadDashboardOptions): Promise<void>;
|
||||
transformResponseToScene(rsp: T | null, options: LoadDashboardOptions): DashboardScene | null;
|
||||
reloadDashboard(params: LoadDashboardOptions['params']): Promise<void>;
|
||||
reloadDashboard(queryParams: UrlQueryMap): Promise<void>;
|
||||
loadSnapshot(slug: string): Promise<void>;
|
||||
setDashboardCache(cacheKey: string, dashboard: T): void;
|
||||
clearSceneCache(): void;
|
||||
@@ -108,7 +96,7 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
implements DashboardScenePageStateManagerLike<T>
|
||||
{
|
||||
abstract fetchDashboard(options: LoadDashboardOptions): Promise<T | null>;
|
||||
abstract reloadDashboard(params: LoadDashboardOptions['params']): Promise<void>;
|
||||
abstract reloadDashboard(queryParams: UrlQueryMap): Promise<void>;
|
||||
abstract transformResponseToScene(rsp: T | null, options: LoadDashboardOptions): DashboardScene | null;
|
||||
abstract loadSnapshotScene(slug: string): Promise<DashboardScene>;
|
||||
|
||||
@@ -274,7 +262,7 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
restoreDashboardStateFromLocalStorage(dashboard);
|
||||
}
|
||||
|
||||
this.setState({ dashboard: dashboard, isLoading: false, options });
|
||||
this.setState({ dashboard: dashboard, isLoading: false });
|
||||
const measure = stopMeasure(LOAD_SCENE_MEASUREMENT);
|
||||
const queryController = sceneGraph.getQueryController(dashboard);
|
||||
|
||||
@@ -418,16 +406,13 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
uid,
|
||||
route,
|
||||
urlFolderUid,
|
||||
params,
|
||||
}: LoadDashboardOptions): Promise<DashboardDTO | null> {
|
||||
const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid;
|
||||
|
||||
if (!params) {
|
||||
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||
|
||||
if (cachedDashboard) {
|
||||
return cachedDashboard;
|
||||
}
|
||||
if (cachedDashboard) {
|
||||
return cachedDashboard;
|
||||
}
|
||||
|
||||
let rsp: DashboardDTO;
|
||||
@@ -454,17 +439,7 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
return await dashboardLoaderSrv.loadDashboard('public', '', uid);
|
||||
}
|
||||
default:
|
||||
const queryParams = params
|
||||
? {
|
||||
version: params.version,
|
||||
scopes: params.scopes,
|
||||
from: params.timeRange.from,
|
||||
to: params.timeRange.to,
|
||||
...params.variables,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
rsp = await dashboardLoaderSrv.loadDashboard(type || 'db', slug || '', uid, queryParams);
|
||||
rsp = await dashboardLoaderSrv.loadDashboard(type || 'db', slug || '', uid);
|
||||
|
||||
if (route === DashboardRoutes.Embedded) {
|
||||
rsp.meta.isEmbedded = true;
|
||||
@@ -504,30 +479,20 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
return rsp;
|
||||
}
|
||||
|
||||
public async reloadDashboard(params: LoadDashboardOptions['params']) {
|
||||
const stateOptions = this.state.options;
|
||||
public async reloadDashboard(queryParams: UrlQueryMap): Promise<void> {
|
||||
const dashboard = this.state.dashboard;
|
||||
|
||||
if (!stateOptions) {
|
||||
if (!dashboard || !dashboard.state.uid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
...stateOptions,
|
||||
params,
|
||||
};
|
||||
|
||||
if (
|
||||
isEqual(options.params?.variables, stateOptions.params?.variables) &&
|
||||
isEqual(options.params?.scopes, stateOptions.params?.scopes)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const uid = dashboard.state.uid;
|
||||
|
||||
try {
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
const rsp = await this.fetchDashboard(options);
|
||||
const fromCache = this.getSceneFromCache(options.uid);
|
||||
const rsp = await dashboardLoaderSrv.loadDashboard('db', dashboard.state.meta.slug, uid, queryParams);
|
||||
const fromCache = this.getSceneFromCache(uid);
|
||||
|
||||
// check if cached db version is same as both
|
||||
// response and current db state. There are scenarios where they can differ
|
||||
@@ -564,16 +529,16 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
const scene = transformSaveModelToScene(rsp);
|
||||
|
||||
// we need to call and restore dashboard state on every reload that pulls a new dashboard version
|
||||
if (config.featureToggles.preserveDashboardStateWhenNavigating && Boolean(options.uid)) {
|
||||
if (config.featureToggles.preserveDashboardStateWhenNavigating && Boolean(uid)) {
|
||||
restoreDashboardStateFromLocalStorage(scene);
|
||||
}
|
||||
|
||||
this.setSceneCache(options.uid, scene);
|
||||
|
||||
this.setState({ dashboard: scene, isLoading: false, options });
|
||||
this.setSceneCache(uid, scene);
|
||||
this.setState({ dashboard: scene, isLoading: false });
|
||||
} catch (err) {
|
||||
const status = getStatusFromError(err);
|
||||
const message = getMessageFromError(err);
|
||||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
loadError: {
|
||||
@@ -581,6 +546,7 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
status,
|
||||
},
|
||||
});
|
||||
|
||||
if (err instanceof DashboardVersionError) {
|
||||
throw err;
|
||||
}
|
||||
@@ -634,15 +600,14 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
uid,
|
||||
route,
|
||||
urlFolderUid,
|
||||
params,
|
||||
}: LoadDashboardOptions): Promise<DashboardWithAccessInfo<DashboardV2Spec> | null> {
|
||||
const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid;
|
||||
if (!params) {
|
||||
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||
if (cachedDashboard) {
|
||||
return cachedDashboard;
|
||||
}
|
||||
|
||||
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||
if (cachedDashboard) {
|
||||
return cachedDashboard;
|
||||
}
|
||||
|
||||
let rsp: DashboardWithAccessInfo<DashboardV2Spec>;
|
||||
try {
|
||||
switch (route) {
|
||||
@@ -656,16 +621,8 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
return await this.dashboardLoader.loadDashboard('public', '', uid);
|
||||
}
|
||||
default:
|
||||
const queryParams = params
|
||||
? {
|
||||
version: params.version,
|
||||
scopes: params.scopes,
|
||||
from: params.timeRange.from,
|
||||
to: params.timeRange.to,
|
||||
...params.variables,
|
||||
}
|
||||
: undefined;
|
||||
rsp = await this.dashboardLoader.loadDashboard(type || 'db', slug || '', uid, queryParams);
|
||||
rsp = await this.dashboardLoader.loadDashboard(type || 'db', slug || '', uid);
|
||||
|
||||
if (route === DashboardRoutes.Embedded) {
|
||||
throw new Error('Method not implemented.');
|
||||
// rsp.meta.isEmbedded = true;
|
||||
@@ -699,30 +656,20 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
return rsp;
|
||||
}
|
||||
|
||||
public async reloadDashboard(params: LoadDashboardOptions['params']): Promise<void> {
|
||||
const stateOptions = this.state.options;
|
||||
public async reloadDashboard(queryParams: UrlQueryMap): Promise<void> {
|
||||
const dashboard = this.state.dashboard;
|
||||
|
||||
if (!stateOptions) {
|
||||
if (!dashboard || !dashboard.state.uid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
...stateOptions,
|
||||
params,
|
||||
};
|
||||
|
||||
if (
|
||||
isEqual(options.params?.variables, stateOptions.params?.variables) &&
|
||||
isEqual(options.params?.scopes, stateOptions.params?.scopes)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const uid = dashboard.state.uid;
|
||||
|
||||
try {
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
const rsp = await this.fetchDashboard(options);
|
||||
const fromCache = this.getSceneFromCache(options.uid);
|
||||
const rsp = await this.dashboardLoader.loadDashboard('db', dashboard.state.meta.slug, uid, queryParams);
|
||||
const fromCache = this.getSceneFromCache(uid);
|
||||
|
||||
if (
|
||||
fromCache &&
|
||||
@@ -750,13 +697,13 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
const scene = transformSaveModelSchemaV2ToScene(rsp);
|
||||
|
||||
// we need to call and restore dashboard state on every reload that pulls a new dashboard version
|
||||
if (config.featureToggles.preserveDashboardStateWhenNavigating && Boolean(options.uid)) {
|
||||
if (config.featureToggles.preserveDashboardStateWhenNavigating && Boolean(uid)) {
|
||||
restoreDashboardStateFromLocalStorage(scene);
|
||||
}
|
||||
|
||||
this.setSceneCache(options.uid, scene);
|
||||
this.setSceneCache(uid, scene);
|
||||
|
||||
this.setState({ dashboard: scene, isLoading: false, options });
|
||||
this.setState({ dashboard: scene, isLoading: false });
|
||||
} catch (err) {
|
||||
const status = getStatusFromError(err);
|
||||
const message = getMessageFromError(err);
|
||||
@@ -815,8 +762,8 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta
|
||||
);
|
||||
}
|
||||
|
||||
public async reloadDashboard(params: LoadDashboardOptions['params']) {
|
||||
return this.withVersionHandling((manager) => manager.reloadDashboard.call(this, params));
|
||||
public async reloadDashboard(queryParams: UrlQueryMap) {
|
||||
return this.withVersionHandling((manager) => manager.reloadDashboard.call(this, queryParams));
|
||||
}
|
||||
|
||||
public getDashboardFromCache(uid: string) {
|
||||
@@ -864,6 +811,14 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
public getSceneFromCache(key: string) {
|
||||
return this.activeManager.getSceneFromCache(key);
|
||||
}
|
||||
|
||||
public setSceneCache(cacheKey: string, scene: DashboardScene): void {
|
||||
this.activeManager.setSceneCache(cacheKey, scene);
|
||||
}
|
||||
|
||||
public getCache() {
|
||||
return this.activeManager.getCache();
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import {
|
||||
sceneGraph,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneTimeRangeLike,
|
||||
sceneUtils,
|
||||
SceneVariable,
|
||||
VariableDependencyConfig,
|
||||
} from '@grafana/scenes';
|
||||
import { createLogger } from '@grafana/ui';
|
||||
|
||||
import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager';
|
||||
|
||||
@@ -16,12 +18,12 @@ import { DashboardScene } from './DashboardScene';
|
||||
export interface DashboardReloadBehaviorState extends SceneObjectState {
|
||||
reloadOnParamsChange?: boolean;
|
||||
uid?: string;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBehaviorState> {
|
||||
private _timeRange: SceneTimeRangeLike | undefined;
|
||||
private _dashboardScene: DashboardScene | undefined;
|
||||
private _prevState?: UrlQueryMap;
|
||||
private _log = createLogger('DashboardReloadBehavior');
|
||||
|
||||
constructor(state: DashboardReloadBehaviorState) {
|
||||
super(state);
|
||||
@@ -37,61 +39,70 @@ export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBeha
|
||||
return;
|
||||
}
|
||||
|
||||
this._timeRange = sceneGraph.getTimeRange(this);
|
||||
this._dashboardScene = sceneGraph.getAncestor(this, DashboardScene);
|
||||
|
||||
this._variableDependency = new VariableDependencyConfig(this, {
|
||||
onAnyVariableChanged: this.reloadDashboard,
|
||||
onAnyVariableChanged: (variable: SceneVariable) => {
|
||||
this._log.logger('onAnyVariableChanged', variable.state.name, JSON.stringify(variable.getValue()));
|
||||
this.reloadDashboard();
|
||||
},
|
||||
dependsOnScopes: true,
|
||||
});
|
||||
|
||||
this._subs.add(
|
||||
this._timeRange.subscribeToState((newState, prevState) => {
|
||||
if (!isEqual(newState.value, prevState.value)) {
|
||||
this.reloadDashboard();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.reloadDashboard();
|
||||
});
|
||||
}
|
||||
|
||||
private getCurrentState(): UrlQueryMap {
|
||||
const scopes = sceneGraph.getScopes(this) ?? [];
|
||||
const timeRange = sceneGraph.getTimeRange(this).state.value;
|
||||
|
||||
return {
|
||||
scopes: scopes.map((scope) => scope.metadata.name),
|
||||
from: timeRange.from.toISOString(),
|
||||
to: timeRange.to.toISOString(),
|
||||
...sceneUtils.getUrlState(this._dashboardScene?.state.$variables!),
|
||||
version: this._dashboardScene?.state.version,
|
||||
};
|
||||
}
|
||||
|
||||
private isEditing() {
|
||||
return !!this._dashboardScene?.state.isEditing;
|
||||
}
|
||||
|
||||
private isWaitingForVariables() {
|
||||
const varSet = sceneGraph.getVariables(this.parent!);
|
||||
|
||||
return varSet.state.variables.some((variable) => varSet.isVariableLoadingOrWaitingToUpdate(variable));
|
||||
}
|
||||
|
||||
private reloadDashboard() {
|
||||
if (this.isEditing() || this.isWaitingForVariables()) {
|
||||
this._log.logger('DashboardReloadBehavior reloadDashboard isEditing or waiting for variables, skipping reload');
|
||||
return;
|
||||
}
|
||||
|
||||
const newState = this.getCurrentState();
|
||||
const prevState = this._prevState ?? {};
|
||||
|
||||
// Ignore time range changes for now
|
||||
prevState.from = newState.from;
|
||||
prevState.to = newState.to;
|
||||
|
||||
const stateChanged = !isEqual(newState, this._prevState);
|
||||
|
||||
this._log.logger(
|
||||
`DashboardReloadBehavior reloadDashboard stateChanged ${stateChanged ? 'true' : 'false'}`,
|
||||
this._prevState,
|
||||
newState
|
||||
);
|
||||
|
||||
if (!stateChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._prevState = newState;
|
||||
|
||||
// This is wrapped in setTimeout in order to allow variables and scopes to be set in the URL before actually reloading the dashboard
|
||||
setTimeout(() => {
|
||||
const scopes = sceneGraph.getScopes(this) ?? [];
|
||||
|
||||
getDashboardScenePageStateManager().reloadDashboard({
|
||||
version: this.state.version!,
|
||||
scopes: scopes.map((scope) => scope.metadata.name),
|
||||
// We're not using the getUrlState from timeRange since it makes more sense to pass the absolute timestamps as opposed to relative time
|
||||
timeRange: {
|
||||
from: this._timeRange!.state.value.from.toISOString(),
|
||||
to: this._timeRange!.state.value.to.toISOString(),
|
||||
},
|
||||
variables: sceneGraph.getVariables(this).state.variables.reduce<UrlQueryMap>(
|
||||
(acc, variable) => ({
|
||||
...acc,
|
||||
...variable.urlSync?.getUrlState(),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
});
|
||||
getDashboardScenePageStateManager().reloadDashboard(newState);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
new DashboardReloadBehavior({
|
||||
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && false,
|
||||
uid: dashboardId?.toString(),
|
||||
version: 1,
|
||||
}),
|
||||
],
|
||||
$data: new DashboardDataLayerSet({
|
||||
|
||||
@@ -308,7 +308,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
||||
new DashboardReloadBehavior({
|
||||
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
||||
uid,
|
||||
version: oldModel.version,
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export class ScopesService implements ScopesContextValue {
|
||||
}
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const scopes = queryParams.getAll('scopes');
|
||||
//const scopesFromState = this.state.value.map((scope) => scope.metadata.name);
|
||||
if (scopes.length) {
|
||||
// We only update scopes but never delete them. This is to keep the scopes in memory if user navigates to
|
||||
// page that does not use scopes (like from dashboard to dashboard list back to dashboard). If user
|
||||
@@ -93,12 +94,7 @@ export class ScopesService implements ScopesContextValue {
|
||||
const oldScopeNames = prev.selectedScopes.map((scope) => scope.scope.metadata.name);
|
||||
const newScopeNames = state.selectedScopes.map((scope) => scope.scope.metadata.name);
|
||||
if (!isEqual(oldScopeNames, newScopeNames)) {
|
||||
this.locationService.partial(
|
||||
{
|
||||
scopes: newScopeNames,
|
||||
},
|
||||
true
|
||||
);
|
||||
this.locationService.partial({ scopes: newScopeNames }, true);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -212,7 +212,10 @@ export class ScopesSelectorService extends ScopesServiceBase<ScopesSelectorServi
|
||||
* before for example by toggling the scopes in the scoped tree UI.
|
||||
*/
|
||||
private setNewScopes = async (treeScopes = this.state.treeScopes) => {
|
||||
if (isEqual(treeScopes, getTreeScopesFromSelectedScopes(this.state.selectedScopes))) {
|
||||
const newNames = treeScopes.map(({ scopeName }) => scopeName);
|
||||
const currentNames = this.state.selectedScopes.map((scope) => scope.scope.metadata.name);
|
||||
|
||||
if (isEqual(newNames, currentNames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ jest.mock('@grafana/runtime', () => ({
|
||||
|
||||
describe('Dashboard reload', () => {
|
||||
let dashboardReloadSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'log').mockImplementation();
|
||||
});
|
||||
beforeAll(() => {
|
||||
config.featureToggles.scopeFilters = true;
|
||||
config.featureToggles.groupByVariable = true;
|
||||
|
||||
@@ -3443,11 +3443,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:^6.12.0":
|
||||
version: 6.16.0
|
||||
resolution: "@grafana/scenes-react@npm:6.16.0"
|
||||
"@grafana/scenes-react@npm:^6.18.0":
|
||||
version: 6.18.0
|
||||
resolution: "@grafana/scenes-react@npm:6.18.0"
|
||||
dependencies:
|
||||
"@grafana/scenes": "npm:6.16.0"
|
||||
"@grafana/scenes": "npm:6.18.0"
|
||||
lru-cache: "npm:^10.2.2"
|
||||
react-use: "npm:^17.4.0"
|
||||
peerDependencies:
|
||||
@@ -3459,13 +3459,13 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/a1a7824b1c8f05fd5149d93a5aa0037014d5ff3e777669d5212c2426812973510dbeea225cb4f5d5e5a9038910a590d6d170b5ebe681c5be6e2fd951a3fabc07
|
||||
checksum: 10/7173458c5a4c128f3026fc3ad5b7cb135cd4a99340939c2c6f3081b2f8d7028347f27ba79259c0f8aa2315f59abc5cccdd2110b3a343b2f4c2ab576236dc60e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.16.0, @grafana/scenes@npm:^6.12.0":
|
||||
version: 6.16.0
|
||||
resolution: "@grafana/scenes@npm:6.16.0"
|
||||
"@grafana/scenes@npm:6.18.0, @grafana/scenes@npm:^6.18.0":
|
||||
version: 6.18.0
|
||||
resolution: "@grafana/scenes@npm:6.18.0"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.26.16"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.16"
|
||||
@@ -3483,7 +3483,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/e379ba53fc835b6b3a6eb19d7527a6e775c10684d4eed650f85e5ad8c3fdb682fe20428c2ea467e66fc9050b19b19eb46ebed2ef5253dd1c3f6a58fd04d42205
|
||||
checksum: 10/9363488c0a5f57745497a5887ec50a3fdee7c93a3b40c83e000470bd60b437d9614d1cf2d62209f1415e6c58432f2d2dc1287c1c762381582fc21b986722656b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -17777,8 +17777,8 @@ __metadata:
|
||||
"@grafana/plugin-ui": "npm:0.10.6"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:^6.12.0"
|
||||
"@grafana/scenes-react": "npm:^6.12.0"
|
||||
"@grafana/scenes": "npm:^6.18.0"
|
||||
"@grafana/scenes-react": "npm:^6.18.0"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/test-utils": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user