From 8f6cdd4cda702cf4e161dd8dd16e26f5a9bb41fc Mon Sep 17 00:00:00 2001 From: juanicabanas Date: Fri, 4 Nov 2022 15:08:50 -0300 Subject: [PATCH] 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 --- .../src/selectors/pages.ts | 1 + .../dashboard/api/publicDashboardApi.ts | 34 +++--- .../SharePublicDashboard.test.tsx | 61 ++++++++-- .../SharePublicDashboard.tsx | 112 +++++++++++++----- .../DeletePublicDashboardButton.tsx | 49 +++++--- .../DeletePublicDashboardModal.tsx | 2 +- .../PublicDashboardListTable.tsx | 13 +- 7 files changed, 195 insertions(+), 77 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 9b968cb284d..4bdc8923d45 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -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', diff --git a/public/app/features/dashboard/api/publicDashboardApi.ts b/public/app/features/dashboard/api/publicDashboardApi.ts index 460739f7581..4249547a967 100644 --- a/public/app/features/dashboard/api/publicDashboardApi.ts +++ b/public/app/features/dashboard/api/publicDashboardApi.ts @@ -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({ 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({ 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({ 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({ query: () => ({ @@ -100,25 +100,25 @@ export const publicDashboardApi = createApi({ }), providesTags: ['AuditTablePublicDashboard'], }), - deletePublicDashboard: builder.mutation({ + deletePublicDashboard: builder.mutation({ 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', + ], }), }), }); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx index 15cdbec7a7d..fa0eb33020e 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx @@ -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(); + }); }); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.tsx index 4af0c3d7ff8..cb5972ba1c3 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.tsx @@ -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 ( <> @@ -120,7 +158,7 @@ export const SharePublicDashboard = (props: Props) => { > Welcome to Grafana public dashboards alpha!

- {isFetchingLoading && } + {(isGetLoading || isFetching) && }
{dashboardHasTemplateVariables(dashboardVariables) && !publicDashboardPersisted(publicDashboard) ? ( @@ -137,9 +175,7 @@ export const SharePublicDashboard = (props: Props) => {
@@ -148,7 +184,7 @@ export const SharePublicDashboard = (props: Props) => { setEnabledSwitch((prevState) => ({ isEnabled: !prevState.isEnabled, wasTouched: true })) @@ -193,10 +229,28 @@ export const SharePublicDashboard = (props: Props) => { )} - - {isSaveLoading && } + + + {publicDashboard && hasWritePermissions && ( + + Delete public dashboard + + )} + + {(isSaveLoading || isFetching) && } )} diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx index 837098f1a4c..125aebe4d3e 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx @@ -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 ( {({ showModal, hideModal }) => ( )} diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx index aa5dacc86e1..bd2ef614546 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal.tsx @@ -12,7 +12,7 @@ const Body = ({ title }: { title?: string }) => {

Do you want to delete this public dashboard?

{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'}

diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx index 2b624e53a1d..bd5416f730a 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx @@ -94,7 +94,15 @@ export const PublicDashboardListTable = () => { {hasWritePermissions && ( - + } + > + + )} @@ -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;