mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Replace feature toggle with configuration setting * Fix permission alert * Update documentation * Add back feature toggle * revert unwanted commited changes * fix tests * run prettier * Update SharePublicDashboard.test.tsx * fix linter and frontend tests * Update api.go * Apply docs edit from code review Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> * Update index.md * Update docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> * add isPublicDashboardsEnabled + test * fix test * update ff description in registry * move isPublicDashboardsEnabled * revert getConfig() update --------- Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
411 lines
15 KiB
TypeScript
411 lines
15 KiB
TypeScript
import { screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { rest } from 'msw';
|
|
import { setupServer } from 'msw/node';
|
|
|
|
import 'whatwg-fetch';
|
|
import { BootData, DataQuery } from '@grafana/data/src';
|
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
|
import { reportInteraction, setEchoSrv } from '@grafana/runtime';
|
|
import { Panel } from '@grafana/schema';
|
|
import config from 'app/core/config';
|
|
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 { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
|
|
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
|
|
|
|
import { shareDashboardType } from '../utils';
|
|
|
|
import * as sharePublicDashboardUtils from './SharePublicDashboardUtils';
|
|
import {
|
|
getExistentPublicDashboardResponse,
|
|
mockDashboard,
|
|
pubdashResponse,
|
|
renderSharePublicDashboard,
|
|
} from './utilsTest';
|
|
|
|
const server = setupServer();
|
|
|
|
jest.mock('@grafana/runtime', () => ({
|
|
...jest.requireActual('@grafana/runtime'),
|
|
getBackendSrv: () => backendSrv,
|
|
reportInteraction: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('app/features/dashboard-scene/utils/interactions', () => ({
|
|
DashboardInteractions: {
|
|
...jest.requireActual('app/features/dashboard-scene/utils/interactions').DashboardInteractions,
|
|
sharingTabChanged: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
|
|
|
let originalBootData: BootData;
|
|
|
|
beforeAll(() => {
|
|
setEchoSrv(new Echo());
|
|
originalBootData = config.bootData;
|
|
config.appUrl = 'http://dashboards.grafana.com/';
|
|
config.bootData = {
|
|
user: {
|
|
orgId: 1,
|
|
},
|
|
navTree: [
|
|
{
|
|
text: 'Section name',
|
|
id: 'section',
|
|
url: 'section',
|
|
children: [
|
|
{ text: 'Child1', id: 'child1', url: 'section/child1' },
|
|
{ text: 'Child2', id: 'child2', url: 'section/child2' },
|
|
],
|
|
},
|
|
],
|
|
} as BootData;
|
|
|
|
server.listen({ onUnhandledRequest: 'bypass' });
|
|
});
|
|
|
|
beforeEach(() => {
|
|
config.featureToggles.publicDashboards = true;
|
|
config.publicDashboardsEnabled = true;
|
|
|
|
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
|
|
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
|
|
});
|
|
|
|
afterAll(() => {
|
|
config.bootData = originalBootData;
|
|
server.close();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
server.resetHandlers();
|
|
});
|
|
|
|
const getNonExistentPublicDashboardResponse = () =>
|
|
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
|
return res(
|
|
ctx.status(404),
|
|
ctx.json({
|
|
message: 'Public dashboard not found',
|
|
messageId: 'publicdashboards.notFound',
|
|
statusCode: 404,
|
|
traceID: '',
|
|
})
|
|
);
|
|
});
|
|
const getErrorPublicDashboardResponse = () =>
|
|
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
|
return res(ctx.status(500));
|
|
});
|
|
|
|
const alertTests = () => {
|
|
it('when user has no write permissions, warning is shown', async () => {
|
|
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
|
|
|
|
await renderSharePublicDashboard();
|
|
expect(screen.queryByTestId(selectors.NoUpsertPermissionsWarningAlert)).toBeInTheDocument();
|
|
});
|
|
it('when dashboard has template variables, warning is shown', async () => {
|
|
jest.spyOn(sharePublicDashboardUtils, 'dashboardHasTemplateVariables').mockReturnValue(true);
|
|
|
|
await renderSharePublicDashboard();
|
|
expect(screen.queryByTestId(selectors.TemplateVariablesWarningAlert)).toBeInTheDocument();
|
|
});
|
|
it('when dashboard has unsupported datasources, warning is shown', async () => {
|
|
const panelModel = {
|
|
targets: [
|
|
{
|
|
datasource: { type: 'notSupportedDatasource', uid: 'abc123' },
|
|
} as DataQuery,
|
|
] as DataQuery[],
|
|
} as unknown as Panel;
|
|
const dashboard = createDashboardModelFixture({
|
|
id: 1,
|
|
panels: [panelModel],
|
|
});
|
|
|
|
await renderSharePublicDashboard({ dashboard });
|
|
expect(screen.queryByTestId(selectors.UnsupportedDataSourcesWarningAlert)).toBeInTheDocument();
|
|
});
|
|
};
|
|
|
|
describe('SharePublic', () => {
|
|
beforeEach(() => {
|
|
server.use(getExistentPublicDashboardResponse());
|
|
});
|
|
it('does not render share panel when public dashboards feature is disabled using config setting', async () => {
|
|
config.publicDashboardsEnabled = false;
|
|
await renderSharePublicDashboard(undefined, false);
|
|
|
|
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
|
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
|
|
});
|
|
it('does not render share panel when public dashboards feature is disabled using feature toggle', async () => {
|
|
config.featureToggles.publicDashboards = false;
|
|
await renderSharePublicDashboard(undefined, false);
|
|
|
|
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
|
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
|
|
});
|
|
it('renders default relative time in settings summary when they are closed', async () => {
|
|
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
|
|
|
|
//@ts-ignore
|
|
mockDashboard.originalTime = { from: 'now-6h', to: 'now' };
|
|
|
|
await renderSharePublicDashboard();
|
|
await waitFor(() => screen.getByText('Time range ='));
|
|
|
|
expect(screen.getByText('Last 6 hours')).toBeInTheDocument();
|
|
});
|
|
it('renders default relative time in settings when they are open', async () => {
|
|
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
|
|
|
|
//@ts-ignore
|
|
mockDashboard.originalTime = { from: 'now-6h', to: 'now' };
|
|
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
expect(screen.queryAllByText('Last 6 hours')).toHaveLength(2);
|
|
});
|
|
it('when modal is opened, then checkboxes are enabled but create button is disabled', async () => {
|
|
server.use(getNonExistentPublicDashboardResponse());
|
|
await renderSharePublicDashboard();
|
|
|
|
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeEnabled();
|
|
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeEnabled();
|
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeEnabled();
|
|
expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
|
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
|
});
|
|
it('when fetch errors happen, then all inputs remain disabled', async () => {
|
|
server.use(getErrorPublicDashboardResponse());
|
|
|
|
await renderSharePublicDashboard();
|
|
|
|
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
|
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('SharePublic - New config setup', () => {
|
|
beforeEach(() => {
|
|
server.use(getNonExistentPublicDashboardResponse());
|
|
});
|
|
|
|
it('renders when public dashboards feature is enabled', async () => {
|
|
await renderSharePublicDashboard();
|
|
|
|
await screen.findByText('Welcome to public dashboards!');
|
|
expect(screen.getByText('Generate public URL')).toBeInTheDocument();
|
|
|
|
expect(screen.queryByTestId(selectors.WillBePublicCheckbox)).toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.LimitedDSCheckbox)).toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.CostIncreaseCheckbox)).toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.CreateButton)).toBeInTheDocument();
|
|
|
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
|
});
|
|
it('when modal is opened, then create button is disabled', async () => {
|
|
await renderSharePublicDashboard();
|
|
expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
|
|
});
|
|
it('when checkboxes are filled, then create button is enabled', async () => {
|
|
await renderSharePublicDashboard();
|
|
|
|
await userEvent.click(screen.getByTestId(selectors.WillBePublicCheckbox));
|
|
await userEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
|
|
await userEvent.click(screen.getByTestId(selectors.CostIncreaseCheckbox));
|
|
|
|
expect(screen.getByTestId(selectors.CreateButton)).toBeEnabled();
|
|
});
|
|
alertTests();
|
|
});
|
|
|
|
describe('SharePublic - Already persisted', () => {
|
|
beforeEach(() => {
|
|
server.use(getExistentPublicDashboardResponse());
|
|
});
|
|
|
|
it('when modal is opened, then delete button is enabled', async () => {
|
|
await renderSharePublicDashboard();
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
|
|
});
|
|
});
|
|
it('when fetch is done, then inputs are checked and delete button is enabled', async () => {
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
|
|
});
|
|
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).toBeEnabled();
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).not.toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
|
|
});
|
|
it('inputs and delete button are disabled because of lack of permissions', async () => {
|
|
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
expect(await screen.findByTestId(selectors.EnableTimeRangeSwitch)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).toBeDisabled();
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).not.toBeChecked();
|
|
|
|
expect(screen.queryByTestId(selectors.DeleteButton)).toBeDisabled();
|
|
});
|
|
it('when modal is opened, then time range switch is enabled and not checked when its not checked in the db', async () => {
|
|
server.use(
|
|
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
|
return res(
|
|
ctx.status(200),
|
|
ctx.json({
|
|
...pubdashResponse,
|
|
timeSelectionEnabled: false,
|
|
})
|
|
);
|
|
})
|
|
);
|
|
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
const enableTimeRangeSwitch = await screen.findByTestId(selectors.EnableTimeRangeSwitch);
|
|
await waitFor(() => {
|
|
expect(enableTimeRangeSwitch).toBeEnabled();
|
|
expect(enableTimeRangeSwitch).not.toBeChecked();
|
|
});
|
|
});
|
|
it('when pubdash is enabled, then link url is available', async () => {
|
|
await renderSharePublicDashboard();
|
|
expect(screen.getByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
|
|
});
|
|
it('when pubdash is disabled in the db, then link url is not copyable and switch is checked', async () => {
|
|
server.use(
|
|
rest.get('/api/dashboards/uid/:dashboardUid/public-dashboards', (req, res, ctx) => {
|
|
return res(
|
|
ctx.status(200),
|
|
ctx.json({
|
|
isEnabled: false,
|
|
annotationsEnabled: false,
|
|
uid: 'a-uid',
|
|
dashboardUid: req.params.dashboardUid,
|
|
accessToken: 'an-access-token',
|
|
})
|
|
);
|
|
})
|
|
);
|
|
|
|
await renderSharePublicDashboard();
|
|
|
|
expect(await screen.findByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.CopyUrlButton)).not.toBeChecked();
|
|
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).toBeChecked();
|
|
});
|
|
it('does not render email sharing section', async () => {
|
|
await renderSharePublicDashboard();
|
|
|
|
expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingInput)).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingInviteButton)).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingList)).not.toBeInTheDocument();
|
|
});
|
|
alertTests();
|
|
});
|
|
|
|
describe('SharePublic - Report interactions', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
server.use(getExistentPublicDashboardResponse());
|
|
server.use(
|
|
rest.patch('/api/dashboards/uid/:dashboardUid/public-dashboards/:uid', (req, res, ctx) =>
|
|
res(
|
|
ctx.status(200),
|
|
ctx.json({
|
|
...pubdashResponse,
|
|
dashboardUid: req.params.dashboardUid,
|
|
})
|
|
)
|
|
)
|
|
);
|
|
});
|
|
|
|
it('reports interaction when public dashboard tab is clicked', async () => {
|
|
await renderSharePublicDashboard();
|
|
|
|
await waitFor(() => {
|
|
expect(DashboardInteractions.sharingTabChanged).toHaveBeenCalledTimes(1);
|
|
expect(DashboardInteractions.sharingTabChanged).lastCalledWith({ item: shareDashboardType.publicDashboard });
|
|
});
|
|
});
|
|
|
|
it('reports interaction when time range is clicked', async () => {
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
|
|
});
|
|
await userEvent.click(screen.getByTestId(selectors.EnableTimeRangeSwitch));
|
|
|
|
await waitFor(() => {
|
|
expect(reportInteraction).toHaveBeenCalledTimes(1);
|
|
expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_time_picker_clicked', {
|
|
enabled: !pubdashResponse.timeSelectionEnabled,
|
|
});
|
|
});
|
|
});
|
|
|
|
it('reports interaction when show annotations is clicked', async () => {
|
|
await renderSharePublicDashboard();
|
|
await userEvent.click(screen.getByText('Settings'));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
|
});
|
|
await userEvent.click(screen.getByTestId(selectors.EnableAnnotationsSwitch));
|
|
|
|
await waitFor(() => {
|
|
expect(reportInteraction).toHaveBeenCalledTimes(1);
|
|
expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_annotations_clicked', {
|
|
enabled: !pubdashResponse.annotationsEnabled,
|
|
});
|
|
});
|
|
});
|
|
it('reports interaction when pause is clicked', async () => {
|
|
await renderSharePublicDashboard();
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId(selectors.PauseSwitch)).toBeEnabled();
|
|
});
|
|
await userEvent.click(screen.getByTestId(selectors.PauseSwitch));
|
|
|
|
await waitFor(() => {
|
|
expect(reportInteraction).toHaveBeenCalledTimes(1);
|
|
expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_pause_clicked', {
|
|
paused: pubdashResponse.isEnabled,
|
|
});
|
|
});
|
|
});
|
|
});
|