index.tsx 6.5 KB

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