webVitalsLandingPage.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import {useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  4. import Breadcrumbs from 'sentry/components/breadcrumbs';
  5. import {LinkButton} from 'sentry/components/button';
  6. import FeatureBadge from 'sentry/components/featureBadge';
  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 PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  11. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  12. import {IconChevron} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import useProjects from 'sentry/utils/useProjects';
  18. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  19. import WebVitalMeters from 'sentry/views/performance/browser/webVitals/components/webVitalMeters';
  20. import {PagePerformanceTable} from 'sentry/views/performance/browser/webVitals/pagePerformanceTable';
  21. import {PageSamplePerformanceTable} from 'sentry/views/performance/browser/webVitals/pageSamplePerformanceTable';
  22. import {PerformanceScoreChart} from 'sentry/views/performance/browser/webVitals/performanceScoreChart';
  23. import {calculatePerformanceScore} from 'sentry/views/performance/browser/webVitals/utils/calculatePerformanceScore';
  24. import {WebVitals} from 'sentry/views/performance/browser/webVitals/utils/types';
  25. import {useProjectWebVitalsQuery} from 'sentry/views/performance/browser/webVitals/utils/useProjectWebVitalsQuery';
  26. import {WebVitalsDetailPanel} from 'sentry/views/performance/browser/webVitals/webVitalsDetailPanel';
  27. import {ModulePageProviders} from 'sentry/views/performance/database/modulePageProviders';
  28. export default function WebVitalsLandingPage() {
  29. const organization = useOrganization();
  30. const location = useLocation();
  31. const {projects} = useProjects();
  32. const transaction = location.query.transaction
  33. ? Array.isArray(location.query.transaction)
  34. ? location.query.transaction[0]
  35. : location.query.transaction
  36. : undefined;
  37. const project = useMemo(
  38. () => projects.find(p => p.id === String(location.query.project)),
  39. [projects, location.query.project]
  40. );
  41. const [state, setState] = useState<{webVital: WebVitals | null}>({
  42. webVital: null,
  43. });
  44. const {data: projectData, isLoading} = useProjectWebVitalsQuery({transaction});
  45. const noTransactions = !isLoading && projectData?.data[0]['count()'] === 0;
  46. const projectScore =
  47. isLoading || noTransactions
  48. ? undefined
  49. : calculatePerformanceScore({
  50. lcp: projectData?.data[0]['p75(measurements.lcp)'] as number,
  51. fcp: projectData?.data[0]['p75(measurements.fcp)'] as number,
  52. cls: projectData?.data[0]['p75(measurements.cls)'] as number,
  53. ttfb: projectData?.data[0]['p75(measurements.ttfb)'] as number,
  54. fid: projectData?.data[0]['p75(measurements.fid)'] as number,
  55. });
  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. <FeatureBadge type="alpha" />
  77. </Layout.Title>
  78. </Layout.HeaderContent>
  79. </Layout.Header>
  80. <Layout.Body>
  81. <FeedbackWidget />
  82. <Layout.Main fullWidth>
  83. <TopMenuContainer>
  84. {transaction && (
  85. <ViewAllPagesButton
  86. to={{...location, query: {...location.query, transaction: undefined}}}
  87. >
  88. <IconChevron direction="left" /> {t('View All Pages')}
  89. </ViewAllPagesButton>
  90. )}
  91. <PageFilterBar condensed>
  92. <ProjectPageFilter />
  93. <DatePageFilter />
  94. </PageFilterBar>
  95. </TopMenuContainer>
  96. <PerformanceScoreChartContainer>
  97. <PerformanceScoreChart
  98. projectScore={projectScore}
  99. transaction={transaction}
  100. isProjectScoreLoading={isLoading}
  101. webVital={state.webVital}
  102. />
  103. </PerformanceScoreChartContainer>
  104. <WebVitalMeters
  105. projectData={projectData}
  106. projectScore={projectScore}
  107. onClick={webVital => setState({...state, webVital})}
  108. transaction={transaction}
  109. />
  110. {!transaction && <PagePerformanceTable />}
  111. {transaction && <PageSamplePerformanceTable transaction={transaction} />}
  112. </Layout.Main>
  113. </Layout.Body>
  114. <WebVitalsDetailPanel
  115. webVital={state.webVital}
  116. onClose={() => {
  117. setState({...state, webVital: null});
  118. }}
  119. />
  120. </ModulePageProviders>
  121. );
  122. }
  123. const ViewAllPagesButton = styled(LinkButton)`
  124. margin-right: ${space(1)};
  125. `;
  126. const TopMenuContainer = styled('div')`
  127. display: flex;
  128. `;
  129. const PerformanceScoreChartContainer = styled('div')`
  130. margin-bottom: ${space(1)};
  131. `;