mirror of https://github.com/grafana/grafana.git
Scopes: Pass selected scopes to dashboard JSON fetching (#89157)
This commit is contained in:
parent
c88de7f4d0
commit
543e71eb28
|
|
@ -197,4 +197,5 @@ export interface FeatureToggles {
|
||||||
failWrongDSUID?: boolean;
|
failWrongDSUID?: boolean;
|
||||||
databaseReadReplica?: boolean;
|
databaseReadReplica?: boolean;
|
||||||
zanzana?: boolean;
|
zanzana?: boolean;
|
||||||
|
passScopeToDashboardApi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1344,6 +1344,17 @@ var (
|
||||||
HideFromDocs: true,
|
HideFromDocs: true,
|
||||||
HideFromAdminPage: true,
|
HideFromAdminPage: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "passScopeToDashboardApi",
|
||||||
|
Description: "Enables the passing of scopes to dashboards fetching in Grafana",
|
||||||
|
FrontendOnly: false,
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
Owner: grafanaDashboardsSquad,
|
||||||
|
RequiresRestart: false,
|
||||||
|
AllowSelfServe: false,
|
||||||
|
HideFromDocs: true,
|
||||||
|
HideFromAdminPage: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,3 +178,4 @@ ssoSettingsLDAP,experimental,@grafana/identity-access-team,false,false,false
|
||||||
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||||
databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false
|
databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,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
|
||||||
|
|
|
||||||
|
|
|
@ -722,4 +722,8 @@ const (
|
||||||
// FlagZanzana
|
// FlagZanzana
|
||||||
// Use openFGA as authorization engine.
|
// Use openFGA as authorization engine.
|
||||||
FlagZanzana = "zanzana"
|
FlagZanzana = "zanzana"
|
||||||
|
|
||||||
|
// FlagPassScopeToDashboardApi
|
||||||
|
// Enables the passing of scopes to dashboards fetching in Grafana
|
||||||
|
FlagPassScopeToDashboardApi = "passScopeToDashboardApi"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1678,6 +1678,20 @@
|
||||||
"requiresDevMode": true
|
"requiresDevMode": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "passScopeToDashboardApi",
|
||||||
|
"resourceVersion": "1718290335877",
|
||||||
|
"creationTimestamp": "2024-06-13T14:52:15Z"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"description": "Enables the passing of scopes to dashboards fetching in Grafana",
|
||||||
|
"stage": "experimental",
|
||||||
|
"codeowner": "@grafana/dashboards-squad",
|
||||||
|
"hideFromAdminPage": true,
|
||||||
|
"hideFromDocs": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "pdfTables",
|
"name": "pdfTables",
|
||||||
|
|
@ -2335,4 +2349,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,11 +173,11 @@ describe('DashboardScenePageStateManager', () => {
|
||||||
|
|
||||||
const loader = new DashboardScenePageStateManager({});
|
const loader = new DashboardScenePageStateManager({});
|
||||||
|
|
||||||
expect(loader.getFromCache('fake-dash')).toBeNull();
|
expect(loader.getDashboardFromCache('fake-dash')).toBeNull();
|
||||||
|
|
||||||
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
await loader.loadDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||||
|
|
||||||
expect(loader.getFromCache('fake-dash')).toBeDefined();
|
expect(loader.getDashboardFromCache('fake-dash')).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load dashboard DTO from cache if requested again within 2s', async () => {
|
it('should load dashboard DTO from cache if requested again within 2s', async () => {
|
||||||
|
|
@ -186,7 +186,7 @@ describe('DashboardScenePageStateManager', () => {
|
||||||
|
|
||||||
const loader = new DashboardScenePageStateManager({});
|
const loader = new DashboardScenePageStateManager({});
|
||||||
|
|
||||||
expect(loader.getFromCache('fake-dash')).toBeNull();
|
expect(loader.getDashboardFromCache('fake-dash')).toBeNull();
|
||||||
|
|
||||||
await loader.fetchDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
await loader.fetchDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal });
|
||||||
expect(loadDashSpy).toHaveBeenCalledTimes(1);
|
expect(loadDashSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
@ -228,7 +228,7 @@ describe('DashboardScenePageStateManager', () => {
|
||||||
keepDashboardFromExploreInLocalStorage: false,
|
keepDashboardFromExploreInLocalStorage: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(loader.getFromCache('fake-dash')).toBeNull();
|
expect(loader.getDashboardFromCache('fake-dash')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking';
|
import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking';
|
||||||
import { DashboardDTO, DashboardRoutes } from 'app/types';
|
import { DashboardDTO, DashboardRoutes } from 'app/types';
|
||||||
|
|
||||||
|
import { getScopesFromUrl } from '../../dashboard/utils/getScopesFromUrl';
|
||||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { buildNewDashboardSaveModel } from '../serialization/buildNewDashboardSaveModel';
|
import { buildNewDashboardSaveModel } from '../serialization/buildNewDashboardSaveModel';
|
||||||
|
|
@ -83,7 +84,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||||
}
|
}
|
||||||
|
|
||||||
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.getDashboardFromCache(cacheKey);
|
||||||
|
|
||||||
if (cachedDashboard) {
|
if (cachedDashboard) {
|
||||||
return cachedDashboard;
|
return cachedDashboard;
|
||||||
|
|
@ -142,7 +143,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not cache new dashboards
|
// Do not cache new dashboards
|
||||||
this.dashboardCache = { dashboard: rsp, ts: Date.now(), cacheKey };
|
this.setDashboardCache(cacheKey, rsp);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore cancelled errors
|
// Ignore cancelled errors
|
||||||
if (isFetchError(e) && e.cancelled) {
|
if (isFetchError(e) && e.cancelled) {
|
||||||
|
|
@ -220,7 +221,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||||
|
|
||||||
const rsp = await this.fetchDashboard(options);
|
const rsp = await this.fetchDashboard(options);
|
||||||
|
|
||||||
const fromCache = this.cache[options.uid];
|
const fromCache = this.getSceneFromCache(options.uid);
|
||||||
|
|
||||||
// When coming from Explore, skip returnning scene from cache
|
// When coming from Explore, skip returnning scene from cache
|
||||||
if (!comingFromExplore) {
|
if (!comingFromExplore) {
|
||||||
|
|
@ -234,7 +235,7 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||||
|
|
||||||
// Cache scene only if not coming from Explore, we don't want to cache temporary dashboard
|
// Cache scene only if not coming from Explore, we don't want to cache temporary dashboard
|
||||||
if (options.uid && !comingFromExplore) {
|
if (options.uid && !comingFromExplore) {
|
||||||
this.cache[options.uid] = scene;
|
this.setSceneCache(options.uid, scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
return scene;
|
return scene;
|
||||||
|
|
@ -249,8 +250,9 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
|
||||||
throw new Error('Dashboard not found');
|
throw new Error('Dashboard not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFromCache(cacheKey: string) {
|
public getDashboardFromCache(cacheKey: string) {
|
||||||
const cachedDashboard = this.dashboardCache;
|
const cachedDashboard = this.dashboardCache;
|
||||||
|
cacheKey = this.getCacheKey(cacheKey);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cachedDashboard &&
|
cachedDashboard &&
|
||||||
|
|
@ -275,12 +277,38 @@ 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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearDashboardCache() {
|
public clearDashboardCache() {
|
||||||
this.dashboardCache = undefined;
|
this.dashboardCache = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSceneFromCache(cacheKey: string) {
|
||||||
|
cacheKey = this.getCacheKey(cacheKey);
|
||||||
|
|
||||||
|
return this.cache[cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSceneCache(cacheKey: string, scene: DashboardScene) {
|
||||||
|
cacheKey = this.getCacheKey(cacheKey);
|
||||||
|
|
||||||
|
this.cache[cacheKey] = scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCacheKey(cacheKey: string): string {
|
||||||
|
const scopesSearchParams = getScopesFromUrl();
|
||||||
|
|
||||||
|
if (!scopesSearchParams?.has('scopes')) {
|
||||||
|
return cacheKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
scopesSearchParams.sort();
|
||||||
|
|
||||||
|
return `${cacheKey}__scp__${scopesSearchParams.toString()}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let stateManager: DashboardScenePageStateManager | null = null;
|
let stateManager: DashboardScenePageStateManager | null = null;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { act, cleanup, waitFor } from '@testing-library/react';
|
import { act, cleanup, waitFor } from '@testing-library/react';
|
||||||
import userEvents from '@testing-library/user-event';
|
import userEvents from '@testing-library/user-event';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import { sceneGraph } from '@grafana/scenes';
|
import { sceneGraph } from '@grafana/scenes';
|
||||||
|
import { getDashboardAPI, setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||||
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
||||||
|
|
||||||
import { ScopesFiltersScene } from './ScopesFiltersScene';
|
import { ScopesFiltersScene } from './ScopesFiltersScene';
|
||||||
|
|
@ -33,8 +34,7 @@ import {
|
||||||
getDashboardsContainer,
|
getDashboardsContainer,
|
||||||
getDashboardsExpand,
|
getDashboardsExpand,
|
||||||
getDashboardsSearch,
|
getDashboardsSearch,
|
||||||
mocksNodes,
|
getMock,
|
||||||
mocksScopeDashboardBindings,
|
|
||||||
mocksScopes,
|
mocksScopes,
|
||||||
queryAllDashboard,
|
queryAllDashboard,
|
||||||
queryFiltersApply,
|
queryFiltersApply,
|
||||||
|
|
@ -52,31 +52,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
get: jest.fn().mockImplementation((url: string, params: { parent: string; scope: string[]; query?: string }) => {
|
get: getMock,
|
||||||
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_node_children')) {
|
|
||||||
return {
|
|
||||||
items: mocksNodes.filter(
|
|
||||||
({ parent, spec: { title } }) => parent === params.parent && title.includes(params.query ?? '')
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.startsWith('/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 === name) ?? {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_dashboard_bindings')) {
|
|
||||||
return {
|
|
||||||
items: mocksScopeDashboardBindings.filter(({ spec: { scope: bindingScope } }) =>
|
|
||||||
params.scope.includes(bindingScope)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -109,6 +85,7 @@ describe('ScopesScene', () => {
|
||||||
fetchScopeSpy.mockClear();
|
fetchScopeSpy.mockClear();
|
||||||
fetchSelectedScopesSpy.mockClear();
|
fetchSelectedScopesSpy.mockClear();
|
||||||
fetchSuggestedDashboardsSpy.mockClear();
|
fetchSuggestedDashboardsSpy.mockClear();
|
||||||
|
getMock.mockClear();
|
||||||
|
|
||||||
dashboardScene = buildTestScene();
|
dashboardScene = buildTestScene();
|
||||||
scopesScene = dashboardScene.state.scopes!;
|
scopesScene = dashboardScene.state.scopes!;
|
||||||
|
|
@ -418,4 +395,54 @@ describe('ScopesScene', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Dashboards API', () => {
|
||||||
|
describe('Feature flag off', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
config.featureToggles.scopeFilters = true;
|
||||||
|
config.featureToggles.passScopeToDashboardApi = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setDashboardAPI(undefined);
|
||||||
|
locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Legacy API should not pass the scopes', () => {
|
||||||
|
config.featureToggles.kubernetesDashboards = false;
|
||||||
|
getDashboardAPI().getDashboardDTO('1');
|
||||||
|
expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('K8s API should not pass the scopes', () => {
|
||||||
|
config.featureToggles.kubernetesDashboards = true;
|
||||||
|
getDashboardAPI().getDashboardDTO('1');
|
||||||
|
expect(getMock).toHaveBeenCalledWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Feature flag on', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
config.featureToggles.scopeFilters = true;
|
||||||
|
config.featureToggles.passScopeToDashboardApi = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setDashboardAPI(undefined);
|
||||||
|
locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Legacy API should pass the scopes', () => {
|
||||||
|
config.featureToggles.kubernetesDashboards = false;
|
||||||
|
getDashboardAPI().getDashboardDTO('1');
|
||||||
|
expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', { scopes: ['scope1', 'scope2', 'scope3'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('K8s API should not pass the scopes', () => {
|
||||||
|
config.featureToggles.kubernetesDashboards = true;
|
||||||
|
getDashboardAPI().getDashboardDTO('1');
|
||||||
|
expect(getMock).toHaveBeenCalledWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,46 @@ export const fetchScopeSpy = jest.spyOn(api, 'fetchScope');
|
||||||
export const fetchSelectedScopesSpy = jest.spyOn(api, 'fetchSelectedScopes');
|
export const fetchSelectedScopesSpy = jest.spyOn(api, 'fetchSelectedScopes');
|
||||||
export const fetchSuggestedDashboardsSpy = jest.spyOn(api, 'fetchSuggestedDashboards');
|
export const fetchSuggestedDashboardsSpy = jest.spyOn(api, 'fetchSuggestedDashboards');
|
||||||
|
|
||||||
|
export const getMock = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((url: string, params: { parent: string; scope: string[]; query?: string }) => {
|
||||||
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_node_children')) {
|
||||||
|
return {
|
||||||
|
items: mocksNodes.filter(
|
||||||
|
({ parent, spec: { title } }) => parent === params.parent && title.includes(params.query ?? '')
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('/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 === name) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/find/scope_dashboard_bindings')) {
|
||||||
|
return {
|
||||||
|
items: mocksScopeDashboardBindings.filter(({ spec: { scope: 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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
tree: {
|
tree: {
|
||||||
search: (nodeId: string) => `scopes-tree-${nodeId}-search`,
|
search: (nodeId: string) => `scopes-tree-${nodeId}-search`,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ 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 { DashboardDTO, DashboardDataDTO } from 'app/types';
|
import { DashboardDTO, DashboardDataDTO } from 'app/types';
|
||||||
|
|
||||||
|
import { getScopesFromUrl } from '../utils/getScopesFromUrl';
|
||||||
|
|
||||||
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): Promise<DashboardDTO>;
|
||||||
|
|
@ -35,7 +37,11 @@ class LegacyDashboardAPI implements DashboardAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDashboardDTO(uid: string): Promise<DashboardDTO> {
|
getDashboardDTO(uid: string): Promise<DashboardDTO> {
|
||||||
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`);
|
const scopesSearchParams = getScopesFromUrl();
|
||||||
|
const scopes = scopesSearchParams?.getAll('scopes') ?? [];
|
||||||
|
const queryParams = scopes.length > 0 ? { scopes } : undefined;
|
||||||
|
|
||||||
|
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`, queryParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,3 +88,10 @@ export function getDashboardAPI() {
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setDashboardAPI(override: DashboardAPI | undefined) {
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
throw new Error('dashboardAPI can be only overridden in test environment');
|
||||||
|
}
|
||||||
|
instance = override;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export class DashboardLoaderSrv {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else if (uid) {
|
} else if (uid) {
|
||||||
const cachedDashboard = stateManager.getFromCache(uid);
|
const cachedDashboard = stateManager.getDashboardFromCache(uid);
|
||||||
if (cachedDashboard) {
|
if (cachedDashboard) {
|
||||||
return Promise.resolve(cachedDashboard);
|
return Promise.resolve(cachedDashboard);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ async function fetchDashboard(
|
||||||
switch (args.routeName) {
|
switch (args.routeName) {
|
||||||
case DashboardRoutes.Home: {
|
case DashboardRoutes.Home: {
|
||||||
const stateManager = getDashboardScenePageStateManager();
|
const stateManager = getDashboardScenePageStateManager();
|
||||||
const cachedDashboard = stateManager.getFromCache(HOME_DASHBOARD_CACHE_KEY);
|
const cachedDashboard = stateManager.getDashboardFromCache(HOME_DASHBOARD_CACHE_KEY);
|
||||||
|
|
||||||
if (cachedDashboard) {
|
if (cachedDashboard) {
|
||||||
return cachedDashboard;
|
return cachedDashboard;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { config, locationService } from '@grafana/runtime';
|
||||||
|
|
||||||
|
export function getScopesFromUrl(): URLSearchParams | undefined {
|
||||||
|
if (!config.featureToggles.scopeFilters || !config.featureToggles.passScopeToDashboardApi) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = locationService.getSearchObject();
|
||||||
|
const rawScopes = queryParams['scopes'] ?? [];
|
||||||
|
const scopes = Array.isArray(rawScopes) ? rawScopes : [rawScopes];
|
||||||
|
|
||||||
|
return new URLSearchParams(scopes.map((scope) => ['scopes', String(scope)]));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue