From 8d5baf471134397d95dda6fd38c81d8571734a1a Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 25 Nov 2025 16:43:28 -0500 Subject: [PATCH 1/3] updates prebuilt dashboard preview in the table and grid manage view to display properly. Also disable delete and edit buttons for prebuilt dashboards --- .../views/dashboards/editAccessSelector.tsx | 13 ++++++++++-- .../views/dashboards/manage/dashboardGrid.tsx | 5 +++++ .../dashboards/manage/dashboardTable.tsx | 18 +++++++++++++++-- static/app/views/dashboards/manage/index.tsx | 20 ++++++++++++++++++- static/app/views/dashboards/types.tsx | 1 + 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/static/app/views/dashboards/editAccessSelector.tsx b/static/app/views/dashboards/editAccessSelector.tsx index dc5f92bbfab0f9..a0ddefd71a74d3 100644 --- a/static/app/views/dashboards/editAccessSelector.tsx +++ b/static/app/views/dashboards/editAccessSelector.tsx @@ -34,6 +34,7 @@ import type { interface EditAccessSelectorProps { dashboard: DashboardDetails | DashboardListItem; + disabled?: boolean; listOnly?: boolean; onChangeEditAccess?: (newDashboardPermissions: DashboardPermissions) => void; } @@ -46,6 +47,7 @@ function EditAccessSelector({ dashboard, onChangeEditAccess, listOnly = false, + disabled = false, }: EditAccessSelectorProps) { const currentUser: User = useUser(); const dashboardCreator: User | undefined = dashboard.createdBy; @@ -320,6 +322,7 @@ function EditAccessSelector({ }} priority="primary" disabled={ + disabled || !userCanEditDashboardPermissions || isEqual(getDashboardPermissions(), { ...dashboard.permissions, @@ -369,14 +372,20 @@ function EditAccessSelector({ onSearch={debounce(val => void onSearch(val), DEFAULT_DEBOUNCE_DURATION)} strategy="fixed" preventOverflowOptions={{mainAxis: false}} + disabled={disabled} /> ); + const tooltipTitle = disabled + ? t('Prebuilt dashboards cannot be edited') + : t('Only the creator of this dashboard can manage editor access'); + return ( {dropdownMenu} diff --git a/static/app/views/dashboards/manage/dashboardGrid.tsx b/static/app/views/dashboards/manage/dashboardGrid.tsx index 12b295fafb3ecb..bcf8977743a0c7 100644 --- a/static/app/views/dashboards/manage/dashboardGrid.tsx +++ b/static/app/views/dashboards/manage/dashboardGrid.tsx @@ -16,6 +16,7 @@ import {IconEllipsis} from 'sentry/icons'; import {t, tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; +import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useQueryClient} from 'sentry/utils/queryClient'; import withApi from 'sentry/utils/withApi'; @@ -122,6 +123,10 @@ function DashboardGrid({ onConfirm: () => handleDeleteDashboard(dashboard, 'grid'), }); }, + disabled: defined(dashboard.prebuiltId), + tooltip: defined(dashboard.prebuiltId) + ? t('Prebuilt dashboards cannot be deleted') + : undefined, }, ]; diff --git a/static/app/views/dashboards/manage/dashboardTable.tsx b/static/app/views/dashboards/manage/dashboardTable.tsx index f911d81ba47fef..80d73fdf6d3375 100644 --- a/static/app/views/dashboards/manage/dashboardTable.tsx +++ b/static/app/views/dashboards/manage/dashboardTable.tsx @@ -4,6 +4,7 @@ import type {Location} from 'history'; import cloneDeep from 'lodash/cloneDeep'; import {Flex} from '@sentry/scraps/layout'; +import {Tooltip} from '@sentry/scraps/tooltip/tooltip'; import { updateDashboardFavorite, @@ -27,6 +28,7 @@ import {IconCopy, IconDelete, IconStar} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; +import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useQueryClient} from 'sentry/utils/queryClient'; import {decodeScalar} from 'sentry/utils/queryString'; @@ -229,7 +231,11 @@ function DashboardTable({ ) : ( - + + + + + ); } @@ -253,6 +259,7 @@ function DashboardTable({ dashboard={dataRow} onChangeEditAccess={onChangeEditAccess} listOnly + disabled={defined(dataRow.prebuiltId)} // Prebuilt dashboards cannot be edited /> ); } @@ -309,7 +316,14 @@ function DashboardTable({ data-test-id="dashboard-delete" icon={} size="sm" - disabled={dashboards && dashboards.length <= 1} + disabled={ + (dashboards && dashboards.length <= 1) || defined(dataRow.prebuiltId) + } + title={ + defined(dataRow.prebuiltId) + ? t('Prebuilt dashboards cannot be deleted') + : undefined + } /> diff --git a/static/app/views/dashboards/manage/index.tsx b/static/app/views/dashboards/manage/index.tsx index 436c9dc7c2bd1a..eb775eca126120 100644 --- a/static/app/views/dashboards/manage/index.tsx +++ b/static/app/views/dashboards/manage/index.tsx @@ -50,6 +50,7 @@ import OwnedDashboardsTable, { } from 'sentry/views/dashboards/manage/tableView/ownedDashboardsTable'; import type {DashboardsLayout} from 'sentry/views/dashboards/manage/types'; import type {DashboardDetails, DashboardListItem} from 'sentry/views/dashboards/types'; +import {PREBUILT_DASHBOARDS} from 'sentry/views/dashboards/utils/prebuiltConfigs'; import RouteError from 'sentry/views/routeError'; import DashboardGrid from './dashboardGrid'; @@ -155,7 +156,7 @@ function ManageDashboards() { }); const { - data: dashboards, + data: dashboardsWithoutPrebuiltConfigs, isLoading, isError, error, @@ -183,6 +184,23 @@ function ManageDashboards() { } ); + const dashboards = dashboardsWithoutPrebuiltConfigs?.map(dashboard => { + if (dashboard.prebuiltId) { + return { + ...dashboard, + widgetDisplay: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map( + widget => widget.displayType + ), + widgetPreview: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map(widget => ({ + displayType: widget.displayType, + layout: widget.layout ?? null, + })), + projects: [], + }; + } + return dashboard; + }); + const ownedDashboards = useOwnedDashboards({ query: decodeScalar(location.query.query, ''), cursor: decodeScalar(location.query[OWNED_CURSOR_KEY], ''), diff --git a/static/app/views/dashboards/types.tsx b/static/app/views/dashboards/types.tsx index a99d11f29b6dd1..e7e5043d4f8649 100644 --- a/static/app/views/dashboards/types.tsx +++ b/static/app/views/dashboards/types.tsx @@ -168,6 +168,7 @@ export type DashboardListItem = { isFavorited?: boolean; lastVisited?: string; permissions?: DashboardPermissions; + prebuiltId?: PrebuiltDashboardId; }; export enum DashboardFilterKeys { From d61427a20da3dec652dbc270e4419fc862c0065a Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 25 Nov 2025 18:08:02 -0500 Subject: [PATCH 2/3] check existence --- static/app/views/dashboards/manage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/dashboards/manage/index.tsx b/static/app/views/dashboards/manage/index.tsx index eb775eca126120..46b8d412e9c789 100644 --- a/static/app/views/dashboards/manage/index.tsx +++ b/static/app/views/dashboards/manage/index.tsx @@ -185,7 +185,7 @@ function ManageDashboards() { ); const dashboards = dashboardsWithoutPrebuiltConfigs?.map(dashboard => { - if (dashboard.prebuiltId) { + if (dashboard.prebuiltId && dashboard.prebuiltId in PREBUILT_DASHBOARDS) { return { ...dashboard, widgetDisplay: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map( From 770ae0e219bf6c68a5efaf3f5f5614e52abdb813 Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Wed, 26 Nov 2025 15:57:36 -0500 Subject: [PATCH 3/3] memoize --- static/app/views/dashboards/manage/index.tsx | 40 +++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/static/app/views/dashboards/manage/index.tsx b/static/app/views/dashboards/manage/index.tsx index 46b8d412e9c789..c462d655f665b4 100644 --- a/static/app/views/dashboards/manage/index.tsx +++ b/static/app/views/dashboards/manage/index.tsx @@ -1,4 +1,4 @@ -import {useEffect, useRef, useState} from 'react'; +import {useEffect, useMemo, useRef, useState} from 'react'; import styled from '@emotion/styled'; import type {Query} from 'history'; import debounce from 'lodash/debounce'; @@ -184,22 +184,28 @@ function ManageDashboards() { } ); - const dashboards = dashboardsWithoutPrebuiltConfigs?.map(dashboard => { - if (dashboard.prebuiltId && dashboard.prebuiltId in PREBUILT_DASHBOARDS) { - return { - ...dashboard, - widgetDisplay: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map( - widget => widget.displayType - ), - widgetPreview: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map(widget => ({ - displayType: widget.displayType, - layout: widget.layout ?? null, - })), - projects: [], - }; - } - return dashboard; - }); + const dashboards = useMemo( + () => + dashboardsWithoutPrebuiltConfigs?.map(dashboard => { + if (dashboard.prebuiltId && dashboard.prebuiltId in PREBUILT_DASHBOARDS) { + return { + ...dashboard, + widgetDisplay: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map( + widget => widget.displayType + ), + widgetPreview: PREBUILT_DASHBOARDS[dashboard.prebuiltId].widgets.map( + widget => ({ + displayType: widget.displayType, + layout: widget.layout ?? null, + }) + ), + projects: [], + }; + } + return dashboard; + }), + [dashboardsWithoutPrebuiltConfigs] + ); const ownedDashboards = useOwnedDashboards({ query: decodeScalar(location.query.query, ''),