webVitalsLandingPage.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import {Fragment, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import omit from 'lodash/omit';
  4. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  5. import Breadcrumbs from 'sentry/components/breadcrumbs';
  6. import {LinkButton} from 'sentry/components/button';
  7. import FeedbackWidget from 'sentry/components/feedback/widget/feedbackWidget';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  10. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  11. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  12. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  13. import {IconChevron} from 'sentry/icons';
  14. import {t} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import {useLocation} from 'sentry/utils/useLocation';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import useProjects from 'sentry/utils/useProjects';
  19. import useRouter from 'sentry/utils/useRouter';
  20. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  21. import WebVitalMeters from 'sentry/views/performance/browser/webVitals/components/webVitalMeters';
  22. import {PagePerformanceTable} from 'sentry/views/performance/browser/webVitals/pagePerformanceTable';
  23. import {PageSamplePerformanceTable} from 'sentry/views/performance/browser/webVitals/pageSamplePerformanceTable';
  24. import {PerformanceScoreChart} from 'sentry/views/performance/browser/webVitals/performanceScoreChart';
  25. import {calculatePerformanceScoreFromTableDataRow} from 'sentry/views/performance/browser/webVitals/utils/calculatePerformanceScore';
  26. import {WebVitals} from 'sentry/views/performance/browser/webVitals/utils/types';
  27. import {useOnboardingProject} from 'sentry/views/performance/browser/webVitals/utils/useOnboardingProject';
  28. import {useProjectWebVitalsQuery} from 'sentry/views/performance/browser/webVitals/utils/useProjectWebVitalsQuery';
  29. import {WebVitalsDetailPanel} from 'sentry/views/performance/browser/webVitals/webVitalsDetailPanel';
  30. import {ModulePageProviders} from 'sentry/views/performance/database/modulePageProviders';
  31. import Onboarding from 'sentry/views/performance/onboarding';
  32. export default function WebVitalsLandingPage() {
  33. const organization = useOrganization();
  34. const location = useLocation();
  35. const {projects} = useProjects();
  36. const onboardingProject = useOnboardingProject();
  37. const router = useRouter();
  38. const transaction = location.query.transaction
  39. ? Array.isArray(location.query.transaction)
  40. ? location.query.transaction[0]
  41. : location.query.transaction
  42. : undefined;
  43. const project = useMemo(
  44. () => projects.find(p => p.id === String(location.query.project)),
  45. [projects, location.query.project]
  46. );
  47. const [state, setState] = useState<{webVital: WebVitals | null}>({
  48. webVital: (location.query.webVital as WebVitals) ?? null,
  49. });
  50. const {data: projectData, isLoading} = useProjectWebVitalsQuery({transaction});
  51. const noTransactions = !isLoading && !projectData?.data?.[0]['count()'];
  52. const projectScore =
  53. isLoading || noTransactions
  54. ? undefined
  55. : calculatePerformanceScoreFromTableDataRow(projectData?.data?.[0]);
  56. return (
  57. <ModulePageProviders title={[t('Performance'), t('Web Vitals')].join(' — ')}>
  58. <Layout.Header>
  59. <Layout.HeaderContent>
  60. <Breadcrumbs
  61. crumbs={[
  62. {
  63. label: 'Performance',
  64. to: normalizeUrl(`/organizations/${organization.slug}/performance/`),
  65. preservePageFilters: true,
  66. },
  67. {
  68. label: 'Web Vitals',
  69. },
  70. ...(transaction ? [{label: 'Page Overview'}] : []),
  71. ]}
  72. />
  73. <Layout.Title>
  74. {transaction && project && <ProjectAvatar project={project} size={24} />}
  75. {transaction ?? t('Web Vitals')}
  76. </Layout.Title>
  77. </Layout.HeaderContent>
  78. </Layout.Header>
  79. <Layout.Body>
  80. <FeedbackWidget />
  81. <Layout.Main fullWidth>
  82. <TopMenuContainer>
  83. {transaction && (
  84. <ViewAllPagesButton
  85. to={{...location, query: {...location.query, transaction: undefined}}}
  86. >
  87. <IconChevron direction="left" /> {t('View All Pages')}
  88. </ViewAllPagesButton>
  89. )}
  90. <PageFilterBar condensed>
  91. <ProjectPageFilter />
  92. <EnvironmentPageFilter />
  93. <DatePageFilter />
  94. </PageFilterBar>
  95. </TopMenuContainer>
  96. {onboardingProject && (
  97. <OnboardingContainer>
  98. <Onboarding organization={organization} project={onboardingProject} />
  99. </OnboardingContainer>
  100. )}
  101. {!onboardingProject && (
  102. <Fragment>
  103. <PerformanceScoreChartContainer>
  104. <PerformanceScoreChart
  105. projectScore={projectScore}
  106. transaction={transaction}
  107. isProjectScoreLoading={isLoading}
  108. webVital={state.webVital}
  109. />
  110. </PerformanceScoreChartContainer>
  111. <WebVitalMeters
  112. projectData={projectData}
  113. projectScore={projectScore}
  114. onClick={webVital => setState({...state, webVital})}
  115. transaction={transaction}
  116. />
  117. {!transaction && <PagePerformanceTable />}
  118. {transaction && <PageSamplePerformanceTable transaction={transaction} />}
  119. </Fragment>
  120. )}
  121. </Layout.Main>
  122. </Layout.Body>
  123. <WebVitalsDetailPanel
  124. webVital={state.webVital}
  125. onClose={() => {
  126. router.replace({
  127. pathname: router.location.pathname,
  128. query: omit(router.location.query, 'webVital'),
  129. });
  130. setState({...state, webVital: null});
  131. }}
  132. />
  133. </ModulePageProviders>
  134. );
  135. }
  136. const ViewAllPagesButton = styled(LinkButton)`
  137. margin-right: ${space(1)};
  138. `;
  139. const TopMenuContainer = styled('div')`
  140. display: flex;
  141. `;
  142. const PerformanceScoreChartContainer = styled('div')`
  143. margin-bottom: ${space(1)};
  144. `;
  145. const OnboardingContainer = styled('div')`
  146. margin-top: ${space(2)};
  147. `;