Dashboards: Reload the dashboard based on time range and filters changes (#94190)
This commit is contained in:
@@ -198,7 +198,7 @@ export interface FeatureToggles {
|
|||||||
ssoSettingsLDAP?: boolean;
|
ssoSettingsLDAP?: boolean;
|
||||||
failWrongDSUID?: boolean;
|
failWrongDSUID?: boolean;
|
||||||
zanzana?: boolean;
|
zanzana?: boolean;
|
||||||
passScopeToDashboardApi?: boolean;
|
reloadDashboardsOnParamsChange?: boolean;
|
||||||
alertingApiServer?: boolean;
|
alertingApiServer?: boolean;
|
||||||
cloudWatchRoundUpEndTime?: boolean;
|
cloudWatchRoundUpEndTime?: boolean;
|
||||||
cloudwatchMetricInsightsCrossAccount?: boolean;
|
cloudwatchMetricInsightsCrossAccount?: boolean;
|
||||||
|
|||||||
@@ -1363,8 +1363,8 @@ var (
|
|||||||
HideFromAdminPage: true,
|
HideFromAdminPage: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "passScopeToDashboardApi",
|
Name: "reloadDashboardsOnParamsChange",
|
||||||
Description: "Enables the passing of scopes to dashboards fetching in Grafana",
|
Description: "Enables reload of dashboards on scopes, time range and variables changes",
|
||||||
FrontendOnly: false,
|
FrontendOnly: false,
|
||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: grafanaDashboardsSquad,
|
Owner: grafanaDashboardsSquad,
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ openSearchBackendFlowEnabled,GA,@grafana/aws-datasources,false,false,false
|
|||||||
ssoSettingsLDAP,preview,@grafana/identity-access-team,false,true,false
|
ssoSettingsLDAP,preview,@grafana/identity-access-team,false,true,false
|
||||||
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||||
zanzana,experimental,@grafana/identity-access-team,false,false,false
|
zanzana,experimental,@grafana/identity-access-team,false,false,false
|
||||||
passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false
|
reloadDashboardsOnParamsChange,experimental,@grafana/dashboards-squad,false,false,false
|
||||||
alertingApiServer,experimental,@grafana/alerting-squad,false,true,false
|
alertingApiServer,experimental,@grafana/alerting-squad,false,true,false
|
||||||
cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false
|
cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false
|
||||||
cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true
|
cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true
|
||||||
|
|||||||
|
@@ -727,9 +727,9 @@ const (
|
|||||||
// Use openFGA as authorization engine.
|
// Use openFGA as authorization engine.
|
||||||
FlagZanzana = "zanzana"
|
FlagZanzana = "zanzana"
|
||||||
|
|
||||||
// FlagPassScopeToDashboardApi
|
// FlagReloadDashboardsOnParamsChange
|
||||||
// Enables the passing of scopes to dashboards fetching in Grafana
|
// Enables reload of dashboards on scopes, time range and variables changes
|
||||||
FlagPassScopeToDashboardApi = "passScopeToDashboardApi"
|
FlagReloadDashboardsOnParamsChange = "reloadDashboardsOnParamsChange"
|
||||||
|
|
||||||
// FlagAlertingApiServer
|
// FlagAlertingApiServer
|
||||||
// Register Alerting APIs with the K8s API server
|
// Register Alerting APIs with the K8s API server
|
||||||
|
|||||||
@@ -2347,11 +2347,26 @@
|
|||||||
"requiresDevMode": true
|
"requiresDevMode": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "reloadDashboardsOnParamsChange",
|
||||||
|
"resourceVersion": "1728903221522",
|
||||||
|
"creationTimestamp": "2024-10-14T10:53:41Z"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"description": "Enables reload of dashboards on scopes, time range and variables changes",
|
||||||
|
"stage": "experimental",
|
||||||
|
"codeowner": "@grafana/dashboards-squad",
|
||||||
|
"hideFromAdminPage": true,
|
||||||
|
"hideFromDocs": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "passScopeToDashboardApi",
|
"name": "passScopeToDashboardApi",
|
||||||
"resourceVersion": "1718290335877",
|
"resourceVersion": "1718290335877",
|
||||||
"creationTimestamp": "2024-06-20T15:49:19Z"
|
"creationTimestamp": "2024-06-20T15:49:19Z",
|
||||||
|
"deletionTimestamp": "2024-10-14T10:53:41Z"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "Enables the passing of scopes to dashboards fetching in Grafana",
|
"description": "Enables the passing of scopes to dashboards fetching in Grafana",
|
||||||
@@ -3305,4 +3320,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('DashboardScenePageStateManager', () => {
|
|||||||
const loader = new DashboardScenePageStateManager({});
|
const loader = new DashboardScenePageStateManager({});
|
||||||
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||||
|
|
||||||
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash');
|
expect(loadDashboardMock).toHaveBeenCalledWith('db', '', 'fake-dash', undefined);
|
||||||
|
|
||||||
// should use cache second time
|
// should use cache second time
|
||||||
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { locationUtil } from '@grafana/data';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
import { locationUtil, UrlQueryMap } from '@grafana/data';
|
||||||
import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||||
import { StateManagerBase } from 'app/core/services/StateManagerBase';
|
import { StateManagerBase } from 'app/core/services/StateManagerBase';
|
||||||
import { getMessageFromError } from 'app/core/utils/errors';
|
import { getMessageFromError } from 'app/core/utils/errors';
|
||||||
@@ -7,7 +9,6 @@ import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoa
|
|||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor';
|
import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor';
|
||||||
import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking';
|
import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking';
|
||||||
import { getSelectedScopesNames } from 'app/features/scopes';
|
|
||||||
import { DashboardDTO, DashboardRoutes } from 'app/types';
|
import { DashboardDTO, DashboardRoutes } from 'app/types';
|
||||||
|
|
||||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||||
@@ -20,6 +21,7 @@ import { updateNavModel } from './utils';
|
|||||||
|
|
||||||
export interface DashboardScenePageState {
|
export interface DashboardScenePageState {
|
||||||
dashboard?: DashboardScene;
|
dashboard?: DashboardScene;
|
||||||
|
options?: LoadDashboardOptions;
|
||||||
panelEditor?: PanelEditor;
|
panelEditor?: PanelEditor;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
loadError?: string;
|
loadError?: string;
|
||||||
@@ -42,6 +44,7 @@ export interface LoadDashboardOptions {
|
|||||||
uid: string;
|
uid: string;
|
||||||
route: DashboardRoutes;
|
route: DashboardRoutes;
|
||||||
urlFolderUid?: string;
|
urlFolderUid?: string;
|
||||||
|
queryParams?: UrlQueryMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> {
|
export class DashboardScenePageStateManager extends StateManagerBase<DashboardScenePageState> {
|
||||||
@@ -52,12 +55,20 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
|
|
||||||
// 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): Promise<DashboardDTO | null> {
|
public async fetchDashboard({
|
||||||
|
uid,
|
||||||
|
route,
|
||||||
|
urlFolderUid,
|
||||||
|
queryParams,
|
||||||
|
}: 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.getDashboardFromCache(cacheKey);
|
|
||||||
|
|
||||||
if (cachedDashboard) {
|
if (!queryParams) {
|
||||||
return cachedDashboard;
|
const cachedDashboard = this.getDashboardFromCache(cacheKey);
|
||||||
|
|
||||||
|
if (cachedDashboard) {
|
||||||
|
return cachedDashboard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rsp: DashboardDTO;
|
let rsp: DashboardDTO;
|
||||||
@@ -86,7 +97,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
return await dashboardLoaderSrv.loadDashboard('public', '', uid);
|
return await dashboardLoaderSrv.loadDashboard('public', '', uid);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid);
|
rsp = await dashboardLoaderSrv.loadDashboard('db', '', uid, queryParams);
|
||||||
|
|
||||||
if (route === DashboardRoutes.Embedded) {
|
if (route === DashboardRoutes.Embedded) {
|
||||||
rsp.meta.isEmbedded = true;
|
rsp.meta.isEmbedded = true;
|
||||||
@@ -159,7 +170,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
restoreDashboardStateFromLocalStorage(dashboard);
|
restoreDashboardStateFromLocalStorage(dashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ dashboard: dashboard, isLoading: false });
|
this.setState({ dashboard: dashboard, isLoading: false, options });
|
||||||
const measure = stopMeasure(LOAD_SCENE_MEASUREMENT);
|
const measure = stopMeasure(LOAD_SCENE_MEASUREMENT);
|
||||||
trackDashboardSceneLoaded(dashboard, measure?.duration);
|
trackDashboardSceneLoaded(dashboard, measure?.duration);
|
||||||
|
|
||||||
@@ -177,6 +188,47 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async reloadDashboard(queryParams?: LoadDashboardOptions['queryParams'] | undefined) {
|
||||||
|
if (!this.state.options) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...this.state.options,
|
||||||
|
queryParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEqual(options, this.state.options)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
|
const rsp = await this.fetchDashboard(options);
|
||||||
|
const fromCache = this.getSceneFromCache(options.uid);
|
||||||
|
|
||||||
|
if (fromCache && fromCache.state.version === rsp?.dashboard.version) {
|
||||||
|
this.setState({ isLoading: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rsp?.dashboard) {
|
||||||
|
this.setState({ isLoading: false, loadError: 'Dashboard not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene = transformSaveModelToScene(rsp);
|
||||||
|
|
||||||
|
this.setSceneCache(options.uid, scene);
|
||||||
|
|
||||||
|
this.setState({ dashboard: scene, isLoading: false, options });
|
||||||
|
} catch (err) {
|
||||||
|
const msg = getMessageFromError(err);
|
||||||
|
this.setState({ isLoading: false, loadError: msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async loadScene(options: LoadDashboardOptions): Promise<DashboardScene | null> {
|
private async loadScene(options: LoadDashboardOptions): Promise<DashboardScene | null> {
|
||||||
this.setState({ dashboard: undefined, isLoading: true });
|
this.setState({ dashboard: undefined, isLoading: true });
|
||||||
|
|
||||||
@@ -209,7 +261,6 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
|
|
||||||
public getDashboardFromCache(cacheKey: string) {
|
public getDashboardFromCache(cacheKey: string) {
|
||||||
const cachedDashboard = this.dashboardCache;
|
const cachedDashboard = this.dashboardCache;
|
||||||
cacheKey = this.getCacheKey(cacheKey);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cachedDashboard &&
|
cachedDashboard &&
|
||||||
@@ -234,8 +285,6 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setDashboardCache(cacheKey: string, dashboard: DashboardDTO) {
|
public setDashboardCache(cacheKey: string, dashboard: DashboardDTO) {
|
||||||
cacheKey = this.getCacheKey(cacheKey);
|
|
||||||
|
|
||||||
this.dashboardCache = { dashboard, ts: Date.now(), cacheKey };
|
this.dashboardCache = { dashboard, ts: Date.now(), cacheKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,30 +293,16 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getSceneFromCache(cacheKey: string) {
|
public getSceneFromCache(cacheKey: string) {
|
||||||
cacheKey = this.getCacheKey(cacheKey);
|
|
||||||
|
|
||||||
return this.cache[cacheKey];
|
return this.cache[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSceneCache(cacheKey: string, scene: DashboardScene) {
|
public setSceneCache(cacheKey: string, scene: DashboardScene) {
|
||||||
cacheKey = this.getCacheKey(cacheKey);
|
|
||||||
|
|
||||||
this.cache[cacheKey] = scene;
|
this.cache[cacheKey] = scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearSceneCache() {
|
public clearSceneCache() {
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCacheKey(cacheKey: string): string {
|
|
||||||
const scopesCacheKey = getSelectedScopesNames().sort().join('__scp__');
|
|
||||||
|
|
||||||
if (!scopesCacheKey) {
|
|
||||||
return cacheKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${cacheKey}__scp__${scopesCacheKey}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let stateManager: DashboardScenePageStateManager | null = null;
|
let stateManager: DashboardScenePageStateManager | null = null;
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
import { UrlQueryMap } from '@grafana/data';
|
||||||
|
import { sceneGraph, SceneObjectBase, SceneObjectState, VariableDependencyConfig } from '@grafana/scenes';
|
||||||
|
import { getClosestScopesFacade, ScopesFacade } from 'app/features/scopes';
|
||||||
|
|
||||||
|
import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager';
|
||||||
|
|
||||||
|
export interface DashboardReloadBehaviorState extends SceneObjectState {
|
||||||
|
reloadOnParamsChange?: boolean;
|
||||||
|
uid?: string;
|
||||||
|
version?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardReloadBehavior extends SceneObjectBase<DashboardReloadBehaviorState> {
|
||||||
|
private _scopesFacade: ScopesFacade | null = null;
|
||||||
|
|
||||||
|
constructor(state: DashboardReloadBehaviorState) {
|
||||||
|
const shouldReload = state.reloadOnParamsChange && state.uid;
|
||||||
|
|
||||||
|
super(state);
|
||||||
|
|
||||||
|
this.reloadDashboard = this.reloadDashboard.bind(this);
|
||||||
|
|
||||||
|
if (shouldReload) {
|
||||||
|
this.addActivationHandler(() => {
|
||||||
|
this._scopesFacade = getClosestScopesFacade(this);
|
||||||
|
|
||||||
|
this._variableDependency = new VariableDependencyConfig(this, {
|
||||||
|
onAnyVariableChanged: this.reloadDashboard,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._scopesFacade?.setState({
|
||||||
|
handler: this.reloadDashboard,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._subs.add(
|
||||||
|
sceneGraph.getTimeRange(this).subscribeToState((newState, prevState) => {
|
||||||
|
if (!isEqual(newState.value, prevState.value)) {
|
||||||
|
this.reloadDashboard();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isEditing() {
|
||||||
|
return this.parent && 'isEditing' in this.parent.state && this.parent.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()) {
|
||||||
|
const timeRange = sceneGraph.getTimeRange(this);
|
||||||
|
|
||||||
|
let params: UrlQueryMap = {
|
||||||
|
version: this.state.version,
|
||||||
|
scopes: this._scopesFacade?.value.map((scope) => scope.metadata.name),
|
||||||
|
...timeRange.urlSync?.getUrlState(),
|
||||||
|
};
|
||||||
|
|
||||||
|
params = sceneGraph.getVariables(this).state.variables.reduce<UrlQueryMap>(
|
||||||
|
(acc, variable) => ({
|
||||||
|
...acc,
|
||||||
|
...variable.urlSync?.getUrlState(),
|
||||||
|
}),
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
getDashboardScenePageStateManager().reloadDashboard(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import { locationService } from '@grafana/runtime';
|
|
||||||
import { sceneGraph } from '@grafana/scenes';
|
import { sceneGraph } from '@grafana/scenes';
|
||||||
import { ScopesFacade } from 'app/features/scopes';
|
import { ScopesFacade } from 'app/features/scopes';
|
||||||
|
|
||||||
export interface DashboardScopesFacadeState {
|
export interface DashboardScopesFacadeState {
|
||||||
reloadOnScopesChange?: boolean;
|
reloadOnParamsChange?: boolean;
|
||||||
uid?: string;
|
uid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardScopesFacade extends ScopesFacade {
|
export class DashboardScopesFacade extends ScopesFacade {
|
||||||
constructor({ reloadOnScopesChange, uid }: DashboardScopesFacadeState) {
|
constructor({ reloadOnParamsChange, uid }: DashboardScopesFacadeState) {
|
||||||
super({
|
super({
|
||||||
handler: (facade) => {
|
handler: (facade) => {
|
||||||
if (reloadOnScopesChange && uid) {
|
if (!reloadOnParamsChange || !uid) {
|
||||||
locationService.reload();
|
|
||||||
} else {
|
|
||||||
sceneGraph.getTimeRange(facade).onRefresh();
|
sceneGraph.getTimeRange(facade).onRefresh();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { DashboardControls } from '../scene/DashboardControls';
|
|||||||
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
|
||||||
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
|
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
|
||||||
import { registerDashboardMacro } from '../scene/DashboardMacro';
|
import { registerDashboardMacro } from '../scene/DashboardMacro';
|
||||||
|
import { DashboardReloadBehavior } from '../scene/DashboardReloadBehavior';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { DashboardScopesFacade } from '../scene/DashboardScopesFacade';
|
import { DashboardScopesFacade } from '../scene/DashboardScopesFacade';
|
||||||
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
|
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
|
||||||
@@ -251,9 +252,16 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
|
|||||||
preserveDashboardSceneStateInLocalStorage,
|
preserveDashboardSceneStateInLocalStorage,
|
||||||
addPanelsOnLoadBehavior,
|
addPanelsOnLoadBehavior,
|
||||||
new DashboardScopesFacade({
|
new DashboardScopesFacade({
|
||||||
reloadOnScopesChange: oldModel.meta.reloadOnScopesChange,
|
reloadOnParamsChange:
|
||||||
|
config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
||||||
uid: oldModel.uid,
|
uid: oldModel.uid,
|
||||||
}),
|
}),
|
||||||
|
new DashboardReloadBehavior({
|
||||||
|
reloadOnParamsChange:
|
||||||
|
config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
|
||||||
|
uid: oldModel.uid,
|
||||||
|
version: oldModel.version,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),
|
||||||
controls: new DashboardControls({
|
controls: new DashboardControls({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UrlQueryMap } from '@grafana/data';
|
||||||
import { config, getBackendSrv } from '@grafana/runtime';
|
import { config, getBackendSrv } from '@grafana/runtime';
|
||||||
import { ScopedResourceClient } from 'app/features/apiserver/client';
|
import { ScopedResourceClient } from 'app/features/apiserver/client';
|
||||||
import {
|
import {
|
||||||
@@ -10,12 +11,11 @@ import {
|
|||||||
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types';
|
||||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||||
import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types';
|
import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types';
|
||||||
import { getSelectedScopesNames } from 'app/features/scopes';
|
|
||||||
import { DashboardDTO, DashboardDataDTO, SaveDashboardResponseDTO } from 'app/types';
|
import { DashboardDTO, DashboardDataDTO, SaveDashboardResponseDTO } from 'app/types';
|
||||||
|
|
||||||
export interface DashboardAPI {
|
export interface DashboardAPI {
|
||||||
/** Get a dashboard with the access control metadata */
|
/** Get a dashboard with the access control metadata */
|
||||||
getDashboardDTO(uid: string): Promise<DashboardDTO>;
|
getDashboardDTO(uid: string, params?: UrlQueryMap): Promise<DashboardDTO>;
|
||||||
/** Save dashboard */
|
/** Save dashboard */
|
||||||
saveDashboard(options: SaveDashboardCommand): Promise<SaveDashboardResponseDTO>;
|
saveDashboard(options: SaveDashboardCommand): Promise<SaveDashboardResponseDTO>;
|
||||||
/** Delete a dashboard */
|
/** Delete a dashboard */
|
||||||
@@ -41,11 +41,8 @@ class LegacyDashboardAPI implements DashboardAPI {
|
|||||||
return getBackendSrv().delete<DeleteDashboardResponse>(`/api/dashboards/uid/${uid}`, { showSuccessAlert });
|
return getBackendSrv().delete<DeleteDashboardResponse>(`/api/dashboards/uid/${uid}`, { showSuccessAlert });
|
||||||
}
|
}
|
||||||
|
|
||||||
getDashboardDTO(uid: string): Promise<DashboardDTO> {
|
getDashboardDTO(uid: string, params?: UrlQueryMap): Promise<DashboardDTO> {
|
||||||
const scopes = config.featureToggles.passScopeToDashboardApi ? getSelectedScopesNames() : [];
|
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`, params);
|
||||||
const queryParams = scopes.length > 0 ? { scopes } : undefined;
|
|
||||||
|
|
||||||
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`, queryParams);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import $ from 'jquery';
|
|||||||
import _, { isFunction } from 'lodash'; // eslint-disable-line lodash/import-scope
|
import _, { isFunction } from 'lodash'; // eslint-disable-line lodash/import-scope
|
||||||
import moment from 'moment'; // eslint-disable-line no-restricted-imports
|
import moment from 'moment'; // eslint-disable-line no-restricted-imports
|
||||||
|
|
||||||
import { AppEvents, dateMath, UrlQueryValue } from '@grafana/data';
|
import { AppEvents, dateMath, UrlQueryMap, UrlQueryValue } from '@grafana/data';
|
||||||
import { getBackendSrv, locationService } from '@grafana/runtime';
|
import { getBackendSrv, locationService } from '@grafana/runtime';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import impressionSrv from 'app/core/services/impression_srv';
|
import impressionSrv from 'app/core/services/impression_srv';
|
||||||
@@ -35,7 +35,12 @@ export class DashboardLoaderSrv {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDashboard(type: UrlQueryValue, slug: string | undefined, uid: string | undefined): Promise<DashboardDTO> {
|
loadDashboard(
|
||||||
|
type: UrlQueryValue,
|
||||||
|
slug: string | undefined,
|
||||||
|
uid: string | undefined,
|
||||||
|
params?: UrlQueryMap
|
||||||
|
): Promise<DashboardDTO> {
|
||||||
const stateManager = getDashboardScenePageStateManager();
|
const stateManager = getDashboardScenePageStateManager();
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
@@ -77,13 +82,15 @@ export class DashboardLoaderSrv {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else if (uid) {
|
} else if (uid) {
|
||||||
const cachedDashboard = stateManager.getDashboardFromCache(uid);
|
if (!params) {
|
||||||
if (cachedDashboard) {
|
const cachedDashboard = stateManager.getDashboardFromCache(uid);
|
||||||
return Promise.resolve(cachedDashboard);
|
if (cachedDashboard) {
|
||||||
|
return Promise.resolve(cachedDashboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
promise = getDashboardAPI()
|
promise = getDashboardAPI()
|
||||||
.getDashboardDTO(uid)
|
.getDashboardDTO(uid, params)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.meta.isFolder) {
|
if (result.meta.isFolder) {
|
||||||
appEvents.emit(AppEvents.alertError, ['Dashboard not found']);
|
appEvents.emit(AppEvents.alertError, ['Dashboard not found']);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
import { setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||||
|
|
||||||
import { updateScopes } from './utils/actions';
|
import { 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';
|
||||||
@@ -14,6 +15,45 @@ 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;
|
||||||
@@ -21,18 +61,38 @@ describe('Dashboard reload', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
setDashboardAPI(undefined);
|
||||||
await resetScenes();
|
await resetScenes();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Does not reload the dashboard without UID', async () => {
|
describe('reloadDashboardsOnParamsChange off', () => {
|
||||||
renderDashboard({ uid: undefined }, { reloadOnScopesChange: true });
|
describe('reloadOnParamsChange off', () => {
|
||||||
await updateScopes(['grafana']);
|
it('with UID - no reload', () => runTest(false, false, true, false));
|
||||||
expectNotDashboardReload();
|
it('without UID - no reload', () => runTest(false, false, false, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reloadOnParamsChange on', () => {
|
||||||
|
it('with UID - no reload', () => runTest(false, true, true, false));
|
||||||
|
it('without UID - no reload', () => runTest(false, true, false, false));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Reloads the dashboard with UID', async () => {
|
describe('reloadDashboardsOnParamsChange on', () => {
|
||||||
renderDashboard({}, { reloadOnScopesChange: true });
|
describe('reloadOnParamsChange off', () => {
|
||||||
await updateScopes(['grafana']);
|
it('with UID - no reload', () => runTest(true, false, true, false));
|
||||||
expectDashboardReload();
|
it('without UID - no reload', () => runTest(true, false, false, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reloadOnParamsChange on', () => {
|
||||||
|
describe('edit mode on', () => {
|
||||||
|
it('with UID - no reload', () => runTest(true, true, true, true));
|
||||||
|
it('without UID - no reload', () => runTest(true, true, false, true));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edit mode off', () => {
|
||||||
|
it('with UID - reload', () => runTest(true, true, true, false));
|
||||||
|
it('without UID - no reload', () => runTest(true, true, false, false));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import { config } from '@grafana/runtime';
|
|
||||||
import { setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
|
||||||
|
|
||||||
import { getDashboardDTO, updateScopes } from './utils/actions';
|
|
||||||
import { expectNewDashboardDTO, expectOldDashboardDTO } from './utils/assertions';
|
|
||||||
import { getDatasource, getInstanceSettings, getMock } from './utils/mocks';
|
|
||||||
import { renderDashboard, resetScenes } from './utils/render';
|
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
...jest.requireActual('@grafana/runtime'),
|
|
||||||
useChromeHeaderHeight: jest.fn(),
|
|
||||||
getBackendSrv: () => ({ get: getMock }),
|
|
||||||
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
|
|
||||||
usePluginLinks: jest.fn().mockReturnValue({ links: [] }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const runTest = async (passScopes: boolean, kubernetesApi: boolean) => {
|
|
||||||
config.featureToggles.scopeFilters = true;
|
|
||||||
config.featureToggles.passScopeToDashboardApi = passScopes;
|
|
||||||
config.featureToggles.kubernetesDashboards = kubernetesApi;
|
|
||||||
setDashboardAPI(undefined);
|
|
||||||
renderDashboard({}, { reloadOnScopesChange: true });
|
|
||||||
await updateScopes(['grafana', 'mimir']);
|
|
||||||
await getDashboardDTO();
|
|
||||||
|
|
||||||
if (kubernetesApi) {
|
|
||||||
return expectNewDashboardDTO();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passScopes) {
|
|
||||||
return expectOldDashboardDTO(['grafana', 'mimir']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expectOldDashboardDTO();
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Dashboards API', () => {
|
|
||||||
afterEach(async () => {
|
|
||||||
setDashboardAPI(undefined);
|
|
||||||
await resetScenes();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Legacy API should not pass the scopes with feature flag off', async () => runTest(false, false));
|
|
||||||
it('K8s API should not pass the scopes with feature flag off', async () => runTest(false, true));
|
|
||||||
it('Legacy API should pass the scopes with feature flag on', async () => runTest(true, false));
|
|
||||||
it('K8s API should not pass the scopes with feature flag on', async () => runTest(true, true));
|
|
||||||
});
|
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import { act, fireEvent } from '@testing-library/react';
|
import { act, fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
import { getDashboardAPI, setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
import { DateTime, makeTimeRange, dateMath } from '@grafana/data';
|
||||||
|
import { MultiValueVariable, sceneGraph, VariableValue } from '@grafana/scenes';
|
||||||
|
import { defaultTimeZone, TimeZone } from '@grafana/schema';
|
||||||
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
||||||
|
|
||||||
import { scopesSelectorScene } from '../../instance';
|
import { scopesSelectorScene } from '../../instance';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
dashboardReloadSpy,
|
||||||
fetchDashboardsSpy,
|
fetchDashboardsSpy,
|
||||||
fetchNodesSpy,
|
fetchNodesSpy,
|
||||||
fetchScopeSpy,
|
fetchScopeSpy,
|
||||||
fetchSelectedScopesSpy,
|
fetchSelectedScopesSpy,
|
||||||
getMock,
|
getMock,
|
||||||
locationReloadSpy,
|
|
||||||
} from './mocks';
|
} from './mocks';
|
||||||
import {
|
import {
|
||||||
getDashboardFolderExpand,
|
getDashboardFolderExpand,
|
||||||
@@ -40,7 +42,7 @@ export const clearMocks = () => {
|
|||||||
fetchScopeSpy.mockClear();
|
fetchScopeSpy.mockClear();
|
||||||
fetchSelectedScopesSpy.mockClear();
|
fetchSelectedScopesSpy.mockClear();
|
||||||
fetchDashboardsSpy.mockClear();
|
fetchDashboardsSpy.mockClear();
|
||||||
locationReloadSpy.mockClear();
|
dashboardReloadSpy.mockClear();
|
||||||
getMock.mockClear();
|
getMock.mockClear();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,7 +89,24 @@ export const expandDashboardFolder = (folder: string) => click(() => getDashboar
|
|||||||
export const enterEditMode = async (dashboardScene: DashboardScene) =>
|
export const enterEditMode = async (dashboardScene: DashboardScene) =>
|
||||||
act(async () => dashboardScene.onEnterEditMode());
|
act(async () => dashboardScene.onEnterEditMode());
|
||||||
|
|
||||||
export const getDashboardDTO = async () => {
|
export const updateTimeRange = async (
|
||||||
setDashboardAPI(undefined);
|
dashboardScene: DashboardScene,
|
||||||
await getDashboardAPI().getDashboardDTO('1');
|
from: DateTime | string = 'now-6h',
|
||||||
|
to: DateTime | string = 'now',
|
||||||
|
timeZone: TimeZone = defaultTimeZone
|
||||||
|
) =>
|
||||||
|
act(async () =>
|
||||||
|
sceneGraph
|
||||||
|
.getTimeRange(dashboardScene)
|
||||||
|
.onTimeRangeChange(makeTimeRange(dateMath.parse(from, false, timeZone)!, dateMath.parse(to, false, timeZone)!))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateVariable = async (dashboardScene: DashboardScene, name: string, value: VariableValue) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const variable = sceneGraph.lookupVariable(name, dashboardScene) as MultiValueVariable;
|
||||||
|
|
||||||
|
return act(async () => variable.changeValueTo(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateMyVar = async (dashboardScene: DashboardScene, value: '1' | '2') =>
|
||||||
|
updateVariable(dashboardScene, 'myVar', value);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getMock, locationReloadSpy } from './mocks';
|
import { dashboardReloadSpy } from './mocks';
|
||||||
import {
|
import {
|
||||||
getDashboard,
|
getDashboard,
|
||||||
getDashboardsContainer,
|
getDashboardsContainer,
|
||||||
@@ -77,13 +77,8 @@ export const expectDashboardNotInDocument = (uid: string) => expectNotInDocument
|
|||||||
export const expectDashboardLength = (uid: string, length: number) =>
|
export const expectDashboardLength = (uid: string, length: number) =>
|
||||||
expect(queryAllDashboard(uid)).toHaveLength(length);
|
expect(queryAllDashboard(uid)).toHaveLength(length);
|
||||||
|
|
||||||
export const expectNotDashboardReload = () => expect(locationReloadSpy).not.toHaveBeenCalled();
|
export const expectNotDashboardReload = () => expect(dashboardReloadSpy).not.toHaveBeenCalled();
|
||||||
export const expectDashboardReload = () => expect(locationReloadSpy).toHaveBeenCalled();
|
export const expectDashboardReload = () => expect(dashboardReloadSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
export const expectOldDashboardDTO = (scopes?: string[]) =>
|
|
||||||
expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', scopes ? { scopes } : undefined);
|
|
||||||
export const expectNewDashboardDTO = () =>
|
|
||||||
expect(getMock).toHaveBeenCalledWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto');
|
|
||||||
|
|
||||||
export const expectSelectedScopePath = (name: string, path: string[] | undefined) =>
|
export const expectSelectedScopePath = (name: string, path: string[] | undefined) =>
|
||||||
expect(getSelectedScope(name)?.path).toEqual(path);
|
expect(getSelectedScope(name)?.path).toEqual(path);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Scope, ScopeDashboardBinding, ScopeNode } from '@grafana/data';
|
import { Scope, ScopeDashboardBinding, ScopeNode } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
|
||||||
import { DataSourceRef } from '@grafana/schema/dist/esm/common/common.gen';
|
import { DataSourceRef } from '@grafana/schema/dist/esm/common/common.gen';
|
||||||
|
import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager';
|
||||||
|
|
||||||
import * as api from '../../internal/api';
|
import * as api from '../../internal/api';
|
||||||
|
|
||||||
@@ -373,48 +373,50 @@ export const fetchNodesSpy = jest.spyOn(api, 'fetchNodes');
|
|||||||
export const fetchScopeSpy = jest.spyOn(api, 'fetchScope');
|
export const fetchScopeSpy = jest.spyOn(api, 'fetchScope');
|
||||||
export const fetchSelectedScopesSpy = jest.spyOn(api, 'fetchSelectedScopes');
|
export const fetchSelectedScopesSpy = jest.spyOn(api, 'fetchSelectedScopes');
|
||||||
export const fetchDashboardsSpy = jest.spyOn(api, 'fetchDashboards');
|
export const fetchDashboardsSpy = jest.spyOn(api, 'fetchDashboards');
|
||||||
export const locationReloadSpy = jest.spyOn(locationService, 'reload');
|
export const dashboardReloadSpy = jest.spyOn(getDashboardScenePageStateManager(), 'reloadDashboard');
|
||||||
|
|
||||||
export const getMock = jest
|
export const getMock = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementation((url: string, params: { parent: string; scope: string[]; query?: string }) => {
|
.mockImplementation(
|
||||||
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_node_children')) {
|
(url: string, params: { parent: string; scope: string[]; query?: string } & Record<string, string | string[]>) => {
|
||||||
return {
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_node_children')) {
|
||||||
items: mocksNodes.filter(
|
return {
|
||||||
({ parent, spec: { title } }) =>
|
items: mocksNodes.filter(
|
||||||
parent === params.parent && title.toLowerCase().includes((params.query ?? '').toLowerCase())
|
({ parent, spec: { title } }) =>
|
||||||
),
|
parent === params.parent && title.toLowerCase().includes((params.query ?? '').toLowerCase())
|
||||||
};
|
),
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopes/')) {
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopes/')) {
|
||||||
const name = url.replace('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopes/', '');
|
const name = url.replace('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopes/', '');
|
||||||
|
|
||||||
return mocksScopes.find((scope) => scope.metadata.name.toLowerCase() === name.toLowerCase()) ?? {};
|
return mocksScopes.find((scope) => scope.metadata.name.toLowerCase() === name.toLowerCase()) ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_dashboard_bindings')) {
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_dashboard_bindings')) {
|
||||||
return {
|
return {
|
||||||
items: mocksScopeDashboardBindings.filter(({ spec: { scope: bindingScope } }) =>
|
items: mocksScopeDashboardBindings.filter(({ spec: { scope: bindingScope } }) =>
|
||||||
params.scope.includes(bindingScope)
|
params.scope.includes(bindingScope)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('/api/dashboards/uid/')) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/')) {
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
name: '1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (url.startsWith('/api/dashboards/uid/')) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
if (url.startsWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/')) {
|
|
||||||
return {
|
|
||||||
metadata: {
|
|
||||||
name: '1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateScopeDashboardBinding = (dashboardTitle: string, groups?: string[], dashboardId?: string) => ({
|
const generateScopeDashboardBinding = (dashboardTitle: string, groups?: string[], dashboardId?: string) => ({
|
||||||
metadata: { name: `${dashboardTitle}-name` },
|
metadata: { name: `${dashboardTitle}-name` },
|
||||||
|
|||||||
@@ -49,6 +49,50 @@ const getDashboardDTO: (
|
|||||||
name: 'groupBy',
|
name: 'groupBy',
|
||||||
type: 'groupby',
|
type: 'groupby',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
current: {
|
||||||
|
text: ['1'],
|
||||||
|
value: ['1'],
|
||||||
|
},
|
||||||
|
multi: true,
|
||||||
|
name: 'myVar',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
selected: true,
|
||||||
|
text: '1',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: '2',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: '1, 2',
|
||||||
|
type: 'custom',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
current: {
|
||||||
|
text: ['1'],
|
||||||
|
value: ['1'],
|
||||||
|
},
|
||||||
|
multi: true,
|
||||||
|
name: 'myVar2',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
selected: true,
|
||||||
|
text: '1',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selected: false,
|
||||||
|
text: '2',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
query: '1, 2',
|
||||||
|
type: 'custom',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
panels: [
|
panels: [
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ export interface DashboardMeta {
|
|||||||
// until we use the resource as the main container
|
// until we use the resource as the main container
|
||||||
k8s?: Partial<ObjectMeta>;
|
k8s?: Partial<ObjectMeta>;
|
||||||
|
|
||||||
// This is a property added specifically for edge cases where dashboards should be reloaded on scopes changes
|
// This is a property added specifically for edge cases where dashboards should be reloaded on scopes, time range or variables changes
|
||||||
// This property is not persisted in the DB but its existence is controlled by the API
|
// This property is not persisted in the DB but its existence is controlled by the API
|
||||||
reloadOnScopesChange?: boolean;
|
reloadOnParamsChange?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnnotationActions {
|
export interface AnnotationActions {
|
||||||
|
|||||||
Reference in New Issue
Block a user