-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat(dashboards): Add optional onboarding widgets for prebuilt dashboards #110244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
73669d1
5f70bf7
ec9d5d7
1edb9c7
4729752
d347040
66dd217
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import emptyStateImg from 'sentry-images/spot/performance-waiting-for-span.svg'; | ||
|
|
||
| import {LinkButton} from '@sentry/scraps/button'; | ||
| import {Image} from '@sentry/scraps/image'; | ||
| import {Flex} from '@sentry/scraps/layout'; | ||
| import {Heading, Text} from '@sentry/scraps/text'; | ||
|
|
||
| import Panel from 'sentry/components/panels/panel'; | ||
| import {t} from 'sentry/locale'; | ||
|
|
||
| interface OverviewOnboardingPanelProps { | ||
| heading: string; | ||
| } | ||
|
|
||
| export function GenericOnboarding({heading}: OverviewOnboardingPanelProps) { | ||
| return ( | ||
| <Panel> | ||
| <Flex justify="center"> | ||
| <Flex padding="xl" align="center" wrap="wrap-reverse" gap="3xl" maxWidth="1000px"> | ||
| <Flex direction="column" gap="xl" flex="5" align="start"> | ||
| <Heading as="h3" size="xl"> | ||
| {heading} | ||
| </Heading> | ||
|
|
||
| <Text as="p" size="md"> | ||
| {t( | ||
| 'Send telemetry data to Sentry for this project to start using this dashboard. Set up your SDK to begin monitoring your application.' | ||
| )} | ||
| </Text> | ||
|
|
||
| <LinkButton | ||
| priority="primary" | ||
| external | ||
| href="https://docs.sentry.io/product/dashboards/" | ||
| > | ||
| {t('Read the Docs')} | ||
| </LinkButton> | ||
| </Flex> | ||
|
|
||
| <Flex flex="3" justify="center"> | ||
| <Image src={emptyStateImg} alt="" width="100%" /> | ||
| </Flex> | ||
| </Flex> | ||
| </Flex> | ||
| </Panel> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import usePageFilters from 'sentry/components/pageFilters/usePageFilters'; | ||
| import {getSelectedProjectList} from 'sentry/utils/project/useSelectedProjectsHaveField'; | ||
| import useOrganization from 'sentry/utils/useOrganization'; | ||
| import useProjects from 'sentry/utils/useProjects'; | ||
| import type {PrebuiltDashboardId} from 'sentry/views/dashboards/utils/prebuiltConfigs'; | ||
| import {PREBUILT_DASHBOARDS} from 'sentry/views/dashboards/utils/prebuiltConfigs'; | ||
| import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; | ||
| import {ModulesOnboardingPanel} from 'sentry/views/insights/common/components/modulesOnboarding'; | ||
| import {useHasFirstSpan} from 'sentry/views/insights/common/queries/useHasFirstSpan'; | ||
| import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject'; | ||
| import {Onboarding as AgentOnboarding} from 'sentry/views/insights/pages/agents/onboarding'; | ||
| import {Onboarding as MCPOnboarding} from 'sentry/views/insights/pages/mcp/onboarding'; | ||
| import {ModuleName} from 'sentry/views/insights/types'; | ||
| import {LegacyOnboarding} from 'sentry/views/performance/onboarding'; | ||
|
|
||
| import {GenericOnboarding} from './genericOnboarding'; | ||
|
|
||
| interface PrebuiltDashboardOnboardingGateProps { | ||
| children: React.ReactNode; | ||
| prebuiltId?: PrebuiltDashboardId; | ||
| } | ||
|
|
||
| export function PrebuiltDashboardOnboardingGate({ | ||
| prebuiltId, | ||
| children, | ||
| }: PrebuiltDashboardOnboardingGateProps) { | ||
| const organization = useOrganization(); | ||
| const {projects} = useProjects(); | ||
| const pageFilters = usePageFilters(); | ||
|
|
||
| const onboarding = prebuiltId ? PREBUILT_DASHBOARDS[prebuiltId]?.onboarding : undefined; | ||
|
|
||
| const moduleName = | ||
| onboarding?.type === 'module' ? onboarding.moduleName : ModuleName.OTHER; | ||
|
|
||
| // First project that doesn't have any span data at all | ||
| const onboardingProject = useOnboardingProject(); | ||
| const hasAnySpanData = !onboardingProject; | ||
|
|
||
| // Whether the selected projects have span of the required type | ||
| const hasFirstSpan = useHasFirstSpan(moduleName); | ||
|
|
||
| if (!onboarding) { | ||
| return children; | ||
| } | ||
|
|
||
| // If the dashboard uses module-specific onboarding, check whether | ||
| // module-specific data is available | ||
| if (onboarding.type === 'module') { | ||
| if (!hasAnySpanData) { | ||
| return ( | ||
| <ModuleLayout.Full> | ||
| <LegacyOnboarding organization={organization} project={onboardingProject} /> | ||
| </ModuleLayout.Full> | ||
| ); | ||
| } | ||
|
|
||
| if (!hasFirstSpan) { | ||
| return ( | ||
| <ModuleLayout.Full> | ||
| <ModulesOnboardingPanel moduleName={onboarding.moduleName} /> | ||
| </ModuleLayout.Full> | ||
| ); | ||
| } | ||
|
|
||
| return children; | ||
| } | ||
|
|
||
| const selectedProjects = getSelectedProjectList( | ||
| pageFilters.selection.projects, | ||
| projects | ||
| ); | ||
|
|
||
| const hasData = onboarding.requiredProjectFlags.some(flag => | ||
| selectedProjects.some(p => p[flag] === true) | ||
| ); | ||
|
|
||
| if (hasData) { | ||
| return children; | ||
| } | ||
|
|
||
| if (onboarding.type === 'custom') { | ||
| if (onboarding.componentId === 'agent-monitoring') { | ||
| return <AgentOnboarding />; | ||
| } | ||
|
|
||
| return <MCPOnboarding />; | ||
| } | ||
|
|
||
| return <GenericOnboarding heading={onboarding.description} />; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import type {Project} from 'sentry/types/project'; | ||
| import {type DashboardDetails} from 'sentry/views/dashboards/types'; | ||
| import {AI_AGENTS_MODELS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/ai/aiAgentsModels'; | ||
| import {AI_AGENTS_OVERVIEW_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/ai/aiAgentsOverview'; | ||
|
|
@@ -27,6 +28,7 @@ import {QUEUE_SUMMARY_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebu | |
| import {SESSION_HEALTH_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/sessionHealth'; | ||
| import {WEB_VITALS_SUMMARY_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/webVitals/pageSummary'; | ||
| import {WEB_VITALS_PREBUILT_CONFIG} from 'sentry/views/dashboards/utils/prebuiltConfigs/webVitals/webVitals'; | ||
| import type {ModulesWithOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding'; | ||
|
|
||
| export enum PrebuiltDashboardId { | ||
| FRONTEND_SESSION_HEALTH = 1, | ||
|
|
@@ -59,7 +61,36 @@ export enum PrebuiltDashboardId { | |
| BACKEND_CACHES = 28, | ||
| } | ||
|
|
||
| export type PrebuiltDashboard = Omit<DashboardDetails, 'id'>; | ||
| /** Boolean flags on Project that indicate whether telemetry data has been received. */ | ||
| type ProjectTelemetryFlag = Extract< | ||
| keyof Project, | ||
| `hasInsights${string}` | 'hasSessions' | ||
| >; | ||
|
|
||
| export type OnboardingConfig = | ||
| | { | ||
| moduleName: ModulesWithOnboarding; | ||
| // Single-module onboarding: shows ModulesOnboardingPanel | ||
| type: 'module'; | ||
| requiredProjectFlags?: ProjectTelemetryFlag[]; | ||
| } | ||
| | { | ||
| componentId: 'agent-monitoring' | 'mcp'; | ||
| requiredProjectFlags: ProjectTelemetryFlag[]; | ||
| // Custom onboarding component (AI Agents, MCP) | ||
| type: 'custom'; | ||
| } | ||
| | { | ||
| description: string; | ||
| requiredProjectFlags: ProjectTelemetryFlag[]; | ||
| // Overview dashboard onboarding: shows a generic onboarding panel | ||
| // when NONE of the listed project flags are set | ||
| type: 'overview'; | ||
| }; | ||
|
Comment on lines
+71
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do wonder if having three types is necessary here? In fact i'm wondering if we need types at all here, although it would require some rejigging, it feels simpler imo if each prebuilt config just mapped to the id of their onboarding component. If we didn't care about duplication, we could even just make this a function that returns a onboarding component.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They do have slightly different behaviour because depending on the type of onboarding, it has to run different checks. Plus, the different onboarding components accept different kinds of props 😬 |
||
|
|
||
| export type PrebuiltDashboard = Omit<DashboardDetails, 'id'> & { | ||
| onboarding?: OnboardingConfig; | ||
| }; | ||
|
|
||
| // NOTE: These configs must be in sync with the prebuilt dashboards declared in | ||
| // the backend in the `PREBUILT_DASHBOARDS` constant. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.