content.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {Alert} from 'sentry/components/alert';
  5. import {Button} from 'sentry/components/button';
  6. import {CompactSelect} from 'sentry/components/compactSelect';
  7. import * as Layout from 'sentry/components/layouts/thirds';
  8. import ExternalLink from 'sentry/components/links/externalLink';
  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 {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  13. import {TransactionSearchQueryBuilder} from 'sentry/components/performance/transactionSearchQueryBuilder';
  14. import {t, tct} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import type {Organization} from 'sentry/types/organization';
  17. import {trackAnalytics} from 'sentry/utils/analytics';
  18. import type EventView from 'sentry/utils/discover/eventView';
  19. import type {WebVital} from 'sentry/utils/fields';
  20. import Histogram from 'sentry/utils/performance/histogram';
  21. import {FILTER_OPTIONS} from 'sentry/utils/performance/histogram/constants';
  22. import VitalsCardsDiscoverQuery from 'sentry/utils/performance/vitals/vitalsCardsDiscoverQuery';
  23. import {decodeScalar} from 'sentry/utils/queryString';
  24. import {useNavigate} from 'sentry/utils/useNavigate';
  25. import {VITAL_GROUPS, ZOOM_KEYS} from './constants';
  26. import {isMissingVitalsData} from './utils';
  27. import VitalsPanel from './vitalsPanel';
  28. type Props = {
  29. eventView: EventView;
  30. location: Location;
  31. organization: Organization;
  32. };
  33. function VitalsContent(props: Props) {
  34. const {location, organization, eventView} = props;
  35. const navigate = useNavigate();
  36. const query = decodeScalar(location.query.query, '');
  37. const handleSearch = (newQuery: string) => {
  38. const queryParams = normalizeDateTimeParams({
  39. ...(location.query || {}),
  40. query: newQuery,
  41. });
  42. // do not propagate pagination when making a new search
  43. delete queryParams.cursor;
  44. navigate({
  45. pathname: location.pathname,
  46. query: queryParams,
  47. });
  48. };
  49. const allVitals = VITAL_GROUPS.reduce((keys: WebVital[], {vitals}) => {
  50. return keys.concat(vitals);
  51. }, []);
  52. return (
  53. <Histogram location={location} zoomKeys={ZOOM_KEYS}>
  54. {({activeFilter, handleFilterChange, handleResetView, isZoomed}) => (
  55. <Layout.Main fullWidth>
  56. <VitalsCardsDiscoverQuery
  57. eventView={eventView}
  58. orgSlug={organization.slug}
  59. location={location}
  60. vitals={allVitals}
  61. >
  62. {results => {
  63. const shouldDisplayMissingVitalsAlert =
  64. !results.isLoading && isMissingVitalsData(results.vitalsData, allVitals);
  65. return (
  66. <Fragment>
  67. {shouldDisplayMissingVitalsAlert && (
  68. <Alert type="info" showIcon>
  69. {tct(
  70. 'If this page is looking a little bare, keep in mind not all browsers support these vitals. [link]',
  71. {
  72. link: (
  73. <ExternalLink href="https://docs.sentry.io/product/performance/web-vitals/#browser-support">
  74. {t('Read more about browser support.')}
  75. </ExternalLink>
  76. ),
  77. }
  78. )}
  79. </Alert>
  80. )}
  81. <FilterActions>
  82. <PageFilterBar condensed>
  83. <EnvironmentPageFilter />
  84. <DatePageFilter />
  85. </PageFilterBar>
  86. <StyledSearchBarWrapper>
  87. <TransactionSearchQueryBuilder
  88. projects={eventView.project}
  89. initialQuery={query}
  90. onSearch={handleSearch}
  91. searchSource="transaction_events"
  92. />
  93. </StyledSearchBarWrapper>
  94. <CompactSelect
  95. value={activeFilter.value}
  96. options={FILTER_OPTIONS}
  97. onChange={opt => {
  98. trackAnalytics('performance_views.vitals.filter_changed', {
  99. organization,
  100. value: opt.value,
  101. });
  102. handleFilterChange(opt.value);
  103. }}
  104. triggerProps={{prefix: t('Outliers')}}
  105. triggerLabel={activeFilter.label}
  106. />
  107. <Button
  108. onClick={() => {
  109. trackAnalytics('performance_views.vitals.reset_view', {
  110. organization,
  111. });
  112. handleResetView();
  113. }}
  114. disabled={!isZoomed}
  115. data-test-id="reset-view"
  116. >
  117. {t('Reset View')}
  118. </Button>
  119. </FilterActions>
  120. <VitalsPanel
  121. organization={organization}
  122. location={location}
  123. eventView={eventView}
  124. dataFilter={activeFilter.value}
  125. results={results}
  126. />
  127. </Fragment>
  128. );
  129. }}
  130. </VitalsCardsDiscoverQuery>
  131. </Layout.Main>
  132. )}
  133. </Histogram>
  134. );
  135. }
  136. const FilterActions = styled('div')`
  137. display: grid;
  138. gap: ${space(2)};
  139. margin-bottom: ${space(2)};
  140. @media (min-width: ${p => p.theme.breakpoints.small}) {
  141. grid-template-columns: repeat(3, min-content);
  142. }
  143. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  144. grid-template-columns: auto 1fr auto auto;
  145. }
  146. `;
  147. const StyledSearchBarWrapper = styled('div')`
  148. @media (min-width: ${p => p.theme.breakpoints.small}) {
  149. order: 1;
  150. grid-column: 1/6;
  151. }
  152. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  153. order: initial;
  154. grid-column: auto;
  155. }
  156. `;
  157. export default VitalsContent;