mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: Add delete public dashboard button in public dashboard modal (#58095)
- Delete public dashboard button added in public dashboard modal - Delete public dashboard button refactored in order to be used in audit table and public dashboard modal - Tests added - RTK Query api modified, in order to keep cached data because of having to show public dashboard modal once delete modal is closed. - RTK Query specific cached data invalidated for public dashboard - Save button text changed: Create public dashboard when it was never created. Save public dashboard when there's a public dashboard already created - Public Dashboard modal subscribed to DashboardModel metadata changes
This commit is contained in:
parent
ae30a0688a
commit
8f6cdd4cda
@ -188,6 +188,7 @@ export const Pages = {
|
|||||||
EnableSwitch: 'data-testid public dashboard on off switch',
|
EnableSwitch: 'data-testid public dashboard on off switch',
|
||||||
EnableAnnotationsSwitch: 'data-testid public dashboard on off switch for annotations',
|
EnableAnnotationsSwitch: 'data-testid public dashboard on off switch for annotations',
|
||||||
SaveConfigButton: 'data-testid public dashboard save config button',
|
SaveConfigButton: 'data-testid public dashboard save config button',
|
||||||
|
DeleteButton: 'data-testid public dashboard delete button',
|
||||||
CopyUrlInput: 'data-testid public dashboard copy url input',
|
CopyUrlInput: 'data-testid public dashboard copy url input',
|
||||||
CopyUrlButton: 'data-testid public dashboard copy url button',
|
CopyUrlButton: 'data-testid public dashboard copy url button',
|
||||||
TemplateVariablesWarningAlert: 'data-testid public dashboard disabled template variables alert',
|
TemplateVariablesWarningAlert: 'data-testid public dashboard disabled template variables alert',
|
||||||
|
@ -36,7 +36,7 @@ export const publicDashboardApi = createApi({
|
|||||||
reducerPath: 'publicDashboardApi',
|
reducerPath: 'publicDashboardApi',
|
||||||
baseQuery: retry(backendSrvBaseQuery({ baseUrl: '/api/dashboards' }), { maxRetries: 0 }),
|
baseQuery: retry(backendSrvBaseQuery({ baseUrl: '/api/dashboards' }), { maxRetries: 0 }),
|
||||||
tagTypes: ['PublicDashboard', 'AuditTablePublicDashboard'],
|
tagTypes: ['PublicDashboard', 'AuditTablePublicDashboard'],
|
||||||
keepUnusedDataFor: 0,
|
refetchOnMountOrArgChange: true,
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getPublicDashboard: builder.query<PublicDashboard, string>({
|
getPublicDashboard: builder.query<PublicDashboard, string>({
|
||||||
query: (dashboardUid) => ({
|
query: (dashboardUid) => ({
|
||||||
@ -53,7 +53,7 @@ export const publicDashboardApi = createApi({
|
|||||||
dispatch(notifyApp(createErrorNotification(customError?.error?.data?.message)));
|
dispatch(notifyApp(createErrorNotification(customError?.error?.data?.message)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
providesTags: ['PublicDashboard'],
|
providesTags: (result, error, dashboardUid) => [{ type: 'PublicDashboard', id: dashboardUid }],
|
||||||
}),
|
}),
|
||||||
createPublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
createPublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||||
query: (params) => ({
|
query: (params) => ({
|
||||||
@ -72,7 +72,7 @@ export const publicDashboardApi = createApi({
|
|||||||
publicDashboardEnabled: data.isEnabled,
|
publicDashboardEnabled: data.isEnabled,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
invalidatesTags: ['PublicDashboard'],
|
invalidatesTags: (result, error, { payload }) => [{ type: 'PublicDashboard', id: payload.dashboardUid }],
|
||||||
}),
|
}),
|
||||||
updatePublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
updatePublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||||
query: (params) => ({
|
query: (params) => ({
|
||||||
@ -92,7 +92,7 @@ export const publicDashboardApi = createApi({
|
|||||||
publicDashboardEnabled: data.isEnabled,
|
publicDashboardEnabled: data.isEnabled,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
invalidatesTags: ['PublicDashboard'],
|
invalidatesTags: (result, error, { payload }) => [{ type: 'PublicDashboard', id: payload.dashboardUid }],
|
||||||
}),
|
}),
|
||||||
listPublicDashboards: builder.query<ListPublicDashboardResponse[], void>({
|
listPublicDashboards: builder.query<ListPublicDashboardResponse[], void>({
|
||||||
query: () => ({
|
query: () => ({
|
||||||
@ -100,25 +100,25 @@ export const publicDashboardApi = createApi({
|
|||||||
}),
|
}),
|
||||||
providesTags: ['AuditTablePublicDashboard'],
|
providesTags: ['AuditTablePublicDashboard'],
|
||||||
}),
|
}),
|
||||||
deletePublicDashboard: builder.mutation<void, { dashboardTitle: string; dashboardUid: string; uid: string }>({
|
deletePublicDashboard: builder.mutation<void, { dashboard?: DashboardModel; dashboardUid: string; uid: string }>({
|
||||||
query: (params) => ({
|
query: (params) => ({
|
||||||
url: `/uid/${params.dashboardUid}/public-dashboards/${params.uid}`,
|
url: `/uid/${params.dashboardUid}/public-dashboards/${params.uid}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
}),
|
}),
|
||||||
async onQueryStarted({ dashboardTitle }, { dispatch, queryFulfilled }) {
|
async onQueryStarted({ dashboard, uid }, { dispatch, queryFulfilled }) {
|
||||||
await queryFulfilled;
|
await queryFulfilled;
|
||||||
dispatch(
|
dispatch(notifyApp(createSuccessNotification('Public dashboard deleted!')));
|
||||||
notifyApp(
|
|
||||||
createSuccessNotification(
|
dashboard?.updateMeta({
|
||||||
'Public dashboard deleted',
|
hasPublicDashboard: false,
|
||||||
!!dashboardTitle
|
publicDashboardUid: uid,
|
||||||
? `Public dashboard for ${dashboardTitle} has been deleted`
|
publicDashboardEnabled: false,
|
||||||
: `Public dashboard has been deleted`
|
});
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
invalidatesTags: ['AuditTablePublicDashboard'],
|
invalidatesTags: (result, error, { dashboardUid }) => [
|
||||||
|
{ type: 'PublicDashboard', id: dashboardUid },
|
||||||
|
'AuditTablePublicDashboard',
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -100,7 +100,6 @@ describe('SharePublic', () => {
|
|||||||
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
||||||
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
|
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders share panel when public dashboards feature is enabled', async () => {
|
it('renders share panel when public dashboards feature is enabled', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
|
||||||
@ -110,8 +109,23 @@ describe('SharePublic', () => {
|
|||||||
fireEvent.click(screen.getByText('Public dashboard'));
|
fireEvent.click(screen.getByText('Public dashboard'));
|
||||||
|
|
||||||
await screen.findByText('Welcome to Grafana public dashboards alpha!');
|
await screen.findByText('Welcome to Grafana public dashboards alpha!');
|
||||||
|
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
it('renders public dashboard modal without delete button because no public dashboard was already created', async () => {
|
||||||
|
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
|
||||||
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
|
||||||
|
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
||||||
|
expect(screen.getByRole('tablist')).toHaveTextContent('Public dashboard');
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Public dashboard'));
|
||||||
|
|
||||||
|
await screen.findByText('Welcome to Grafana public dashboards alpha!');
|
||||||
|
|
||||||
|
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
it('renders default relative time in input', async () => {
|
it('renders default relative time in input', async () => {
|
||||||
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
|
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
|
||||||
|
|
||||||
@ -137,13 +151,15 @@ describe('SharePublic', () => {
|
|||||||
mockDashboard.meta.hasPublicDashboard = true;
|
mockDashboard.meta.hasPublicDashboard = true;
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
|
||||||
expect(await screen.findByTestId('Spinner')).toBeInTheDocument();
|
screen.getAllByTestId('Spinner');
|
||||||
|
|
||||||
|
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeDisabled();
|
expect(screen.getByTestId(selectors.EnableSwitch)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('when fetch errors happen, then all inputs remain disabled', async () => {
|
it('when fetch errors happen, then all inputs remain disabled', async () => {
|
||||||
mockDashboard.meta.hasPublicDashboard = true;
|
mockDashboard.meta.hasPublicDashboard = true;
|
||||||
@ -154,14 +170,16 @@ describe('SharePublic', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeDisabled();
|
expect(screen.getByTestId(selectors.EnableSwitch)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeDisabled();
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeDisabled();
|
||||||
|
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
// test checking if current version of dashboard in state is persisted to db
|
// test checking if current version of dashboard in state is persisted to db
|
||||||
});
|
});
|
||||||
@ -183,7 +201,9 @@ describe('SharePublic - New config setup', () => {
|
|||||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeEnabled();
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeEnabled();
|
||||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeEnabled();
|
expect(screen.getByTestId(selectors.EnableSwitch)).toBeEnabled();
|
||||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||||
});
|
});
|
||||||
it('when checkboxes are filled, then save button remains disabled', async () => {
|
it('when checkboxes are filled, then save button remains disabled', async () => {
|
||||||
@ -194,6 +214,7 @@ describe('SharePublic - New config setup', () => {
|
|||||||
fireEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
|
fireEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
|
||||||
fireEvent.click(screen.getByTestId(selectors.CostIncreaseCheckbox));
|
fireEvent.click(screen.getByTestId(selectors.CostIncreaseCheckbox));
|
||||||
|
|
||||||
|
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||||
});
|
});
|
||||||
it('when checkboxes and switch are filled, then save button is enabled', async () => {
|
it('when checkboxes and switch are filled, then save button is enabled', async () => {
|
||||||
@ -207,6 +228,10 @@ describe('SharePublic - New config setup', () => {
|
|||||||
|
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
||||||
});
|
});
|
||||||
|
it('when hasPublicDashboard flag is false, then button text is Create', async () => {
|
||||||
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('SharePublic - Already persisted', () => {
|
describe('SharePublic - Already persisted', () => {
|
||||||
@ -228,33 +253,44 @@ describe('SharePublic - Already persisted', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when modal is opened, then save button is enabled', async () => {
|
it('when modal is opened, then save button and delete button are enabled', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
|
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
|
||||||
|
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
||||||
});
|
});
|
||||||
|
it('delete button is not rendered because lack of permissions', async () => {
|
||||||
|
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
|
||||||
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
|
expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
it('when modal is opened, then annotations toggle is enabled and checked when its enabled in the db', async () => {
|
it('when modal is opened, then annotations toggle is enabled and checked when its enabled in the db', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
|
||||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();
|
||||||
});
|
});
|
||||||
it('when fetch is done, then loader spinner is gone, inputs are disabled and save button is enabled', async () => {
|
it('when fetch is done, then loader spinner is gone, inputs are disabled and save button is enabled', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
||||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||||
|
|
||||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeEnabled();
|
expect(screen.getByTestId(selectors.EnableSwitch)).toBeEnabled();
|
||||||
|
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
||||||
|
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
|
||||||
});
|
});
|
||||||
it('when pubdash is enabled, then link url is available', async () => {
|
it('when pubdash is enabled, then link url is available', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
expect(screen.getByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
|
expect(screen.getByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('when pubdash is disabled in the db, then link url is not available and annotations toggle is disabled', async () => {
|
it('when pubdash is disabled in the db, then link url is not available and annotations toggle is disabled', async () => {
|
||||||
@ -274,16 +310,21 @@ describe('SharePublic - Already persisted', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
expect(screen.queryByTestId(selectors.CopyUrlInput)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(selectors.CopyUrlInput)).not.toBeInTheDocument();
|
||||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).not.toBeChecked();
|
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).not.toBeChecked();
|
||||||
});
|
});
|
||||||
it('when pubdash is disabled by the user, then link url is not available', async () => {
|
it('when pubdash is disabled by the user, then link url is not available', async () => {
|
||||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId(selectors.EnableSwitch));
|
fireEvent.click(screen.getByTestId(selectors.EnableSwitch));
|
||||||
expect(screen.queryByTestId(selectors.CopyUrlInput)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(selectors.CopyUrlInput)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
it('when hasPublicDashboard flag is true, then button text is Save', async () => {
|
||||||
|
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||||
|
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||||
|
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,23 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||||
import { reportInteraction } from '@grafana/runtime/src';
|
import { reportInteraction } from '@grafana/runtime/src';
|
||||||
import { Alert, Button, ClipboardButton, Field, HorizontalGroup, Input, useStyles2, Spinner } from '@grafana/ui/src';
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
ClipboardButton,
|
||||||
|
Field,
|
||||||
|
HorizontalGroup,
|
||||||
|
Input,
|
||||||
|
useStyles2,
|
||||||
|
Spinner,
|
||||||
|
ModalsContext,
|
||||||
|
useForceUpdate,
|
||||||
|
} from '@grafana/ui/src';
|
||||||
|
import { Layout } from '@grafana/ui/src/components/Layout/Layout';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import {
|
import {
|
||||||
useGetPublicDashboardQuery,
|
useGetPublicDashboardQuery,
|
||||||
@ -21,22 +34,31 @@ import {
|
|||||||
publicDashboardPersisted,
|
publicDashboardPersisted,
|
||||||
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||||
import { ShareModalTabProps } from 'app/features/dashboard/components/ShareModal/types';
|
import { ShareModalTabProps } from 'app/features/dashboard/components/ShareModal/types';
|
||||||
|
import { useIsDesktop } from 'app/features/dashboard/utils/screen';
|
||||||
|
import { DeletePublicDashboardButton } from 'app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton';
|
||||||
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
|
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
|
import { DashboardMetaChangedEvent } from '../../../../../types/events';
|
||||||
|
import { ShareModal } from '../ShareModal';
|
||||||
|
|
||||||
interface Props extends ShareModalTabProps {}
|
interface Props extends ShareModalTabProps {}
|
||||||
|
|
||||||
export const SharePublicDashboard = (props: Props) => {
|
export const SharePublicDashboard = (props: Props) => {
|
||||||
|
const forceUpdate = useForceUpdate();
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const { showModal, hideModal } = useContext(ModalsContext);
|
||||||
|
const isDesktop = useIsDesktop();
|
||||||
|
|
||||||
const dashboardVariables = props.dashboard.getVariables();
|
const dashboardVariables = props.dashboard.getVariables();
|
||||||
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||||
const styles = useStyles2(getStyles);
|
const { hasPublicDashboard } = props.dashboard.meta;
|
||||||
|
|
||||||
const [hasPublicDashboard, setHasPublicDashboard] = useState(props.dashboard.meta.hasPublicDashboard);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isFetchingLoading,
|
isLoading: isGetLoading,
|
||||||
data: publicDashboard,
|
data: publicDashboard,
|
||||||
isError: isFetchingError,
|
isError: isGetError,
|
||||||
|
isFetching,
|
||||||
} = useGetPublicDashboardQuery(props.dashboard.uid, {
|
} = useGetPublicDashboardQuery(props.dashboard.uid, {
|
||||||
// if we don't have a public dashboard, don't try to load public dashboard
|
// if we don't have a public dashboard, don't try to load public dashboard
|
||||||
skip: !hasPublicDashboard,
|
skip: !hasPublicDashboard,
|
||||||
@ -57,8 +79,12 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
const [annotationsEnabled, setAnnotationsEnabled] = useState(false);
|
const [annotationsEnabled, setAnnotationsEnabled] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const eventSubs = new Subscription();
|
||||||
|
eventSubs.add(props.dashboard.events.subscribe(DashboardMetaChangedEvent, forceUpdate));
|
||||||
reportInteraction('grafana_dashboards_public_share_viewed');
|
reportInteraction('grafana_dashboards_public_share_viewed');
|
||||||
}, []);
|
|
||||||
|
return () => eventSubs.unsubscribe();
|
||||||
|
}, [props.dashboard.events, forceUpdate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (publicDashboardPersisted(publicDashboard)) {
|
if (publicDashboardPersisted(publicDashboard)) {
|
||||||
@ -73,19 +99,30 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
|
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
|
||||||
}, [publicDashboard]);
|
}, [publicDashboard]);
|
||||||
|
|
||||||
const isLoading = isFetchingLoading || isSaveLoading || isUpdateLoading;
|
const isLoading = isGetLoading || isSaveLoading || isUpdateLoading;
|
||||||
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
|
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
|
||||||
const acknowledged = acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
|
const acknowledged = acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
|
||||||
const isSaveEnabled = useMemo(
|
const isSaveDisabled = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!hasWritePermissions ||
|
!hasWritePermissions ||
|
||||||
!acknowledged ||
|
!acknowledged ||
|
||||||
props.dashboard.hasUnsavedChanges() ||
|
props.dashboard.hasUnsavedChanges() ||
|
||||||
isLoading ||
|
isLoading ||
|
||||||
isFetchingError ||
|
isFetching ||
|
||||||
|
isGetError ||
|
||||||
(!publicDashboardPersisted(publicDashboard) && !enabledSwitch.wasTouched),
|
(!publicDashboardPersisted(publicDashboard) && !enabledSwitch.wasTouched),
|
||||||
[hasWritePermissions, acknowledged, props.dashboard, isLoading, isFetchingError, enabledSwitch, publicDashboard]
|
[
|
||||||
|
hasWritePermissions,
|
||||||
|
acknowledged,
|
||||||
|
props.dashboard,
|
||||||
|
isLoading,
|
||||||
|
isGetError,
|
||||||
|
enabledSwitch,
|
||||||
|
publicDashboard,
|
||||||
|
isFetching,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
const isDeleteDisabled = isLoading || isFetching || isGetError;
|
||||||
|
|
||||||
const onSavePublicConfig = async () => {
|
const onSavePublicConfig = async () => {
|
||||||
reportInteraction('grafana_dashboards_public_create_clicked');
|
reportInteraction('grafana_dashboards_public_create_clicked');
|
||||||
@ -96,20 +133,21 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// create or update based on whether we have existing uid
|
// create or update based on whether we have existing uid
|
||||||
|
hasPublicDashboard ? updatePublicDashboard(req) : createPublicDashboard(req);
|
||||||
if (hasPublicDashboard) {
|
|
||||||
await updatePublicDashboard(req).unwrap();
|
|
||||||
setHasPublicDashboard(true);
|
|
||||||
} else {
|
|
||||||
await createPublicDashboard(req).unwrap();
|
|
||||||
setHasPublicDashboard(true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAcknowledge = (field: string, checked: boolean) => {
|
const onAcknowledge = (field: string, checked: boolean) => {
|
||||||
setAcknowledgements((prevState) => ({ ...prevState, [field]: checked }));
|
setAcknowledgements((prevState) => ({ ...prevState, [field]: checked }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDismissDelete = () => {
|
||||||
|
showModal(ShareModal, {
|
||||||
|
dashboard: props.dashboard,
|
||||||
|
onDismiss: hideModal,
|
||||||
|
activeTab: 'share',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
@ -120,7 +158,7 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
>
|
>
|
||||||
Welcome to Grafana public dashboards alpha!
|
Welcome to Grafana public dashboards alpha!
|
||||||
</p>
|
</p>
|
||||||
{isFetchingLoading && <Spinner />}
|
{(isGetLoading || isFetching) && <Spinner />}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{dashboardHasTemplateVariables(dashboardVariables) && !publicDashboardPersisted(publicDashboard) ? (
|
{dashboardHasTemplateVariables(dashboardVariables) && !publicDashboardPersisted(publicDashboard) ? (
|
||||||
@ -137,9 +175,7 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
<hr />
|
<hr />
|
||||||
<div className={styles.checkboxes}>
|
<div className={styles.checkboxes}>
|
||||||
<AcknowledgeCheckboxes
|
<AcknowledgeCheckboxes
|
||||||
disabled={
|
disabled={publicDashboardPersisted(publicDashboard) || !hasWritePermissions || isLoading || isGetError}
|
||||||
publicDashboardPersisted(publicDashboard) || !hasWritePermissions || isLoading || isFetchingError
|
|
||||||
}
|
|
||||||
acknowledgements={acknowledgements}
|
acknowledgements={acknowledgements}
|
||||||
onAcknowledge={onAcknowledge}
|
onAcknowledge={onAcknowledge}
|
||||||
/>
|
/>
|
||||||
@ -148,7 +184,7 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
<Configuration
|
<Configuration
|
||||||
isAnnotationsEnabled={annotationsEnabled}
|
isAnnotationsEnabled={annotationsEnabled}
|
||||||
dashboard={props.dashboard}
|
dashboard={props.dashboard}
|
||||||
disabled={!hasWritePermissions || isLoading || isFetchingError}
|
disabled={!hasWritePermissions || isLoading || isGetError}
|
||||||
isPubDashEnabled={enabledSwitch.isEnabled}
|
isPubDashEnabled={enabledSwitch.isEnabled}
|
||||||
onToggleEnabled={() =>
|
onToggleEnabled={() =>
|
||||||
setEnabledSwitch((prevState) => ({ isEnabled: !prevState.isEnabled, wasTouched: true }))
|
setEnabledSwitch((prevState) => ({ isEnabled: !prevState.isEnabled, wasTouched: true }))
|
||||||
@ -193,10 +229,28 @@ export const SharePublicDashboard = (props: Props) => {
|
|||||||
<Alert title="You don't have permissions to create or update a public dashboard" severity="warning" />
|
<Alert title="You don't have permissions to create or update a public dashboard" severity="warning" />
|
||||||
)}
|
)}
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<Button disabled={isSaveEnabled} onClick={onSavePublicConfig} data-testid={selectors.SaveConfigButton}>
|
<Layout orientation={isDesktop ? 0 : 1}>
|
||||||
Save sharing configuration
|
<Button disabled={isSaveDisabled} onClick={onSavePublicConfig} data-testid={selectors.SaveConfigButton}>
|
||||||
</Button>
|
{hasPublicDashboard ? 'Save public dashboard' : 'Create public dashboard'}
|
||||||
{isSaveLoading && <Spinner />}
|
</Button>
|
||||||
|
{publicDashboard && hasWritePermissions && (
|
||||||
|
<DeletePublicDashboardButton
|
||||||
|
disabled={isDeleteDisabled}
|
||||||
|
data-testid={selectors.DeleteButton}
|
||||||
|
onDismiss={onDismissDelete}
|
||||||
|
variant="destructive"
|
||||||
|
dashboard={props.dashboard}
|
||||||
|
publicDashboard={{
|
||||||
|
uid: publicDashboard.uid,
|
||||||
|
dashboardUid: props.dashboard.uid,
|
||||||
|
title: props.dashboard.title,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete public dashboard
|
||||||
|
</DeletePublicDashboardButton>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
{(isSaveLoading || isFetching) && <Spinner />}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -1,47 +1,60 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
import { Button, ModalsController, ButtonProps } from '@grafana/ui/src';
|
||||||
import { Button, ComponentSize, Icon, ModalsController, Spinner } from '@grafana/ui/src';
|
import { useDeletePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi';
|
||||||
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
import { useDeletePublicDashboardMutation } from '../../../dashboard/api/publicDashboardApi';
|
|
||||||
import { ListPublicDashboardResponse } from '../../types';
|
|
||||||
|
|
||||||
import { DeletePublicDashboardModal } from './DeletePublicDashboardModal';
|
import { DeletePublicDashboardModal } from './DeletePublicDashboardModal';
|
||||||
|
|
||||||
|
export interface PublicDashboardDeletion {
|
||||||
|
uid: string;
|
||||||
|
dashboardUid: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const DeletePublicDashboardButton = ({
|
export const DeletePublicDashboardButton = ({
|
||||||
|
dashboard,
|
||||||
publicDashboard,
|
publicDashboard,
|
||||||
size,
|
loader,
|
||||||
|
children,
|
||||||
|
onDismiss,
|
||||||
|
...rest
|
||||||
}: {
|
}: {
|
||||||
publicDashboard: ListPublicDashboardResponse;
|
dashboard?: DashboardModel;
|
||||||
size: ComponentSize;
|
publicDashboard: PublicDashboardDeletion;
|
||||||
}) => {
|
loader?: JSX.Element;
|
||||||
|
children: React.ReactNode;
|
||||||
|
onDismiss?: () => void;
|
||||||
|
} & ButtonProps) => {
|
||||||
const [deletePublicDashboard, { isLoading }] = useDeletePublicDashboardMutation();
|
const [deletePublicDashboard, { isLoading }] = useDeletePublicDashboardMutation();
|
||||||
|
|
||||||
const onDeletePublicDashboardClick = (pd: ListPublicDashboardResponse, onDelete: () => void) => {
|
const onDeletePublicDashboardClick = (pd: PublicDashboardDeletion, onDelete: () => void) => {
|
||||||
deletePublicDashboard({ uid: pd.uid, dashboardUid: pd.dashboardUid, dashboardTitle: pd.title });
|
deletePublicDashboard({
|
||||||
|
dashboard,
|
||||||
|
uid: pd.uid,
|
||||||
|
dashboardUid: pd.dashboardUid,
|
||||||
|
});
|
||||||
onDelete();
|
onDelete();
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectors = e2eSelectors.pages.PublicDashboards;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalsController>
|
<ModalsController>
|
||||||
{({ showModal, hideModal }) => (
|
{({ showModal, hideModal }) => (
|
||||||
<Button
|
<Button
|
||||||
fill="text"
|
|
||||||
aria-label="Delete public dashboard"
|
aria-label="Delete public dashboard"
|
||||||
title="Delete public dashboard"
|
title="Delete public dashboard"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
showModal(DeletePublicDashboardModal, {
|
showModal(DeletePublicDashboardModal, {
|
||||||
dashboardTitle: publicDashboard.title,
|
dashboardTitle: publicDashboard.title,
|
||||||
onConfirm: () => onDeletePublicDashboardClick(publicDashboard, hideModal),
|
onConfirm: () => onDeletePublicDashboardClick(publicDashboard, hideModal),
|
||||||
onDismiss: hideModal,
|
onDismiss: () => {
|
||||||
|
onDismiss ? onDismiss() : hideModal();
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
data-testid={selectors.ListItem.trashcanButton}
|
{...rest}
|
||||||
size={size}
|
|
||||||
>
|
>
|
||||||
{isLoading ? <Spinner /> : <Icon size={size} name="trash-alt" />}
|
{isLoading && loader ? loader : children}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ModalsController>
|
</ModalsController>
|
||||||
|
@ -12,7 +12,7 @@ const Body = ({ title }: { title?: string }) => {
|
|||||||
<p className={styles.title}>Do you want to delete this public dashboard?</p>
|
<p className={styles.title}>Do you want to delete this public dashboard?</p>
|
||||||
<p className={styles.description}>
|
<p className={styles.description}>
|
||||||
{title
|
{title
|
||||||
? `This will delete the public dashboard for ${title}. Your dashboard will not be deleted.`
|
? `This will delete the public dashboard for "${title}". Your dashboard will not be deleted.`
|
||||||
: 'Orphaned public dashboard will be deleted'}
|
: 'Orphaned public dashboard will be deleted'}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
|
@ -94,7 +94,15 @@ export const PublicDashboardListTable = () => {
|
|||||||
<Icon size={responsiveSize} name="cog" />
|
<Icon size={responsiveSize} name="cog" />
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
{hasWritePermissions && (
|
{hasWritePermissions && (
|
||||||
<DeletePublicDashboardButton publicDashboard={pd} size={responsiveSize} />
|
<DeletePublicDashboardButton
|
||||||
|
variant="primary"
|
||||||
|
fill="text"
|
||||||
|
data-testid={selectors.ListItem.trashcanButton}
|
||||||
|
publicDashboard={pd}
|
||||||
|
loader={<Spinner />}
|
||||||
|
>
|
||||||
|
<Icon size={responsiveSize} name="trash-alt" />
|
||||||
|
</DeletePublicDashboardButton>
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</td>
|
</td>
|
||||||
@ -134,9 +142,10 @@ function getStyles(theme: GrafanaTheme2, isMobile: boolean) {
|
|||||||
orphanedTitle: css`
|
orphanedTitle: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: ${theme.spacing(1)};
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: ${theme.spacing(0, 1, 0, 0)};
|
margin: ${theme.spacing(0)};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
Loading…
Reference in New Issue
Block a user