Browse Source

feat(insights): module empty states (#73891)

Everything here is internal only for now, this PR updates the module
onboarding, and wraps the other modules with the onboarding component.
Any old onboarding content will be removed in the release PR.

Text that should be used is found here
https://www.notion.so/sentry/Insights-Pending-for-M-Launch-4c9162936fbc4a6897085851c503c0f1

Also note: there is duplicate images between this PR and the module
upsells (https://github.com/getsentry/getsentry/pull/14519/files), I
didn't realize they would share the same images.
Once this is merged, i will delete the `getsentry` duplicates and use
the ones from `sentry`

<img width="1493" alt="image"
src="https://github.com/getsentry/sentry/assets/44422760/3db1cde5-7788-4c44-b601-7233c9645868">
Dominik Buszowiecki 8 months ago
parent
commit
4e3454ba66

+ 5 - 1
static/app/views/insights/browser/resources/views/resourcesLandingPage.tsx

@@ -27,8 +27,10 @@ import {
   useResourceModuleFilters,
 } from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
 import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
+import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
 import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
 import {DomainSelector} from 'sentry/views/insights/common/views/spans/selectors/domainSelector';
+import {ModuleName} from 'sentry/views/insights/types';
 
 const {SPAN_OP, SPAN_DOMAIN} = BrowserStarfishFields;
 
@@ -76,7 +78,9 @@ function ResourcesLandingPage() {
                 ]}
               />
             </FilterOptionsContainer>
-            <ResourceView />
+            <ModulesOnboarding moduleName={ModuleName.RESOURCE}>
+              <ResourceView />
+            </ModulesOnboarding>
           </Layout.Main>
         </Layout.Body>
       </PageAlertProvider>

+ 2 - 0
static/app/views/insights/browser/webVitals/settings.ts

@@ -2,6 +2,8 @@ import {t} from 'sentry/locale';
 
 export const MODULE_TITLE = t('Web Vitals');
 export const BASE_URL = 'browser/pageloads';
+export const DATA_TYPE = t('Web Vitals');
+export const DATA_TYPE_PLURAL = t('Web Vitals');
 
 export const MODULE_DESCRIPTION = t(
   'Measure the quality of real user experience in your web applications using industry standard quality signals.'

+ 53 - 51
static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx

@@ -36,6 +36,7 @@ import {
 import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types';
 import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType';
 import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
+import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
 import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
 import {useHasDataTrackAnalytics} from 'sentry/views/insights/common/utils/useHasDataTrackAnalytics';
 import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
@@ -99,59 +100,60 @@ export function WebVitalsLandingPage() {
             </PageFilterBar>
             <BrowserTypeSelector />
           </TopMenuContainer>
-
-          {onboardingProject && (
-            <OnboardingContainer>
-              <Onboarding organization={organization} project={onboardingProject} />
-            </OnboardingContainer>
-          )}
-          {!onboardingProject && (
-            <Fragment>
-              <PerformanceScoreChartContainer>
-                <PerformanceScoreChart
-                  projectScore={projectScore}
-                  isProjectScoreLoading={isLoading || isProjectScoresLoading}
-                  webVital={state.webVital}
-                  browserTypes={browserTypes}
-                />
-              </PerformanceScoreChartContainer>
-              <WebVitalMetersContainer>
-                <WebVitalMeters
-                  projectData={projectData}
-                  projectScore={projectScore}
-                  onClick={webVital => setState({...state, webVital})}
-                />
-              </WebVitalMetersContainer>
-              <PagePerformanceTable />
-              <PagesTooltipContainer>
-                <Tooltip
-                  isHoverable
-                  title={
-                    <div>
-                      <div>
-                        {tct(
-                          'If pages you expect to see are missing, your framework is most likely not supported by the SDK, or your traffic is coming from unsupported browsers. Find supported browsers and frameworks [link:here].',
-                          {
-                            link: (
-                              <ExternalLink href="https://docs.sentry.io/product/insights/web-vitals/#prerequisites-and-limitations" />
-                            ),
-                          }
-                        )}
-                      </div>
-                      <br />
+          <ModulesOnboarding moduleName={ModuleName.VITAL}>
+            {onboardingProject && (
+              <OnboardingContainer>
+                <Onboarding organization={organization} project={onboardingProject} />
+              </OnboardingContainer>
+            )}
+            {!onboardingProject && (
+              <Fragment>
+                <PerformanceScoreChartContainer>
+                  <PerformanceScoreChart
+                    projectScore={projectScore}
+                    isProjectScoreLoading={isLoading || isProjectScoresLoading}
+                    webVital={state.webVital}
+                    browserTypes={browserTypes}
+                  />
+                </PerformanceScoreChartContainer>
+                <WebVitalMetersContainer>
+                  <WebVitalMeters
+                    projectData={projectData}
+                    projectScore={projectScore}
+                    onClick={webVital => setState({...state, webVital})}
+                  />
+                </WebVitalMetersContainer>
+                <PagePerformanceTable />
+                <PagesTooltipContainer>
+                  <Tooltip
+                    isHoverable
+                    title={
                       <div>
-                        {t(
-                          'Keep your JavaScript SDK updated to the latest version for the best Web Vitals support.'
-                        )}
+                        <div>
+                          {tct(
+                            'If pages you expect to see are missing, your framework is most likely not supported by the SDK, or your traffic is coming from unsupported browsers. Find supported browsers and frameworks [link:here].',
+                            {
+                              link: (
+                                <ExternalLink href="https://docs.sentry.io/product/insights/web-vitals/#prerequisites-and-limitations" />
+                              ),
+                            }
+                          )}
+                        </div>
+                        <br />
+                        <div>
+                          {t(
+                            'Keep your JavaScript SDK updated to the latest version for the best Web Vitals support.'
+                          )}
+                        </div>
                       </div>
-                    </div>
-                  }
-                >
-                  <PagesTooltip>{t('Why are my pages not showing up?')}</PagesTooltip>
-                </Tooltip>
-              </PagesTooltipContainer>
-            </Fragment>
-          )}
+                    }
+                  >
+                    <PagesTooltip>{t('Why are my pages not showing up?')}</PagesTooltip>
+                  </Tooltip>
+                </PagesTooltipContainer>
+              </Fragment>
+            )}
+          </ModulesOnboarding>
         </Layout.Main>
       </Layout.Body>
       <WebVitalsDetailPanel

+ 2 - 0
static/app/views/insights/cache/settings.ts

@@ -3,6 +3,8 @@ import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types';
 
 export const MODULE_TITLE = t('Caches');
 export const BASE_URL = 'caches';
+export const DATA_TYPE = t('Cache');
+export const DATA_TYPE_PLURAL = t('Caches');
 
 // NOTE: Awkward typing, but without it `RELEASE_LEVEL` is narrowed and the comparison is not allowed
 export const releaseLevelAsBadgeProps = {

+ 381 - 10
static/app/views/insights/common/components/modulesOnboarding.tsx

@@ -1,14 +1,38 @@
+import {Fragment} from 'react';
 import styled from '@emotion/styled';
+import {PlatformIcon} from 'platformicons';
+import type {PLATFORM_TO_ICON} from 'platformicons/build/platformIcon';
 
+import appStartPreviewImg from 'sentry-images/insights/module-upsells/insights-app-starts-module-charts.svg';
+import assetsPreviewImg from 'sentry-images/insights/module-upsells/insights-assets-module-charts.svg';
+import cachesPreviewImg from 'sentry-images/insights/module-upsells/insights-caches-module-charts.svg';
+import llmPreviewImg from 'sentry-images/insights/module-upsells/insights-llm-module-charts.svg';
+import queriesPreviewImg from 'sentry-images/insights/module-upsells/insights-queries-module-charts.svg';
+import queuesPreviewImg from 'sentry-images/insights/module-upsells/insights-queues-module-charts.svg';
+import requestPreviewImg from 'sentry-images/insights/module-upsells/insights-requests-module-charts.svg';
+import screenLoadsPreviewImg from 'sentry-images/insights/module-upsells/insights-screen-loads-module-charts.svg';
+import webVitalsPreviewImg from 'sentry-images/insights/module-upsells/insights-web-vitals-module-charts.svg';
 import emptyStateImg from 'sentry-images/spot/performance-waiting-for-span.svg';
 
+import {LinkButton} from 'sentry/components/button';
+import ExternalLink from 'sentry/components/links/externalLink';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import Panel from 'sentry/components/panels/panel';
+import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
+import useOrganization from 'sentry/utils/useOrganization';
 import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
+import type {TitleableModuleNames} from 'sentry/views/insights/common/components/modulePageProviders';
 import {useHasFirstSpan} from 'sentry/views/insights/common/queries/useHasFirstSpan';
 import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
-import type {ModuleName} from 'sentry/views/insights/types';
+import {
+  MODULE_DATA_TYPES,
+  MODULE_DATA_TYPES_PLURAL,
+  MODULE_PRODUCT_DOC_LINKS,
+} from 'sentry/views/insights/settings';
+import {ModuleName} from 'sentry/views/insights/types';
+
+type PlatformIcons = keyof typeof PLATFORM_TO_ICON;
 
 export function ModulesOnboarding({
   children,
@@ -17,21 +41,38 @@ export function ModulesOnboarding({
 }: {
   children: React.ReactNode;
   moduleName: ModuleName;
-  onboardingContent: React.ReactNode;
+  onboardingContent?: React.ReactNode;
 }) {
+  const organization = useOrganization();
   const onboardingProject = useOnboardingProject();
   const hasData = useHasFirstSpan(moduleName);
+  const hasEmptyStateFeature = organization.features.includes(
+    'insights-empty-state-page'
+  );
+
+  if (hasEmptyStateFeature && (onboardingProject || !hasData)) {
+    return (
+      <ModuleLayout.Full>
+        <ModulesOnboardingPanel moduleName={moduleName} />
+      </ModuleLayout.Full>
+    );
+  }
 
-  if (onboardingProject || !hasData) {
+  if (onboardingContent && (onboardingProject || !hasData)) {
     return (
       <ModuleLayout.Full>
-        <ModulesOnboardingPanel>{onboardingContent}</ModulesOnboardingPanel>
+        <OldModulesOnboardingPanel>{onboardingContent}</OldModulesOnboardingPanel>
       </ModuleLayout.Full>
     );
   }
+
   if (!onboardingProject && hasData) {
     return children;
   }
+
+  if (!onboardingContent) {
+    return children;
+  }
   // TODO: Add an error state?
   return (
     <ModuleLayout.Full>
@@ -40,7 +81,7 @@ export function ModulesOnboarding({
   );
 }
 
-function ModulesOnboardingPanel({children}: {children: React.ReactNode}) {
+function OldModulesOnboardingPanel({children}: {children: React.ReactNode}) {
   return (
     <Panel>
       <Container>
@@ -51,24 +92,354 @@ function ModulesOnboardingPanel({children}: {children: React.ReactNode}) {
   );
 }
 
+function ModulesOnboardingPanel({moduleName}: {moduleName: ModuleName}) {
+  const emptyStateContent = EMPTY_STATE_CONTENT[moduleName];
+
+  return (
+    <Panel>
+      <Container>
+        <ContentContainer>
+          <Fragment>
+            <Header>{emptyStateContent.heading}</Header>
+            <p>
+              {emptyStateContent.description}{' '}
+              <ExternalLink href={MODULE_PRODUCT_DOC_LINKS[moduleName]}>
+                {t('Read Docs')}
+              </ExternalLink>
+            </p>
+          </Fragment>
+          <SplitContainer>
+            <ModulePreview moduleName={moduleName} />
+            <ValueProp>
+              {emptyStateContent.valuePropDescription}
+              <ul>
+                {emptyStateContent.valuePropPoints.map(point => (
+                  <li key={point?.toString()}>{point}</li>
+                ))}
+              </ul>
+            </ValueProp>
+          </SplitContainer>
+        </ContentContainer>
+        <PerfImage src={emptyStateImg} />
+        <LinkButton
+          priority="primary"
+          external
+          href={MODULE_PRODUCT_DOC_LINKS[moduleName]}
+        >
+          {t('Read the docs')}
+        </LinkButton>
+      </Container>
+    </Panel>
+  );
+}
+
+type ModulePreviewProps = {moduleName: ModuleName};
+
+function ModulePreview({moduleName}: ModulePreviewProps) {
+  const emptyStateContent = EMPTY_STATE_CONTENT[moduleName];
+  return (
+    <ModulePreviewContainer>
+      <ModulePreviewImage src={emptyStateContent.imageSrc} />
+      {emptyStateContent.supportedSdks && (
+        <SupportedSdkContainer>
+          <div>{t('Supporting Today: ')}</div>
+          <SupportedSdkList>
+            {emptyStateContent.supportedSdks.map(sdk => (
+              <SupportedSdkIconContainer key={sdk}>
+                <PlatformIcon platform={sdk} size={'25px'} />
+              </SupportedSdkIconContainer>
+            ))}
+          </SupportedSdkList>
+        </SupportedSdkContainer>
+      )}
+    </ModulePreviewContainer>
+  );
+}
+
 const PerfImage = styled('img')`
-  width: 260px;
+  width: 400px;
   user-select: none;
   position: absolute;
-  bottom: 0;
+  top: 0;
   right: 0;
-  padding-right: ${space(1)};
+  padding-right: ${space(2)};
+  padding-top: ${space(4)};
 `;
 
 const Container = styled('div')`
   position: relative;
   overflow: hidden;
   min-height: 160px;
-  padding: ${space(4)} ${space(4)} 0;
+  padding: ${space(4)};
 `;
 
 const ContentContainer = styled('div')`
   position: relative;
-  width: 70%;
+  width: 60%;
   z-index: 1;
 `;
+
+const Header = styled('h3')`
+  margin-bottom: ${space(1)};
+`;
+
+const SplitContainer = styled(Panel)`
+  display: flex;
+  justify-content: center;
+`;
+
+const ModulePreviewImage = styled('img')`
+  max-width: 100%;
+  display: block;
+  margin: auto;
+  margin-bottom: ${space(2)};
+  object-fit: contain;
+`;
+
+const ModulePreviewContainer = styled('div')`
+  flex: 2;
+  width: 100%;
+  height: 100%;
+  padding: ${space(3)};
+  background-color: ${p => p.theme.backgroundSecondary};
+`;
+
+const SupportedSdkContainer = styled('div')`
+  display: flex;
+  flex-direction: column;
+  gap: ${space(1)};
+  align-items: center;
+  color: ${p => p.theme.gray300};
+`;
+
+const SupportedSdkList = styled('div')`
+  display: flex;
+  gap: ${space(0.5)};
+`;
+
+const SupportedSdkIconContainer = styled('div')`
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: ${p => p.theme.gray100};
+  width: 42px;
+  height: 42px;
+  border-radius: 3px;
+`;
+
+const ValueProp = styled('div')`
+  flex: 1;
+  padding: ${space(3)};
+  ul {
+    margin-top: ${space(1)};
+  }
+`;
+
+type EmptyStateContent = {
+  description: React.ReactNode;
+  heading: React.ReactNode;
+  imageSrc: any;
+  valuePropDescription: React.ReactNode;
+  valuePropPoints: React.ReactNode[];
+  supportedSdks?: PlatformIcons[];
+};
+
+const EMPTY_STATE_CONTENT: Record<TitleableModuleNames, EmptyStateContent> = {
+  app_start: {
+    heading: t('Don’t lose the race at the starting line'),
+    description: tct(
+      'Monitor cold and warm [dataTypePlural] and track down the operations and releases contributing regression.',
+      {
+        dataTypePlural:
+          MODULE_DATA_TYPES_PLURAL[ModuleName.APP_START].toLocaleLowerCase(),
+      }
+    ),
+    valuePropDescription: tct(`Mobile [dataType] insights give you visibility into:`, {
+      dataType: MODULE_DATA_TYPES[ModuleName.APP_START],
+    }),
+    valuePropPoints: [
+      t('Application start duration broken down by release.'),
+      t('Performance by device class.'),
+      t('Real user performance metrics.'),
+    ],
+    imageSrc: appStartPreviewImg,
+  },
+  ai: {
+    heading: t('Find out what your LLM model is actually saying'),
+    description: tct(
+      'Get insights into critical [dataType] metrics, like token usage, to monitor and fix issues with AI pipelines.',
+      {
+        dataType: MODULE_DATA_TYPES[ModuleName.AI],
+      }
+    ),
+    valuePropDescription: tct(
+      'See what your [dataTypePlural] are doing in production by monitoring:',
+      {
+        dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.AI],
+      }
+    ),
+    valuePropPoints: [
+      t('Token cost and usage per-provider and per-pipeline.'),
+      tct('The inputs and outputs of [dataType] calls.', {
+        dataType: MODULE_DATA_TYPES[ModuleName.AI],
+      }),
+      tct('Performance and timing information about [dataTypePlural] in production.', {
+        dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.AI],
+      }),
+    ],
+    imageSrc: llmPreviewImg,
+  },
+  // Mobile UI is not released yet
+  'mobile-ui': {
+    heading: t('TODO'),
+    description: t('TODO'),
+    valuePropDescription: t('Mobile UI load insights include:'),
+    valuePropPoints: [],
+    imageSrc: screenLoadsPreviewImg,
+  },
+  cache: {
+    heading: t('Bringing you one less hard problem in computer science'),
+    description: t(
+      'We’ll tell you if the parts of your application that interact with caches are hitting cache as often as intended, and whether caching is providing the performance improvements expected.'
+    ),
+    valuePropDescription: tct('[dataType] insights include:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.CACHE],
+    }),
+    valuePropPoints: [
+      t('Throughput of your cached endpoints.'),
+      tct('Average [dataType] hit and miss duration.', {
+        dataType: MODULE_DATA_TYPES[ModuleName.CACHE].toLocaleLowerCase(),
+      }),
+      t('Hit / miss ratio of keys accessed by your application.'),
+    ],
+    imageSrc: cachesPreviewImg,
+    supportedSdks: [
+      'ruby',
+      'python',
+      'javascript',
+      'java',
+      'dotnet',
+      'php-laravel',
+      'php-symfony',
+      'python-django',
+    ],
+  },
+  db: {
+    heading: tct(
+      'Fix the slow [dataTypePlural] you honestly intended to get back to later',
+      {dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.DB].toLocaleLowerCase()}
+    ),
+    description: tct(
+      'Investigate the performance of database [dataTypePlural] and get the information necessary to improve them.',
+      {dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.DB].toLocaleLowerCase()}
+    ),
+    valuePropDescription: tct('[dataType] insights give you visibility into:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.DB],
+    }),
+    valuePropPoints: [
+      tct('Slow [dataTypePlural].', {
+        dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.DB].toLocaleLowerCase(),
+      }),
+      tct('High volume [dataTypePlural].', {
+        dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.DB].toLocaleLowerCase(),
+      }),
+      t('Outlier database spans.'),
+    ],
+    imageSrc: queriesPreviewImg,
+  },
+  http: {
+    heading: t(
+      'Are your API dependencies working as well as their landing page promised? '
+    ),
+    description: t(
+      'See the outbound HTTP requests being made to internal and external APIs, allowing you to understand trends in status codes, latency, and throughput.'
+    ),
+    valuePropDescription: tct('[dataType] insights give you visibility into:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.HTTP],
+    }),
+    valuePropPoints: [
+      t('Anomalies in status codes by domain.'),
+      t('Request throughput by domain.'),
+      t('Average duration of requests.'),
+    ],
+    imageSrc: requestPreviewImg,
+  },
+  resource: {
+    heading: t('Is your favourite animated gif worth the time it takes to load?'),
+    description: tct(
+      'Find large and slow-to-load [dataTypePlurl] used by your application and understand their impact on page performance.',
+      {dataTypePlurl: MODULE_DATA_TYPES_PLURAL[ModuleName.RESOURCE].toLocaleLowerCase()}
+    ),
+    valuePropDescription: tct('[dataType] insights give you visibility into:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.RESOURCE],
+    }),
+    valuePropPoints: [
+      tct('[dataType] performance broken down by category and domain.', {
+        dataType: MODULE_DATA_TYPES[ModuleName.RESOURCE],
+      }),
+      tct(
+        'Which routes are loading [dataTypePlural], and whether they’re blocking rendering.',
+        {
+          dataTypePlural:
+            MODULE_DATA_TYPES_PLURAL[ModuleName.RESOURCE].toLocaleLowerCase(),
+        }
+      ),
+      tct('[dataType] size and whether it’s growing over time.', {
+        dataType: MODULE_DATA_TYPES[ModuleName.RESOURCE],
+      }),
+    ],
+    imageSrc: assetsPreviewImg,
+  },
+  vital: {
+    heading: t('Finally answer, is this page slow for everyone or just me?'),
+    description: t(
+      'Get industry standard metrics telling you the quality of user experience on a web page and see what needs improving.'
+    ),
+    valuePropDescription: tct('[dataType] insights give you visibility into:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.VITAL],
+    }),
+    valuePropPoints: [
+      t('Performance scores broken down by route.'),
+      t('Performance metrics for operations that affect screen load performance.'),
+      t('Drill down to real user sessions.'),
+    ],
+    imageSrc: webVitalsPreviewImg,
+  },
+  queue: {
+    heading: t('Ensure your background jobs aren’t being sent to /dev/null'),
+    description: tct(
+      'Understand the health and performance impact that [dataTypePlural] have on your application and diagnose errors tied to jobs.',
+      {
+        dataTypePlural: MODULE_DATA_TYPES_PLURAL[ModuleName.QUEUE].toLocaleLowerCase(),
+      }
+    ),
+    valuePropDescription: tct('[dataType] insights give you visibility into:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.QUEUE],
+    }),
+    valuePropPoints: [
+      t('Metrics for how long jobs spend processing and waiting in queue.'),
+      t('Job error rates and retry counts.'),
+      t('Published vs., processed job volume.'),
+    ],
+    imageSrc: queuesPreviewImg,
+  },
+  screen_load: {
+    heading: t('Perhaps 255 items was too large of a pagination size'),
+    description: tct(
+      'View the most active [dataTypePlural] in your mobile application and monitor your releases for screen load performance.',
+      {
+        dataTypePlural:
+          MODULE_DATA_TYPES_PLURAL[ModuleName.SCREEN_LOAD].toLocaleLowerCase(),
+      }
+    ),
+    valuePropDescription: tct('[dataType] insights include:', {
+      dataType: MODULE_DATA_TYPES[ModuleName.RESOURCE],
+    }),
+    valuePropPoints: [
+      t('Compare metrics across releases, root causing performance degradations.'),
+      t('See performance by device class.'),
+      t('Drill down to real user sessions.'),
+    ],
+    imageSrc: screenLoadsPreviewImg,
+  },
+};

+ 55 - 47
static/app/views/insights/database/views/databaseLandingPage.tsx

@@ -23,6 +23,7 @@ import useOrganization from 'sentry/utils/useOrganization';
 import {useSynchronizeCharts} from 'sentry/views/insights/common/components/chart';
 import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
 import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
+import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
 import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
 import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
 import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
@@ -192,55 +193,62 @@ export function DatabaseLandingPage() {
                 <DatePageFilter />
               </PageFilterBar>
             </ModuleLayout.Full>
-
-            {onboardingProject && (
-              <ModuleLayout.Full>
-                <Onboarding organization={organization} project={onboardingProject} />
-              </ModuleLayout.Full>
-            )}
-            {!onboardingProject && (
-              <Fragment>
-                <ModuleLayout.Half>
-                  <ThroughputChart
-                    series={throughputData['spm()']}
-                    isLoading={isThroughputDataLoading}
-                    error={throughputError}
-                  />
-                </ModuleLayout.Half>
-
-                <ModuleLayout.Half>
-                  <DurationChart
-                    series={[durationData[`${selectedAggregate}(span.self_time)`]]}
-                    isLoading={isDurationDataLoading}
-                    error={durationError}
-                  />
-                </ModuleLayout.Half>
-
-                <ModuleLayout.Full>
-                  <FilterOptionsContainer>
-                    <SelectorContainer>
-                      <ActionSelector moduleName={moduleName} value={spanAction ?? ''} />
-                    </SelectorContainer>
-
-                    <SelectorContainer>
-                      <DomainSelector moduleName={moduleName} value={spanDomain ?? ''} />
-                    </SelectorContainer>
-                  </FilterOptionsContainer>
-                </ModuleLayout.Full>
-
-                <ModuleLayout.Full>
-                  <SearchBar
-                    query={spanDescription}
-                    placeholder={t('Search for more Queries')}
-                    onSearch={handleSearch}
-                  />
-                </ModuleLayout.Full>
-
+            <ModulesOnboarding moduleName={ModuleName.DB}>
+              {onboardingProject && (
                 <ModuleLayout.Full>
-                  <QueriesTable response={queryListResponse} sort={sort} />
+                  <Onboarding organization={organization} project={onboardingProject} />
                 </ModuleLayout.Full>
-              </Fragment>
-            )}
+              )}
+              {!onboardingProject && (
+                <Fragment>
+                  <ModuleLayout.Half>
+                    <ThroughputChart
+                      series={throughputData['spm()']}
+                      isLoading={isThroughputDataLoading}
+                      error={throughputError}
+                    />
+                  </ModuleLayout.Half>
+
+                  <ModuleLayout.Half>
+                    <DurationChart
+                      series={[durationData[`${selectedAggregate}(span.self_time)`]]}
+                      isLoading={isDurationDataLoading}
+                      error={durationError}
+                    />
+                  </ModuleLayout.Half>
+
+                  <ModuleLayout.Full>
+                    <FilterOptionsContainer>
+                      <SelectorContainer>
+                        <ActionSelector
+                          moduleName={moduleName}
+                          value={spanAction ?? ''}
+                        />
+                      </SelectorContainer>
+
+                      <SelectorContainer>
+                        <DomainSelector
+                          moduleName={moduleName}
+                          value={spanDomain ?? ''}
+                        />
+                      </SelectorContainer>
+                    </FilterOptionsContainer>
+                  </ModuleLayout.Full>
+
+                  <ModuleLayout.Full>
+                    <SearchBar
+                      query={spanDescription}
+                      placeholder={t('Search for more Queries')}
+                      onSearch={handleSearch}
+                    />
+                  </ModuleLayout.Full>
+
+                  <ModuleLayout.Full>
+                    <QueriesTable response={queryListResponse} sort={sort} />
+                  </ModuleLayout.Full>
+                </Fragment>
+              )}
+            </ModulesOnboarding>
           </ModuleLayout.Layout>
         </Layout.Main>
       </Layout.Body>

+ 56 - 53
static/app/views/insights/http/views/httpLandingPage.tsx

@@ -21,6 +21,7 @@ import useOrganization from 'sentry/utils/useOrganization';
 import {useSynchronizeCharts} from 'sentry/views/insights/common/components/chart';
 import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
 import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
+import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
 import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
 import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
 import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
@@ -183,64 +184,66 @@ export function HTTPLandingPage() {
               </PageFilterBar>
             </ModuleLayout.Full>
 
-            {onboardingProject && (
-              <ModuleLayout.Full>
-                <Onboarding organization={organization} project={onboardingProject} />
-              </ModuleLayout.Full>
-            )}
+            <ModulesOnboarding moduleName={ModuleName.HTTP}>
+              {onboardingProject && (
+                <ModuleLayout.Full>
+                  <Onboarding organization={organization} project={onboardingProject} />
+                </ModuleLayout.Full>
+              )}
 
-            {!onboardingProject && (
-              <Fragment>
-                <ModuleLayout.Third>
-                  <ThroughputChart
-                    series={throughputData['spm()']}
-                    isLoading={isThroughputDataLoading}
-                    error={throughputError}
-                  />
-                </ModuleLayout.Third>
+              {!onboardingProject && (
+                <Fragment>
+                  <ModuleLayout.Third>
+                    <ThroughputChart
+                      series={throughputData['spm()']}
+                      isLoading={isThroughputDataLoading}
+                      error={throughputError}
+                    />
+                  </ModuleLayout.Third>
 
-                <ModuleLayout.Third>
-                  <DurationChart
-                    series={[durationData[`avg(span.self_time)`]]}
-                    isLoading={isDurationDataLoading}
-                    error={durationError}
-                  />
-                </ModuleLayout.Third>
+                  <ModuleLayout.Third>
+                    <DurationChart
+                      series={[durationData[`avg(span.self_time)`]]}
+                      isLoading={isDurationDataLoading}
+                      error={durationError}
+                    />
+                  </ModuleLayout.Third>
 
-                <ModuleLayout.Third>
-                  <ResponseRateChart
-                    series={[
-                      {
-                        ...responseCodeData[`http_response_rate(3)`],
-                        seriesName: t('3XX'),
-                      },
-                      {
-                        ...responseCodeData[`http_response_rate(4)`],
-                        seriesName: t('4XX'),
-                      },
-                      {
-                        ...responseCodeData[`http_response_rate(5)`],
-                        seriesName: t('5XX'),
-                      },
-                    ]}
-                    isLoading={isResponseCodeDataLoading}
-                    error={responseCodeError}
-                  />
-                </ModuleLayout.Third>
+                  <ModuleLayout.Third>
+                    <ResponseRateChart
+                      series={[
+                        {
+                          ...responseCodeData[`http_response_rate(3)`],
+                          seriesName: t('3XX'),
+                        },
+                        {
+                          ...responseCodeData[`http_response_rate(4)`],
+                          seriesName: t('4XX'),
+                        },
+                        {
+                          ...responseCodeData[`http_response_rate(5)`],
+                          seriesName: t('5XX'),
+                        },
+                      ]}
+                      isLoading={isResponseCodeDataLoading}
+                      error={responseCodeError}
+                    />
+                  </ModuleLayout.Third>
 
-                <ModuleLayout.Full>
-                  <SearchBar
-                    query={query['span.domain']}
-                    placeholder={t('Search for more domains')}
-                    onSearch={handleSearch}
-                  />
-                </ModuleLayout.Full>
+                  <ModuleLayout.Full>
+                    <SearchBar
+                      query={query['span.domain']}
+                      placeholder={t('Search for more domains')}
+                      onSearch={handleSearch}
+                    />
+                  </ModuleLayout.Full>
 
-                <ModuleLayout.Full>
-                  <DomainsTable response={domainsListResponse} sort={sort} />
-                </ModuleLayout.Full>
-              </Fragment>
-            )}
+                  <ModuleLayout.Full>
+                    <DomainsTable response={domainsListResponse} sort={sort} />
+                  </ModuleLayout.Full>
+                </Fragment>
+              )}
+            </ModulesOnboarding>
           </ModuleLayout.Layout>
         </Layout.Main>
       </Layout.Body>

+ 3 - 0
static/app/views/insights/llmMonitoring/settings.ts

@@ -4,6 +4,9 @@ import {t} from 'sentry/locale';
 export const MODULE_TITLE = t('LLM Monitoring');
 export const BASE_URL = 'llm-monitoring';
 
+export const DATA_TYPE = t('LLM');
+export const DATA_TYPE_PLURAL = t('LLMs');
+
 export const RELEASE_LEVEL: BadgeType = 'beta';
 
 export const releaseLevelAsBadgeProps = {

+ 2 - 0
static/app/views/insights/mobile/appStarts/settings.ts

@@ -2,6 +2,8 @@ import {t} from 'sentry/locale';
 
 export const MODULE_TITLE = t('App Starts');
 export const BASE_URL = 'mobile/app-startup';
+export const DATA_TYPE = t('App Start');
+export const DATA_TYPE_PLURAL = t('App Starts');
 
 export const MODULE_DESCRIPTION = t(
   'Improve the latency associated with your application starting up. '

+ 7 - 4
static/app/views/insights/mobile/common/components/screensTemplate.tsx

@@ -17,6 +17,7 @@ import {browserHistory} from 'sentry/utils/browserHistory';
 import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
+import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding';
 import {ReleaseComparisonSelector} from 'sentry/views/insights/common/components/releaseSelector';
 import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
 import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
@@ -93,10 +94,12 @@ export default function ScreensTemplate({
             </Container>
             <PageAlert />
             <ErrorBoundary mini>
-              {onboardingProject && (
-                <Onboarding organization={organization} project={onboardingProject} />
-              )}
-              {!onboardingProject && content}
+              <ModulesOnboarding moduleName={moduleName}>
+                {onboardingProject && (
+                  <Onboarding organization={organization} project={onboardingProject} />
+                )}
+                {!onboardingProject && content}
+              </ModulesOnboarding>
             </ErrorBoundary>
           </Layout.Main>
         </Layout.Body>

Some files were not shown because too many files changed in this diff