From 37fe29e5d32e9dc72622a2646f94481ca798c99a Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Mon, 9 Mar 2026 11:05:07 -0400 Subject: [PATCH 1/4] feat(dashboards): Add pre-favorited sync for prebuilt dashboards Add a sync mechanism that automatically favorites certain prebuilt dashboards for users on first load. This mirrors the pattern used in explore saved queries. Changes: - Add optional `pre_favorited` field to `PrebuiltDashboard` TypedDict - Add `get_enabled_prebuilt_dashboards()` shared helper to consolidate the option/feature-flag logic used by both sync functions - Add `sync_prebuilt_dashboards_favorited()` which creates `DashboardFavoriteUser` records for pre-favorited prebuilt dashboards - Use separate locks: org-scoped for dashboard sync, user-scoped for favorite sync - Mark Backend Overview, Web Vitals, Mobile Vitals, AI Agents Overview, and MCP Overview as pre-favorited Co-Authored-By: Claude Opus 4.6 --- .../endpoints/organization_dashboards.py | 97 +++++++++++++++---- 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/src/sentry/dashboards/endpoints/organization_dashboards.py b/src/sentry/dashboards/endpoints/organization_dashboards.py index 677a6143fe06a1..ba405ae1e9e58f 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboards.py +++ b/src/sentry/dashboards/endpoints/organization_dashboards.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import IntEnum -from typing import Any, TypedDict +from typing import Any, Required, TypedDict import sentry_sdk from django.db import IntegrityError, router, transaction @@ -92,9 +92,10 @@ class PrebuiltDashboardId(IntEnum): BACKEND_CACHES = 28 -class PrebuiltDashboard(TypedDict): - prebuilt_id: PrebuiltDashboardId - title: str +class PrebuiltDashboard(TypedDict, total=False): + prebuilt_id: Required[PrebuiltDashboardId] + title: Required[str] + pre_favorited: bool # Prebuilt dashboards store minimal fields in the database. The actual dashboard and widget settings are @@ -130,6 +131,7 @@ class PrebuiltDashboard(TypedDict): { "prebuilt_id": PrebuiltDashboardId.WEB_VITALS, "title": "Web Vitals", + "pre_favorited": True, }, { "prebuilt_id": PrebuiltDashboardId.WEB_VITALS_SUMMARY, @@ -138,6 +140,7 @@ class PrebuiltDashboard(TypedDict): { "prebuilt_id": PrebuiltDashboardId.MOBILE_VITALS, "title": "Mobile Vitals", + "pre_favorited": True, }, { "prebuilt_id": PrebuiltDashboardId.MOBILE_VITALS_APP_STARTS, @@ -154,6 +157,7 @@ class PrebuiltDashboard(TypedDict): { "prebuilt_id": PrebuiltDashboardId.BACKEND_OVERVIEW, "title": "Backend Overview", + "pre_favorited": True, }, { "prebuilt_id": PrebuiltDashboardId.MOBILE_SESSION_HEALTH, @@ -190,10 +194,12 @@ class PrebuiltDashboard(TypedDict): { "prebuilt_id": PrebuiltDashboardId.AI_AGENTS_OVERVIEW, "title": "AI Agents Overview", + "pre_favorited": True, }, { "prebuilt_id": PrebuiltDashboardId.MCP_OVERVIEW, "title": "MCP Overview", + "pre_favorited": True, }, { "prebuilt_id": PrebuiltDashboardId.LARAVEL_OVERVIEW, @@ -222,6 +228,25 @@ class PrebuiltDashboard(TypedDict): ] +def get_enabled_prebuilt_dashboards( + organization: Organization, +) -> list[PrebuiltDashboard]: + """ + Returns the list of prebuilt dashboards that are enabled for the given organization, + based on the prebuilt-dashboard-ids option and the sync-all feature flag. + """ + enabled_prebuilt_dashboard_ids = options.get("dashboards.prebuilt-dashboard-ids") + return [ + dashboard + for dashboard in PREBUILT_DASHBOARDS + if dashboard["prebuilt_id"] in enabled_prebuilt_dashboard_ids + or features.has( + "organizations:dashboards-sync-all-registered-prebuilt-dashboards", + organization, + ) + ] + + def sync_prebuilt_dashboards(organization: Organization) -> None: """ Queries the database to check if prebuilt dashboards have a Dashboard record and @@ -230,16 +255,7 @@ def sync_prebuilt_dashboards(organization: Organization) -> None: """ with transaction.atomic(router.db_for_write(Dashboard)): - enabled_prebuilt_dashboard_ids = options.get("dashboards.prebuilt-dashboard-ids") - enabled_prebuilt_dashboards = [ - dashboard - for dashboard in PREBUILT_DASHBOARDS - if dashboard["prebuilt_id"] in enabled_prebuilt_dashboard_ids - or features.has( - "organizations:dashboards-sync-all-registered-prebuilt-dashboards", - organization, - ) - ] + enabled_prebuilt_dashboards = get_enabled_prebuilt_dashboards(organization) saved_prebuilt_dashboards = Dashboard.objects.filter( organization=organization, @@ -277,6 +293,42 @@ def sync_prebuilt_dashboards(organization: Organization) -> None: ).exclude(prebuilt_id__in=prebuilt_ids).delete() +def sync_prebuilt_dashboards_favorited(organization: Organization, user_id: int) -> None: + """ + Checks if pre-favorited prebuilt dashboards have a DashboardFavoriteUser record for the + user, and creates them if they don't. This ensures that certain prebuilt dashboards are + favorited by default for all users. + """ + enabled_prebuilt_dashboards = get_enabled_prebuilt_dashboards(organization) + pre_favorited_ids = [ + d["prebuilt_id"] for d in enabled_prebuilt_dashboards if d.get("pre_favorited") + ] + if not pre_favorited_ids: + return + + with transaction.atomic(router.db_for_write(DashboardFavoriteUser)): + prebuilt_dashboard_ids_without_favorite = ( + Dashboard.objects.filter( + organization=organization, + prebuilt_id__in=pre_favorited_ids, + ) + .exclude( + id__in=DashboardFavoriteUser.objects.filter( + organization=organization, + user_id=user_id, + ).values_list("dashboard_id", flat=True) + ) + .order_by("prebuilt_id") + .values_list("id", flat=True) + ) + for dashboard_id in prebuilt_dashboard_ids_without_favorite: + DashboardFavoriteUser.objects.insert_favorite_dashboard( + organization=organization, + user_id=user_id, + dashboard=Dashboard.objects.get(id=dashboard_id), + ) + + class OrganizationDashboardsPermission(OrganizationPermission): scope_map = { "GET": ["org:read", "org:write", "org:admin"], @@ -360,11 +412,22 @@ def get(self, request: Request, organization: Organization) -> Response: name="sync_prebuilt_dashboards", ) with lock.acquire(): - # Adds prebuilt dashboards to the database if they don't exist. - # Deletes old prebuilt dashboards from the database if they should no longer exist. sync_prebuilt_dashboards(organization) except UnableToAcquireLock: - # Another process is already syncing the prebuilt dashboards. We can skip syncing this time. + pass + except Exception as err: + sentry_sdk.capture_exception(err) + + # Favorite pre-favorited prebuilt dashboards for the user + try: + favorite_lock = locks.get( + f"dashboards:sync_prebuilt_dashboards_favorited:{organization.id}:{request.user.id}", + duration=10, + name="sync_prebuilt_dashboards_favorited", + ) + with favorite_lock.acquire(): + sync_prebuilt_dashboards_favorited(organization, request.user.id) + except UnableToAcquireLock: pass except Exception as err: sentry_sdk.capture_exception(err) From a5ba2a38ec408caffa1e9575904049cc35e31d80 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Mon, 9 Mar 2026 11:19:02 -0400 Subject: [PATCH 2/4] ref(dashboards): Evaluate sync-all feature flag once in get_enabled_prebuilt_dashboards Move the feature flag check outside the list comprehension so it is evaluated once rather than once per dashboard entry. Co-Authored-By: Claude Opus 4.6 --- .../dashboards/endpoints/organization_dashboards.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sentry/dashboards/endpoints/organization_dashboards.py b/src/sentry/dashboards/endpoints/organization_dashboards.py index ba405ae1e9e58f..8ff98d34ddf8dc 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboards.py +++ b/src/sentry/dashboards/endpoints/organization_dashboards.py @@ -236,14 +236,17 @@ def get_enabled_prebuilt_dashboards( based on the prebuilt-dashboard-ids option and the sync-all feature flag. """ enabled_prebuilt_dashboard_ids = options.get("dashboards.prebuilt-dashboard-ids") + should_sync_all_registered_prebuilt_dashboards = features.has( + "organizations:dashboards-sync-all-registered-prebuilt-dashboards", + organization, + ) + all_prebuilt_dashboards = [dashboard for dashboard in PREBUILT_DASHBOARDS] + if should_sync_all_registered_prebuilt_dashboards: + return all_prebuilt_dashboards return [ dashboard - for dashboard in PREBUILT_DASHBOARDS + for dashboard in all_prebuilt_dashboards if dashboard["prebuilt_id"] in enabled_prebuilt_dashboard_ids - or features.has( - "organizations:dashboards-sync-all-registered-prebuilt-dashboards", - organization, - ) ] From 53322a3e26a09229dfdbe9419b66d988ad326d84 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Mon, 9 Mar 2026 13:14:32 -0400 Subject: [PATCH 3/4] ref(dashboards): Gate favorite sync behind feature flag Only sync pre-favorited prebuilt dashboards when the dashboards-sync-all-registered-prebuilt-dashboards flag is enabled, to limit the rollout until we confirm the sync works properly. Co-Authored-By: Claude Opus 4.6 --- .../endpoints/organization_dashboards.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/sentry/dashboards/endpoints/organization_dashboards.py b/src/sentry/dashboards/endpoints/organization_dashboards.py index 8ff98d34ddf8dc..3853b642faab82 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboards.py +++ b/src/sentry/dashboards/endpoints/organization_dashboards.py @@ -422,18 +422,23 @@ def get(self, request: Request, organization: Organization) -> Response: sentry_sdk.capture_exception(err) # Favorite pre-favorited prebuilt dashboards for the user - try: - favorite_lock = locks.get( - f"dashboards:sync_prebuilt_dashboards_favorited:{organization.id}:{request.user.id}", - duration=10, - name="sync_prebuilt_dashboards_favorited", - ) - with favorite_lock.acquire(): - sync_prebuilt_dashboards_favorited(organization, request.user.id) - except UnableToAcquireLock: - pass - except Exception as err: - sentry_sdk.capture_exception(err) + # TODO - remove this flag check once we confirm the sync is proper, this should be done for all users + if features.has( + "organizations:dashboards-sync-all-registered-prebuilt-dashboards", + organization, + ): + try: + favorite_lock = locks.get( + f"dashboards:sync_prebuilt_dashboards_favorited:{organization.id}:{request.user.id}", + duration=10, + name="sync_prebuilt_dashboards_favorited", + ) + with favorite_lock.acquire(): + sync_prebuilt_dashboards_favorited(organization, request.user.id) + except UnableToAcquireLock: + pass + except Exception as err: + sentry_sdk.capture_exception(err) filters = request.query_params.getlist("filter") From c29ad734552086a0af8b0747409f2ea8e9496bb6 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki Date: Mon, 9 Mar 2026 13:25:48 -0400 Subject: [PATCH 4/4] perf(dashboards): Fix N+1 in favorite sync and add tests Fetch full Dashboard objects directly instead of fetching IDs with values_list and re-querying each one individually, eliminating N+1 queries in sync_prebuilt_dashboards_favorited. Add tests covering favorite creation for pre-favorited prebuilt dashboards, idempotency, and feature flag gating. Co-Authored-By: Claude Opus 4.6 --- .../endpoints/organization_dashboards.py | 7 ++--- .../endpoints/test_organization_dashboards.py | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/sentry/dashboards/endpoints/organization_dashboards.py b/src/sentry/dashboards/endpoints/organization_dashboards.py index 3853b642faab82..3190dd94dfa923 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboards.py +++ b/src/sentry/dashboards/endpoints/organization_dashboards.py @@ -310,7 +310,7 @@ def sync_prebuilt_dashboards_favorited(organization: Organization, user_id: int) return with transaction.atomic(router.db_for_write(DashboardFavoriteUser)): - prebuilt_dashboard_ids_without_favorite = ( + prebuilt_dashboards_without_favorite = ( Dashboard.objects.filter( organization=organization, prebuilt_id__in=pre_favorited_ids, @@ -322,13 +322,12 @@ def sync_prebuilt_dashboards_favorited(organization: Organization, user_id: int) ).values_list("dashboard_id", flat=True) ) .order_by("prebuilt_id") - .values_list("id", flat=True) ) - for dashboard_id in prebuilt_dashboard_ids_without_favorite: + for dashboard in prebuilt_dashboards_without_favorite: DashboardFavoriteUser.objects.insert_favorite_dashboard( organization=organization, user_id=user_id, - dashboard=Dashboard.objects.get(id=dashboard_id), + dashboard=dashboard, ) diff --git a/tests/sentry/dashboards/endpoints/test_organization_dashboards.py b/tests/sentry/dashboards/endpoints/test_organization_dashboards.py index 644220367890c1..810b7f05a0131f 100644 --- a/tests/sentry/dashboards/endpoints/test_organization_dashboards.py +++ b/tests/sentry/dashboards/endpoints/test_organization_dashboards.py @@ -2269,6 +2269,37 @@ def test_get_with_exclude_prebuilt(self) -> None: assert response.status_code == 200 assert len(response.data) == total_count - prebuilt_dashboards_count + def test_endpoint_creates_favorites_for_pre_favorited_prebuilt_dashboards(self) -> None: + assert ( + DashboardFavoriteUser.objects.filter( + organization=self.organization, user_id=self.user.id + ).count() + == 0 + ) + + pre_favorited_prebuilt_ids = { + d["prebuilt_id"] for d in PREBUILT_DASHBOARDS if d.get("pre_favorited") + } + + with self.feature( + [ + "organizations:dashboards-prebuilt-insights-dashboards", + "organizations:dashboards-sync-all-registered-prebuilt-dashboards", + ] + ): + response = self.do_request("get", self.url) + assert response.status_code == 200 + + favorites = DashboardFavoriteUser.objects.filter( + organization=self.organization, user_id=self.user.id + ) + favorited_prebuilt_ids = set( + Dashboard.objects.filter( + id__in=favorites.values_list("dashboard_id", flat=True) + ).values_list("prebuilt_id", flat=True) + ) + assert favorited_prebuilt_ids == pre_favorited_prebuilt_ids + def test_post_with_text_widget(self) -> None: with self.feature("organizations:dashboards-text-widgets"): data = {