content.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import {browserHistory} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import {Location} from 'history';
  4. import omit from 'lodash/omit';
  5. import Button from 'sentry/components/button';
  6. import CompactSelect from 'sentry/components/compactSelect';
  7. import DatePageFilter from 'sentry/components/datePageFilter';
  8. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  9. import SearchBar from 'sentry/components/events/searchBar';
  10. import * as Layout from 'sentry/components/layouts/thirds';
  11. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  12. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  13. import {t} from 'sentry/locale';
  14. import space from 'sentry/styles/space';
  15. import {Organization} from 'sentry/types';
  16. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  17. import EventView from 'sentry/utils/discover/eventView';
  18. import {WebVital} from 'sentry/utils/fields';
  19. import {decodeScalar} from 'sentry/utils/queryString';
  20. import {useRoutes} from 'sentry/utils/useRoutes';
  21. import Filter, {filterToSearchConditions, SpanOperationBreakdownFilter} from '../filter';
  22. import {SetStateAction} from '../types';
  23. import EventsTable from './eventsTable';
  24. import {EventsDisplayFilterName, getEventsFilterOptions} from './utils';
  25. type Props = {
  26. eventView: EventView;
  27. eventsDisplayFilterName: EventsDisplayFilterName;
  28. location: Location;
  29. onChangeEventsDisplayFilter: (eventsDisplayFilterName: EventsDisplayFilterName) => void;
  30. onChangeSpanOperationBreakdownFilter: (newFilter: SpanOperationBreakdownFilter) => void;
  31. organization: Organization;
  32. setError: SetStateAction<string | undefined>;
  33. spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
  34. totalEventCount: string;
  35. transactionName: string;
  36. percentileValues?: Record<EventsDisplayFilterName, number>;
  37. webVital?: WebVital;
  38. };
  39. export const TRANSACTIONS_LIST_TITLES: Readonly<string[]> = [
  40. t('event id'),
  41. t('user'),
  42. t('operation duration'),
  43. t('total duration'),
  44. t('trace id'),
  45. t('timestamp'),
  46. ];
  47. function EventsContent(props: Props) {
  48. const {
  49. location,
  50. organization,
  51. eventView: originalEventView,
  52. transactionName,
  53. spanOperationBreakdownFilter,
  54. webVital,
  55. setError,
  56. totalEventCount,
  57. } = props;
  58. const routes = useRoutes();
  59. const eventView = originalEventView.clone();
  60. const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice();
  61. if (webVital) {
  62. transactionsListTitles.splice(3, 0, webVital);
  63. }
  64. const spanOperationBreakdownConditions = filterToSearchConditions(
  65. spanOperationBreakdownFilter,
  66. location
  67. );
  68. if (spanOperationBreakdownConditions) {
  69. eventView.query = `${eventView.query} ${spanOperationBreakdownConditions}`.trim();
  70. transactionsListTitles.splice(2, 1, t('%s duration', spanOperationBreakdownFilter));
  71. }
  72. if (organization.features.includes('session-replay-ui')) {
  73. transactionsListTitles.push(t('replay'));
  74. }
  75. return (
  76. <Layout.Main fullWidth>
  77. <Search {...props} />
  78. <EventsTable
  79. totalEventCount={totalEventCount}
  80. eventView={eventView}
  81. organization={organization}
  82. routes={routes}
  83. location={location}
  84. setError={setError}
  85. columnTitles={transactionsListTitles}
  86. transactionName={transactionName}
  87. />
  88. </Layout.Main>
  89. );
  90. }
  91. function Search(props: Props) {
  92. const {
  93. eventView,
  94. location,
  95. organization,
  96. spanOperationBreakdownFilter,
  97. onChangeSpanOperationBreakdownFilter,
  98. eventsDisplayFilterName,
  99. onChangeEventsDisplayFilter,
  100. percentileValues,
  101. } = props;
  102. const handleSearch = (query: string) => {
  103. const queryParams = normalizeDateTimeParams({
  104. ...(location.query || {}),
  105. query,
  106. });
  107. // do not propagate pagination when making a new search
  108. const searchQueryParams = omit(queryParams, 'cursor');
  109. browserHistory.push({
  110. pathname: location.pathname,
  111. query: searchQueryParams,
  112. });
  113. };
  114. const query = decodeScalar(location.query.query, '');
  115. const eventsFilterOptions = getEventsFilterOptions(
  116. spanOperationBreakdownFilter,
  117. percentileValues
  118. );
  119. const handleDiscoverButtonClick = () => {
  120. trackAdvancedAnalyticsEvent('performance_views.all_events.open_in_discover', {
  121. organization,
  122. });
  123. };
  124. return (
  125. <FilterActions>
  126. <Filter
  127. organization={organization}
  128. currentFilter={spanOperationBreakdownFilter}
  129. onChangeFilter={onChangeSpanOperationBreakdownFilter}
  130. />
  131. <PageFilterBar condensed>
  132. <EnvironmentPageFilter />
  133. <DatePageFilter alignDropdown="left" />
  134. </PageFilterBar>
  135. <StyledSearchBar
  136. organization={organization}
  137. projectIds={eventView.project}
  138. query={query}
  139. fields={eventView.fields}
  140. onSearch={handleSearch}
  141. />
  142. <CompactSelect
  143. triggerProps={{prefix: t('Percentile')}}
  144. value={eventsDisplayFilterName}
  145. onChange={opt => onChangeEventsDisplayFilter(opt.value)}
  146. options={Object.entries(eventsFilterOptions).map(([name, filter]) => ({
  147. value: name as EventsDisplayFilterName,
  148. label: filter.label,
  149. }))}
  150. />
  151. <Button
  152. to={eventView.getResultsViewUrlTarget(organization.slug)}
  153. onClick={handleDiscoverButtonClick}
  154. >
  155. {t('Open in Discover')}
  156. </Button>
  157. </FilterActions>
  158. );
  159. }
  160. const FilterActions = styled('div')`
  161. display: grid;
  162. gap: ${space(2)};
  163. margin-bottom: ${space(2)};
  164. @media (min-width: ${p => p.theme.breakpoints.small}) {
  165. grid-template-columns: repeat(4, min-content);
  166. }
  167. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  168. grid-template-columns: auto auto 1fr auto auto;
  169. }
  170. `;
  171. const StyledSearchBar = styled(SearchBar)`
  172. @media (min-width: ${p => p.theme.breakpoints.small}) {
  173. order: 1;
  174. grid-column: 1/6;
  175. }
  176. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  177. order: initial;
  178. grid-column: auto;
  179. }
  180. `;
  181. export default EventsContent;