diff --git a/static/app/views/dashboards/controls.tsx b/static/app/views/dashboards/controls.tsx index 16441fd70732cd..af1799e81b08ba 100644 --- a/static/app/views/dashboards/controls.tsx +++ b/static/app/views/dashboards/controls.tsx @@ -4,14 +4,14 @@ import styled from '@emotion/styled'; import {updateDashboardFavorite} from 'sentry/actionCreators/dashboards'; import Feature from 'sentry/components/acl/feature'; import FeatureDisabled from 'sentry/components/acl/featureDisabled'; -import Confirm from 'sentry/components/confirm'; +import Confirm, {openConfirmModal} from 'sentry/components/confirm'; import {Button} from 'sentry/components/core/button'; import {ButtonBar} from 'sentry/components/core/button/buttonBar'; import {Tooltip} from 'sentry/components/core/tooltip'; import {DropdownMenu, type MenuItemProps} from 'sentry/components/dropdownMenu'; import {Hovercard} from 'sentry/components/hovercard'; import LoadingIndicator from 'sentry/components/loadingIndicator'; -import {IconAdd, IconDownload, IconEdit, IconStar} from 'sentry/icons'; +import {IconAdd, IconCopy, IconDownload, IconEdit, IconStar} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; @@ -19,12 +19,14 @@ import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useQueryClient} from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; +import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {useUser} from 'sentry/utils/useUser'; import {useUserTeams} from 'sentry/utils/useUserTeams'; import {DASHBOARD_SAVING_MESSAGE} from 'sentry/views/dashboards/constants'; import {DashboardCreateLimitWrapper} from 'sentry/views/dashboards/createLimitWrapper'; import EditAccessSelector from 'sentry/views/dashboards/editAccessSelector'; +import {useDuplicatePrebuiltDashboard} from 'sentry/views/dashboards/hooks/useDuplicateDashboard'; import {DataSet} from 'sentry/views/dashboards/widgetBuilder/utils'; import {checkUserHasEditAccess} from './utils/checkUserHasEditAccess'; @@ -84,6 +86,14 @@ function Controls({ const currentUser = useUser(); const {teams: userTeams} = useUserTeams(); const api = useApi(); + const navigate = useNavigate(); + + const {duplicatePrebuiltDashboard, isLoading: isLoadingDuplicatePrebuiltDashboard} = + useDuplicatePrebuiltDashboard({ + onSuccess: (newDashboard: DashboardDetails) => { + navigate(`/organizations/${organization.slug}/dashboard/${newDashboard.id}/`); + }, + }); const isPrebuiltDashboard = defined(dashboard.prebuiltId); @@ -297,7 +307,7 @@ function Controls({ )} {renderEditButton(hasFeature)} - {hasFeature && !isPrebuiltDashboard ? ( + {hasFeature && !isPrebuiltDashboard && ( - ) : null} + )} + {hasFeature && isPrebuiltDashboard && ( + + {({ + hasReachedDashboardLimit, + isLoading: isLoadingDashboardsLimit, + limitMessage, + }) => { + const isLoading = + isLoadingDuplicatePrebuiltDashboard || isLoadingDashboardsLimit; + return ( + + + + ); + }} + + )} )} diff --git a/static/app/views/dashboards/hooks/useDuplicateDashboard.tsx b/static/app/views/dashboards/hooks/useDuplicateDashboard.tsx index f586d39b8714bf..95738738dfbf50 100644 --- a/static/app/views/dashboards/hooks/useDuplicateDashboard.tsx +++ b/static/app/views/dashboards/hooks/useDuplicateDashboard.tsx @@ -1,4 +1,4 @@ -import {useCallback} from 'react'; +import {useCallback, useState} from 'react'; import {createDashboard, fetchDashboard} from 'sentry/actionCreators/dashboards'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; @@ -6,11 +6,15 @@ import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; -import type {DashboardListItem} from 'sentry/views/dashboards/types'; +import type {DashboardDetails, DashboardListItem} from 'sentry/views/dashboards/types'; import {cloneDashboard} from 'sentry/views/dashboards/utils'; +import { + PREBUILT_DASHBOARDS, + type PrebuiltDashboardId, +} from 'sentry/views/dashboards/utils/prebuiltConfigs'; interface UseDuplicateDashboardProps { - onSuccess: () => void; + onSuccess?: (copiedDashboard: DashboardDetails) => void; } export function useDuplicateDashboard({onSuccess}: UseDuplicateDashboardProps) { @@ -27,13 +31,18 @@ export function useDuplicateDashboard({onSuccess}: UseDuplicateDashboardProps) { ); const newDashboard = cloneDashboard(dashboardDetail); newDashboard.widgets.map(widget => (widget.id = undefined)); - await createDashboard(api, organization.slug, newDashboard, true); + const copiedDashboard = await createDashboard( + api, + organization.slug, + newDashboard, + true + ); trackAnalytics('dashboards_manage.duplicate', { organization, dashboard_id: parseInt(dashboard.id, 10), view_type: viewType, }); - onSuccess(); + onSuccess?.(copiedDashboard); addSuccessMessage(t('Dashboard duplicated')); } catch (e) { addErrorMessage(t('Error duplicating Dashboard')); @@ -44,3 +53,42 @@ export function useDuplicateDashboard({onSuccess}: UseDuplicateDashboardProps) { return duplicateDashboard; } + +export function useDuplicatePrebuiltDashboard({onSuccess}: UseDuplicateDashboardProps) { + const api = useApi(); + const organization = useOrganization(); + const [isLoading, setIsLoading] = useState(false); + + const duplicatePrebuiltDashboard = useCallback( + async (prebuiltId?: PrebuiltDashboardId) => { + if (!prebuiltId) { + throw new Error( + 'Prebuilt dashboard ID is required to duplicate a prebuilt dashboard' + ); + } + const prebuiltDashboard = {id: '-1', ...PREBUILT_DASHBOARDS[prebuiltId]}; + try { + const newDashboard = cloneDashboard(prebuiltDashboard); + delete newDashboard.prebuiltId; + newDashboard.title = `${newDashboard.title} copy`; + newDashboard.widgets.map(widget => (widget.id = undefined)); + setIsLoading(true); + const copiedDashboard = await createDashboard( + api, + organization.slug, + newDashboard, + true + ); + onSuccess?.(copiedDashboard); + addSuccessMessage(t('Dashboard duplicated')); + } catch (e) { + addErrorMessage(t('Error duplicating Dashboard')); + } finally { + setIsLoading(false); + } + }, + [api, organization, onSuccess] + ); + + return {duplicatePrebuiltDashboard, isLoading}; +}