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',
|
||||
EnableAnnotationsSwitch: 'data-testid public dashboard on off switch for annotations',
|
||||
SaveConfigButton: 'data-testid public dashboard save config button',
|
||||
DeleteButton: 'data-testid public dashboard delete button',
|
||||
CopyUrlInput: 'data-testid public dashboard copy url input',
|
||||
CopyUrlButton: 'data-testid public dashboard copy url button',
|
||||
TemplateVariablesWarningAlert: 'data-testid public dashboard disabled template variables alert',
|
||||
|
@ -36,7 +36,7 @@ export const publicDashboardApi = createApi({
|
||||
reducerPath: 'publicDashboardApi',
|
||||
baseQuery: retry(backendSrvBaseQuery({ baseUrl: '/api/dashboards' }), { maxRetries: 0 }),
|
||||
tagTypes: ['PublicDashboard', 'AuditTablePublicDashboard'],
|
||||
keepUnusedDataFor: 0,
|
||||
refetchOnMountOrArgChange: true,
|
||||
endpoints: (builder) => ({
|
||||
getPublicDashboard: builder.query<PublicDashboard, string>({
|
||||
query: (dashboardUid) => ({
|
||||
@ -53,7 +53,7 @@ export const publicDashboardApi = createApi({
|
||||
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 }>({
|
||||
query: (params) => ({
|
||||
@ -72,7 +72,7 @@ export const publicDashboardApi = createApi({
|
||||
publicDashboardEnabled: data.isEnabled,
|
||||
});
|
||||
},
|
||||
invalidatesTags: ['PublicDashboard'],
|
||||
invalidatesTags: (result, error, { payload }) => [{ type: 'PublicDashboard', id: payload.dashboardUid }],
|
||||
}),
|
||||
updatePublicDashboard: builder.mutation<PublicDashboard, { dashboard: DashboardModel; payload: PublicDashboard }>({
|
||||
query: (params) => ({
|
||||
@ -92,7 +92,7 @@ export const publicDashboardApi = createApi({
|
||||
publicDashboardEnabled: data.isEnabled,
|
||||
});
|
||||
},
|
||||
invalidatesTags: ['PublicDashboard'],
|
||||
invalidatesTags: (result, error, { payload }) => [{ type: 'PublicDashboard', id: payload.dashboardUid }],
|
||||
}),
|
||||
listPublicDashboards: builder.query<ListPublicDashboardResponse[], void>({
|
||||
query: () => ({
|
||||
@ -100,25 +100,25 @@ export const publicDashboardApi = createApi({
|
||||
}),
|
||||
providesTags: ['AuditTablePublicDashboard'],
|
||||
}),
|
||||
deletePublicDashboard: builder.mutation<void, { dashboardTitle: string; dashboardUid: string; uid: string }>({
|
||||
deletePublicDashboard: builder.mutation<void, { dashboard?: DashboardModel; dashboardUid: string; uid: string }>({
|
||||
query: (params) => ({
|
||||
url: `/uid/${params.dashboardUid}/public-dashboards/${params.uid}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
async onQueryStarted({ dashboardTitle }, { dispatch, queryFulfilled }) {
|
||||
async onQueryStarted({ dashboard, uid }, { dispatch, queryFulfilled }) {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
notifyApp(
|
||||
createSuccessNotification(
|
||||
'Public dashboard deleted',
|
||||
!!dashboardTitle
|
||||
? `Public dashboard for ${dashboardTitle} has been deleted`
|
||||
: `Public dashboard has been deleted`
|
||||
)
|
||||
)
|
||||
);
|
||||
dispatch(notifyApp(createSuccessNotification('Public dashboard deleted!')));
|
||||
|
||||
dashboard?.updateMeta({
|
||||
hasPublicDashboard: false,
|
||||
publicDashboardUid: uid,
|
||||
publicDashboardEnabled: false,
|
||||
});
|
||||
},
|
||||
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')).not.toHaveTextContent('Public dashboard');
|
||||
});
|
||||
|
||||
it('renders share panel when public dashboards feature is enabled', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
|
||||
@ -110,8 +109,23 @@ describe('SharePublic', () => {
|
||||
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 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 () => {
|
||||
expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });
|
||||
|
||||
@ -137,13 +151,15 @@ describe('SharePublic', () => {
|
||||
mockDashboard.meta.hasPublicDashboard = true;
|
||||
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.LimitedDSCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.EnableSwitch)).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 () => {
|
||||
mockDashboard.meta.hasPublicDashboard = true;
|
||||
@ -154,14 +170,16 @@ describe('SharePublic', () => {
|
||||
);
|
||||
|
||||
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.LimitedDSCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeDisabled();
|
||||
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||
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
|
||||
});
|
||||
@ -183,7 +201,9 @@ describe('SharePublic - New config setup', () => {
|
||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeEnabled();
|
||||
expect(screen.getByTestId(selectors.EnableSwitch)).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();
|
||||
});
|
||||
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.CostIncreaseCheckbox));
|
||||
|
||||
expect(screen.getByText('Create public dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeDisabled();
|
||||
});
|
||||
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();
|
||||
});
|
||||
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', () => {
|
||||
@ -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 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();
|
||||
});
|
||||
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 () => {
|
||||
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)).toBeChecked();
|
||||
});
|
||||
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 waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||
|
||||
expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
|
||||
expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
|
||||
|
||||
expect(screen.getByTestId(selectors.EnableSwitch)).toBeEnabled();
|
||||
expect(screen.getByText('Save public dashboard')).toBeInTheDocument();
|
||||
expect(screen.getByTestId(selectors.SaveConfigButton)).toBeEnabled();
|
||||
expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
|
||||
});
|
||||
it('when pubdash is enabled, then link url is available', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||
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 () => {
|
||||
@ -274,16 +310,21 @@ describe('SharePublic - Already persisted', () => {
|
||||
);
|
||||
|
||||
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.getByTestId(selectors.EnableAnnotationsSwitch)).not.toBeChecked();
|
||||
});
|
||||
it('when pubdash is disabled by the user, then link url is not available', async () => {
|
||||
await renderSharePublicDashboard({ panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {} });
|
||||
await waitForElementToBeRemoved(screen.getByTestId('Spinner'));
|
||||
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
|
||||
|
||||
fireEvent.click(screen.getByTestId(selectors.EnableSwitch));
|
||||
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 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 { selectors as e2eSelectors } from '@grafana/e2e-selectors/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 {
|
||||
useGetPublicDashboardQuery,
|
||||
@ -21,22 +34,31 @@ import {
|
||||
publicDashboardPersisted,
|
||||
} from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
|
||||
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 { AccessControlAction } from 'app/types';
|
||||
|
||||
import { DashboardMetaChangedEvent } from '../../../../../types/events';
|
||||
import { ShareModal } from '../ShareModal';
|
||||
|
||||
interface Props extends ShareModalTabProps {}
|
||||
|
||||
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 selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const [hasPublicDashboard, setHasPublicDashboard] = useState(props.dashboard.meta.hasPublicDashboard);
|
||||
const { hasPublicDashboard } = props.dashboard.meta;
|
||||
|
||||
const {
|
||||
isLoading: isFetchingLoading,
|
||||
isLoading: isGetLoading,
|
||||
data: publicDashboard,
|
||||
isError: isFetchingError,
|
||||
isError: isGetError,
|
||||
isFetching,
|
||||
} = useGetPublicDashboardQuery(props.dashboard.uid, {
|
||||
// if we don't have a public dashboard, don't try to load public dashboard
|
||||
skip: !hasPublicDashboard,
|
||||
@ -57,8 +79,12 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
const [annotationsEnabled, setAnnotationsEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const eventSubs = new Subscription();
|
||||
eventSubs.add(props.dashboard.events.subscribe(DashboardMetaChangedEvent, forceUpdate));
|
||||
reportInteraction('grafana_dashboards_public_share_viewed');
|
||||
}, []);
|
||||
|
||||
return () => eventSubs.unsubscribe();
|
||||
}, [props.dashboard.events, forceUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (publicDashboardPersisted(publicDashboard)) {
|
||||
@ -73,19 +99,30 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
setEnabledSwitch((prevState) => ({ ...prevState, isEnabled: !!publicDashboard?.isEnabled }));
|
||||
}, [publicDashboard]);
|
||||
|
||||
const isLoading = isFetchingLoading || isSaveLoading || isUpdateLoading;
|
||||
const isLoading = isGetLoading || isSaveLoading || isUpdateLoading;
|
||||
const hasWritePermissions = contextSrv.hasAccess(AccessControlAction.DashboardsPublicWrite, isOrgAdmin());
|
||||
const acknowledged = acknowledgements.public && acknowledgements.datasources && acknowledgements.usage;
|
||||
const isSaveEnabled = useMemo(
|
||||
const isSaveDisabled = useMemo(
|
||||
() =>
|
||||
!hasWritePermissions ||
|
||||
!acknowledged ||
|
||||
props.dashboard.hasUnsavedChanges() ||
|
||||
isLoading ||
|
||||
isFetchingError ||
|
||||
isFetching ||
|
||||
isGetError ||
|
||||
(!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 () => {
|
||||
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
|
||||
|
||||
if (hasPublicDashboard) {
|
||||
await updatePublicDashboard(req).unwrap();
|
||||
setHasPublicDashboard(true);
|
||||
} else {
|
||||
await createPublicDashboard(req).unwrap();
|
||||
setHasPublicDashboard(true);
|
||||
}
|
||||
hasPublicDashboard ? updatePublicDashboard(req) : createPublicDashboard(req);
|
||||
};
|
||||
|
||||
const onAcknowledge = (field: string, checked: boolean) => {
|
||||
setAcknowledgements((prevState) => ({ ...prevState, [field]: checked }));
|
||||
};
|
||||
|
||||
const onDismissDelete = () => {
|
||||
showModal(ShareModal, {
|
||||
dashboard: props.dashboard,
|
||||
onDismiss: hideModal,
|
||||
activeTab: 'share',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<HorizontalGroup>
|
||||
@ -120,7 +158,7 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
>
|
||||
Welcome to Grafana public dashboards alpha!
|
||||
</p>
|
||||
{isFetchingLoading && <Spinner />}
|
||||
{(isGetLoading || isFetching) && <Spinner />}
|
||||
</HorizontalGroup>
|
||||
<div className={styles.content}>
|
||||
{dashboardHasTemplateVariables(dashboardVariables) && !publicDashboardPersisted(publicDashboard) ? (
|
||||
@ -137,9 +175,7 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
<hr />
|
||||
<div className={styles.checkboxes}>
|
||||
<AcknowledgeCheckboxes
|
||||
disabled={
|
||||
publicDashboardPersisted(publicDashboard) || !hasWritePermissions || isLoading || isFetchingError
|
||||
}
|
||||
disabled={publicDashboardPersisted(publicDashboard) || !hasWritePermissions || isLoading || isGetError}
|
||||
acknowledgements={acknowledgements}
|
||||
onAcknowledge={onAcknowledge}
|
||||
/>
|
||||
@ -148,7 +184,7 @@ export const SharePublicDashboard = (props: Props) => {
|
||||
<Configuration
|
||||
isAnnotationsEnabled={annotationsEnabled}
|
||||
dashboard={props.dashboard}
|
||||
disabled={!hasWritePermissions || isLoading || isFetchingError}
|
||||
disabled={!hasWritePermissions || isLoading || isGetError}
|
||||
isPubDashEnabled={enabledSwitch.isEnabled}
|
||||
onToggleEnabled={() =>
|
||||
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" />
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
<Button disabled={isSaveEnabled} onClick={onSavePublicConfig} data-testid={selectors.SaveConfigButton}>
|
||||
Save sharing configuration
|
||||
</Button>
|
||||
{isSaveLoading && <Spinner />}
|
||||
<Layout orientation={isDesktop ? 0 : 1}>
|
||||
<Button disabled={isSaveDisabled} onClick={onSavePublicConfig} data-testid={selectors.SaveConfigButton}>
|
||||
{hasPublicDashboard ? 'Save public dashboard' : 'Create public dashboard'}
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
|
@ -1,47 +1,60 @@
|
||||
import React from 'react';
|
||||
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||
import { Button, ComponentSize, Icon, ModalsController, Spinner } from '@grafana/ui/src';
|
||||
|
||||
import { useDeletePublicDashboardMutation } from '../../../dashboard/api/publicDashboardApi';
|
||||
import { ListPublicDashboardResponse } from '../../types';
|
||||
import { Button, ModalsController, ButtonProps } from '@grafana/ui/src';
|
||||
import { useDeletePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
|
||||
import { DeletePublicDashboardModal } from './DeletePublicDashboardModal';
|
||||
|
||||
export interface PublicDashboardDeletion {
|
||||
uid: string;
|
||||
dashboardUid: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const DeletePublicDashboardButton = ({
|
||||
dashboard,
|
||||
publicDashboard,
|
||||
size,
|
||||
loader,
|
||||
children,
|
||||
onDismiss,
|
||||
...rest
|
||||
}: {
|
||||
publicDashboard: ListPublicDashboardResponse;
|
||||
size: ComponentSize;
|
||||
}) => {
|
||||
dashboard?: DashboardModel;
|
||||
publicDashboard: PublicDashboardDeletion;
|
||||
loader?: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
onDismiss?: () => void;
|
||||
} & ButtonProps) => {
|
||||
const [deletePublicDashboard, { isLoading }] = useDeletePublicDashboardMutation();
|
||||
|
||||
const onDeletePublicDashboardClick = (pd: ListPublicDashboardResponse, onDelete: () => void) => {
|
||||
deletePublicDashboard({ uid: pd.uid, dashboardUid: pd.dashboardUid, dashboardTitle: pd.title });
|
||||
const onDeletePublicDashboardClick = (pd: PublicDashboardDeletion, onDelete: () => void) => {
|
||||
deletePublicDashboard({
|
||||
dashboard,
|
||||
uid: pd.uid,
|
||||
dashboardUid: pd.dashboardUid,
|
||||
});
|
||||
onDelete();
|
||||
};
|
||||
|
||||
const selectors = e2eSelectors.pages.PublicDashboards;
|
||||
|
||||
return (
|
||||
<ModalsController>
|
||||
{({ showModal, hideModal }) => (
|
||||
<Button
|
||||
fill="text"
|
||||
aria-label="Delete public dashboard"
|
||||
title="Delete public dashboard"
|
||||
onClick={() =>
|
||||
showModal(DeletePublicDashboardModal, {
|
||||
dashboardTitle: publicDashboard.title,
|
||||
onConfirm: () => onDeletePublicDashboardClick(publicDashboard, hideModal),
|
||||
onDismiss: hideModal,
|
||||
onDismiss: () => {
|
||||
onDismiss ? onDismiss() : hideModal();
|
||||
},
|
||||
})
|
||||
}
|
||||
data-testid={selectors.ListItem.trashcanButton}
|
||||
size={size}
|
||||
{...rest}
|
||||
>
|
||||
{isLoading ? <Spinner /> : <Icon size={size} name="trash-alt" />}
|
||||
{isLoading && loader ? loader : children}
|
||||
</Button>
|
||||
)}
|
||||
</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.description}>
|
||||
{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'}
|
||||
</p>
|
||||
</>
|
||||
|
@ -94,7 +94,15 @@ export const PublicDashboardListTable = () => {
|
||||
<Icon size={responsiveSize} name="cog" />
|
||||
</LinkButton>
|
||||
{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>
|
||||
</td>
|
||||
@ -134,9 +142,10 @@ function getStyles(theme: GrafanaTheme2, isMobile: boolean) {
|
||||
orphanedTitle: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${theme.spacing(1)};
|
||||
|
||||
p {
|
||||
margin: ${theme.spacing(0, 1, 0, 0)};
|
||||
margin: ${theme.spacing(0)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
Loading…
Reference in New Issue
Block a user