mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Fix dashboard reload behavior (#96427)
This commit is contained in:
parent
8e0e3397a3
commit
984fbac1ad
@ -44,7 +44,15 @@ export interface LoadDashboardOptions {
|
|||||||
uid: string;
|
uid: string;
|
||||||
route: DashboardRoutes;
|
route: DashboardRoutes;
|
||||||
urlFolderUid?: string;
|
urlFolderUid?: string;
|
||||||
queryParams?: UrlQueryMap;
|
params?: {
|
||||||
|
version: number;
|
||||||
|
scopes: string[];
|
||||||
|
timeRange: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
};
|
||||||
|
variables: UrlQueryMap;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> {
|
export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> {
|
||||||
@ -59,11 +67,11 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
uid,
|
uid,
|
||||||
route,
|
route,
|
||||||
urlFolderUid,
|
urlFolderUid,
|
||||||
queryParams,
|
params,
|
||||||
}: LoadDashboardOptions): Promise<DashboardDTO | null> {
|
}: LoadDashboardOptions): Promise<DashboardDTO | null> {
|
||||||
const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid;
|
const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid;
|
||||||
|
|
||||||
if (!queryParams) {
|
if (!params) {
|
||||||
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||||
|
|
||||||
if (cachedDashboard) {
|
if (cachedDashboard) {
|
||||||
@ -97,6 +105,15 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
return await dashboardLoaderSrv.loadDashboard('public', '', uid);
|
return await dashboardLoaderSrv.loadDashboard('public', '', uid);
|
||||||
}
|
}
|
||||||
default:
|
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('db', '', uid, queryParams);
|
rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid, queryParams);
|
||||||
|
|
||||||
if (route === DashboardRoutes.Embedded) {
|
if (route === DashboardRoutes.Embedded) {
|
||||||
@ -188,17 +205,26 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reloadDashboard(queryParams?: LoadDashboardOptions['queryParams'] | undefined) {
|
public async reloadDashboard(params: LoadDashboardOptions['params']) {
|
||||||
if (!this.state.options) {
|
const stateOptions = this.state.options;
|
||||||
|
|
||||||
|
if (!stateOptions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
...this.state.options,
|
...stateOptions,
|
||||||
queryParams,
|
params,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEqual(options, this.state.options)) {
|
// We shouldn't check all params since:
|
||||||
|
// - version doesn't impact the new dashboard, and it's there for increased compatibility
|
||||||
|
// - time range is almost always different for relative time ranges and absolute time ranges do not trigger subsequent reloads
|
||||||
|
// - other params don't affect the dashboard content
|
||||||
|
if (
|
||||||
|
isEqual(options.params?.variables, stateOptions.params?.variables) &&
|
||||||
|
isEqual(options.params?.scopes, stateOptions.params?.scopes)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { debounce, isEqual } from 'lodash';
|
||||||
|
|
||||||
import { UrlQueryMap } from '@grafana/data';
|
import { UrlQueryMap } from '@grafana/data';
|
||||||
import { sceneGraph, SceneObjectBase, SceneObjectState, VariableDependencyConfig } from '@grafana/scenes';
|
import { sceneGraph, SceneObjectBase, SceneObjectState, VariableDependencyConfig } from '@grafana/scenes';
|
||||||
import { getClosestScopesFacade, ScopesFacade } from 'app/features/scopes';
|
import { getClosestScopesFacade } from 'app/features/scopes';
|
||||||
|
|
||||||
import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager';
|
import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager';
|
||||||
|
|
||||||
@ -13,27 +13,25 @@ export interface DashboardReloadBehaviorState extends SceneObjectState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBehaviorState> {
|
export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBehaviorState> {
|
||||||
private _scopesFacade: ScopesFacade | null = null;
|
|
||||||
|
|
||||||
constructor(state: DashboardReloadBehaviorState) {
|
constructor(state: DashboardReloadBehaviorState) {
|
||||||
const shouldReload = state.reloadOnParamsChange && state.uid;
|
const shouldReload = state.reloadOnParamsChange && state.uid;
|
||||||
|
|
||||||
super(state);
|
super(state);
|
||||||
|
|
||||||
this.reloadDashboard = this.reloadDashboard.bind(this);
|
// Sometimes the reload is triggered multiple subsequent times
|
||||||
|
// Debouncing it prevents double/triple reloads
|
||||||
|
this.reloadDashboard = debounce(this.reloadDashboard).bind(this);
|
||||||
|
|
||||||
if (shouldReload) {
|
if (shouldReload) {
|
||||||
this.addActivationHandler(() => {
|
this.addActivationHandler(() => {
|
||||||
this._scopesFacade = getClosestScopesFacade(this);
|
getClosestScopesFacade(this)?.setState({
|
||||||
|
handler: this.reloadDashboard,
|
||||||
|
});
|
||||||
|
|
||||||
this._variableDependency = new VariableDependencyConfig(this, {
|
this._variableDependency = new VariableDependencyConfig(this, {
|
||||||
onAnyVariableChanged: this.reloadDashboard,
|
onAnyVariableChanged: this.reloadDashboard,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._scopesFacade?.setState({
|
|
||||||
handler: this.reloadDashboard,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._subs.add(
|
this._subs.add(
|
||||||
sceneGraph.getTimeRange(this).subscribeToState((newState, prevState) => {
|
sceneGraph.getTimeRange(this).subscribeToState((newState, prevState) => {
|
||||||
if (!isEqual(newState.value, prevState.value)) {
|
if (!isEqual(newState.value, prevState.value)) {
|
||||||
@ -41,6 +39,8 @@ export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBeha
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.reloadDashboard();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,21 +59,25 @@ export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBeha
|
|||||||
if (!this.isEditing() && !this.isWaitingForVariables()) {
|
if (!this.isEditing() && !this.isWaitingForVariables()) {
|
||||||
const timeRange = sceneGraph.getTimeRange(this);
|
const timeRange = sceneGraph.getTimeRange(this);
|
||||||
|
|
||||||
let params: UrlQueryMap = {
|
// This is wrapped in setTimeout in order to allow variables and scopes to be set in the URL before actually reloading the dashboard
|
||||||
version: this.state.version,
|
setTimeout(() => {
|
||||||
scopes: this._scopesFacade?.value.map((scope) => scope.metadata.name),
|
getDashboardScenePageStateManager().reloadDashboard({
|
||||||
...timeRange.urlSync?.getUrlState(),
|
version: this.state.version!,
|
||||||
};
|
scopes: getClosestScopesFacade(this)?.value.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
|
||||||
params = sceneGraph.getVariables(this).state.variables.reduce<UrlQueryMap>(
|
timeRange: {
|
||||||
(acc, variable) => ({
|
from: timeRange.state.value.from.toISOString(),
|
||||||
...acc,
|
to: timeRange.state.value.to.toISOString(),
|
||||||
...variable.urlSync?.getUrlState(),
|
},
|
||||||
}),
|
variables: sceneGraph.getVariables(this).state.variables.reduce<UrlQueryMap>(
|
||||||
params
|
(acc, variable) => ({
|
||||||
);
|
...acc,
|
||||||
|
...variable.urlSync?.getUrlState(),
|
||||||
getDashboardScenePageStateManager().reloadDashboard(params);
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
import { setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||||
|
import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager';
|
||||||
|
|
||||||
import { enterEditMode, updateMyVar, updateScopes, updateTimeRange } from './utils/actions';
|
import { clearMocks, enterEditMode, updateMyVar, updateScopes, updateTimeRange } from './utils/actions';
|
||||||
import { expectDashboardReload, expectNotDashboardReload } from './utils/assertions';
|
import { expectDashboardReload, expectNotDashboardReload } from './utils/assertions';
|
||||||
import { getDatasource, getInstanceSettings, getMock } from './utils/mocks';
|
import { getDatasource, getInstanceSettings, getMock } from './utils/mocks';
|
||||||
import { renderDashboard, resetScenes } from './utils/render';
|
import { renderDashboard, resetScenes } from './utils/render';
|
||||||
@ -15,84 +16,66 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
|
usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const runTest = async (
|
|
||||||
reloadDashboardsOnParamsChange: boolean,
|
|
||||||
reloadOnParamsChange: boolean,
|
|
||||||
withUid: boolean,
|
|
||||||
editMode: boolean
|
|
||||||
) => {
|
|
||||||
config.featureToggles.reloadDashboardsOnParamsChange = reloadDashboardsOnParamsChange;
|
|
||||||
setDashboardAPI(undefined);
|
|
||||||
const uid = 'dash-1';
|
|
||||||
const dashboardScene = renderDashboard({ uid: withUid ? uid : undefined }, { reloadOnParamsChange });
|
|
||||||
|
|
||||||
if (editMode) {
|
|
||||||
await enterEditMode(dashboardScene);
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldReload = reloadDashboardsOnParamsChange && reloadOnParamsChange && withUid && !editMode;
|
|
||||||
|
|
||||||
await updateTimeRange(dashboardScene);
|
|
||||||
if (!shouldReload) {
|
|
||||||
expectNotDashboardReload();
|
|
||||||
} else {
|
|
||||||
expectDashboardReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateMyVar(dashboardScene, '2');
|
|
||||||
if (!shouldReload) {
|
|
||||||
expectNotDashboardReload();
|
|
||||||
} else {
|
|
||||||
expectDashboardReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateScopes(['grafana']);
|
|
||||||
if (!shouldReload) {
|
|
||||||
expectNotDashboardReload();
|
|
||||||
} else {
|
|
||||||
expectDashboardReload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Dashboard reload', () => {
|
describe('Dashboard reload', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
config.featureToggles.scopeFilters = true;
|
config.featureToggles.scopeFilters = true;
|
||||||
config.featureToggles.groupByVariable = true;
|
config.featureToggles.groupByVariable = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
it.each([
|
||||||
setDashboardAPI(undefined);
|
[false, false, false, false],
|
||||||
await resetScenes();
|
[false, false, true, false],
|
||||||
});
|
[false, true, false, false],
|
||||||
|
[false, true, true, false],
|
||||||
|
[true, false, false, false],
|
||||||
|
[true, false, true, false],
|
||||||
|
[true, true, false, true],
|
||||||
|
[true, true, true, true],
|
||||||
|
[true, true, false, false],
|
||||||
|
[true, true, true, false],
|
||||||
|
])(
|
||||||
|
`reloadDashboardsOnParamsChange: %s, reloadOnParamsChange: %s, withUid: %s, editMode: %s`,
|
||||||
|
async (reloadDashboardsOnParamsChange, reloadOnParamsChange, withUid, editMode) => {
|
||||||
|
config.featureToggles.reloadDashboardsOnParamsChange = reloadDashboardsOnParamsChange;
|
||||||
|
setDashboardAPI(undefined);
|
||||||
|
|
||||||
describe('reloadDashboardsOnParamsChange off', () => {
|
const dashboardScene = renderDashboard({ uid: withUid ? 'dash-1' : undefined }, { reloadOnParamsChange });
|
||||||
describe('reloadOnParamsChange off', () => {
|
|
||||||
it('with UID - no reload', () => runTest(false, false, true, false));
|
|
||||||
it('without UID - no reload', () => runTest(false, false, false, false));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reloadOnParamsChange on', () => {
|
if (editMode) {
|
||||||
it('with UID - no reload', () => runTest(false, true, true, false));
|
await enterEditMode(dashboardScene);
|
||||||
it('without UID - no reload', () => runTest(false, true, false, false));
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reloadDashboardsOnParamsChange on', () => {
|
const shouldReload = reloadDashboardsOnParamsChange && reloadOnParamsChange && withUid && !editMode;
|
||||||
describe('reloadOnParamsChange off', () => {
|
|
||||||
it('with UID - no reload', () => runTest(true, false, true, false));
|
|
||||||
it('without UID - no reload', () => runTest(true, false, false, false));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reloadOnParamsChange on', () => {
|
await updateTimeRange(dashboardScene);
|
||||||
describe('edit mode on', () => {
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
it('with UID - no reload', () => runTest(true, true, true, true));
|
if (!shouldReload) {
|
||||||
it('without UID - no reload', () => runTest(true, true, false, true));
|
expectNotDashboardReload();
|
||||||
});
|
} else {
|
||||||
|
expectDashboardReload();
|
||||||
|
}
|
||||||
|
|
||||||
describe('edit mode off', () => {
|
await updateMyVar(dashboardScene, '2');
|
||||||
it('with UID - reload', () => runTest(true, true, true, false));
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
it('without UID - no reload', () => runTest(true, true, false, false));
|
if (!shouldReload) {
|
||||||
});
|
expectNotDashboardReload();
|
||||||
});
|
} else {
|
||||||
});
|
expectDashboardReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateScopes(['grafana']);
|
||||||
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
|
if (!shouldReload) {
|
||||||
|
expectNotDashboardReload();
|
||||||
|
} else {
|
||||||
|
expectDashboardReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDashboardScenePageStateManager().clearDashboardCache();
|
||||||
|
getDashboardScenePageStateManager().clearSceneCache();
|
||||||
|
setDashboardAPI(undefined);
|
||||||
|
await resetScenes();
|
||||||
|
clearMocks();
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user