Scopes: Reload dashboards when on scopes change (#92804)

This commit is contained in:
Bogdan Matei 2024-09-12 14:58:45 +03:00 committed by GitHub
parent ce7533eb3b
commit 2a2813b577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1471 additions and 1181 deletions

View File

@ -0,0 +1,22 @@
import { locationService } from '@grafana/runtime';
import { sceneGraph } from '@grafana/scenes';
import { ScopesFacade } from 'app/features/scopes';
export interface DashboardScopesFacadeState {
reloadOnScopesChange?: boolean;
uid?: string;
}
export class DashboardScopesFacade extends ScopesFacade {
constructor({ reloadOnScopesChange, uid }: DashboardScopesFacadeState) {
super({
handler: (facade) => {
if (reloadOnScopesChange && uid) {
locationService.reload();
} else {
sceneGraph.getTimeRange(facade).onRefresh();
}
},
});
}
}

View File

@ -19,10 +19,8 @@ import {
SceneDataLayerProvider,
SceneDataLayerControls,
UserActionEvent,
sceneGraph,
} from '@grafana/scenes';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { ScopesFacade } from 'app/features/scopes';
import { DashboardDTO, DashboardDataDTO } from 'app/types';
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
@ -32,6 +30,7 @@ import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DashboardGridItem, RepeatDirection } from '../scene/DashboardGridItem';
import { registerDashboardMacro } from '../scene/DashboardMacro';
import { DashboardScene } from '../scene/DashboardScene';
import { DashboardScopesFacade } from '../scene/DashboardScopesFacade';
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavior';
@ -245,8 +244,9 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
registerPanelInteractionsReporter,
new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }),
preserveDashboardSceneStateInLocalStorage,
new ScopesFacade({
handler: (facade) => sceneGraph.getTimeRange(facade).onRefresh(),
new DashboardScopesFacade({
reloadOnScopesChange: oldModel.meta.reloadOnScopesChange,
uid: oldModel.uid,
}),
],
$data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }),

View File

@ -42,7 +42,7 @@ class LegacyDashboardAPI implements DashboardAPI {
}
getDashboardDTO(uid: string): Promise<DashboardDTO> {
const scopes = getSelectedScopesNames();
const scopes = config.featureToggles.passScopeToDashboardApi ? getSelectedScopesNames() : [];
const queryParams = scopes.length > 0 ? { scopes } : undefined;
return getBackendSrv().get<DashboardDTO>(`/api/dashboards/uid/${uid}`, queryParams);

View File

@ -0,0 +1,38 @@
import { config } from '@grafana/runtime';
import { updateScopes } from './utils/actions';
import { expectDashboardReload, expectNotDashboardReload } 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 }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Dashboard reload', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
afterEach(async () => {
await resetScenes();
});
it('Does not reload the dashboard without UID', async () => {
renderDashboard({ uid: undefined }, { reloadOnScopesChange: true });
await updateScopes(['grafana']);
expectNotDashboardReload();
});
it('Reloads the dashboard with UID', async () => {
renderDashboard({}, { reloadOnScopesChange: true });
await updateScopes(['grafana']);
expectDashboardReload();
});
});

View File

@ -1,84 +1,48 @@
import { cleanup } from '@testing-library/react';
import { config } from '@grafana/runtime';
import { setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { config, locationService } from '@grafana/runtime';
import { getDashboardAPI, setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { initializeScopes } from '../instance';
import { getMock } from './utils/mocks';
import { resetScenes } from './utils/render';
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'),
getBackendSrv: () => ({
get: getMock,
}),
useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Scopes', () => {
describe('Dashboards API', () => {
describe('Feature flag off', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.passScopeToDashboardApi = false;
});
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();
beforeEach(() => {
setDashboardAPI(undefined);
locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3');
});
if (kubernetesApi) {
return expectNewDashboardDTO();
}
afterEach(() => {
resetScenes();
cleanup();
});
if (passScopes) {
return expectOldDashboardDTO(['grafana', 'mimir']);
}
it('Legacy API should not pass the scopes', async () => {
config.featureToggles.kubernetesDashboards = false;
await getDashboardAPI().getDashboardDTO('1');
expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', undefined);
});
return expectOldDashboardDTO();
};
it('K8s API should not pass the scopes', async () => {
config.featureToggles.kubernetesDashboards = true;
await getDashboardAPI().getDashboardDTO('1');
expect(getMock).toHaveBeenCalledWith(
'/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto'
);
});
});
describe('Feature flag on', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.passScopeToDashboardApi = true;
});
beforeEach(() => {
setDashboardAPI(undefined);
locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3');
initializeScopes();
});
afterEach(() => {
resetScenes();
cleanup();
});
it('Legacy API should pass the scopes', async () => {
config.featureToggles.kubernetesDashboards = false;
await getDashboardAPI().getDashboardDTO('1');
expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', { scopes: ['scope1', 'scope2', 'scope3'] });
});
it('K8s API should not pass the scopes', async () => {
config.featureToggles.kubernetesDashboards = true;
await getDashboardAPI().getDashboardDTO('1');
expect(getMock).toHaveBeenCalledWith(
'/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto'
);
});
});
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));
});

View File

@ -0,0 +1,250 @@
import { config } from '@grafana/runtime';
import {
clearNotFound,
expandDashboardFolder,
searchDashboards,
toggleDashboards,
updateScopes,
} from './utils/actions';
import {
expectDashboardFolderNotInDocument,
expectDashboardInDocument,
expectDashboardLength,
expectDashboardNotInDocument,
expectDashboardSearchValue,
expectDashboardsSearch,
expectNoDashboardsForFilter,
expectNoDashboardsForScope,
expectNoDashboardsNoScopes,
expectNoDashboardsSearch,
} from './utils/assertions';
import { fetchDashboardsSpy, 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 }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Dashboards list', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
beforeEach(() => {
renderDashboard();
});
afterEach(async () => {
await resetScenes();
});
it('Does not fetch dashboards list when the list is not expanded', async () => {
await updateScopes(['mimir']);
expect(fetchDashboardsSpy).not.toHaveBeenCalled();
});
it('Fetches dashboards list when the list is expanded', async () => {
await toggleDashboards();
await updateScopes(['mimir']);
expect(fetchDashboardsSpy).toHaveBeenCalled();
});
it('Fetches dashboards list when the list is expanded after scope selection', async () => {
await updateScopes(['mimir']);
await toggleDashboards();
expect(fetchDashboardsSpy).toHaveBeenCalled();
});
it('Shows dashboards for multiple scopes', async () => {
await toggleDashboards();
await updateScopes(['grafana']);
await expandDashboardFolder('General');
await expandDashboardFolder('Observability');
await expandDashboardFolder('Usage');
expectDashboardFolderNotInDocument('Components');
expectDashboardFolderNotInDocument('Investigations');
expectDashboardInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardInDocument('observability-backend-errors');
expectDashboardInDocument('observability-backend-logs');
expectDashboardInDocument('observability-frontend-errors');
expectDashboardInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardInDocument('frontend');
expectDashboardInDocument('overview');
expectDashboardInDocument('stats');
expectDashboardNotInDocument('multiple3-datasource-errors');
expectDashboardNotInDocument('multiple4-datasource-logs');
expectDashboardNotInDocument('multiple0-ingester');
expectDashboardNotInDocument('multiple1-distributor');
expectDashboardNotInDocument('multiple2-compacter');
expectDashboardNotInDocument('another-stats');
await updateScopes(['grafana', 'mimir']);
await expandDashboardFolder('General');
await expandDashboardFolder('Observability');
await expandDashboardFolder('Usage');
await expandDashboardFolder('Components');
await expandDashboardFolder('Investigations');
expectDashboardInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardInDocument('observability-backend-errors');
expectDashboardInDocument('observability-backend-logs');
expectDashboardInDocument('observability-frontend-errors');
expectDashboardInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardInDocument('frontend');
expectDashboardInDocument('overview');
expectDashboardInDocument('stats');
expectDashboardLength('multiple3-datasource-errors', 2);
expectDashboardLength('multiple4-datasource-logs', 2);
expectDashboardLength('multiple0-ingester', 2);
expectDashboardLength('multiple1-distributor', 2);
expectDashboardLength('multiple2-compacter', 2);
expectDashboardInDocument('another-stats');
await updateScopes(['grafana']);
await expandDashboardFolder('General');
await expandDashboardFolder('Observability');
await expandDashboardFolder('Usage');
expectDashboardFolderNotInDocument('Components');
expectDashboardFolderNotInDocument('Investigations');
expectDashboardInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardInDocument('observability-backend-errors');
expectDashboardInDocument('observability-backend-logs');
expectDashboardInDocument('observability-frontend-errors');
expectDashboardInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardInDocument('frontend');
expectDashboardInDocument('overview');
expectDashboardInDocument('stats');
expectDashboardFolderNotInDocument('multiple3-datasource-errors');
expectDashboardFolderNotInDocument('multiple4-datasource-logs');
expectDashboardFolderNotInDocument('multiple0-ingester');
expectDashboardFolderNotInDocument('multiple1-distributor');
expectDashboardFolderNotInDocument('multiple2-compacter');
expectDashboardFolderNotInDocument('another-stats');
});
it('Filters the dashboards list for dashboards', async () => {
await toggleDashboards();
await updateScopes(['grafana']);
await expandDashboardFolder('General');
await expandDashboardFolder('Observability');
await expandDashboardFolder('Usage');
expectDashboardInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardInDocument('observability-backend-errors');
expectDashboardInDocument('observability-backend-logs');
expectDashboardInDocument('observability-frontend-errors');
expectDashboardInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardInDocument('frontend');
expectDashboardInDocument('overview');
expectDashboardInDocument('stats');
await searchDashboards('Stats');
expectDashboardFolderNotInDocument('general-data-sources');
expectDashboardFolderNotInDocument('general-usage');
expectDashboardFolderNotInDocument('observability-backend-errors');
expectDashboardFolderNotInDocument('observability-backend-logs');
expectDashboardFolderNotInDocument('observability-frontend-errors');
expectDashboardFolderNotInDocument('observability-frontend-logs');
expectDashboardFolderNotInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardFolderNotInDocument('usage-usage-overview');
expectDashboardFolderNotInDocument('frontend');
expectDashboardFolderNotInDocument('overview');
expectDashboardInDocument('stats');
});
it('Filters the dashboards list for folders', async () => {
await toggleDashboards();
await updateScopes(['grafana']);
await expandDashboardFolder('General');
await expandDashboardFolder('Observability');
await expandDashboardFolder('Usage');
expectDashboardInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardInDocument('observability-backend-errors');
expectDashboardInDocument('observability-backend-logs');
expectDashboardInDocument('observability-frontend-errors');
expectDashboardInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardInDocument('frontend');
expectDashboardInDocument('overview');
expectDashboardInDocument('stats');
await searchDashboards('Usage');
expectDashboardFolderNotInDocument('general-data-sources');
expectDashboardInDocument('general-usage');
expectDashboardFolderNotInDocument('observability-backend-errors');
expectDashboardFolderNotInDocument('observability-backend-logs');
expectDashboardFolderNotInDocument('observability-frontend-errors');
expectDashboardFolderNotInDocument('observability-frontend-logs');
expectDashboardInDocument('usage-data-sources');
expectDashboardInDocument('usage-stats');
expectDashboardInDocument('usage-usage-overview');
expectDashboardFolderNotInDocument('frontend');
expectDashboardFolderNotInDocument('overview');
expectDashboardFolderNotInDocument('stats');
});
it('Deduplicates the dashboards list', async () => {
await toggleDashboards();
await updateScopes(['dev', 'ops']);
await expandDashboardFolder('Cardinality Management');
await expandDashboardFolder('Usage Insights');
expectDashboardLength('cardinality-management-labels', 1);
expectDashboardLength('cardinality-management-metrics', 1);
expectDashboardLength('cardinality-management-overview', 1);
expectDashboardLength('usage-insights-alertmanager', 1);
expectDashboardLength('usage-insights-data-sources', 1);
expectDashboardLength('usage-insights-metrics-ingestion', 1);
expectDashboardLength('usage-insights-overview', 1);
expectDashboardLength('usage-insights-query-errors', 1);
expectDashboardLength('billing-usage', 1);
});
it('Shows a proper message when no scopes are selected', async () => {
await toggleDashboards();
expectNoDashboardsNoScopes();
expectNoDashboardsSearch();
});
it('Does not show the input when there are no dashboards found for scope', async () => {
await toggleDashboards();
await updateScopes(['cloud']);
expectNoDashboardsForScope();
expectNoDashboardsSearch();
});
it('Shows the input and a message when there are no dashboards found for filter', async () => {
await toggleDashboards();
await updateScopes(['mimir']);
await searchDashboards('unknown');
expectDashboardsSearch();
expectNoDashboardsForFilter();
await clearNotFound();
expectDashboardSearchValue('');
});
});

View File

@ -0,0 +1,27 @@
import { config } from '@grafana/runtime';
import { scopesSelectorScene } from '../instance';
import { getDatasource, getInstanceSettings, getMock } from './utils/mocks';
import { renderDashboard } from './utils/render';
jest.mock('@grafana/runtime', () => ({
__esModule: true,
...jest.requireActual('@grafana/runtime'),
useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({ get: getMock }),
getDataSourceSrv: () => ({ get: getDatasource, getInstanceSettings }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Feature flag off', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = false;
config.featureToggles.groupByVariable = true;
});
it('Does not initialize', () => {
renderDashboard();
expect(scopesSelectorScene).toBeNull();
});
});

View File

@ -1,687 +0,0 @@
import { act, cleanup, waitFor } from '@testing-library/react';
import userEvents from '@testing-library/user-event';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { config, setPluginImportUtils } from '@grafana/runtime';
import { sceneGraph } from '@grafana/scenes';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { initializeScopes, scopesDashboardsScene, scopesSelectorScene } from '../instance';
import { getClosestScopesFacade } from '../utils';
import {
fetchDashboardsSpy,
fetchNodesSpy,
fetchScopeSpy,
fetchSelectedScopesSpy,
getMock,
mocksScopes,
} from './utils/mocks';
import { buildTestScene, renderDashboard, resetScenes } from './utils/render';
import {
getDashboard,
getDashboardFolderExpand,
getDashboardsExpand,
getDashboardsSearch,
getNotFoundForFilter,
getNotFoundForFilterClear,
getNotFoundForScope,
getNotFoundNoScopes,
getPersistedApplicationsMimirSelect,
getPersistedApplicationsMimirTitle,
getResultApplicationsCloudDevSelect,
getResultApplicationsCloudExpand,
getResultApplicationsCloudOpsSelect,
getResultApplicationsCloudSelect,
getResultApplicationsExpand,
getResultApplicationsGrafanaSelect,
getResultApplicationsGrafanaTitle,
getResultApplicationsMimirSelect,
getResultApplicationsMimirTitle,
getResultCloudDevRadio,
getResultCloudExpand,
getResultCloudOpsRadio,
getResultCloudSelect,
getSelectorApply,
getSelectorCancel,
getSelectorInput,
getTreeHeadline,
getTreeSearch,
queryAllDashboard,
queryDashboard,
queryDashboardFolderExpand,
queryDashboardsContainer,
queryDashboardsSearch,
queryPersistedApplicationsGrafanaTitle,
queryPersistedApplicationsMimirTitle,
queryResultApplicationsCloudTitle,
queryResultApplicationsGrafanaTitle,
queryResultApplicationsMimirTitle,
querySelectorApply,
} from './utils/selectors';
jest.mock('@grafana/runtime', () => ({
__esModule: true,
...jest.requireActual('@grafana/runtime'),
useChromeHeaderHeight: jest.fn(),
getBackendSrv: () => ({
get: getMock,
}),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
const panelPlugin = getPanelPlugin({
id: 'table',
skipDataQuery: true,
});
config.panels['table'] = panelPlugin.meta;
setPluginImportUtils({
importPanelPlugin: () => Promise.resolve(panelPlugin),
getPanelPluginFromCache: () => undefined,
});
describe('Scopes', () => {
describe('Feature flag off', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = false;
config.featureToggles.groupByVariable = true;
initializeScopes();
});
it('Does not initialize', () => {
const dashboardScene = buildTestScene();
dashboardScene.activate();
expect(scopesSelectorScene).toBeNull();
});
});
describe('Feature flag on', () => {
let dashboardScene: DashboardScene;
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(jest.fn());
fetchNodesSpy.mockClear();
fetchScopeSpy.mockClear();
fetchSelectedScopesSpy.mockClear();
fetchDashboardsSpy.mockClear();
getMock.mockClear();
initializeScopes();
dashboardScene = buildTestScene();
renderDashboard(dashboardScene);
});
afterEach(() => {
resetScenes();
cleanup();
});
describe('Tree', () => {
it('Navigates through scopes nodes', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsCloudExpand());
await userEvents.click(getResultApplicationsExpand());
});
it('Fetches scope details on select', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await waitFor(() => expect(fetchScopeSpy).toHaveBeenCalledTimes(1));
});
it('Selects the proper scopes', async () => {
await act(async () =>
scopesSelectorScene?.updateScopes([
{ scopeName: 'grafana', path: [] },
{ scopeName: 'mimir', path: [] },
])
);
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
expect(getResultApplicationsGrafanaSelect()).toBeChecked();
expect(getResultApplicationsMimirSelect()).toBeChecked();
});
it('Can select scopes from same level', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getResultApplicationsCloudSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Grafana, Mimir, Cloud');
});
it('Can select a node from an inner level', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getResultApplicationsCloudExpand());
await userEvents.click(getResultApplicationsCloudDevSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Dev');
});
it('Can select a node from an upper level', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultCloudSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Cloud');
});
it('Respects only one select per container', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultCloudExpand());
await userEvents.click(getResultCloudDevRadio());
expect(getResultCloudDevRadio().checked).toBe(true);
expect(getResultCloudOpsRadio().checked).toBe(false);
await userEvents.click(getResultCloudOpsRadio());
expect(getResultCloudDevRadio().checked).toBe(false);
expect(getResultCloudOpsRadio().checked).toBe(true);
});
it('Search works', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.type(getTreeSearch(), 'Cloud');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
expect(queryResultApplicationsGrafanaTitle()).not.toBeInTheDocument();
expect(queryResultApplicationsMimirTitle()).not.toBeInTheDocument();
expect(getResultApplicationsCloudSelect()).toBeInTheDocument();
await userEvents.clear(getTreeSearch());
await userEvents.type(getTreeSearch(), 'Grafana');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
expect(getResultApplicationsGrafanaSelect()).toBeInTheDocument();
expect(queryResultApplicationsCloudTitle()).not.toBeInTheDocument();
});
it('Opens to a selected scope', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultCloudExpand());
await userEvents.click(getSelectorApply());
await userEvents.click(getSelectorInput());
expect(queryResultApplicationsMimirTitle()).toBeInTheDocument();
});
it('Persists a scope', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'grafana');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
expect(getPersistedApplicationsMimirTitle()).toBeInTheDocument();
expect(queryPersistedApplicationsGrafanaTitle()).not.toBeInTheDocument();
expect(queryResultApplicationsMimirTitle()).not.toBeInTheDocument();
expect(getResultApplicationsGrafanaTitle()).toBeInTheDocument();
});
it('Does not persist a retrieved scope', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'mimir');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
expect(queryPersistedApplicationsMimirTitle()).not.toBeInTheDocument();
expect(getResultApplicationsMimirTitle()).toBeInTheDocument();
});
it('Removes persisted nodes', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'grafana');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
await userEvents.clear(getTreeSearch());
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
expect(queryPersistedApplicationsMimirTitle()).not.toBeInTheDocument();
expect(queryPersistedApplicationsGrafanaTitle()).not.toBeInTheDocument();
expect(getResultApplicationsMimirTitle()).toBeInTheDocument();
expect(getResultApplicationsGrafanaTitle()).toBeInTheDocument();
});
it('Persists nodes from search', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.type(getTreeSearch(), 'mimir');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'unknown');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(4));
expect(getPersistedApplicationsMimirTitle()).toBeInTheDocument();
await userEvents.clear(getTreeSearch());
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(5));
expect(getResultApplicationsMimirTitle()).toBeInTheDocument();
expect(getResultApplicationsGrafanaTitle()).toBeInTheDocument();
});
it('Selects a persisted scope', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'grafana');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Mimir, Grafana');
});
it('Deselects a persisted scope', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.type(getTreeSearch(), 'grafana');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Mimir, Grafana');
await userEvents.click(getSelectorInput());
await userEvents.click(getPersistedApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toBe('Grafana');
});
it('Shows the proper headline', async () => {
await userEvents.click(getSelectorInput());
expect(getTreeHeadline()).toHaveTextContent('Recommended');
await userEvents.type(getTreeSearch(), 'Applications');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(2));
expect(getTreeHeadline()).toHaveTextContent('Results');
await userEvents.type(getTreeSearch(), 'unknown');
await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3));
expect(getTreeHeadline()).toHaveTextContent('No results found for your query');
});
});
describe('Selector', () => {
it('Opens', async () => {
await userEvents.click(getSelectorInput());
expect(getSelectorApply()).toBeInTheDocument();
});
it('Fetches scope details on save', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultCloudSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => expect(fetchSelectedScopesSpy).toHaveBeenCalled());
expect(getClosestScopesFacade(dashboardScene)?.value).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'cloud')
);
});
it('Does not save the scopes on close', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultCloudSelect());
await userEvents.click(getSelectorCancel());
await waitFor(() => expect(fetchSelectedScopesSpy).not.toHaveBeenCalled());
expect(getClosestScopesFacade(dashboardScene)?.value).toEqual([]);
});
it('Shows selected scopes', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultCloudSelect());
await userEvents.click(getSelectorApply());
expect(getSelectorInput().value).toEqual('Cloud');
});
});
describe('Dashboards list', () => {
it('Toggles expanded state', async () => {
await userEvents.click(getDashboardsExpand());
expect(getNotFoundNoScopes()).toBeInTheDocument();
});
it('Does not fetch dashboards list when the list is not expanded', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => expect(fetchDashboardsSpy).not.toHaveBeenCalled());
});
it('Fetches dashboards list when the list is expanded', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => expect(fetchDashboardsSpy).toHaveBeenCalled());
});
it('Fetches dashboards list when the list is expanded after scope selection', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardsExpand());
await waitFor(() => expect(fetchDashboardsSpy).toHaveBeenCalled());
});
it('Shows dashboards for multiple scopes', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('General'));
await userEvents.click(getDashboardFolderExpand('Observability'));
await userEvents.click(getDashboardFolderExpand('Usage'));
expect(queryDashboardFolderExpand('Components')).not.toBeInTheDocument();
expect(queryDashboardFolderExpand('Investigations')).not.toBeInTheDocument();
expect(getDashboard('general-data-sources')).toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(getDashboard('observability-backend-errors')).toBeInTheDocument();
expect(getDashboard('observability-backend-logs')).toBeInTheDocument();
expect(getDashboard('observability-frontend-errors')).toBeInTheDocument();
expect(getDashboard('observability-frontend-logs')).toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(getDashboard('frontend')).toBeInTheDocument();
expect(getDashboard('overview')).toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
expect(queryDashboard('multiple3-datasource-errors')).not.toBeInTheDocument();
expect(queryDashboard('multiple4-datasource-logs')).not.toBeInTheDocument();
expect(queryDashboard('multiple0-ingester')).not.toBeInTheDocument();
expect(queryDashboard('multiple1-distributor')).not.toBeInTheDocument();
expect(queryDashboard('multiple2-compacter')).not.toBeInTheDocument();
expect(queryDashboard('another-stats')).not.toBeInTheDocument();
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('General'));
await userEvents.click(getDashboardFolderExpand('Observability'));
await userEvents.click(getDashboardFolderExpand('Usage'));
await userEvents.click(getDashboardFolderExpand('Components'));
await userEvents.click(getDashboardFolderExpand('Investigations'));
expect(getDashboard('general-data-sources')).toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(getDashboard('observability-backend-errors')).toBeInTheDocument();
expect(getDashboard('observability-backend-logs')).toBeInTheDocument();
expect(getDashboard('observability-frontend-errors')).toBeInTheDocument();
expect(getDashboard('observability-frontend-logs')).toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(getDashboard('frontend')).toBeInTheDocument();
expect(getDashboard('overview')).toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
expect(queryAllDashboard('multiple3-datasource-errors')).toHaveLength(2);
expect(queryAllDashboard('multiple4-datasource-logs')).toHaveLength(2);
expect(queryAllDashboard('multiple0-ingester')).toHaveLength(2);
expect(queryAllDashboard('multiple1-distributor')).toHaveLength(2);
expect(queryAllDashboard('multiple2-compacter')).toHaveLength(2);
expect(getDashboard('another-stats')).toBeInTheDocument();
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('General'));
await userEvents.click(getDashboardFolderExpand('Observability'));
await userEvents.click(getDashboardFolderExpand('Usage'));
expect(queryDashboardFolderExpand('Components')).not.toBeInTheDocument();
expect(queryDashboardFolderExpand('Investigations')).not.toBeInTheDocument();
expect(getDashboard('general-data-sources')).toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(getDashboard('observability-backend-errors')).toBeInTheDocument();
expect(getDashboard('observability-backend-logs')).toBeInTheDocument();
expect(getDashboard('observability-frontend-errors')).toBeInTheDocument();
expect(getDashboard('observability-frontend-logs')).toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(getDashboard('frontend')).toBeInTheDocument();
expect(getDashboard('overview')).toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
expect(queryDashboard('multiple3-datasource-errors')).not.toBeInTheDocument();
expect(queryDashboard('multiple4-datasource-logs')).not.toBeInTheDocument();
expect(queryDashboard('multiple0-ingester')).not.toBeInTheDocument();
expect(queryDashboard('multiple1-distributor')).not.toBeInTheDocument();
expect(queryDashboard('multiple2-compacter')).not.toBeInTheDocument();
expect(queryDashboard('another-stats')).not.toBeInTheDocument();
});
it('Filters the dashboards list for dashboards', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('General'));
await userEvents.click(getDashboardFolderExpand('Observability'));
await userEvents.click(getDashboardFolderExpand('Usage'));
expect(getDashboard('general-data-sources')).toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(getDashboard('observability-backend-errors')).toBeInTheDocument();
expect(getDashboard('observability-backend-logs')).toBeInTheDocument();
expect(getDashboard('observability-frontend-errors')).toBeInTheDocument();
expect(getDashboard('observability-frontend-logs')).toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(getDashboard('frontend')).toBeInTheDocument();
expect(getDashboard('overview')).toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
await userEvents.type(getDashboardsSearch(), 'Stats');
await waitFor(() => {
expect(queryDashboard('general-data-sources')).not.toBeInTheDocument();
expect(queryDashboard('general-usage')).not.toBeInTheDocument();
expect(queryDashboard('observability-backend-errors')).not.toBeInTheDocument();
expect(queryDashboard('observability-backend-logs')).not.toBeInTheDocument();
expect(queryDashboard('observability-frontend-errors')).not.toBeInTheDocument();
expect(queryDashboard('observability-frontend-logs')).not.toBeInTheDocument();
expect(queryDashboard('usage-data-sources')).not.toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(queryDashboard('usage-usage-overview')).not.toBeInTheDocument();
expect(queryDashboard('frontend')).not.toBeInTheDocument();
expect(queryDashboard('overview')).not.toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
});
});
it('Filters the dashboards list for folders', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('General'));
await userEvents.click(getDashboardFolderExpand('Observability'));
await userEvents.click(getDashboardFolderExpand('Usage'));
expect(getDashboard('general-data-sources')).toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(getDashboard('observability-backend-errors')).toBeInTheDocument();
expect(getDashboard('observability-backend-logs')).toBeInTheDocument();
expect(getDashboard('observability-frontend-errors')).toBeInTheDocument();
expect(getDashboard('observability-frontend-logs')).toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(getDashboard('frontend')).toBeInTheDocument();
expect(getDashboard('overview')).toBeInTheDocument();
expect(getDashboard('stats')).toBeInTheDocument();
await userEvents.type(getDashboardsSearch(), 'Usage');
await waitFor(() => {
expect(queryDashboard('general-data-sources')).not.toBeInTheDocument();
expect(getDashboard('general-usage')).toBeInTheDocument();
expect(queryDashboard('observability-backend-errors')).not.toBeInTheDocument();
expect(queryDashboard('observability-backend-logs')).not.toBeInTheDocument();
expect(queryDashboard('observability-frontend-errors')).not.toBeInTheDocument();
expect(queryDashboard('observability-frontend-logs')).not.toBeInTheDocument();
expect(getDashboard('usage-data-sources')).toBeInTheDocument();
expect(getDashboard('usage-stats')).toBeInTheDocument();
expect(getDashboard('usage-usage-overview')).toBeInTheDocument();
expect(queryDashboard('frontend')).not.toBeInTheDocument();
expect(queryDashboard('overview')).not.toBeInTheDocument();
expect(queryDashboard('stats')).not.toBeInTheDocument();
});
});
it('Deduplicates the dashboards list', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsCloudExpand());
await userEvents.click(getResultApplicationsCloudDevSelect());
await userEvents.click(getResultApplicationsCloudOpsSelect());
await userEvents.click(getSelectorApply());
await userEvents.click(getDashboardFolderExpand('Cardinality Management'));
await userEvents.click(getDashboardFolderExpand('Usage Insights'));
expect(queryAllDashboard('cardinality-management-labels')).toHaveLength(1);
expect(queryAllDashboard('cardinality-management-metrics')).toHaveLength(1);
expect(queryAllDashboard('cardinality-management-overview')).toHaveLength(1);
expect(queryAllDashboard('usage-insights-alertmanager')).toHaveLength(1);
expect(queryAllDashboard('usage-insights-data-sources')).toHaveLength(1);
expect(queryAllDashboard('usage-insights-metrics-ingestion')).toHaveLength(1);
expect(queryAllDashboard('usage-insights-overview')).toHaveLength(1);
expect(queryAllDashboard('usage-insights-query-errors')).toHaveLength(1);
expect(queryAllDashboard('billing-usage')).toHaveLength(1);
});
it('Shows a proper message when no scopes are selected', async () => {
await userEvents.click(getDashboardsExpand());
expect(getNotFoundNoScopes()).toBeInTheDocument();
expect(queryDashboardsSearch()).not.toBeInTheDocument();
});
it('Does not show the input when there are no dashboards found for scope', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultCloudSelect());
await userEvents.click(getSelectorApply());
expect(getNotFoundForScope()).toBeInTheDocument();
expect(queryDashboardsSearch()).not.toBeInTheDocument();
});
it('Shows the input and a message when there are no dashboards found for filter', async () => {
await userEvents.click(getDashboardsExpand());
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await userEvents.type(getDashboardsSearch(), 'unknown');
await waitFor(() => {
expect(queryDashboardsSearch()).toBeInTheDocument();
expect(getNotFoundForFilter()).toBeInTheDocument();
});
await userEvents.click(getNotFoundForFilterClear());
await waitFor(() => {
expect(getDashboardsSearch().value).toBe('');
});
});
});
describe('View mode', () => {
it('Enters view mode', async () => {
await act(async () => dashboardScene.onEnterEditMode());
expect(scopesSelectorScene?.state?.isReadOnly).toEqual(true);
expect(scopesDashboardsScene?.state?.isPanelOpened).toEqual(false);
});
it('Closes selector on enter', async () => {
await userEvents.click(getSelectorInput());
await act(async () => dashboardScene.onEnterEditMode());
expect(querySelectorApply()).not.toBeInTheDocument();
});
it('Closes dashboards list on enter', async () => {
await userEvents.click(getDashboardsExpand());
await act(async () => dashboardScene.onEnterEditMode());
expect(queryDashboardsContainer()).not.toBeInTheDocument();
});
it('Does not open selector when view mode is active', async () => {
await act(async () => dashboardScene.onEnterEditMode());
await userEvents.click(getSelectorInput());
expect(querySelectorApply()).not.toBeInTheDocument();
});
it('Disables the expand button when view mode is active', async () => {
await act(async () => dashboardScene.onEnterEditMode());
expect(getDashboardsExpand()).toBeDisabled();
});
});
describe('Enrichers', () => {
it('Data requests', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'grafana')
);
});
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'grafana' || name === 'mimir')
);
});
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'mimir')
);
});
});
it('Filters requests', async () => {
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsExpand());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'grafana')
);
});
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsMimirSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'grafana' || name === 'mimir')
);
});
await userEvents.click(getSelectorInput());
await userEvents.click(getResultApplicationsGrafanaSelect());
await userEvents.click(getSelectorApply());
await waitFor(() => {
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'mimir')
);
});
});
});
});
});

View File

@ -0,0 +1,83 @@
import { config } from '@grafana/runtime';
import { sceneGraph } from '@grafana/scenes';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { getClosestScopesFacade } from '../utils';
import { applyScopes, cancelScopes, openSelector, selectResultCloud, updateScopes } from './utils/actions';
import { expectNotDashboardReload, expectScopesSelectorValue } from './utils/assertions';
import { fetchSelectedScopesSpy, getDatasource, getInstanceSettings, getMock, mocksScopes } 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 }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Selector', () => {
let dashboardScene: DashboardScene;
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
beforeEach(() => {
dashboardScene = renderDashboard();
});
afterEach(async () => {
await resetScenes();
});
it('Fetches scope details on save', async () => {
await openSelector();
await selectResultCloud();
await applyScopes();
expect(fetchSelectedScopesSpy).toHaveBeenCalled();
expect(getClosestScopesFacade(dashboardScene)?.value).toEqual(
mocksScopes.filter(({ metadata: { name } }) => name === 'cloud')
);
});
it('Does not save the scopes on close', async () => {
await openSelector();
await selectResultCloud();
await cancelScopes();
expect(fetchSelectedScopesSpy).not.toHaveBeenCalled();
expect(getClosestScopesFacade(dashboardScene)?.value).toEqual([]);
});
it('Shows selected scopes', async () => {
await updateScopes(['grafana']);
expectScopesSelectorValue('Grafana');
});
it('Does not reload the dashboard on scope change', async () => {
await updateScopes(['grafana']);
expectNotDashboardReload();
});
it('Adds scopes to enrichers', async () => {
const queryRunner = sceneGraph.getQueryController(dashboardScene)!;
await updateScopes(['grafana']);
let scopes = mocksScopes.filter(({ metadata: { name } }) => name === 'grafana');
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(scopes);
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(scopes);
await updateScopes(['grafana', 'mimir']);
scopes = mocksScopes.filter(({ metadata: { name } }) => name === 'grafana' || name === 'mimir');
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(scopes);
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(scopes);
await updateScopes(['mimir']);
scopes = mocksScopes.filter(({ metadata: { name } }) => name === 'mimir');
expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual(scopes);
expect(dashboardScene.enrichFiltersRequest().scopes).toEqual(scopes);
});
});

View File

@ -0,0 +1,247 @@
import { config } from '@grafana/runtime';
import {
applyScopes,
clearScopesSearch,
expandResultApplications,
expandResultApplicationsCloud,
expandResultCloud,
openSelector,
searchScopes,
selectPersistedApplicationsMimir,
selectResultApplicationsCloud,
selectResultApplicationsCloudDev,
selectResultApplicationsGrafana,
selectResultApplicationsMimir,
selectResultCloud,
selectResultCloudDev,
selectResultCloudOps,
updateScopes,
} from './utils/actions';
import {
expectPersistedApplicationsGrafanaNotPresent,
expectPersistedApplicationsMimirNotPresent,
expectPersistedApplicationsMimirPresent,
expectResultApplicationsCloudNotPresent,
expectResultApplicationsCloudPresent,
expectResultApplicationsGrafanaNotPresent,
expectResultApplicationsGrafanaPresent,
expectResultApplicationsGrafanaSelected,
expectResultApplicationsMimirNotPresent,
expectResultApplicationsMimirPresent,
expectResultApplicationsMimirSelected,
expectResultCloudDevNotSelected,
expectResultCloudDevSelected,
expectResultCloudOpsNotSelected,
expectResultCloudOpsSelected,
expectScopesHeadline,
expectScopesSelectorValue,
} from './utils/assertions';
import { fetchNodesSpy, fetchScopeSpy, 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 }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('Tree', () => {
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
beforeEach(() => {
renderDashboard();
});
afterEach(async () => {
await resetScenes();
});
it('Fetches scope details on select', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsGrafana();
expect(fetchScopeSpy).toHaveBeenCalledTimes(1);
});
it('Selects the proper scopes', async () => {
await updateScopes(['grafana', 'mimir']);
await openSelector();
await expandResultApplications();
expectResultApplicationsGrafanaSelected();
expectResultApplicationsMimirSelected();
});
it('Can select scopes from same level', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsGrafana();
await selectResultApplicationsMimir();
await selectResultApplicationsCloud();
await applyScopes();
expectScopesSelectorValue('Grafana, Mimir, Cloud');
});
it('Can select a node from an inner level', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsGrafana();
await expandResultApplicationsCloud();
await selectResultApplicationsCloudDev();
await applyScopes();
expectScopesSelectorValue('Dev');
});
it('Can select a node from an upper level', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsGrafana();
await expandResultApplications();
await selectResultCloud();
await applyScopes();
expectScopesSelectorValue('Cloud');
});
it('Respects only one select per container', async () => {
await openSelector();
await expandResultCloud();
await selectResultCloudDev();
expectResultCloudDevSelected();
expectResultCloudOpsNotSelected();
await selectResultCloudOps();
expectResultCloudDevNotSelected();
expectResultCloudOpsSelected();
});
it('Search works', async () => {
await openSelector();
await expandResultApplications();
await searchScopes('Cloud');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
expectResultApplicationsGrafanaNotPresent();
expectResultApplicationsMimirNotPresent();
expectResultApplicationsCloudPresent();
await clearScopesSearch();
expect(fetchNodesSpy).toHaveBeenCalledTimes(4);
await searchScopes('Grafana');
expect(fetchNodesSpy).toHaveBeenCalledTimes(5);
expectResultApplicationsGrafanaPresent();
expectResultApplicationsCloudNotPresent();
});
it('Opens to a selected scope', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await expandResultApplications();
await expandResultCloud();
await applyScopes();
await openSelector();
expectResultApplicationsMimirPresent();
});
it('Persists a scope', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await searchScopes('grafana');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
expectPersistedApplicationsMimirPresent();
expectPersistedApplicationsGrafanaNotPresent();
expectResultApplicationsMimirNotPresent();
expectResultApplicationsGrafanaPresent();
});
it('Does not persist a retrieved scope', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await searchScopes('mimir');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
expectPersistedApplicationsMimirNotPresent();
expectResultApplicationsMimirPresent();
});
it('Removes persisted nodes', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await searchScopes('grafana');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
await clearScopesSearch();
expect(fetchNodesSpy).toHaveBeenCalledTimes(4);
expectPersistedApplicationsMimirNotPresent();
expectPersistedApplicationsGrafanaNotPresent();
expectResultApplicationsMimirPresent();
expectResultApplicationsGrafanaPresent();
});
it('Persists nodes from search', async () => {
await openSelector();
await expandResultApplications();
await searchScopes('mimir');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
await selectResultApplicationsMimir();
await searchScopes('unknown');
expect(fetchNodesSpy).toHaveBeenCalledTimes(4);
expectPersistedApplicationsMimirPresent();
await clearScopesSearch();
expect(fetchNodesSpy).toHaveBeenCalledTimes(5);
expectResultApplicationsMimirPresent();
expectResultApplicationsGrafanaPresent();
});
it('Selects a persisted scope', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await searchScopes('grafana');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
await selectResultApplicationsGrafana();
await applyScopes();
expectScopesSelectorValue('Mimir, Grafana');
});
it('Deselects a persisted scope', async () => {
await openSelector();
await expandResultApplications();
await selectResultApplicationsMimir();
await searchScopes('grafana');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
await selectResultApplicationsGrafana();
await applyScopes();
expectScopesSelectorValue('Mimir, Grafana');
await openSelector();
await selectPersistedApplicationsMimir();
await applyScopes();
expectScopesSelectorValue('Grafana');
});
it('Shows the proper headline', async () => {
await openSelector();
expectScopesHeadline('Recommended');
await searchScopes('Applications');
expect(fetchNodesSpy).toHaveBeenCalledTimes(2);
expectScopesHeadline('Results');
await searchScopes('unknown');
expect(fetchNodesSpy).toHaveBeenCalledTimes(3);
expectScopesHeadline('No results found for your query');
});
});

View File

@ -10,356 +10,354 @@ import {
dashboardWithTwoFolders,
} from './utils/mocks';
describe('Scopes', () => {
describe('Utils', () => {
describe('groupDashboards', () => {
it('Assigns dashboards without groups to root folder', () => {
expect(groupDashboards([dashboardWithoutFolder])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {},
dashboards: {
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
describe('Utils', () => {
describe('groupDashboards', () => {
it('Assigns dashboards without groups to root folder', () => {
expect(groupDashboards([dashboardWithoutFolder])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {},
dashboards: {
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
},
});
});
it('Assigns dashboards with root group to root folder', () => {
expect(groupDashboards([dashboardWithRootFolder])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {},
dashboards: {
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder],
},
},
},
});
});
it('Merges folders from multiple dashboards', () => {
expect(groupDashboards([dashboardWithOneFolder, dashboardWithTwoFolders])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
},
},
dashboards: {},
},
});
});
it('Merges scopes from multiple dashboards', () => {
expect(groupDashboards([dashboardWithTwoFolders, alternativeDashboardWithTwoFolders])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
},
},
dashboards: {},
},
});
});
it('Matches snapshot', () => {
expect(
groupDashboards([
dashboardWithoutFolder,
dashboardWithOneFolder,
dashboardWithTwoFolders,
alternativeDashboardWithTwoFolders,
dashboardWithRootFolder,
alternativeDashboardWithRootFolder,
dashboardWithRootFolderAndOtherFolder,
])
).toEqual({
'': {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder, alternativeDashboardWithRootFolder],
},
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
},
folders: {
'Folder 1': {
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
folders: {},
isExpanded: false,
title: 'Folder 1',
},
'Folder 2': {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
folders: {},
isExpanded: false,
title: 'Folder 2',
},
'Folder 3': {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
},
folders: {},
isExpanded: false,
title: 'Folder 3',
},
},
isExpanded: true,
title: '',
},
});
},
});
});
describe('filterFolders', () => {
it('Shows folders matching criteria', () => {
expect(
filterFolders(
{
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
it('Assigns dashboards with root group to root folder', () => {
expect(groupDashboards([dashboardWithRootFolder])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {},
dashboards: {
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder],
},
'Folder'
)
).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
dashboards: {},
},
});
},
});
});
it('Shows dashboards matching criteria', () => {
expect(
filterFolders(
{
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Random ID': {
dashboard: 'Random ID',
dashboardTitle: 'Random Title',
items: [],
},
},
},
it('Merges folders from multiple dashboards', () => {
expect(groupDashboards([dashboardWithOneFolder, dashboardWithTwoFolders])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
'Random ID': {
dashboard: 'Random ID',
dashboardTitle: 'Random Title',
items: [],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
},
'dash'
)
).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
'Folder 2': {
title: 'Folder 2',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
});
dashboards: {},
},
});
});
it('Merges scopes from multiple dashboards', () => {
expect(groupDashboards([dashboardWithTwoFolders, alternativeDashboardWithTwoFolders])).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: false,
folders: {},
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
},
},
dashboards: {},
},
});
});
it('Matches snapshot', () => {
expect(
groupDashboards([
dashboardWithoutFolder,
dashboardWithOneFolder,
dashboardWithTwoFolders,
alternativeDashboardWithTwoFolders,
dashboardWithRootFolder,
alternativeDashboardWithRootFolder,
dashboardWithRootFolderAndOtherFolder,
])
).toEqual({
'': {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder, alternativeDashboardWithRootFolder],
},
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
},
folders: {
'Folder 1': {
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
folders: {},
isExpanded: false,
title: 'Folder 1',
},
'Folder 2': {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
folders: {},
isExpanded: false,
title: 'Folder 2',
},
'Folder 3': {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
},
folders: {},
isExpanded: false,
title: 'Folder 3',
},
},
isExpanded: true,
title: '',
},
});
});
});
describe('filterFolders', () => {
it('Shows folders matching criteria', () => {
expect(
filterFolders(
{
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
'Folder'
)
).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
dashboards: {},
},
});
});
it('Shows dashboards matching criteria', () => {
expect(
filterFolders(
{
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: false,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
'Folder 2': {
title: 'Folder 2',
isExpanded: true,
folders: {},
dashboards: {
'Random ID': {
dashboard: 'Random ID',
dashboardTitle: 'Random Title',
items: [],
},
},
},
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
'Random ID': {
dashboard: 'Random ID',
dashboardTitle: 'Random Title',
items: [],
},
},
},
},
'dash'
)
).toEqual({
'': {
title: '',
isExpanded: true,
folders: {
'Folder 1': {
title: 'Folder 1',
isExpanded: true,
folders: {},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
},
dashboards: {
'Dashboard ID': {
dashboard: 'Dashboard ID',
dashboardTitle: 'Dashboard Title',
items: [],
},
},
},
});
});
});

View File

@ -0,0 +1,93 @@
import { act, fireEvent } from '@testing-library/react';
import { getDashboardAPI, setDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { scopesSelectorScene } from '../../instance';
import {
fetchDashboardsSpy,
fetchNodesSpy,
fetchScopeSpy,
fetchSelectedScopesSpy,
getMock,
locationReloadSpy,
} from './mocks';
import {
getDashboardFolderExpand,
getDashboardsExpand,
getDashboardsSearch,
getNotFoundForFilterClear,
getPersistedApplicationsMimirSelect,
getResultApplicationsCloudDevSelect,
getResultApplicationsCloudExpand,
getResultApplicationsCloudSelect,
getResultApplicationsExpand,
getResultApplicationsGrafanaSelect,
getResultApplicationsMimirSelect,
getResultCloudDevRadio,
getResultCloudExpand,
getResultCloudOpsRadio,
getResultCloudSelect,
getSelectorApply,
getSelectorCancel,
getSelectorInput,
getTreeSearch,
} from './selectors';
export const clearMocks = () => {
fetchNodesSpy.mockClear();
fetchScopeSpy.mockClear();
fetchSelectedScopesSpy.mockClear();
fetchDashboardsSpy.mockClear();
locationReloadSpy.mockClear();
getMock.mockClear();
};
const click = async (selector: () => HTMLElement) => act(() => fireEvent.click(selector()));
const type = async (selector: () => HTMLInputElement, value: string) => {
await act(() => fireEvent.input(selector(), { target: { value } }));
await jest.runOnlyPendingTimersAsync();
};
export const updateScopes = async (scopes: string[]) =>
act(async () =>
scopesSelectorScene?.updateScopes(
scopes.map((scopeName) => ({
scopeName,
path: [],
}))
)
);
export const openSelector = async () => click(getSelectorInput);
export const applyScopes = async () => {
await click(getSelectorApply);
await jest.runOnlyPendingTimersAsync();
};
export const cancelScopes = async () => click(getSelectorCancel);
export const searchScopes = async (value: string) => type(getTreeSearch, value);
export const clearScopesSearch = async () => type(getTreeSearch, '');
export const expandResultApplications = async () => click(getResultApplicationsExpand);
export const expandResultApplicationsCloud = async () => click(getResultApplicationsCloudExpand);
export const expandResultCloud = async () => click(getResultCloudExpand);
export const selectResultApplicationsGrafana = async () => click(getResultApplicationsGrafanaSelect);
export const selectPersistedApplicationsMimir = async () => click(getPersistedApplicationsMimirSelect);
export const selectResultApplicationsMimir = async () => click(getResultApplicationsMimirSelect);
export const selectResultApplicationsCloud = async () => click(getResultApplicationsCloudSelect);
export const selectResultApplicationsCloudDev = async () => click(getResultApplicationsCloudDevSelect);
export const selectResultCloud = async () => click(getResultCloudSelect);
export const selectResultCloudDev = async () => click(getResultCloudDevRadio);
export const selectResultCloudOps = async () => click(getResultCloudOpsRadio);
export const toggleDashboards = async () => click(getDashboardsExpand);
export const searchDashboards = async (value: string) => type(getDashboardsSearch, value);
export const clearNotFound = async () => click(getNotFoundForFilterClear);
export const expandDashboardFolder = (folder: string) => click(() => getDashboardFolderExpand(folder));
export const enterEditMode = async (dashboardScene: DashboardScene) =>
act(async () => dashboardScene.onEnterEditMode());
export const getDashboardDTO = async () => {
setDashboardAPI(undefined);
await getDashboardAPI().getDashboardDTO('1');
};

View File

@ -0,0 +1,82 @@
import { getMock, locationReloadSpy } from './mocks';
import {
getDashboard,
getDashboardsExpand,
getDashboardsSearch,
getNotFoundForFilter,
getNotFoundForScope,
getNotFoundNoScopes,
getPersistedApplicationsMimirSelect,
getResultApplicationsCloudSelect,
getResultApplicationsGrafanaSelect,
getResultApplicationsMimirSelect,
getResultCloudDevRadio,
getResultCloudOpsRadio,
getSelectorInput,
getTreeHeadline,
queryAllDashboard,
queryDashboard,
queryDashboardFolderExpand,
queryDashboardsContainer,
queryDashboardsSearch,
queryPersistedApplicationsGrafanaSelect,
queryPersistedApplicationsMimirSelect,
queryResultApplicationsCloudSelect,
queryResultApplicationsGrafanaSelect,
queryResultApplicationsMimirSelect,
querySelectorApply,
} from './selectors';
const expectInDocument = (selector: () => HTMLElement) => expect(selector()).toBeInTheDocument();
const expectNotInDocument = (selector: () => HTMLElement | null) => expect(selector()).not.toBeInTheDocument();
const expectChecked = (selector: () => HTMLInputElement) => expect(selector()).toBeChecked();
const expectRadioChecked = (selector: () => HTMLInputElement) => expect(selector().checked).toBe(true);
const expectRadioNotChecked = (selector: () => HTMLInputElement) => expect(selector().checked).toBe(false);
const expectValue = (selector: () => HTMLInputElement, value: string) => expect(selector().value).toBe(value);
const expectTextContent = (selector: () => HTMLElement, text: string) => expect(selector()).toHaveTextContent(text);
const expectDisabled = (selector: () => HTMLElement) => expect(selector()).toBeDisabled();
export const expectScopesSelectorClosed = () => expectNotInDocument(querySelectorApply);
export const expectScopesSelectorValue = (value: string) => expectValue(getSelectorInput, value);
export const expectScopesHeadline = (value: string) => expectTextContent(getTreeHeadline, value);
export const expectPersistedApplicationsGrafanaNotPresent = () =>
expectNotInDocument(queryPersistedApplicationsGrafanaSelect);
export const expectResultApplicationsGrafanaSelected = () => expectChecked(getResultApplicationsGrafanaSelect);
export const expectResultApplicationsGrafanaPresent = () => expectInDocument(getResultApplicationsGrafanaSelect);
export const expectResultApplicationsGrafanaNotPresent = () =>
expectNotInDocument(queryResultApplicationsGrafanaSelect);
export const expectPersistedApplicationsMimirPresent = () => expectInDocument(getPersistedApplicationsMimirSelect);
export const expectPersistedApplicationsMimirNotPresent = () =>
expectNotInDocument(queryPersistedApplicationsMimirSelect);
export const expectResultApplicationsMimirSelected = () => expectChecked(getResultApplicationsMimirSelect);
export const expectResultApplicationsMimirPresent = () => expectInDocument(getResultApplicationsMimirSelect);
export const expectResultApplicationsMimirNotPresent = () => expectNotInDocument(queryResultApplicationsMimirSelect);
export const expectResultApplicationsCloudPresent = () => expectInDocument(getResultApplicationsCloudSelect);
export const expectResultApplicationsCloudNotPresent = () => expectNotInDocument(queryResultApplicationsCloudSelect);
export const expectResultCloudDevSelected = () => expectRadioChecked(getResultCloudDevRadio);
export const expectResultCloudDevNotSelected = () => expectRadioNotChecked(getResultCloudDevRadio);
export const expectResultCloudOpsSelected = () => expectRadioChecked(getResultCloudOpsRadio);
export const expectResultCloudOpsNotSelected = () => expectRadioNotChecked(getResultCloudOpsRadio);
export const expectDashboardsDisabled = () => expectDisabled(getDashboardsExpand);
export const expectDashboardsClosed = () => expectNotInDocument(queryDashboardsContainer);
export const expectNoDashboardsSearch = () => expectNotInDocument(queryDashboardsSearch);
export const expectDashboardsSearch = () => expectInDocument(getDashboardsSearch);
export const expectNoDashboardsNoScopes = () => expectInDocument(getNotFoundNoScopes);
export const expectNoDashboardsForScope = () => expectInDocument(getNotFoundForScope);
export const expectNoDashboardsForFilter = () => expectInDocument(getNotFoundForFilter);
export const expectDashboardSearchValue = (value: string) => expectValue(getDashboardsSearch, value);
export const expectDashboardFolderNotInDocument = (uid: string) =>
expectNotInDocument(() => queryDashboardFolderExpand(uid));
export const expectDashboardInDocument = (uid: string) => expectInDocument(() => getDashboard(uid));
export const expectDashboardNotInDocument = (uid: string) => expectNotInDocument(() => queryDashboard(uid));
export const expectDashboardLength = (uid: string, length: number) =>
expect(queryAllDashboard(uid)).toHaveLength(length);
export const expectNotDashboardReload = () => expect(locationReloadSpy).not.toHaveBeenCalled();
export const expectDashboardReload = () => expect(locationReloadSpy).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');

View File

@ -1,4 +1,6 @@
import { Scope, ScopeDashboardBinding, ScopeNode } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { DataSourceRef } from '@grafana/schema/dist/esm/common/common.gen';
import * as api from '../../internal/api';
@ -371,6 +373,7 @@ export const fetchNodesSpy = jest.spyOn(api, 'fetchNodes');
export const fetchScopeSpy = jest.spyOn(api, 'fetchScope');
export const fetchSelectedScopesSpy = jest.spyOn(api, 'fetchSelectedScopes');
export const fetchDashboardsSpy = jest.spyOn(api, 'fetchDashboards');
export const locationReloadSpy = jest.spyOn(locationService, 'reload');
export const getMock = jest
.fn()
@ -448,3 +451,39 @@ export const dashboardWithRootFolderAndOtherFolder: ScopeDashboardBinding = gene
'With root folder and other folder',
['', 'Folder 3']
);
export const getDatasource = async (ref: DataSourceRef) => {
if (ref.uid === '-- Grafana --') {
return {
id: 1,
uid: '-- Grafana --',
name: 'grafana',
type: 'grafana',
meta: {
id: 'grafana',
},
};
}
return {
meta: {
id: 'grafana-testdata-datasource',
},
name: 'grafana-testdata-datasource',
type: 'grafana-testdata-datasource',
uid: 'gdev-testdata',
getRef: () => {
return { type: 'grafana-testdata-datasource', uid: 'gdev-testdata' };
},
};
};
export const getInstanceSettings = () => ({
id: 1,
uid: 'gdev-testdata',
name: 'testDs1',
type: 'grafana-testdata-datasource',
meta: {
id: 'grafana-testdata-datasource',
},
});

View File

@ -1,92 +1,166 @@
import { cleanup } from '@testing-library/react';
import { KBarProvider } from 'kbar';
import { render } from 'test/test-utils';
import {
AdHocFiltersVariable,
behaviors,
GroupByVariable,
sceneGraph,
SceneGridItem,
SceneGridLayout,
SceneQueryRunner,
SceneTimeRange,
SceneVariableSet,
VizPanel,
} from '@grafana/scenes';
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
import { config, setPluginImportUtils } from '@grafana/runtime';
import { defaultDashboard } from '@grafana/schema';
import { AppChrome } from 'app/core/components/AppChrome/AppChrome';
import { DashboardControls } from 'app/features/dashboard-scene/scene//DashboardControls';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { transformSaveModelToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelToScene';
import { DashboardDataDTO, DashboardDTO, DashboardMeta } from 'app/types';
import { ScopesFacade } from '../../ScopesFacadeScene';
import { scopesDashboardsScene, scopesSelectorScene } from '../../instance';
import { initializeScopes, scopesDashboardsScene, scopesSelectorScene } from '../../instance';
import { getInitialDashboardsState } from '../../internal/ScopesDashboardsScene';
import { initialSelectorState } from '../../internal/ScopesSelectorScene';
import { DASHBOARDS_OPENED_KEY } from '../../internal/const';
export function buildTestScene(overrides: Partial<DashboardScene> = {}) {
return new DashboardScene({
import { clearMocks } from './actions';
const getDashboardDTO: (
overrideDashboard: Partial<DashboardDataDTO>,
overrideMeta: Partial<DashboardMeta>
) => DashboardDTO = (overrideDashboard, overrideMeta) => ({
dashboard: {
...defaultDashboard,
title: 'hello',
uid: 'dash-1',
description: 'hello description',
tags: ['tag1', 'tag2'],
editable: true,
$timeRange: new SceneTimeRange({
timeZone: 'browser',
}),
controls: new DashboardControls({}),
$behaviors: [
new behaviors.CursorSync({}),
new ScopesFacade({
handler: (facade) => sceneGraph.getTimeRange(facade).onRefresh(),
}),
],
$variables: new SceneVariableSet({
variables: [
new AdHocFiltersVariable({
name: 'adhoc',
datasource: { uid: 'my-ds-uid' },
}),
new GroupByVariable({
name: 'groupby',
datasource: { uid: 'my-ds-uid' },
}),
templating: {
list: [
{
datasource: {
type: 'datasource',
uid: 'grafana',
},
filters: [],
name: 'Filters',
type: 'adhoc',
},
{
current: {
text: [],
value: [],
},
datasource: {
type: 'datasource',
uid: 'grafana',
},
description: '',
label: 'Group By',
name: 'groupBy',
type: 'groupby',
},
],
}),
body: new SceneGridLayout({
children: [
new SceneGridItem({
key: 'griditem-1',
},
panels: [
{
datasource: {
type: 'datasource',
uid: 'grafana',
},
fieldConfig: {
defaults: {
color: {
mode: 'thresholds',
},
custom: {
align: 'auto',
cellOptions: {
type: 'auto',
},
inspect: false,
},
mappings: [],
thresholds: {
mode: 'absolute',
steps: [
{
color: 'green',
value: null,
},
{
color: 'red',
value: 80,
},
],
},
},
overrides: [],
},
gridPos: {
h: 8,
w: 12,
x: 0,
y: 0,
width: 300,
height: 300,
body: new VizPanel({
title: 'Panel A',
key: 'panel-1',
pluginId: 'table',
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
}),
}),
],
}),
...overrides,
});
}
},
id: 1,
options: {
cellHeight: 'sm',
footer: {
countRows: false,
fields: '',
reducer: ['sum'],
show: false,
},
showHeader: true,
},
pluginVersion: '11.3.0-pre',
targets: [
{
refId: 'A',
},
],
title: 'Panel Title',
type: 'table',
},
],
...overrideDashboard,
},
meta: {
...overrideMeta,
},
});
export function renderDashboard(dashboardScene: DashboardScene) {
return render(
const panelPlugin = getPanelPlugin({
id: 'table',
skipDataQuery: true,
});
config.panels['table'] = panelPlugin.meta;
setPluginImportUtils({
importPanelPlugin: () => Promise.resolve(panelPlugin),
getPanelPluginFromCache: () => undefined,
});
export function renderDashboard(
overrideDashboard: Partial<DashboardDataDTO> = {},
overrideMeta: Partial<DashboardMeta> = {}
) {
jest.useFakeTimers({ advanceTimers: true });
jest.spyOn(console, 'error').mockImplementation(jest.fn());
clearMocks();
initializeScopes();
const dto: DashboardDTO = getDashboardDTO(overrideDashboard, overrideMeta);
const scene = transformSaveModelToScene(dto);
render(
<KBarProvider>
<AppChrome>
<dashboardScene.Component model={dashboardScene} />
<scene.Component model={scene} />
</AppChrome>
</KBarProvider>
);
return scene;
}
export function resetScenes() {
export async function resetScenes() {
await jest.runOnlyPendingTimersAsync();
jest.useRealTimers();
scopesSelectorScene?.setState(initialSelectorState);
localStorage.removeItem(DASHBOARDS_OPENED_KEY);
scopesDashboardsScene?.setState(getInitialDashboardsState());
cleanup();
}

View File

@ -53,36 +53,28 @@ export const getNotFoundForFilterClear = () => screen.getByTestId(selectors.dash
export const getTreeSearch = () => screen.getByTestId<HTMLInputElement>(selectors.tree.search);
export const getTreeHeadline = () => screen.getByTestId(selectors.tree.headline);
export const getResultApplicationsExpand = () => screen.getByTestId(selectors.tree.expand('applications', 'result'));
export const queryResultApplicationsGrafanaTitle = () =>
screen.queryByTestId(selectors.tree.title('applications-grafana', 'result'));
export const getResultApplicationsGrafanaTitle = () =>
screen.getByTestId(selectors.tree.title('applications-grafana', 'result'));
export const queryResultApplicationsGrafanaSelect = () =>
screen.queryByTestId<HTMLInputElement>(selectors.tree.select('applications-grafana', 'result'));
export const getResultApplicationsGrafanaSelect = () =>
screen.getByTestId(selectors.tree.select('applications-grafana', 'result'));
export const queryPersistedApplicationsGrafanaTitle = () =>
screen.queryByTestId(selectors.tree.title('applications-grafana', 'persisted'));
export const queryResultApplicationsMimirTitle = () =>
screen.queryByTestId(selectors.tree.title('applications-mimir', 'result'));
export const getResultApplicationsMimirTitle = () =>
screen.getByTestId(selectors.tree.title('applications-mimir', 'result'));
screen.getByTestId<HTMLInputElement>(selectors.tree.select('applications-grafana', 'result'));
export const queryPersistedApplicationsGrafanaSelect = () =>
screen.queryByTestId<HTMLInputElement>(selectors.tree.select('applications-grafana', 'persisted'));
export const queryResultApplicationsMimirSelect = () =>
screen.queryByTestId(selectors.tree.select('applications-mimir', 'result'));
export const getResultApplicationsMimirSelect = () =>
screen.getByTestId(selectors.tree.select('applications-mimir', 'result'));
export const queryPersistedApplicationsMimirTitle = () =>
screen.queryByTestId(selectors.tree.title('applications-mimir', 'persisted'));
export const getPersistedApplicationsMimirTitle = () =>
screen.getByTestId(selectors.tree.title('applications-mimir', 'persisted'));
screen.getByTestId<HTMLInputElement>(selectors.tree.select('applications-mimir', 'result'));
export const queryPersistedApplicationsMimirSelect = () =>
screen.queryByTestId(selectors.tree.select('applications-mimir', 'persisted'));
export const getPersistedApplicationsMimirSelect = () =>
screen.getByTestId(selectors.tree.select('applications-mimir', 'persisted'));
export const queryResultApplicationsCloudTitle = () =>
screen.queryByTestId(selectors.tree.title('applications-cloud', 'result'));
export const queryResultApplicationsCloudSelect = () =>
screen.queryByTestId(selectors.tree.select('applications-cloud', 'result'));
export const getResultApplicationsCloudSelect = () =>
screen.getByTestId(selectors.tree.select('applications-cloud', 'result'));
export const getResultApplicationsCloudExpand = () =>
screen.getByTestId(selectors.tree.expand('applications-cloud', 'result'));
export const getResultApplicationsCloudDevSelect = () =>
screen.getByTestId(selectors.tree.select('applications-cloud-dev', 'result'));
export const getResultApplicationsCloudOpsSelect = () =>
screen.getByTestId(selectors.tree.select('applications-cloud-ops', 'result'));
export const getResultCloudSelect = () => screen.getByTestId(selectors.tree.select('cloud', 'result'));
export const getResultCloudExpand = () => screen.getByTestId(selectors.tree.expand('cloud', 'result'));

View File

@ -0,0 +1,64 @@
import { config } from '@grafana/runtime';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { scopesDashboardsScene, scopesSelectorScene } from '../instance';
import { enterEditMode, openSelector, toggleDashboards } from './utils/actions';
import { expectDashboardsClosed, expectDashboardsDisabled, expectScopesSelectorClosed } 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 }),
usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }),
}));
describe('View mode', () => {
let dashboardScene: DashboardScene;
beforeAll(() => {
config.featureToggles.scopeFilters = true;
config.featureToggles.groupByVariable = true;
});
beforeEach(() => {
dashboardScene = renderDashboard();
});
afterEach(async () => {
await resetScenes();
});
it('Enters view mode', async () => {
await enterEditMode(dashboardScene);
expect(scopesSelectorScene?.state?.isReadOnly).toEqual(true);
expect(scopesDashboardsScene?.state?.isPanelOpened).toEqual(false);
});
it('Closes selector on enter', async () => {
await openSelector();
await enterEditMode(dashboardScene);
expectScopesSelectorClosed();
});
it('Closes dashboards list on enter', async () => {
await toggleDashboards();
await enterEditMode(dashboardScene);
expectDashboardsClosed();
});
it('Does not open selector when view mode is active', async () => {
await enterEditMode(dashboardScene);
await openSelector();
expectScopesSelectorClosed();
});
it('Disables the expand button when view mode is active', async () => {
await enterEditMode(dashboardScene);
expectDashboardsDisabled();
});
});

View File

@ -73,6 +73,10 @@ export interface DashboardMeta {
// yes weird, but this means all the editor structures can exist unchanged
// until we use the resource as the main container
k8s?: Partial<ObjectMeta>;
// This is a property added specifically for edge cases where dashboards should be reloaded on scopes changes
// This property is not persisted in the DB but its existence is controlled by the API
reloadOnScopesChange?: boolean;
}
export interface AnnotationActions {