content.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {browserHistory} from 'react-router';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import first from 'lodash/first';
  6. import omit from 'lodash/omit';
  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 PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  13. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  14. import Pagination from 'sentry/components/pagination';
  15. import {t} from 'sentry/locale';
  16. import space from 'sentry/styles/space';
  17. import type {Organization} from 'sentry/types';
  18. import {defined} from 'sentry/utils';
  19. import EventView from 'sentry/utils/discover/eventView';
  20. import useMedia from 'sentry/utils/useMedia';
  21. import ReplayTable from 'sentry/views/replays/replayTable';
  22. import {ReplayColumns} from 'sentry/views/replays/replayTable/types';
  23. import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
  24. import type {SpanOperationBreakdownFilter} from '../filter';
  25. import {
  26. EventsDisplayFilterName,
  27. getEventsFilterOptions,
  28. PercentileValues,
  29. } from '../transactionEvents/utils';
  30. import type {ReplayListRecordWithTx} from './useReplaysFromTransaction';
  31. type Props = {
  32. eventView: EventView;
  33. eventsDisplayFilterName: EventsDisplayFilterName;
  34. isFetching: boolean;
  35. location: Location<ReplayListLocationQuery>;
  36. organization: Organization;
  37. pageLinks: string | null;
  38. replays: ReplayListRecordWithTx[];
  39. spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
  40. percentileValues?: PercentileValues;
  41. };
  42. function ReplaysContent({
  43. eventView,
  44. eventsDisplayFilterName,
  45. isFetching,
  46. location,
  47. organization,
  48. pageLinks,
  49. replays,
  50. spanOperationBreakdownFilter,
  51. percentileValues,
  52. }: Props) {
  53. const query = location.query;
  54. const theme = useTheme();
  55. const hasRoomForColumns = useMedia(`(min-width: ${theme.breakpoints.small})`);
  56. const eventsFilterOptions = getEventsFilterOptions(
  57. spanOperationBreakdownFilter,
  58. percentileValues
  59. );
  60. function handleChange(key: string) {
  61. return function (value: string | undefined) {
  62. const queryParams = normalizeDateTimeParams({
  63. ...(location.query || {}),
  64. [key]: value,
  65. });
  66. // do not propagate pagination when making a new search
  67. const toOmit = ['cursor'];
  68. if (!defined(value)) {
  69. toOmit.push(key);
  70. }
  71. const searchQueryParams = omit(queryParams, toOmit);
  72. browserHistory.push({
  73. ...location,
  74. query: searchQueryParams,
  75. });
  76. };
  77. }
  78. const handleEventDisplayFilterChange = (newFilterName: EventsDisplayFilterName) => {
  79. const nextQuery: Location['query'] = {
  80. ...location.query,
  81. showTransactions: newFilterName,
  82. };
  83. if (newFilterName === EventsDisplayFilterName.p100) {
  84. delete nextQuery.showTransaction;
  85. }
  86. browserHistory.push({
  87. pathname: location.pathname,
  88. query: nextQuery,
  89. });
  90. };
  91. return (
  92. <Layout.Main fullWidth>
  93. <FilterActions>
  94. <PageFilterBar condensed>
  95. <EnvironmentPageFilter />
  96. <DatePageFilter alignDropdown="left" />
  97. </PageFilterBar>
  98. <StyledSearchBar
  99. organization={organization}
  100. projectIds={eventView.project}
  101. query={query.query}
  102. fields={eventView.fields}
  103. onSearch={handleChange('query')}
  104. />
  105. <PercentileSelect
  106. triggerProps={{prefix: t('Percentile')}}
  107. value={eventsDisplayFilterName}
  108. onChange={opt => handleEventDisplayFilterChange(opt.value)}
  109. options={Object.entries(eventsFilterOptions).map(([name, filter]) => ({
  110. value: name as EventsDisplayFilterName,
  111. label: filter.label,
  112. }))}
  113. />
  114. </FilterActions>
  115. <ReplayTable
  116. fetchError={undefined}
  117. isFetching={isFetching}
  118. replays={replays}
  119. sort={first(eventView.sorts) || {field: 'startedAt', kind: 'asc'}}
  120. visibleColumns={[
  121. ReplayColumns.session,
  122. ...(hasRoomForColumns
  123. ? [ReplayColumns.slowestTransaction, ReplayColumns.startedAt]
  124. : []),
  125. ReplayColumns.duration,
  126. ReplayColumns.countErrors,
  127. ReplayColumns.activity,
  128. ]}
  129. />
  130. <Pagination pageLinks={pageLinks} />
  131. </Layout.Main>
  132. );
  133. }
  134. const FilterActions = styled('div')`
  135. display: grid;
  136. gap: ${space(2)};
  137. margin-bottom: ${space(2)};
  138. grid-template-columns: repeat(2, 1fr);
  139. @media (min-width: ${p => p.theme.breakpoints.small}) {
  140. grid-template-columns: auto 1fr auto;
  141. }
  142. `;
  143. const PercentileSelect = styled(CompactSelect)`
  144. order: 2;
  145. justify-self: flex-end;
  146. @media (min-width: ${p => p.theme.breakpoints.small}) {
  147. order: 3;
  148. }
  149. `;
  150. const StyledSearchBar = styled(SearchBar)`
  151. order: 3;
  152. grid-column: span 2;
  153. @media (min-width: ${p => p.theme.breakpoints.small}) {
  154. order: 2;
  155. grid-column: span 1;
  156. }
  157. `;
  158. export default ReplaysContent;