content.tsx 4.6 KB

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