replays.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {Fragment, useMemo} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import {useTheme} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import DetailedError from 'sentry/components/errors/detailedError';
  6. import List from 'sentry/components/list';
  7. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  8. import PageHeading from 'sentry/components/pageHeading';
  9. import Pagination from 'sentry/components/pagination';
  10. import ReplaysFeatureBadge from 'sentry/components/replays/replaysFeatureBadge';
  11. import {t} from 'sentry/locale';
  12. import {PageContent, PageHeader} from 'sentry/styles/organization';
  13. import space from 'sentry/styles/space';
  14. import EventView from 'sentry/utils/discover/eventView';
  15. import {decodeScalar} from 'sentry/utils/queryString';
  16. import {DEFAULT_SORT, REPLAY_LIST_FIELDS} from 'sentry/utils/replays/fetchReplayList';
  17. import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
  18. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  19. import useMedia from 'sentry/utils/useMedia';
  20. import useOrganization from 'sentry/utils/useOrganization';
  21. import ReplaysFilters from 'sentry/views/replays/filters';
  22. import ReplayTable from 'sentry/views/replays/replayTable';
  23. import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
  24. type Props = RouteComponentProps<{orgId: string}, {}, any, ReplayListLocationQuery>;
  25. function Replays({location}: Props) {
  26. const organization = useOrganization();
  27. const theme = useTheme();
  28. const minWidthIsSmall = useMedia(`(min-width: ${theme.breakpoints.small})`);
  29. const eventView = useMemo(() => {
  30. const query = decodeScalar(location.query.query, '');
  31. const conditions = new MutableSearch(query);
  32. return EventView.fromNewQueryWithLocation(
  33. {
  34. id: '',
  35. name: '',
  36. version: 2,
  37. fields: REPLAY_LIST_FIELDS,
  38. projects: [],
  39. query: conditions.formatString(),
  40. orderby: decodeScalar(location.query.sort, DEFAULT_SORT),
  41. },
  42. location
  43. );
  44. }, [location]);
  45. const {pathname, query} = location;
  46. const {replays, pageLinks, isFetching, fetchError} = useReplayList({
  47. organization,
  48. eventView,
  49. });
  50. if (fetchError && !isFetching) {
  51. const reasons = [
  52. t('The search parameters you selected are invalid in some way'),
  53. t('There is an internal systems error or active issue'),
  54. ];
  55. return (
  56. <DetailedError
  57. hideSupportLinks
  58. heading={t('Sorry, the list of replays could not be found.')}
  59. message={
  60. <div>
  61. <p>{t('This could be due to a handful of reasons:')}</p>
  62. <List symbol="bullet">
  63. {reasons.map((reason, i) => (
  64. <li key={i}>{reason}</li>
  65. ))}
  66. </List>
  67. </div>
  68. }
  69. />
  70. );
  71. }
  72. return (
  73. <Fragment>
  74. <StyledPageHeader>
  75. <HeaderTitle>
  76. <div>
  77. {t('Replays')} <ReplaysFeatureBadge />
  78. </div>
  79. </HeaderTitle>
  80. </StyledPageHeader>
  81. <PageFiltersContainer>
  82. <StyledPageContent>
  83. <ReplaysFilters
  84. query={query.query || ''}
  85. organization={organization}
  86. handleSearchQuery={searchQuery => {
  87. browserHistory.push({
  88. pathname,
  89. query: {
  90. ...query,
  91. cursor: undefined,
  92. query: searchQuery.trim(),
  93. },
  94. });
  95. }}
  96. />
  97. <ReplayTable
  98. isFetching={isFetching}
  99. replays={replays}
  100. showProjectColumn={minWidthIsSmall}
  101. sort={eventView.sorts[0]}
  102. />
  103. <Pagination
  104. pageLinks={pageLinks}
  105. onCursor={(cursor, path, searchQuery) => {
  106. browserHistory.push({
  107. pathname: path,
  108. query: {...searchQuery, cursor},
  109. });
  110. }}
  111. />
  112. </StyledPageContent>
  113. </PageFiltersContainer>
  114. </Fragment>
  115. );
  116. }
  117. const StyledPageHeader = styled(PageHeader)`
  118. background-color: ${p => p.theme.surface100};
  119. min-width: max-content;
  120. margin: ${space(3)} ${space(0)} ${space(4)} ${space(4)};
  121. `;
  122. const StyledPageContent = styled(PageContent)`
  123. box-shadow: 0px 0px 1px ${p => p.theme.gray200};
  124. background-color: ${p => p.theme.background};
  125. `;
  126. const HeaderTitle = styled(PageHeading)`
  127. display: flex;
  128. align-items: center;
  129. justify-content: space-between;
  130. flex: 1;
  131. `;
  132. export default Replays;