replaysErroneousDeadRageCards.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import {Fragment, useMemo} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import {Button} from 'sentry/components/button';
  6. import {IconClose, IconSearch} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {Organization} from 'sentry/types';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import ReplayTable from 'sentry/views/replays/replayTable';
  15. import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
  16. import {ReplayListLocationQuery} from 'sentry/views/replays/types';
  17. function ReplaysErroneousDeadRageCards() {
  18. const organization = useOrganization();
  19. const location = useLocation<ReplayListLocationQuery>();
  20. const newLocation = useMemo(() => {
  21. return {
  22. pathname: '',
  23. search: '',
  24. hash: '',
  25. state: '',
  26. action: 'PUSH' as const,
  27. key: '',
  28. query: {
  29. project: location.query.project,
  30. environment: location.query.environment,
  31. start: location.query.start,
  32. statsPeriod: location.query.statsPeriod,
  33. utc: location.query.utc,
  34. end: location.query.end,
  35. },
  36. };
  37. }, [
  38. location.query.project,
  39. location.query.environment,
  40. location.query.start,
  41. location.query.statsPeriod,
  42. location.query.utc,
  43. location.query.end,
  44. ]);
  45. const eventViewDead = useMemo(() => {
  46. return EventView.fromNewQueryWithLocation(
  47. {
  48. id: '',
  49. name: '',
  50. version: 2,
  51. fields: [
  52. 'activity',
  53. 'duration',
  54. 'count_dead_clicks',
  55. 'id',
  56. 'project_id',
  57. 'user',
  58. 'finished_at',
  59. 'is_archived',
  60. 'started_at',
  61. ],
  62. projects: [],
  63. query: 'count_dead_clicks:>0',
  64. orderby: '-count_dead_clicks',
  65. },
  66. newLocation
  67. );
  68. }, [newLocation]);
  69. const eventViewRage = useMemo(() => {
  70. return EventView.fromNewQueryWithLocation(
  71. {
  72. id: '',
  73. name: '',
  74. version: 2,
  75. fields: [
  76. 'activity',
  77. 'duration',
  78. 'count_rage_clicks',
  79. 'id',
  80. 'project_id',
  81. 'user',
  82. 'finished_at',
  83. 'is_archived',
  84. 'started_at',
  85. ],
  86. projects: [],
  87. query: 'count_rage_clicks:>0',
  88. orderby: '-count_rage_clicks',
  89. },
  90. newLocation
  91. );
  92. }, [newLocation]);
  93. const deadCols = [
  94. ReplayColumn.MOST_DEAD_CLICKS,
  95. ReplayColumn.COUNT_DEAD_CLICKS_NO_HEADER,
  96. ];
  97. const rageCols = [
  98. ReplayColumn.MOST_RAGE_CLICKS,
  99. ReplayColumn.COUNT_RAGE_CLICKS_NO_HEADER,
  100. ];
  101. return (
  102. <SplitCardContainer>
  103. <CardTable
  104. eventView={eventViewDead}
  105. location={newLocation}
  106. organization={organization}
  107. visibleColumns={deadCols}
  108. searchQuery={{
  109. ...location.query,
  110. cursor: undefined,
  111. query: 'count_dead_clicks:>0',
  112. sort: '-count_dead_clicks',
  113. }}
  114. buttonLabel={t('Show all replays with dead clicks')}
  115. />
  116. <CardTable
  117. eventView={eventViewRage}
  118. location={newLocation}
  119. organization={organization}
  120. visibleColumns={rageCols}
  121. searchQuery={{
  122. ...location.query,
  123. cursor: undefined,
  124. query: 'count_rage_clicks:>0',
  125. sort: '-count_rage_clicks',
  126. }}
  127. buttonLabel={t('Show all replays with rage clicks')}
  128. />
  129. </SplitCardContainer>
  130. );
  131. }
  132. function CardTable({
  133. buttonLabel,
  134. eventView,
  135. location,
  136. organization,
  137. searchQuery,
  138. visibleColumns,
  139. }: {
  140. buttonLabel: string;
  141. eventView: EventView;
  142. location: Location;
  143. organization: Organization;
  144. searchQuery: {
  145. cursor: undefined;
  146. query: string;
  147. sort: string;
  148. };
  149. visibleColumns: ReplayColumn[];
  150. }) {
  151. const {replays, isFetching, fetchError} = useReplayList({
  152. eventView,
  153. location,
  154. organization,
  155. perPage: 3,
  156. });
  157. const gridRows = new Array(replays ? (replays.length > 0 ? 3 : 1) : 1)
  158. .fill(' ')
  159. .map(_ => '1fr')
  160. .join(' ');
  161. const emptyLocation = useLocation();
  162. const emptySearchQuery = {
  163. ...emptyLocation.query,
  164. cursor: undefined,
  165. query: '',
  166. sort: '',
  167. };
  168. return (
  169. <Fragment>
  170. <ReplayTable
  171. fetchError={fetchError}
  172. isFetching={isFetching}
  173. replays={replays}
  174. sort={undefined}
  175. visibleColumns={visibleColumns}
  176. saveLocation
  177. gridRows={'auto ' + gridRows}
  178. showDropdownFilters={false}
  179. />
  180. <StyledButton
  181. size="sm"
  182. onClick={() => {
  183. const newQuery =
  184. emptyLocation.query.query === searchQuery.query
  185. ? emptySearchQuery
  186. : searchQuery;
  187. browserHistory.push({
  188. pathname: emptyLocation.pathname,
  189. query: newQuery,
  190. });
  191. }}
  192. icon={
  193. emptyLocation.query.query === searchQuery.query ? (
  194. <IconClose size="xs" />
  195. ) : (
  196. <IconSearch size="xs" />
  197. )
  198. }
  199. >
  200. {emptyLocation.query.query === searchQuery.query
  201. ? t('Clear filter')
  202. : buttonLabel}
  203. </StyledButton>
  204. </Fragment>
  205. );
  206. }
  207. const SplitCardContainer = styled('div')`
  208. display: grid;
  209. grid-template-columns: 1fr 1fr;
  210. grid-template-rows: max-content max-content;
  211. grid-auto-flow: column;
  212. gap: 0 ${space(2)};
  213. align-items: stretch;
  214. `;
  215. const StyledButton = styled(Button)`
  216. width: 100%;
  217. border-top: none;
  218. border-radius: ${p => p.theme.borderRadiusBottom};
  219. padding: ${space(3)};
  220. `;
  221. export default ReplaysErroneousDeadRageCards;