DashboardScene: Detect updated saved dashboard version and load new scene (#81715)

This commit is contained in:
Torkel Ödegaard 2024-02-02 10:53:41 +01:00 committed by GitHub
parent 702e22806c
commit 115dfc0fd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 38 deletions

View File

@ -111,6 +111,40 @@ describe('DashboardScenePageStateManager', () => {
}); });
describe('caching', () => { describe('caching', () => {
it('should take scene from cache if it exists', async () => {
setupLoadDashboardMock({ dashboard: { uid: 'fake-dash', version: 10 }, meta: {} });
const loader = new DashboardScenePageStateManager({});
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
loader.state.dashboard?.onEnterEditMode();
expect(loader.state.dashboard?.state.isEditing).toBe(true);
loader.clearState();
// now load it again
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
// should still be editing
expect(loader.state.dashboard?.state.isEditing).toBe(true);
expect(loader.state.dashboard?.state.version).toBe(10);
loader.clearState();
loader.setDashboardCache('fake-dash', {
dashboard: { title: 'new version', uid: 'fake-dash', version: 11, schemaVersion: 30 },
meta: {},
});
// now load a third time
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
expect(loader.state.dashboard!.state.isEditing).toBe(undefined);
expect(loader.state.dashboard!.state.version).toBe(11);
});
it('should cache the dashboard DTO', async () => { it('should cache the dashboard DTO', async () => {
setupLoadDashboardMock({ dashboard: { uid: 'fake-dash' }, meta: {} }); setupLoadDashboardMock({ dashboard: { uid: 'fake-dash' }, meta: {} });

View File

@ -21,7 +21,7 @@ export interface DashboardScenePageState {
loadError?: string; loadError?: string;
} }
export const DASHBOARD_CACHE_TTL = 2000; export const DASHBOARD_CACHE_TTL = 500;
/** Only used by cache in loading home in DashboardPageProxy and initDashboard (Old arch), can remove this after old dashboard arch is gone */ /** Only used by cache in loading home in DashboardPageProxy and initDashboard (Old arch), can remove this after old dashboard arch is gone */
export const HOME_DASHBOARD_CACHE_KEY = '__grafana_home_uid__'; export const HOME_DASHBOARD_CACHE_KEY = '__grafana_home_uid__';
@ -29,6 +29,7 @@ export const HOME_DASHBOARD_CACHE_KEY = '__grafana_home_uid__';
interface DashboardCacheEntry { interface DashboardCacheEntry {
dashboard: DashboardDTO; dashboard: DashboardDTO;
ts: number; ts: number;
cacheKey: string;
} }
export interface LoadDashboardOptions { export interface LoadDashboardOptions {
@ -39,12 +40,13 @@ export interface LoadDashboardOptions {
export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> { export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> {
private cache: Record<string, DashboardScene> = {}; private cache: Record<string, DashboardScene> = {};
// This is a simplistic, short-term cache for DashboardDTOs to avoid fetching the same dashboard multiple times across a short time span. // This is a simplistic, short-term cache for DashboardDTOs to avoid fetching the same dashboard multiple times across a short time span.
private dashboardCache: Map<string, DashboardCacheEntry> = new Map(); private dashboardCache?: DashboardCacheEntry;
// To eventualy replace the fetchDashboard function from Dashboard redux state management. // To eventualy replace the fetchDashboard function from Dashboard redux state management.
// For now it's a simplistic version to support Home and Normal dashboard routes. // For now it's a simplistic version to support Home and Normal dashboard routes.
public async fetchDashboard({ uid, route, urlFolderUid }: LoadDashboardOptions) { public async fetchDashboard({ uid, route, urlFolderUid }: LoadDashboardOptions): Promise<DashboardDTO | null> {
const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid; const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid;
const cachedDashboard = this.getFromCache(cacheKey); const cachedDashboard = this.getFromCache(cacheKey);
@ -52,7 +54,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
return cachedDashboard; return cachedDashboard;
} }
let rsp: DashboardDTO | undefined; let rsp: DashboardDTO;
try { try {
switch (route) { switch (route) {
@ -84,30 +86,24 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
} }
} }
if (rsp) { if (rsp.meta.url && route !== DashboardRoutes.Embedded) {
if (rsp.meta.url && route !== DashboardRoutes.Embedded) { const dashboardUrl = locationUtil.stripBaseFromUrl(rsp.meta.url);
const dashboardUrl = locationUtil.stripBaseFromUrl(rsp.meta.url); const currentPath = locationService.getLocation().pathname;
const currentPath = locationService.getLocation().pathname; if (dashboardUrl !== currentPath) {
if (dashboardUrl !== currentPath) { // Spread current location to persist search params used for navigation
// Spread current location to persist search params used for navigation locationService.replace({
locationService.replace({ ...locationService.getLocation(),
...locationService.getLocation(), pathname: dashboardUrl,
pathname: dashboardUrl, });
}); console.log('not correct url correcting', dashboardUrl, currentPath);
console.log('not correct url correcting', dashboardUrl, currentPath);
}
}
// Populate nav model in global store according to the folder
await this.initNavModel(rsp);
// Do not cache new dashboards
if (uid) {
this.dashboardCache.set(uid, { dashboard: rsp, ts: Date.now() });
} else if (route === DashboardRoutes.Home) {
this.dashboardCache.set(HOME_DASHBOARD_CACHE_KEY, { dashboard: rsp, ts: Date.now() });
} }
} }
// Populate nav model in global store according to the folder
await this.initNavModel(rsp);
// Do not cache new dashboards
this.dashboardCache = { dashboard: rsp, ts: Date.now(), cacheKey };
} catch (e) { } catch (e) {
// Ignore cancelled errors // Ignore cancelled errors
if (isFetchError(e) && e.cancelled) { if (isFetchError(e) && e.cancelled) {
@ -133,15 +129,16 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
} }
private async loadScene(options: LoadDashboardOptions): Promise<DashboardScene> { private async loadScene(options: LoadDashboardOptions): Promise<DashboardScene> {
const rsp = await this.fetchDashboard(options);
const fromCache = this.cache[options.uid]; const fromCache = this.cache[options.uid];
if (fromCache) {
if (fromCache && fromCache.state.version === rsp?.dashboard.version) {
return fromCache; return fromCache;
} }
this.setState({ isLoading: true }); this.setState({ isLoading: true });
const rsp = await this.fetchDashboard(options);
if (rsp?.dashboard) { if (rsp?.dashboard) {
const scene = transformSaveModelToScene(rsp); const scene = transformSaveModelToScene(rsp);
@ -155,20 +152,20 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
throw new Error('Dashboard not found'); throw new Error('Dashboard not found');
} }
public getFromCache(uid: string) { public getFromCache(cacheKey: string) {
const cachedDashboard = this.dashboardCache.get(uid); const cachedDashboard = this.dashboardCache;
if (cachedDashboard && !this.hasExpired(cachedDashboard)) { if (
cachedDashboard &&
cachedDashboard.cacheKey === cacheKey &&
Date.now() - cachedDashboard?.ts < DASHBOARD_CACHE_TTL
) {
return cachedDashboard.dashboard; return cachedDashboard.dashboard;
} }
return null; return null;
} }
private hasExpired(entry: DashboardCacheEntry) {
return Date.now() - entry.ts > DASHBOARD_CACHE_TTL;
}
private async initNavModel(dashboard: DashboardDTO) { private async initNavModel(dashboard: DashboardDTO) {
// only the folder API has information about ancestors // only the folder API has information about ancestors
// get parent folder (if it exists) and put it in the store // get parent folder (if it exists) and put it in the store
@ -188,8 +185,8 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
this.setState({ dashboard: undefined, loadError: undefined, isLoading: false, panelEditor: undefined }); this.setState({ dashboard: undefined, loadError: undefined, isLoading: false, panelEditor: undefined });
} }
public setDashboardCache(uid: string, dashboard: DashboardDTO) { public setDashboardCache(cacheKey: string, dashboard: DashboardDTO) {
this.dashboardCache.set(uid, { dashboard, ts: Date.now() }); this.dashboardCache = { dashboard, ts: Date.now(), cacheKey };
} }
} }