Просмотр исходного кода

fix(replays): update error and dead/rage click cards (#53472)

Closes getsentry/team-replay#121 by addressing the following issues:

- [x] move filter bar to be above the cards
- [x] env, project, date range selections are now synced with the cards
- [x] override query for 50 per page and only ask for 3 to avoid
overfetching data
- [x] the cards do not refresh when the big table is paginatinated (but
they do refresh when env/project/date range selection changes)
- [x] right table is now sorted by rage clicks instead of dead clicks to
help the team track when the dead/rage click inconsistencies are fixed
(dead clicks should always be > rage clicks)
- [x] if all errors/dead/rage clicks for a time period = 0, instead of
displaying replays with uninteresting data, return no replays

Still need to do:
- [ ] fix buggy loading of card height
- [ ] a "see all replays with [errors/rage/dead clicks] " button under
each table that updates the search bar at the top to show the big
replays list with [errors/rage/dead clicks]:>0, as well as sorting by
top [errors/rage/dead clicks]

Updated card & filter bar placement:
<img width="1185" alt="initial"
src="https://github.com/getsentry/sentry/assets/56095982/4bd3cc70-9f91-4cea-85cb-223138d0feda">

Paginating main table does not affect cards:
<img width="1134" alt="reload"
src="https://github.com/getsentry/sentry/assets/56095982/c80764ae-6467-4df9-890f-43f056a32ea1">

New time frame selection updates the cards:
<img width="1167" alt="SCR-20230724-nlhm"
src="https://github.com/getsentry/sentry/assets/56095982/5917ac92-4c7a-499a-9e36-c03f0b85c7a8">


New project selection updates the cards:
<img width="1165" alt="SCR-20230724-nglq"
src="https://github.com/getsentry/sentry/assets/56095982/72db48f4-deef-4c0c-9f66-44c8d716a44a">

Only querying for 3 replays rather than 50:
<img width="316" alt="SCR-20230724-ngpk"
src="https://github.com/getsentry/sentry/assets/56095982/03bb383e-f23d-422c-8425-933b7325b278">

In the right table, instead of showing 3 replays with all rage/dead
click counts of 0, we return no replays:
<img width="1171" alt="SCR-20230724-ngsw"
src="https://github.com/getsentry/sentry/assets/56095982/0bf7f314-acfd-4d05-9893-56f0bad72fa4">
Michelle Zhang 1 год назад
Родитель
Сommit
e7fac6c017

+ 5 - 0
static/app/utils/replays/fetchReplayList.tsx

@@ -24,6 +24,7 @@ type Props = {
   eventView: EventView;
   location: Location;
   organization: Organization;
+  perPage?: number;
   queryReferrer?: 'issueReplays';
 };
 
@@ -33,6 +34,7 @@ async function fetchReplayList({
   location,
   eventView,
   queryReferrer,
+  perPage,
 }: Props): Promise<Result> {
   try {
     const path = `/organizations/${organization.slug}/replays/`;
@@ -42,6 +44,9 @@ async function fetchReplayList({
     // HACK!!! Because the sort field needs to be in the eventView, but I cannot
     // ask the server for compound fields like `os.name`.
     payload.field = payload.field.map(field => field.split('.')[0]);
+    if (perPage) {
+      payload.per_page = perPage;
+    }
 
     // unique list
     payload.field = Array.from(new Set(payload.field));

+ 4 - 1
static/app/utils/replays/hooks/useReplayList.tsx

@@ -11,6 +11,7 @@ type Options = {
   eventView: EventView;
   location: Location<ReplayListLocationQuery>;
   organization: Organization;
+  perPage?: number;
   queryReferrer?: 'issueReplays';
 };
 
@@ -23,6 +24,7 @@ function useReplayList({
   location,
   organization,
   queryReferrer,
+  perPage,
 }: Options): Result {
   const api = useApi();
 
@@ -45,10 +47,11 @@ function useReplayList({
       location,
       eventView,
       queryReferrer,
+      perPage,
     });
 
     setData({...response, isFetching: false});
-  }, [api, organization, location, eventView, queryReferrer]);
+  }, [api, organization, location, eventView, queryReferrer, perPage]);
 
   useEffect(() => {
     loadReplays();

+ 1 - 1
static/app/views/replays/list.tsx

@@ -31,8 +31,8 @@ function ReplaysListContainer() {
       <PageFiltersContainer>
         <Layout.Body>
           <Layout.Main fullWidth>
-            <ReplaysErroneousDeadRageCards />
             <ReplaysFilters />
+            <ReplaysErroneousDeadRageCards />
             <ReplaysList />
           </Layout.Main>
         </Layout.Body>

+ 84 - 58
static/app/views/replays/list/replaysErroneousDeadRageCards.tsx

@@ -1,3 +1,4 @@
+import {useMemo} from 'react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
@@ -6,75 +7,99 @@ import type {Organization} from 'sentry/types';
 import EventView from 'sentry/utils/discover/eventView';
 import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
 import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
+import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import ReplayTable from 'sentry/views/replays/replayTable';
 import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
+import {ReplayListLocationQuery} from 'sentry/views/replays/types';
 
 function ReplaysErroneousDeadRageCards() {
-  const location: Location = {
-    pathname: '',
-    search: '',
-    query: {},
-    hash: '',
-    state: '',
-    action: 'PUSH',
-    key: '',
-  };
   const organization = useOrganization();
+  const location = useLocation<ReplayListLocationQuery>();
 
-  const eventViewErrors = EventView.fromNewQueryWithLocation(
-    {
-      name: '',
-      version: 2,
-      fields: [
-        'activity',
-        'duration',
-        'count_errors',
-        'id',
-        'project_id',
-        'user',
-        'finished_at',
-        'is_archived',
-        'started_at',
-      ],
-      range: '7d',
-      projects: [],
-      query: '',
-      orderby: '-count_errors',
-    },
-    location
-  );
+  const newLocation = useMemo(() => {
+    return {
+      pathname: '',
+      search: '',
+      hash: '',
+      state: '',
+      action: 'PUSH' as const,
+      key: '',
+      query: {
+        project: location.query.project,
+        environment: location.query.environment,
+        start: location.query.start,
+        statsPeriod: location.query.statsPeriod,
+        utc: location.query.utc,
+        end: location.query.end,
+      },
+    };
+  }, [
+    location.query.project,
+    location.query.environment,
+    location.query.start,
+    location.query.statsPeriod,
+    location.query.utc,
+    location.query.end,
+  ]);
 
-  const eventViewDeadRage = EventView.fromNewQueryWithLocation(
-    {
-      name: '',
-      version: 2,
-      fields: [
-        'activity',
-        'duration',
-        'count_dead_clicks',
-        'count_rage_clicks',
-        'id',
-        'project_id',
-        'user',
-        'finished_at',
-        'is_archived',
-        'started_at',
-      ],
-      range: '7d',
-      projects: [],
-      query: '',
-      orderby: '-count_dead_clicks',
-    },
-    location
-  );
+  const eventViewErrors = useMemo(() => {
+    return EventView.fromNewQueryWithLocation(
+      {
+        id: '',
+        name: '',
+        version: 2,
+        fields: [
+          'activity',
+          'duration',
+          'count_errors',
+          'id',
+          'project_id',
+          'user',
+          'finished_at',
+          'is_archived',
+          'started_at',
+        ],
+        projects: [],
+        query: 'count_errors:>0',
+        orderby: '-count_errors',
+      },
+      newLocation
+    );
+  }, [newLocation]);
+
+  const eventViewDeadRage = useMemo(() => {
+    return EventView.fromNewQueryWithLocation(
+      {
+        id: '',
+        name: '',
+        version: 2,
+        fields: [
+          'activity',
+          'duration',
+          'count_dead_clicks',
+          'count_rage_clicks',
+          'id',
+          'project_id',
+          'user',
+          'finished_at',
+          'is_archived',
+          'started_at',
+        ],
+        projects: [],
+        query: 'count_rage_clicks:>0',
+        orderby: '-count_rage_clicks',
+      },
+      newLocation
+    );
+  }, [newLocation]);
 
   const hasSessionReplay = organization.features.includes('session-replay');
   const hasDeadRageCards = organization.features.includes('replay-error-click-cards');
   const {hasSentOneReplay, fetching} = useHaveSelectedProjectsSentAnyReplayEvents();
 
   const deadRageCols = [
-    ReplayColumn.MOST_DEAD_CLICKS,
+    ReplayColumn.MOST_RAGE_CLICKS,
     ReplayColumn.COUNT_DEAD_CLICKS,
     ReplayColumn.COUNT_RAGE_CLICKS,
   ];
@@ -91,13 +116,13 @@ function ReplaysErroneousDeadRageCards() {
       <SplitCardContainer>
         <CardTable
           eventView={eventViewErrors}
-          location={location}
+          location={newLocation}
           organization={organization}
           visibleColumns={errorCols}
         />
         <CardTable
           eventView={eventViewDeadRage}
-          location={location}
+          location={newLocation}
           organization={organization}
           visibleColumns={deadRageCols}
         />
@@ -121,13 +146,14 @@ function CardTable({
     eventView,
     location,
     organization,
+    perPage: 3,
   });
 
   return (
     <ReplayTable
       fetchError={fetchError}
       isFetching={isFetching}
-      replays={replays?.slice(0, 3)}
+      replays={replays}
       sort={undefined}
       visibleColumns={visibleColumns}
       saveLocation

+ 2 - 2
static/app/views/replays/replayTable/headerCell.tsx

@@ -64,8 +64,8 @@ function HeaderCell({column, sort}: Props) {
     case ReplayColumn.MOST_ERRONEOUS_REPLAYS:
       return <SortableHeader label={t('Most erroneous replays')} />;
 
-    case ReplayColumn.MOST_DEAD_CLICKS:
-      return <SortableHeader label={t('Most dead clicks')} />;
+    case ReplayColumn.MOST_RAGE_CLICKS:
+      return <SortableHeader label={t('Most rage clicks')} />;
 
     case ReplayColumn.SLOWEST_TRANSACTION:
       return (

+ 3 - 3
static/app/views/replays/replayTable/index.tsx

@@ -145,10 +145,10 @@ function ReplayTable({
                     />
                   );
 
-                case ReplayColumn.MOST_DEAD_CLICKS:
+                case ReplayColumn.MOST_RAGE_CLICKS:
                   return (
                     <ReplayCell
-                      key="mostDeadClicks"
+                      key="mostRageClicks"
                       replay={replay}
                       organization={organization}
                       referrer={referrer}
@@ -182,7 +182,7 @@ function ReplayTable({
 
 const flexibleColumns = [
   ReplayColumn.REPLAY,
-  ReplayColumn.MOST_DEAD_CLICKS,
+  ReplayColumn.MOST_RAGE_CLICKS,
   ReplayColumn.MOST_ERRONEOUS_REPLAYS,
 ];
 

+ 1 - 1
static/app/views/replays/replayTable/types.tsx

@@ -6,7 +6,7 @@ export enum ReplayColumn {
   COUNT_RAGE_CLICKS = 'countRageClicks',
   DURATION = 'duration',
   MOST_ERRONEOUS_REPLAYS = 'mostErroneousReplays',
-  MOST_DEAD_CLICKS = 'mostDeadClicks',
+  MOST_RAGE_CLICKS = 'mostRageClicks',
   OS = 'os',
   REPLAY = 'replay',
   SLOWEST_TRANSACTION = 'slowestTransaction',