content.tsx 6.0 KB

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