Browse Source

fix(replays): Replay List shows an Alert inside the table on fetch error (#38454)

Render an error alert inside the list table when an error during fetching is caught.

Closes #38367 

Before:

![Screen Shot 2022-09-06 at 8 23 46
AM](https://user-images.githubusercontent.com/3721977/188634839-8d49688a-03bc-4e86-90aa-798d5123ce80.png)

After:
![Screen Shot 2022-09-06 at 12 57 06
PM](https://user-images.githubusercontent.com/3721977/188695873-0bd85b6d-f8e3-4dd9-9122-9b3efa4c1ae9.png)
Dane Grant 2 years ago
parent
commit
31a2060ea6
2 changed files with 59 additions and 58 deletions
  1. 58 31
      static/app/views/replays/replayTable.tsx
  2. 1 27
      static/app/views/replays/replays.tsx

+ 58 - 31
static/app/views/replays/replayTable.tsx

@@ -2,6 +2,7 @@ import {Fragment} from 'react';
 import {useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 
+import Alert from 'sentry/components/alert';
 import Duration from 'sentry/components/duration';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import UserBadge from 'sentry/components/idBadge/userBadge';
@@ -28,6 +29,7 @@ type Props = {
   replays: undefined | ReplayListRecord[];
   showProjectColumn: boolean;
   sort: Sort;
+  fetchError?: Error;
 };
 
 type RowProps = {
@@ -75,7 +77,7 @@ function SortableHeader({
   );
 }
 
-function ReplayTable({isFetching, replays, showProjectColumn, sort}: Props) {
+function ReplayTable({isFetching, replays, showProjectColumn, sort, fetchError}: Props) {
   const routes = useRoutes();
   const referrer = encodeURIComponent(getRouteStringFromRoutes(routes));
 
@@ -83,41 +85,59 @@ function ReplayTable({isFetching, replays, showProjectColumn, sort}: Props) {
   const theme = useTheme();
   const minWidthIsSmall = useMedia(`(min-width: ${theme.breakpoints.small})`);
 
+  const tableHeaders = [
+    t('Session'),
+    showProjectColumn && minWidthIsSmall ? (
+      <SortableHeader
+        key="projectId"
+        sort={sort}
+        fieldName="projectId"
+        label={t('Project')}
+      />
+    ) : null,
+    <SortableHeader
+      key="startedAt"
+      sort={sort}
+      fieldName="startedAt"
+      label={t('Start Time')}
+    />,
+    <SortableHeader
+      key="duration"
+      sort={sort}
+      fieldName="duration"
+      label={t('Duration')}
+    />,
+    <SortableHeader
+      key="countErrors"
+      sort={sort}
+      fieldName="countErrors"
+      label={t('Errors')}
+    />,
+    t('Activity'),
+  ].filter(Boolean);
+
+  if (fetchError && !isFetching) {
+    return (
+      <StyledPanelTable
+        headers={tableHeaders}
+        showProjectColumn={showProjectColumn}
+        isLoading={false}
+      >
+        <StyledAlert type="error" showIcon>
+          {t(
+            'Sorry, the list of replays could not be loaded. This could be due to invalid search parameters or an internal systems error.'
+          )}
+        </StyledAlert>
+      </StyledPanelTable>
+    );
+  }
+
   return (
     <StyledPanelTable
       isLoading={isFetching}
       isEmpty={replays?.length === 0}
       showProjectColumn={showProjectColumn}
-      headers={[
-        t('Session'),
-        showProjectColumn && minWidthIsSmall ? (
-          <SortableHeader
-            key="projectId"
-            sort={sort}
-            fieldName="projectId"
-            label={t('Project')}
-          />
-        ) : null,
-        <SortableHeader
-          key="startedAt"
-          sort={sort}
-          fieldName="startedAt"
-          label={t('Start Time')}
-        />,
-        <SortableHeader
-          key="duration"
-          sort={sort}
-          fieldName="duration"
-          label={t('Duration')}
-        />,
-        <SortableHeader
-          key="countErrors"
-          sort={sort}
-          fieldName="countErrors"
-          label={t('Errors')}
-        />,
-        t('Activity'),
-      ].filter(Boolean)}
+      headers={tableHeaders}
     >
       {replays?.map(replay => (
         <ReplayTableRow
@@ -223,4 +243,11 @@ const StyledIconCalendarWrapper = styled(IconCalendar)`
   top: -1px;
 `;
 
+const StyledAlert = styled(Alert)`
+  position: relative;
+  bottom: 0.5px;
+  grid-column-start: span 99;
+  margin-bottom: 0;
+`;
+
 export default ReplayTable;

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

@@ -3,9 +3,6 @@ import {browserHistory, RouteComponentProps} from 'react-router';
 import {useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
 
-import DetailedError from 'sentry/components/errors/detailedError';
-import List from 'sentry/components/list';
-import ListItem from 'sentry/components/list/listItem';
 import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
 import PageHeading from 'sentry/components/pageHeading';
 import Pagination from 'sentry/components/pagination';
@@ -55,30 +52,6 @@ function Replays({location}: Props) {
     eventView,
   });
 
-  if (fetchError && !isFetching) {
-    const reasons = [
-      t('The search parameters you selected are invalid in some way'),
-      t('There is an internal systems error or active issue'),
-    ];
-
-    return (
-      <DetailedError
-        hideSupportLinks
-        heading={t('Sorry, the list of replays could not be found.')}
-        message={
-          <div>
-            <p>{t('This could be due to a handful of reasons:')}</p>
-            <List symbol="bullet">
-              {reasons.map((reason, i) => (
-                <ListItem key={i}>{reason}</ListItem>
-              ))}
-            </List>
-          </div>
-        }
-      />
-    );
-  }
-
   return (
     <Fragment>
       <StyledPageHeader>
@@ -106,6 +79,7 @@ function Replays({location}: Props) {
           />
           <ReplayTable
             isFetching={isFetching}
+            fetchError={fetchError}
             replays={replays}
             showProjectColumn={minWidthIsSmall}
             sort={eventView.sorts[0]}