content.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Fragment} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {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 DatePageFilter from 'sentry/components/datePageFilter';
  9. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  10. import SearchBar from 'sentry/components/events/searchBar';
  11. import * as Layout from 'sentry/components/layouts/thirds';
  12. import ExternalLink from 'sentry/components/links/externalLink';
  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 {Organization} from 'sentry/types';
  18. import {trackAnalyticsEvent} from 'sentry/utils/analytics';
  19. import EventView from 'sentry/utils/discover/eventView';
  20. import {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 alignDropdown="left" />
  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. trackAnalyticsEvent({
  97. eventKey: 'performance_views.vitals.filter_changed',
  98. eventName: 'Performance Views: Change vitals filter',
  99. organization_id: organization.id,
  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. trackAnalyticsEvent({
  110. eventKey: 'performance_views.vitals.reset_view',
  111. eventName: 'Performance Views: Reset vitals view',
  112. organization_id: organization.id,
  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 StyledSearchBar = styled(SearchBar)`
  150. @media (min-width: ${p => p.theme.breakpoints.small}) {
  151. order: 1;
  152. grid-column: 1/5;
  153. }
  154. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  155. order: initial;
  156. grid-column: auto;
  157. }
  158. `;
  159. export default VitalsContent;