content.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {Button} from 'sentry/components/button';
  5. import {CompactSelect} from 'sentry/components/compactSelect';
  6. import {Alert} from 'sentry/components/core/alert';
  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.Container>
  69. <Alert type="info" showIcon>
  70. {tct(
  71. 'If this page is looking a little bare, keep in mind not all browsers support these vitals. [link]',
  72. {
  73. link: (
  74. <ExternalLink href="https://docs.sentry.io/product/performance/web-vitals/#browser-support">
  75. {t('Read more about browser support.')}
  76. </ExternalLink>
  77. ),
  78. }
  79. )}
  80. </Alert>
  81. </Alert.Container>
  82. )}
  83. <FilterActions>
  84. <PageFilterBar condensed>
  85. <EnvironmentPageFilter />
  86. <DatePageFilter />
  87. </PageFilterBar>
  88. <StyledSearchBarWrapper>
  89. <TransactionSearchQueryBuilder
  90. projects={eventView.project}
  91. initialQuery={query}
  92. onSearch={handleSearch}
  93. searchSource="transaction_events"
  94. />
  95. </StyledSearchBarWrapper>
  96. <CompactSelect
  97. value={activeFilter.value}
  98. options={FILTER_OPTIONS}
  99. onChange={opt => {
  100. trackAnalytics('performance_views.vitals.filter_changed', {
  101. organization,
  102. value: opt.value,
  103. });
  104. handleFilterChange(opt.value);
  105. }}
  106. triggerProps={{prefix: t('Outliers')}}
  107. triggerLabel={activeFilter.label}
  108. />
  109. <Button
  110. onClick={() => {
  111. trackAnalytics('performance_views.vitals.reset_view', {
  112. organization,
  113. });
  114. handleResetView();
  115. }}
  116. disabled={!isZoomed}
  117. data-test-id="reset-view"
  118. >
  119. {t('Reset View')}
  120. </Button>
  121. </FilterActions>
  122. <VitalsPanel
  123. organization={organization}
  124. location={location}
  125. eventView={eventView}
  126. dataFilter={activeFilter.value}
  127. results={results}
  128. />
  129. </Fragment>
  130. );
  131. }}
  132. </VitalsCardsDiscoverQuery>
  133. </Layout.Main>
  134. )}
  135. </Histogram>
  136. );
  137. }
  138. const FilterActions = styled('div')`
  139. display: grid;
  140. gap: ${space(2)};
  141. margin-bottom: ${space(2)};
  142. @media (min-width: ${p => p.theme.breakpoints.small}) {
  143. grid-template-columns: repeat(3, min-content);
  144. }
  145. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  146. grid-template-columns: auto 1fr auto auto;
  147. }
  148. `;
  149. const StyledSearchBarWrapper = styled('div')`
  150. @media (min-width: ${p => p.theme.breakpoints.small}) {
  151. order: 1;
  152. grid-column: 1/6;
  153. }
  154. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  155. order: initial;
  156. grid-column: auto;
  157. }
  158. `;
  159. export default VitalsContent;