webVitalsLandingPage.tsx 5.5 KB

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