index.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import type {ReactNode} from 'react';
  2. import {Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import {Alert} from 'sentry/components/alert';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import PanelTable from 'sentry/components/panels/panelTable';
  7. import {t} from 'sentry/locale';
  8. import EventView from 'sentry/utils/discover/eventView';
  9. import type {Sort} from 'sentry/utils/discover/fields';
  10. import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import {useRoutes} from 'sentry/utils/useRoutes';
  14. import type {ReplayListRecordWithTx} from 'sentry/views/performance/transactionSummary/transactionReplays/useReplaysWithTxData';
  15. import HeaderCell from 'sentry/views/replays/replayTable/headerCell';
  16. import {
  17. ActivityCell,
  18. BrowserCell,
  19. DeadClickCountCell,
  20. DurationCell,
  21. ErrorCountCell,
  22. OSCell,
  23. RageClickCountCell,
  24. ReplayCell,
  25. TransactionCell,
  26. } from 'sentry/views/replays/replayTable/tableCell';
  27. import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
  28. import type {ReplayListRecord} from 'sentry/views/replays/types';
  29. type Props = {
  30. fetchError: undefined | Error;
  31. isFetching: boolean;
  32. replays: undefined | ReplayListRecord[] | ReplayListRecordWithTx[];
  33. sort: Sort | undefined;
  34. visibleColumns: ReplayColumn[];
  35. emptyMessage?: ReactNode;
  36. gridRows?: string;
  37. showDropdownFilters?: boolean;
  38. };
  39. function ReplayTable({
  40. fetchError,
  41. isFetching,
  42. replays,
  43. sort,
  44. visibleColumns,
  45. emptyMessage,
  46. gridRows,
  47. showDropdownFilters,
  48. }: Props) {
  49. const routes = useRoutes();
  50. const location = useLocation();
  51. const organization = useOrganization();
  52. const tableHeaders = visibleColumns
  53. .filter(Boolean)
  54. .map(column => <HeaderCell key={column} column={column} sort={sort} />);
  55. if (fetchError && !isFetching) {
  56. return (
  57. <StyledPanelTable
  58. headers={tableHeaders}
  59. isLoading={false}
  60. visibleColumns={visibleColumns}
  61. data-test-id="replay-table"
  62. gridRows={undefined}
  63. >
  64. <StyledAlert type="error" showIcon>
  65. {typeof fetchError === 'string'
  66. ? fetchError
  67. : t(
  68. 'Sorry, the list of replays could not be loaded. This could be due to invalid search parameters or an internal systems error.'
  69. )}
  70. </StyledAlert>
  71. </StyledPanelTable>
  72. );
  73. }
  74. const referrer = getRouteStringFromRoutes(routes);
  75. const eventView = EventView.fromLocation(location);
  76. return (
  77. <StyledPanelTable
  78. headers={tableHeaders}
  79. isEmpty={replays?.length === 0}
  80. isLoading={isFetching}
  81. visibleColumns={visibleColumns}
  82. disablePadding
  83. data-test-id="replay-table"
  84. emptyMessage={emptyMessage}
  85. gridRows={isFetching ? undefined : gridRows}
  86. loader={<LoadingIndicator style={{margin: '54px auto'}} />}
  87. >
  88. {replays?.map(replay => {
  89. return (
  90. <Fragment key={replay.id}>
  91. {visibleColumns.map(column => {
  92. switch (column) {
  93. case ReplayColumn.ACTIVITY:
  94. return (
  95. <ActivityCell
  96. key="activity"
  97. replay={replay}
  98. showDropdownFilters={showDropdownFilters}
  99. />
  100. );
  101. case ReplayColumn.BROWSER:
  102. return (
  103. <BrowserCell
  104. key="browser"
  105. replay={replay}
  106. showDropdownFilters={showDropdownFilters}
  107. />
  108. );
  109. case ReplayColumn.COUNT_DEAD_CLICKS:
  110. return (
  111. <DeadClickCountCell
  112. key="countDeadClicks"
  113. replay={replay}
  114. showDropdownFilters={showDropdownFilters}
  115. />
  116. );
  117. case ReplayColumn.COUNT_ERRORS:
  118. return (
  119. <ErrorCountCell
  120. key="countErrors"
  121. replay={replay}
  122. showDropdownFilters={showDropdownFilters}
  123. />
  124. );
  125. case ReplayColumn.COUNT_RAGE_CLICKS:
  126. return (
  127. <RageClickCountCell
  128. key="countRageClicks"
  129. replay={replay}
  130. showDropdownFilters={showDropdownFilters}
  131. />
  132. );
  133. case ReplayColumn.DURATION:
  134. return (
  135. <DurationCell
  136. key="duration"
  137. replay={replay}
  138. showDropdownFilters={showDropdownFilters}
  139. />
  140. );
  141. case ReplayColumn.OS:
  142. return (
  143. <OSCell
  144. key="os"
  145. replay={replay}
  146. showDropdownFilters={showDropdownFilters}
  147. />
  148. );
  149. case ReplayColumn.REPLAY:
  150. return (
  151. <ReplayCell
  152. key="session"
  153. replay={replay}
  154. eventView={eventView}
  155. organization={organization}
  156. referrer={referrer}
  157. referrer_table="main"
  158. />
  159. );
  160. case ReplayColumn.SLOWEST_TRANSACTION:
  161. return (
  162. <TransactionCell
  163. key="slowestTransaction"
  164. replay={replay}
  165. organization={organization}
  166. />
  167. );
  168. default:
  169. return null;
  170. }
  171. })}
  172. </Fragment>
  173. );
  174. })}
  175. </StyledPanelTable>
  176. );
  177. }
  178. const StyledPanelTable = styled(PanelTable)<{
  179. visibleColumns: ReplayColumn[];
  180. gridRows?: string;
  181. }>`
  182. margin-bottom: 0;
  183. grid-template-columns: ${p =>
  184. p.visibleColumns
  185. .filter(Boolean)
  186. .map(column => (column === 'replay' ? 'minmax(100px, 1fr)' : 'max-content'))
  187. .join(' ')};
  188. ${props =>
  189. props.gridRows
  190. ? `grid-template-rows: ${props.gridRows};`
  191. : `grid-template-rows: 44px max-content;`}
  192. `;
  193. const StyledAlert = styled(Alert)`
  194. border-radius: 0;
  195. border-width: 1px 0 0 0;
  196. grid-column: 1/-1;
  197. margin-bottom: 0;
  198. `;
  199. export default ReplayTable;