Dashboard Picker: Update to use correct search + dashboards APIs (#112341)
This commit is contained in:
@@ -1722,11 +1722,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { HttpHandler } from 'msw';
|
||||
|
||||
import dashboardsHandlers from './api/dashboards/handlers';
|
||||
import folderHandlers from './api/folders/handlers';
|
||||
import pluginsHandlers from './api/plugins/handlers';
|
||||
import searchHandlers from './api/search/handlers';
|
||||
import teamsHandlers from './api/teams/handlers';
|
||||
import userHandlers from './api/user/handlers';
|
||||
import appPlatformDashboardv0alpha1Handlers from './apis/dashboard.grafana.app/v0alpha1/handlers';
|
||||
import appPlatformDashboardv1beta1Handlers from './apis/dashboard.grafana.app/v1beta1/handlers';
|
||||
import appPlatformFolderv1beta1Handlers from './apis/folder.grafana.app/v1beta1/handlers';
|
||||
import appPlatformIamv0alpha1Handlers from './apis/iam.grafana.app/v0alpha1/handlers';
|
||||
import appPlatformPreferencesv1alpha1Handlers from './apis/preferences.grafana.app/v1alpha1/handlers';
|
||||
@@ -13,6 +15,7 @@ import appPlatformPreferencesv1alpha1Handlers from './apis/preferences.grafana.a
|
||||
const allHandlers: HttpHandler[] = [
|
||||
// Legacy handlers
|
||||
...teamsHandlers,
|
||||
...dashboardsHandlers,
|
||||
...folderHandlers,
|
||||
...searchHandlers,
|
||||
...pluginsHandlers,
|
||||
@@ -20,6 +23,7 @@ const allHandlers: HttpHandler[] = [
|
||||
|
||||
// App platform handlers
|
||||
...appPlatformDashboardv0alpha1Handlers,
|
||||
...appPlatformDashboardv1beta1Handlers,
|
||||
...appPlatformFolderv1beta1Handlers,
|
||||
...appPlatformIamv0alpha1Handlers,
|
||||
...appPlatformPreferencesv1alpha1Handlers,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { wellFormedTree } from '../../../fixtures/folders';
|
||||
|
||||
const [mockTree] = wellFormedTree();
|
||||
|
||||
const getDashboardHandler = () =>
|
||||
http.get<{ uid: string }>('/api/dashboards/uid/:uid', ({ params }) => {
|
||||
const { uid } = params;
|
||||
const dashboard = mockTree.find((v) => v.item.uid === uid);
|
||||
|
||||
if (!dashboard || dashboard.item.kind !== 'dashboard') {
|
||||
return HttpResponse.json({ message: 'Dashboard not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const { item } = dashboard;
|
||||
|
||||
const parentFolder = mockTree.find((v) => v.item.kind === 'folder' && v.item.uid === item.parentUID);
|
||||
|
||||
return HttpResponse.json({
|
||||
meta: {
|
||||
folderTitle: parentFolder?.item.title,
|
||||
folderUid: parentFolder?.item.uid,
|
||||
},
|
||||
dashboard: {
|
||||
title: item.title,
|
||||
uid: item.uid,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const handlers = [getDashboardHandler()];
|
||||
|
||||
export default handlers;
|
||||
@@ -1,6 +1,8 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { wellFormedTree } from '../../../fixtures/folders';
|
||||
import { mockStarredDashboardsMap } from '../../../fixtures/starred';
|
||||
const [_, { dashbdD }] = wellFormedTree();
|
||||
|
||||
const getStarsHandler = () =>
|
||||
http.get('/api/user/stars', async () => {
|
||||
@@ -21,6 +23,31 @@ const addDashboardStarHandler = () =>
|
||||
return HttpResponse.json({ message: 'Dashboard starred!' });
|
||||
});
|
||||
|
||||
const handlers = [getStarsHandler(), deleteDashboardStarHandler(), addDashboardStarHandler()];
|
||||
const getPreferencesHandler = () =>
|
||||
http.get('/api/user/preferences', async () => {
|
||||
return HttpResponse.json({
|
||||
homeDashboardUID: dashbdD.item.uid,
|
||||
theme: 'light',
|
||||
timezone: 'browser',
|
||||
weekStart: 'monday',
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
language: '',
|
||||
});
|
||||
});
|
||||
|
||||
const updatePreferencesHandler = () =>
|
||||
http.put('/api/user/preferences', async () => {
|
||||
return HttpResponse.json({ message: 'Preferences updated' });
|
||||
});
|
||||
|
||||
const handlers = [
|
||||
getPreferencesHandler(),
|
||||
updatePreferencesHandler(),
|
||||
getStarsHandler(),
|
||||
deleteDashboardStarHandler(),
|
||||
addDashboardStarHandler(),
|
||||
];
|
||||
|
||||
export default handlers;
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { wellFormedTree } from '../../../../fixtures/folders';
|
||||
import { getErrorResponse } from '../../../helpers';
|
||||
const [mockTree] = wellFormedTree();
|
||||
|
||||
const dashboardsTree = mockTree.filter(({ item }) => item.kind === 'dashboard');
|
||||
|
||||
const dashboardToAppPlatform = (dashboard: (typeof mockTree)[number]['item']) => {
|
||||
return {
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
apiVersion: 'dashboard.grafana.app/v1beta1',
|
||||
metadata: {
|
||||
name: dashboard.uid,
|
||||
namespace: 'default',
|
||||
uid: dashboard.uid,
|
||||
creationTimestamp: '2023-01-01T00:00:00Z',
|
||||
annotations: {
|
||||
// TODO: Eventually generalise annotations in fixture data, as required by tests
|
||||
'grafana.app/folder': dashboard.kind === 'dashboard' ? dashboard.parentUID : undefined,
|
||||
},
|
||||
labels: {},
|
||||
},
|
||||
spec: {
|
||||
title: dashboard.title,
|
||||
// TODO: Eventually add more fields to be more accurate to API response, as required by tests
|
||||
},
|
||||
status: {},
|
||||
// TODO: Eventually add access properties, as required by tests
|
||||
};
|
||||
};
|
||||
|
||||
const getDashboardDto = () =>
|
||||
http.get<{ namespace: string; uid: string }>(
|
||||
'/apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid/dto',
|
||||
({ params }) => {
|
||||
const { uid } = params;
|
||||
|
||||
const matchingDashboard = dashboardsTree.find(({ item }) => {
|
||||
return item.uid === uid;
|
||||
});
|
||||
|
||||
if (!matchingDashboard) {
|
||||
return HttpResponse.json(getErrorResponse(`dashboards.dashboard.grafana.app "${uid}" not found`, 404), {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return HttpResponse.json(dashboardToAppPlatform(matchingDashboard.item));
|
||||
}
|
||||
);
|
||||
|
||||
export default [getDashboardDto()];
|
||||
@@ -1,133 +1,68 @@
|
||||
import { noop } from 'lodash';
|
||||
import { Props } from 'react-virtualized-auto-sizer';
|
||||
import { render, screen, userEvent, waitFor } from 'test/test-utils';
|
||||
import { render, screen, testWithFeatureToggles } from 'test/test-utils';
|
||||
|
||||
import { defaultDashboard as defaultDashboardData } from '@grafana/schema';
|
||||
import {
|
||||
Spec as DashboardV2Spec,
|
||||
defaultSpec as defaultDashboardV2Spec,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { setupMockServer } from '@grafana/test-utils/server';
|
||||
import { getFolderFixtures } from '@grafana/test-utils/unstable';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||
import { DashboardSearchItemType } from 'app/features/search/types';
|
||||
import { DashboardDTO } from 'app/types/dashboard';
|
||||
|
||||
import { DashboardPicker } from './DashboardPicker';
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => ({
|
||||
...jest.requireActual('app/core/services/backend_srv'),
|
||||
backendSrv: {
|
||||
...jest.requireActual('app/core/services/backend_srv').backendSrv,
|
||||
search: jest.fn(),
|
||||
},
|
||||
}));
|
||||
setBackendSrv(backendSrv);
|
||||
setupMockServer();
|
||||
|
||||
const getDashboardDTO = jest.fn();
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: getDashboardDTO,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
return ({ children }: Props) =>
|
||||
children({
|
||||
height: 600,
|
||||
scaledHeight: 600,
|
||||
scaledWidth: 1,
|
||||
width: 1,
|
||||
});
|
||||
});
|
||||
|
||||
jest.mocked(backendSrv.search).mockResolvedValue([
|
||||
{
|
||||
uid: 'dash-1',
|
||||
type: DashboardSearchItemType.DashDB,
|
||||
title: 'Dashboard 1',
|
||||
uri: '',
|
||||
url: '',
|
||||
tags: [],
|
||||
isStarred: false,
|
||||
},
|
||||
{
|
||||
uid: 'dash-2',
|
||||
type: DashboardSearchItemType.DashDB,
|
||||
title: 'Dashboard 2',
|
||||
uri: '',
|
||||
url: '',
|
||||
tags: [],
|
||||
isStarred: false,
|
||||
},
|
||||
{
|
||||
uid: 'dash-3',
|
||||
type: DashboardSearchItemType.DashDB,
|
||||
title: 'Dashboard 3',
|
||||
uri: '',
|
||||
url: '',
|
||||
tags: [],
|
||||
isStarred: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const mockDashboard: DashboardDTO = {
|
||||
dashboard: {
|
||||
...defaultDashboardData,
|
||||
uid: 'dash-2',
|
||||
title: 'Dashboard 2',
|
||||
},
|
||||
meta: {},
|
||||
};
|
||||
|
||||
const mockDashboardV2: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
apiVersion: 'v2beta1',
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
spec: {
|
||||
...defaultDashboardV2Spec(),
|
||||
title: 'Dashboard 2',
|
||||
},
|
||||
metadata: {
|
||||
name: 'dash-2',
|
||||
resourceVersion: '0',
|
||||
creationTimestamp: '0',
|
||||
annotations: {},
|
||||
},
|
||||
access: {
|
||||
canEdit: true,
|
||||
canSave: true,
|
||||
canStar: true,
|
||||
canShare: true,
|
||||
},
|
||||
};
|
||||
const [_, { folderA, folderA_dashbdD }] = getFolderFixtures();
|
||||
|
||||
const fixtures: Array<
|
||||
[
|
||||
// Test title
|
||||
string,
|
||||
// Feature toggle setup
|
||||
Parameters<typeof testWithFeatureToggles>[0],
|
||||
]
|
||||
> = [
|
||||
['app platform APIs enabled', { enable: ['kubernetesDashboards'] }],
|
||||
['app platform APIs disabled', {}],
|
||||
];
|
||||
describe('DashboardPicker', () => {
|
||||
describe.each([
|
||||
['v1', mockDashboard],
|
||||
['v2', mockDashboardV2],
|
||||
])('Dashboard %s', (format, dashboard) => {
|
||||
beforeEach(() => {
|
||||
getDashboardDTO.mockResolvedValue(dashboard);
|
||||
});
|
||||
describe.each(fixtures)('%s', (_title, featureToggleSetup) => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
testWithFeatureToggles(featureToggleSetup);
|
||||
|
||||
it('should fetch and display dashboards', async () => {
|
||||
render(<DashboardPicker value="dash-2" onChange={noop} />);
|
||||
render(<DashboardPicker value={folderA_dashbdD.item.uid} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards/Dashboard 2')).toBeInTheDocument();
|
||||
expect(getDashboardDTO).toHaveBeenCalledWith('dash-2', undefined);
|
||||
});
|
||||
expect(await screen.findByText(`${folderA.item.title}/${folderA_dashbdD.item.title}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should search for dashboards', async () => {
|
||||
render(<DashboardPicker onChange={noop} />);
|
||||
it('should search for dashboards and allow selection', async () => {
|
||||
const { user } = render(<DashboardPicker onChange={onChange} />);
|
||||
|
||||
await userEvent.type(screen.getByRole('combobox'), 'Dashboard 2');
|
||||
const expectedDash = folderA_dashbdD.item;
|
||||
const expectedFolder = folderA.item;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards/Dashboard 2')).toBeInTheDocument();
|
||||
});
|
||||
await user.type(screen.getByRole('combobox'), expectedDash.title);
|
||||
|
||||
expect(backendSrv.search).toHaveBeenCalledWith({ type: 'dash-db', query: 'Dashboard 2', limit: 100 });
|
||||
expect(await screen.findByText(`${expectedFolder.title}/${expectedDash.title}`)).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByText(`${expectedFolder.title}/${expectedDash.title}`));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
folderTitle: expectedFolder.title,
|
||||
folderUid: expectedFolder.uid,
|
||||
name: expectedDash.title,
|
||||
uid: expectedDash.uid,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('dashboard v2 (v2beta1 API)', () => {
|
||||
testWithFeatureToggles({ enable: ['dashboardNewLayouts', 'kubernetesDashboards'] });
|
||||
it('renders dashboard correctly', async () => {
|
||||
render(<DashboardPicker value="v2-special-case-override" />);
|
||||
expect(await screen.findByText('TODO')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,11 @@ import { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { AsyncSelectProps, AsyncSelect } from '@grafana/ui';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { AnnoKeyFolder, AnnoKeyFolderTitle } from 'app/features/apiserver/types';
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { isDashboardV2Resource } from 'app/features/dashboard/api/utils';
|
||||
import { DashboardSearchItem } from 'app/features/search/types';
|
||||
import { getGrafanaSearcher } from 'app/features/search/service/searcher';
|
||||
import { DashboardQueryResult } from 'app/features/search/service/types';
|
||||
import { DashboardDTO } from 'app/types/dashboard';
|
||||
|
||||
interface Props extends Omit<AsyncSelectProps<DashboardPickerDTO>, 'value' | 'onChange' | 'loadOptions' | ''> {
|
||||
@@ -15,23 +15,26 @@ interface Props extends Omit<AsyncSelectProps<DashboardPickerDTO>, 'value' | 'on
|
||||
onChange?: (value?: DashboardPickerDTO) => void;
|
||||
}
|
||||
|
||||
export type DashboardPickerDTO = Pick<DashboardDTO['dashboard'], 'uid' | 'title'> &
|
||||
export type DashboardPickerDTO = Pick<DashboardQueryResult, 'uid' | 'name'> &
|
||||
Pick<DashboardDTO['meta'], 'folderUid' | 'folderTitle'>;
|
||||
|
||||
const formatLabel = (folderTitle = 'Dashboards', dashboardTitle: string) => `${folderTitle}/${dashboardTitle}`;
|
||||
|
||||
async function findDashboards(query = '') {
|
||||
return backendSrv.search({ type: 'dash-db', query, limit: 100 }).then((result: DashboardSearchItem[]) => {
|
||||
return result.map((item: DashboardSearchItem) => ({
|
||||
const result = await getGrafanaSearcher().search({ query, kind: ['dashboard'], limit: 100 });
|
||||
const locationInfo = await getGrafanaSearcher().getLocationInfo();
|
||||
|
||||
return result.view.toArray().map((item) => {
|
||||
const folderTitle = locationInfo[item.location]?.name;
|
||||
return {
|
||||
value: {
|
||||
// dashboards uid here is always defined as this endpoint does not return the default home dashboard
|
||||
uid: item.uid!,
|
||||
title: item.title,
|
||||
folderTitle: item.folderTitle,
|
||||
folderUid: item.folderUid,
|
||||
uid: item.uid,
|
||||
name: item.name,
|
||||
folderTitle,
|
||||
folderUid: item.location,
|
||||
},
|
||||
label: formatLabel(item?.folderTitle, item.title),
|
||||
}));
|
||||
label: formatLabel(folderTitle, item.name),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +61,7 @@ export const DashboardPicker = forwardRef<HTMLElement, Props>(
|
||||
setCurrent({
|
||||
value: {
|
||||
uid: dto.metadata.name,
|
||||
title: dto.spec.title,
|
||||
name: dto.spec.title,
|
||||
folderTitle: dto.metadata.annotations?.[AnnoKeyFolderTitle],
|
||||
folderUid: dto.metadata.annotations?.[AnnoKeyFolder],
|
||||
},
|
||||
@@ -69,7 +72,7 @@ export const DashboardPicker = forwardRef<HTMLElement, Props>(
|
||||
setCurrent({
|
||||
value: {
|
||||
uid: dto.dashboard.uid,
|
||||
title: dto.dashboard.title,
|
||||
name: dto.dashboard.title,
|
||||
folderTitle: dto.meta.folderTitle,
|
||||
folderUid: dto.meta.folderUid,
|
||||
},
|
||||
|
||||
@@ -1,197 +1,136 @@
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup';
|
||||
import { getSelectParent, selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
import { render, screen, userEvent, waitFor, within } from 'test/test-utils';
|
||||
|
||||
import { Preferences as UserPreferencesDTO } from '@grafana/schema/src/raw/preferences/x/preferences_types.gen';
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { setupMockServer } from '@grafana/test-utils/server';
|
||||
import { getFolderFixtures } from '@grafana/test-utils/unstable';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
||||
|
||||
import SharedPreferences from './SharedPreferences';
|
||||
|
||||
const selectComboboxOptionInTest = async (input: HTMLElement, optionOrOptions: string) => {
|
||||
await userEvent.click(input);
|
||||
const option = await screen.findByRole('option', { name: optionOrOptions });
|
||||
await userEvent.click(option);
|
||||
setBackendSrv(backendSrv);
|
||||
setupMockServer();
|
||||
|
||||
const getPrefsUpdateRequest = async (requests: Request[]) => {
|
||||
const prefsUpdate = requests.find((r) => r.url.match('/preferences') && r.method === 'PUT');
|
||||
|
||||
return prefsUpdate!.clone().json();
|
||||
};
|
||||
|
||||
jest.mock('app/features/dashboard/api/dashboard_api', () => ({
|
||||
getDashboardAPI: () => ({
|
||||
getDashboardDTO: jest.fn().mockResolvedValue({
|
||||
dashboard: {
|
||||
id: 2,
|
||||
title: 'My Dashboard',
|
||||
uid: 'myDash',
|
||||
templating: {
|
||||
list: [],
|
||||
},
|
||||
panels: [],
|
||||
},
|
||||
meta: {},
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
const [_, { dashbdD, dashbdE }] = getFolderFixtures();
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => {
|
||||
return {
|
||||
backendSrv: {
|
||||
search: jest.fn().mockResolvedValue([
|
||||
{
|
||||
id: 2,
|
||||
title: 'My Dashboard',
|
||||
tags: [],
|
||||
type: '',
|
||||
uid: 'myDash',
|
||||
uri: '',
|
||||
url: '',
|
||||
folderId: 0,
|
||||
folderTitle: '',
|
||||
folderUid: '',
|
||||
folderUrl: '',
|
||||
isStarred: true,
|
||||
slug: '',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Another Dashboard',
|
||||
tags: [],
|
||||
type: '',
|
||||
uid: 'anotherDash',
|
||||
uri: '',
|
||||
url: '',
|
||||
folderId: 0,
|
||||
folderTitle: '',
|
||||
folderUid: '',
|
||||
folderUrl: '',
|
||||
isStarred: true,
|
||||
slug: '',
|
||||
items: [],
|
||||
},
|
||||
]),
|
||||
const selectComboboxOptionInTest = async (input: HTMLElement, optionOrOptions: string | RegExp) => {
|
||||
const user = userEvent.setup();
|
||||
await user.click(input);
|
||||
const option = await screen.findByRole('option', { name: optionOrOptions });
|
||||
await user.click(option);
|
||||
};
|
||||
|
||||
const setup = async () => {
|
||||
const view = render(<SharedPreferences resourceUri="user" preferenceType="user" />);
|
||||
const themeSelect = await screen.findByRole('combobox', { name: 'Interface theme' });
|
||||
await waitFor(() => expect(themeSelect).not.toBeDisabled());
|
||||
return view;
|
||||
};
|
||||
|
||||
const original = window.location;
|
||||
const mockReload = jest.fn();
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: {
|
||||
...original,
|
||||
reload: mockReload,
|
||||
},
|
||||
};
|
||||
});
|
||||
comboboxTestSetup();
|
||||
});
|
||||
|
||||
const mockPreferences: UserPreferencesDTO = {
|
||||
timezone: 'browser',
|
||||
weekStart: 'monday',
|
||||
theme: 'light',
|
||||
homeDashboardUID: 'myDash',
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
language: '',
|
||||
};
|
||||
|
||||
const defaultPreferences: UserPreferencesDTO = {
|
||||
timezone: '',
|
||||
weekStart: '',
|
||||
theme: '',
|
||||
homeDashboardUID: '',
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
language: '',
|
||||
};
|
||||
|
||||
const mockPrefsPatch = jest.fn().mockResolvedValue(undefined);
|
||||
const mockPrefsUpdate = jest.fn().mockResolvedValue(undefined);
|
||||
const mockPrefsLoad = jest.fn().mockResolvedValue(mockPreferences);
|
||||
|
||||
jest.mock('app/core/services/PreferencesService', () => ({
|
||||
PreferencesService: function () {
|
||||
return {
|
||||
patch: mockPrefsPatch,
|
||||
update: mockPrefsUpdate,
|
||||
load: mockPrefsLoad,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
const props = {
|
||||
resourceUri: '/fake-api/user/1',
|
||||
preferenceType: 'user' as const,
|
||||
};
|
||||
afterAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: original,
|
||||
});
|
||||
});
|
||||
|
||||
describe('SharedPreferences', () => {
|
||||
const original = window.location;
|
||||
const mockReload = jest.fn();
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: { reload: mockReload },
|
||||
});
|
||||
comboboxTestSetup();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Object.defineProperty(window, 'location', { configurable: true, value: original });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
render(<SharedPreferences {...props} />);
|
||||
|
||||
await waitFor(() => expect(mockPrefsLoad).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('renders the theme preference', async () => {
|
||||
await setup();
|
||||
const themeSelect = await screen.findByRole('combobox', { name: 'Interface theme' });
|
||||
expect(themeSelect).toHaveValue('Light');
|
||||
await waitFor(() => expect(themeSelect).toHaveValue('Light'));
|
||||
});
|
||||
|
||||
it('renders the home dashboard preference', async () => {
|
||||
await setup();
|
||||
const dashboardSelect = getSelectParent(screen.getByLabelText('Home Dashboard'));
|
||||
await waitFor(() => {
|
||||
expect(dashboardSelect).toHaveTextContent('My Dashboard');
|
||||
expect(dashboardSelect).toHaveTextContent(dashbdD.item.title);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the timezone preference', () => {
|
||||
it('renders the timezone preference', async () => {
|
||||
await setup();
|
||||
const tzSelect = getSelectParent(screen.getByLabelText('Timezone'));
|
||||
expect(tzSelect).toHaveTextContent('Browser Time');
|
||||
});
|
||||
|
||||
it('renders the week start preference', async () => {
|
||||
await setup();
|
||||
const weekSelect = await screen.findByRole('combobox', { name: 'Week start' });
|
||||
expect(weekSelect).toHaveValue('Monday');
|
||||
});
|
||||
|
||||
it('renders the default language preference', async () => {
|
||||
await setup();
|
||||
const langSelect = await screen.findByRole('combobox', { name: /language/i });
|
||||
expect(langSelect).toHaveValue('Default');
|
||||
});
|
||||
|
||||
it('does not render the pseudo-locale', async () => {
|
||||
const { user } = await setup();
|
||||
const langSelect = await screen.findByRole('combobox', { name: /language/i });
|
||||
|
||||
// Open the combobox and wait for the options to be rendered
|
||||
await userEvent.click(langSelect);
|
||||
await user.click(langSelect);
|
||||
expect((await screen.findAllByRole('option'))[0]).toBeInTheDocument();
|
||||
|
||||
// TODO: The input value should be cleared when clicked, but for some reason it's not?
|
||||
// checking langSelect.value beforehand indicates that it is cleared, but after using
|
||||
// userEvent.type the default value comes back?
|
||||
await userEvent.type(
|
||||
langSelect,
|
||||
'{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}Pseudo'
|
||||
);
|
||||
await user.clear(langSelect);
|
||||
await user.type(langSelect, 'Pseudo', {
|
||||
// Don't click on the element again when typing as this would just re-set the value
|
||||
skipClick: true,
|
||||
});
|
||||
|
||||
const option = screen.queryByRole('option', { name: 'Pseudo-locale' });
|
||||
expect(option).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('saves the users new preferences', async () => {
|
||||
const dashboardToSelect = dashbdE.item;
|
||||
const capture = captureRequests();
|
||||
const { user } = await setup();
|
||||
|
||||
await selectComboboxOptionInTest(await screen.findByRole('combobox', { name: 'Interface theme' }), 'Dark');
|
||||
await selectComboboxOptionInTest(
|
||||
await screen.findByRole('combobox', { name: /home dashboard/i }),
|
||||
new RegExp(dashboardToSelect.title)
|
||||
);
|
||||
await selectOptionInTest(screen.getByLabelText('Timezone'), 'Australia/Sydney');
|
||||
await selectComboboxOptionInTest(await screen.findByRole('combobox', { name: 'Week start' }), 'Saturday');
|
||||
await selectComboboxOptionInTest(await screen.findByRole('combobox', { name: /language/i }), 'Français');
|
||||
|
||||
await userEvent.click(screen.getByText('Save'));
|
||||
await user.click(screen.getByText('Save'));
|
||||
|
||||
expect(mockPrefsUpdate).toHaveBeenCalledWith({
|
||||
const requests = await capture;
|
||||
const newPreferences = await getPrefsUpdateRequest(requests);
|
||||
|
||||
expect(newPreferences).toEqual({
|
||||
timezone: 'Australia/Sydney',
|
||||
weekStart: 'saturday',
|
||||
theme: 'dark',
|
||||
homeDashboardUID: 'myDash',
|
||||
homeDashboardUID: dashboardToSelect.uid,
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
@@ -200,12 +139,14 @@ describe('SharedPreferences', () => {
|
||||
});
|
||||
|
||||
it('saves the users default preferences', async () => {
|
||||
const capture = captureRequests();
|
||||
const { user } = await setup();
|
||||
await selectComboboxOptionInTest(await screen.findByRole('combobox', { name: 'Interface theme' }), 'Default');
|
||||
|
||||
// there's no default option in this dropdown - there's a clear selection button
|
||||
// get the parent container, and find the "Clear value" button
|
||||
const dashboardSelect = screen.getByTestId('User preferences home dashboard drop down');
|
||||
await userEvent.click(within(dashboardSelect).getByRole('button', { name: 'Clear value' }));
|
||||
await user.click(within(dashboardSelect).getByRole('button', { name: 'Clear value' }));
|
||||
|
||||
await selectOptionInTest(screen.getByLabelText('Timezone'), 'Default');
|
||||
|
||||
@@ -213,12 +154,24 @@ describe('SharedPreferences', () => {
|
||||
|
||||
await selectComboboxOptionInTest(screen.getByRole('combobox', { name: /language/i }), 'Default');
|
||||
|
||||
await userEvent.click(screen.getByText('Save'));
|
||||
expect(mockPrefsUpdate).toHaveBeenCalledWith(defaultPreferences);
|
||||
await user.click(screen.getByText('Save'));
|
||||
const requests = await capture;
|
||||
const newPreferences = await getPrefsUpdateRequest(requests);
|
||||
expect(newPreferences).toEqual({
|
||||
timezone: '',
|
||||
weekStart: '',
|
||||
theme: '',
|
||||
homeDashboardUID: '',
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
language: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('refreshes the page after saving preferences', async () => {
|
||||
await userEvent.click(screen.getByText('Save'));
|
||||
const { user } = await setup();
|
||||
await user.click(screen.getByText('Save'));
|
||||
expect(mockReload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { DashboardDTO } from 'app/types/dashboard';
|
||||
|
||||
import { DashboardSearchItem } from '../../../search/types';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
export const dashboardApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
search: build.query<DashboardSearchItem[], { query?: string }>({
|
||||
query: ({ query }) => {
|
||||
const params = new URLSearchParams({ type: 'dash-db', limit: '1000', page: '1', sort: 'name_sort' });
|
||||
if (query) {
|
||||
params.set('query', query);
|
||||
}
|
||||
|
||||
return { url: `/api/search?${params.toString()}` };
|
||||
},
|
||||
}),
|
||||
dashboard: build.query<DashboardDTO, { uid: string }>({
|
||||
query: ({ uid }) => ({ url: `/api/dashboards/uid/${uid}` }),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { noop } from 'lodash';
|
||||
import { CSSProperties, useCallback, useMemo, useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
clearButtonStyles,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { getGrafanaSearcher } from 'app/features/search/service/searcher';
|
||||
|
||||
import { DashboardModel } from '../../../../dashboard/state/DashboardModel';
|
||||
import { dashboardApi } from '../../api/dashboardApi';
|
||||
|
||||
import { useDashboardQuery } from './useDashboardQuery';
|
||||
|
||||
@@ -31,9 +31,11 @@ export interface PanelDTO {
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
const collator = new Intl.Collator();
|
||||
|
||||
function panelSort(a: PanelDTO, b: PanelDTO) {
|
||||
if (a.title && b.title) {
|
||||
return a.title.localeCompare(b.title);
|
||||
return collator.compare(a.title, b.title);
|
||||
}
|
||||
if (a.title && !b.title) {
|
||||
return 1;
|
||||
@@ -52,28 +54,34 @@ interface DashboardPickerProps {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
const useFilteredDashboards = (dashboardFilter: string) => {
|
||||
return useAsync(async () => {
|
||||
const results = await getGrafanaSearcher().search({
|
||||
query: dashboardFilter,
|
||||
kind: ['dashboard'],
|
||||
});
|
||||
const locationInfo = await getGrafanaSearcher().getLocationInfo();
|
||||
|
||||
return { dashboards: results.view.toArray(), locationInfo };
|
||||
}, [dashboardFilter]);
|
||||
};
|
||||
|
||||
export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDismiss }: DashboardPickerProps) => {
|
||||
const styles = useStyles2(getPickerStyles);
|
||||
|
||||
const [selectedDashboardUid, setSelectedDashboardUid] = useState(dashboardUid);
|
||||
const [selectedPanelId, setSelectedPanelId] = useState(panelId);
|
||||
|
||||
const [dashboardFilter, setDashboardFilter] = useState('');
|
||||
const [debouncedDashboardFilter, setDebouncedDashboardFilter] = useState('');
|
||||
|
||||
const [panelFilter, setPanelFilter] = useState('');
|
||||
const { useSearchQuery } = dashboardApi;
|
||||
|
||||
const { currentData: filteredDashboards = [], isFetching: isDashSearchFetching } = useSearchQuery({
|
||||
query: debouncedDashboardFilter,
|
||||
});
|
||||
const { value, loading: isDashSearchFetching } = useFilteredDashboards(debouncedDashboardFilter);
|
||||
const { dashboardModel, isFetching: isDashboardFetching } = useDashboardQuery(selectedDashboardUid);
|
||||
|
||||
const handleDashboardChange = useCallback((dashboardUid: string) => {
|
||||
setSelectedDashboardUid(dashboardUid);
|
||||
setSelectedPanelId(undefined);
|
||||
}, []);
|
||||
|
||||
const { dashboards: filteredDashboards = [], locationInfo: locationInfo = {} } = value || {};
|
||||
|
||||
const allDashboardPanels = getVisualPanels(dashboardModel);
|
||||
|
||||
const filteredPanels =
|
||||
@@ -114,18 +122,19 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
||||
const DashboardRow = ({ index, style }: { index: number; style?: CSSProperties }) => {
|
||||
const dashboard = filteredDashboards[index];
|
||||
const isSelected = selectedDashboardUid === dashboard.uid;
|
||||
const folderTitle = locationInfo?.[dashboard.location]?.name ?? 'Dashboards';
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title={dashboard.title}
|
||||
title={dashboard.name}
|
||||
style={style}
|
||||
className={cx(styles.rowButton, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
||||
onClick={() => handleDashboardChange(dashboard.uid)}
|
||||
>
|
||||
<div className={cx(styles.dashboardTitle, styles.rowButtonTitle)}>{dashboard.title}</div>
|
||||
<div className={cx(styles.dashboardTitle, styles.rowButtonTitle)}>{dashboard.name}</div>
|
||||
<div className={styles.dashboardFolder}>
|
||||
<Icon name="folder" /> {dashboard.folderTitle ?? 'Dashboards'}
|
||||
<Icon name="folder" /> {folderTitle}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
|
||||
import { DashboardDTO } from 'app/types/dashboard';
|
||||
|
||||
import { DashboardModel } from '../../../../dashboard/state/DashboardModel';
|
||||
import { dashboardApi } from '../../api/dashboardApi';
|
||||
|
||||
const convertToDashboardModel = memoizeOne((dashboardDTO: DashboardDTO) => {
|
||||
// RTKQuery freezes all returned objects. DashboardModel constructor runs migrations which might change the internal object
|
||||
@@ -13,16 +14,23 @@ const convertToDashboardModel = memoizeOne((dashboardDTO: DashboardDTO) => {
|
||||
});
|
||||
|
||||
export function useDashboardQuery(dashboardUid?: string) {
|
||||
const queryData = dashboardApi.endpoints.dashboard.useQuery(
|
||||
{ uid: dashboardUid ?? '' },
|
||||
{
|
||||
skip: !dashboardUid,
|
||||
selectFromResult: ({ currentData, data, ...rest }) => ({
|
||||
dashboardModel: currentData ? convertToDashboardModel(currentData) : undefined,
|
||||
...rest,
|
||||
}),
|
||||
const [dashboardModel, setDashboardModel] = useState<DashboardModel>();
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
useEffect(() => {
|
||||
if (dashboardUid) {
|
||||
setIsFetching(true);
|
||||
getDashboardAPI()
|
||||
.getDashboardDTO(dashboardUid)
|
||||
.then((dashboard) => {
|
||||
if (!('dashboard' in dashboard)) {
|
||||
console.error('Something went wrong, unexpected dashboard format');
|
||||
} else {
|
||||
setDashboardModel(convertToDashboardModel(dashboard));
|
||||
}
|
||||
setIsFetching(false);
|
||||
});
|
||||
}
|
||||
);
|
||||
}, [dashboardUid]);
|
||||
|
||||
return queryData;
|
||||
return { dashboardModel, isFetching };
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'test/test-utils';
|
||||
import { render, act, screen, waitFor } from 'test/test-utils';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { defaultDashboard } from '@grafana/schema';
|
||||
import { locationService, setBackendSrv, setEchoSrv } from '@grafana/runtime';
|
||||
import { setupMockServer } from '@grafana/test-utils/server';
|
||||
import { getFolderFixtures } from '@grafana/test-utils/unstable';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
import store from 'app/core/store';
|
||||
import { DashboardSearchItemType } from 'app/features/search/types';
|
||||
|
||||
import { AddToDashboardForm, Props } from './AddToDashboardForm';
|
||||
|
||||
const [_, { dashbdE }] = getFolderFixtures();
|
||||
|
||||
setBackendSrv(backendSrv);
|
||||
setupMockServer();
|
||||
|
||||
async function setup(overrides: Partial<Props> = {}) {
|
||||
const props: Props = {
|
||||
buildPanel: () => ({ id: 1, type: 'table', options: { showHeader: false } }),
|
||||
@@ -39,19 +42,17 @@ describe('AddToDashboardButton', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(backendSrv, 'search').mockResolvedValue([]);
|
||||
mocks.contextSrv.hasPermission.mockImplementation(() => true);
|
||||
locationService.push('/');
|
||||
});
|
||||
|
||||
describe('navigation', () => {
|
||||
it('Navigates to dashboard when clicking on "Open"', async () => {
|
||||
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
|
||||
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
|
||||
const openSpy = jest.spyOn(global, 'open');
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
|
||||
|
||||
@@ -63,9 +64,9 @@ describe('AddToDashboardButton', () => {
|
||||
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
|
||||
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
|
||||
expect(openSpy).toHaveBeenCalledWith(expect.anything(), '_blank');
|
||||
expect(locationService.getLocation().pathname).toBe('/');
|
||||
@@ -75,9 +76,9 @@ describe('AddToDashboardButton', () => {
|
||||
describe('Add to new dashboard', () => {
|
||||
describe('Navigate to correct dashboard when saving', () => {
|
||||
it('Navigates to the new dashboard', async () => {
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
|
||||
expect(locationService.getLocation().pathname).toBe('/dashboard/new');
|
||||
@@ -87,21 +88,21 @@ describe('AddToDashboardButton', () => {
|
||||
|
||||
describe('Add to existing dashboard', () => {
|
||||
it('Renders the dashboard picker when switching to "Existing Dashboard"', async () => {
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
|
||||
await user.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
|
||||
expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Does not submit if no dashboard is selected', async () => {
|
||||
locationService.push = jest.fn();
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
await userEvent.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
|
||||
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
await user.click(screen.getByRole<HTMLInputElement>('radio', { name: /existing dashboard/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
|
||||
locationService.push = jest.fn();
|
||||
expect(locationService.push).not.toHaveBeenCalled();
|
||||
@@ -112,72 +113,41 @@ describe('AddToDashboardButton', () => {
|
||||
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
|
||||
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
|
||||
|
||||
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
|
||||
dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
|
||||
meta: {},
|
||||
});
|
||||
const dashboardToSelect = dashbdE.item;
|
||||
|
||||
jest.spyOn(backendSrv, 'search').mockResolvedValue([
|
||||
{
|
||||
uid: 'someUid',
|
||||
isStarred: false,
|
||||
title: 'Dashboard Title',
|
||||
tags: [],
|
||||
type: DashboardSearchItemType.DashDB,
|
||||
uri: 'someUri',
|
||||
url: 'someUrl',
|
||||
},
|
||||
]);
|
||||
const { user } = await setup();
|
||||
|
||||
await setup();
|
||||
await user.click(screen.getByRole('radio', { name: /existing dashboard/i }));
|
||||
await user.click(screen.getByRole('combobox', { name: /dashboard/i }));
|
||||
|
||||
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
|
||||
await screen.findAllByTestId(selectors.components.Select.option);
|
||||
|
||||
await waitFor(async () => {
|
||||
await screen.findByTestId(selectors.components.Select.option);
|
||||
});
|
||||
await user.click(screen.getByText(new RegExp(dashboardToSelect.title)));
|
||||
await user.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
|
||||
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
|
||||
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(openSpy).toBeCalledWith('d/someUid', '_blank');
|
||||
expect(openSpy).toHaveBeenCalledWith(`d/${dashboardToSelect.uid}`, '_blank');
|
||||
});
|
||||
|
||||
it('Navigates to the selected dashboard', async () => {
|
||||
jest.spyOn(backendSrv, 'search').mockResolvedValue([
|
||||
{
|
||||
uid: 'someUid',
|
||||
isStarred: false,
|
||||
title: 'Dashboard Title',
|
||||
tags: [],
|
||||
type: DashboardSearchItemType.DashDB,
|
||||
uri: 'someUri',
|
||||
url: 'someUrl',
|
||||
},
|
||||
]);
|
||||
const dashboardToSelect = dashbdE.item;
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
|
||||
await user.click(screen.getByRole('radio', { name: /existing dashboard/i }));
|
||||
await user.click(screen.getByRole('combobox', { name: /dashboard/i }));
|
||||
|
||||
await waitFor(async () => {
|
||||
await screen.findByTestId(selectors.components.Select.option);
|
||||
});
|
||||
await screen.findAllByTestId(selectors.components.Select.option);
|
||||
|
||||
await userEvent.click(screen.getByTestId(selectors.components.Select.option));
|
||||
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
await user.click(screen.getByText(new RegExp(dashboardToSelect.title)));
|
||||
await user.click(screen.getByRole('button', { name: /open dashboard$/i }));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(locationService.getLocation().pathname).toBe('/d/someUid');
|
||||
expect(locationService.getLocation().pathname).toBe(`/d/${dashboardToSelect.uid}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -230,11 +200,11 @@ describe('Error handling', () => {
|
||||
jest.spyOn(global, 'open').mockReturnValue(null);
|
||||
const removeDashboardSpy = jest.spyOn(store, 'delete');
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await screen.findByRole('alert')).toBeInTheDocument();
|
||||
@@ -248,11 +218,11 @@ describe('Error handling', () => {
|
||||
throw 'SOME ERROR';
|
||||
});
|
||||
|
||||
await setup();
|
||||
const { user } = await setup();
|
||||
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
await user.click(screen.getByRole('button', { name: /open in new tab/i }));
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await screen.findByRole('alert')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user