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

feat(replays): Update the Replay Index page columns and Details page header to match (#45700)

New css in the app to match the
[figma](https://www.figma.com/file/fOXcP5EaaTnerWqrydnyCP/Specs%3A-Index-v3?node-id=190-38289&t=swXsyTNh1ysgRSI6-0):




### Table rows updated:

![SCR-20230316-j17](https://user-images.githubusercontent.com/187460/225747971-e8568a0a-4adc-4c3b-8d87-36b947794ce5.png)


### Details page header updated:
| With error count | No errors | Loading
| --- | --- | --- |
|
![SCR-20230316-j18](https://user-images.githubusercontent.com/187460/225747988-e5b654f4-feaa-47d1-a87b-2fe528ca1daf.png)
|
![SCR-20230316-kn5](https://user-images.githubusercontent.com/187460/225760571-e0fa6349-acc0-4bce-b29e-babc534c9c3e.png)
|
![SCR-20230316-kmu](https://user-images.githubusercontent.com/187460/225760593-d2b497f8-f655-4120-bb00-735a9a3040a3.png)
|


Here's the diff of the header loading state and the header with info:

![SCR-20230316-kni](https://user-images.githubusercontent.com/187460/225760618-cc0033cf-e8c0-4548-b45c-0a9f8f5ddafa.png)





Fixes #42784
Fixes #44759

---------

Co-authored-by: Billy Vong <billyvg@users.noreply.github.com>
Ryan Albrecht 2 лет назад
Родитель
Сommit
7469779a79

+ 38 - 0
fixtures/js-stubs/replayList.ts

@@ -0,0 +1,38 @@
+import {duration} from 'moment';
+
+import type {ReplayListRecord as TReplayListRecord} from 'sentry/views/replays/types';
+
+export function ReplayList(
+  replayListRecords: TReplayListRecord[] = []
+): TReplayListRecord[] {
+  if (replayListRecords.length) {
+    return replayListRecords;
+  }
+  return [
+    {
+      activity: 1,
+      browser: {
+        name: 'Firefox',
+        version: '111.0',
+      },
+      count_errors: 0,
+      duration: duration(30000),
+      finished_at: new Date('2022-09-15T06:54:00+00:00'),
+      id: '346789a703f6454384f1de473b8b9fcc',
+      os: {
+        name: 'sentry.javascript.react',
+        version: '7.42.0',
+      },
+      project_id: '2',
+      started_at: new Date('2022-09-15T06:50:03+00:00'),
+      urls: [],
+      user: {
+        display_name: 'testDisplayName',
+        email: '',
+        id: '147086',
+        ip: '127.0.0.1',
+        username: 'testDisplayName',
+      },
+    },
+  ];
+}

+ 2 - 1
fixtures/js-stubs/types.tsx

@@ -1,4 +1,4 @@
-import type {ReplayRecord} from 'sentry/views/replays/types';
+import type {ReplayListRecord, ReplayRecord} from 'sentry/views/replays/types';
 
 type SimpleStub<T = any> = () => T;
 
@@ -100,6 +100,7 @@ type TestStubFixtures = {
   PullRequest: OverridableStub;
   Release: (params?: any, healthParams?: any) => any;
   ReplayError: OverridableStub;
+  ReplayList: OverridableStubList<ReplayListRecord>;
   ReplayRRWebDivHelloWorld: OverridableStub;
   ReplayRRWebNode: OverridableStub;
   ReplayRecord: OverridableStub<ReplayRecord>;

+ 8 - 3
static/app/components/events/contextSummary/contextIcon.tsx

@@ -1,4 +1,4 @@
-import {css} from '@emotion/react';
+import {css, useTheme} from '@emotion/react';
 import logoAmazon from 'sentry-logos/logo-amazon.svg';
 import logoAmd from 'sentry-logos/logo-amd.svg';
 import logoAndroid from 'sentry-logos/logo-android.svg';
@@ -41,6 +41,7 @@ import logoWindows from 'sentry-logos/logo-windows.svg';
 
 import ConfigStore from 'sentry/stores/configStore';
 import {useLegacyStore} from 'sentry/stores/useLegacyStore';
+import {IconSize} from 'sentry/utils/theme';
 
 const LOGO_MAPPING = {
   'android-phone': logoAndroidPhone,
@@ -126,14 +127,18 @@ function getLogoImage(name: string) {
 
 type Props = {
   name: string;
+  size?: IconSize;
 };
 
-function ContextIcon({name}: Props) {
+function ContextIcon({name, size: providedSize = 'xl'}: Props) {
+  const theme = useTheme();
+  const size = theme.iconSizes[providedSize];
+
   // Apply darkmode CSS to icon when in darkmode
   const isDarkmode = useLegacyStore(ConfigStore).theme === 'dark';
   const extraCass = isDarkmode && INVERT_IN_DARKMODE.includes(name) ? darkCss : null;
 
-  return <img height="32px" width="32px" css={extraCass} src={getLogoImage(name)} />;
+  return <img height={size} width={size} css={extraCass} src={getLogoImage(name)} />;
 }
 
 export default ContextIcon;

+ 37 - 0
static/app/components/replays/contextIcon.tsx

@@ -0,0 +1,37 @@
+import {lazy, Suspense} from 'react';
+import styled from '@emotion/styled';
+
+import {generateIconName} from 'sentry/components/events/contextSummary/utils';
+import LoadingMask from 'sentry/components/loadingMask';
+import {Tooltip} from 'sentry/components/tooltip';
+import {space} from 'sentry/styles/space';
+
+type Props = {
+  name: string;
+  version: undefined | string;
+  className?: string;
+};
+
+const LazyContextIcon = lazy(
+  () => import('sentry/components/events/contextSummary/contextIcon')
+);
+
+const ContextIcon = styled(({className, name, version}: Props) => {
+  const icon = generateIconName(name, version ?? undefined);
+  return (
+    <div className={className}>
+      <Tooltip title={name}>
+        <Suspense fallback={<LoadingMask />}>
+          <LazyContextIcon name={icon} size="sm" />
+        </Suspense>
+      </Tooltip>
+      {version ? <div>{version}</div> : null}
+    </div>
+  );
+})`
+  display: flex;
+  gap: ${space(1)};
+  font-variant-numeric: tabular-nums;
+`;
+
+export default ContextIcon;

+ 1 - 1
static/app/components/replays/deleteButton.tsx → static/app/components/replays/header/deleteButton.tsx

@@ -38,7 +38,7 @@ function DeleteButton() {
       message={t('Are you sure you want to delete this replay?')}
       onConfirm={handleDelete}
     >
-      <Button size="xs" icon={<IconDelete size="xs" />}>
+      <Button size="sm" icon={<IconDelete size="sm" />}>
         {t('Delete')}
       </Button>
     </Confirm>

+ 19 - 14
static/app/components/replays/header/detailsPageBreadcrumbs.tsx

@@ -1,12 +1,14 @@
-import {ComponentProps, Fragment} from 'react';
-import styled from '@emotion/styled';
+import {Fragment} from 'react';
 
 import Breadcrumbs from 'sentry/components/breadcrumbs';
-import Placeholder from 'sentry/components/placeholder';
+import BaseBadge from 'sentry/components/idBadge/baseBadge';
+import HeaderPlaceholder from 'sentry/components/replays/header/headerPlaceholder';
 import ReplaysFeatureBadge from 'sentry/components/replays/replaysFeatureBadge';
 import {t} from 'sentry/locale';
 import EventView from 'sentry/utils/discover/eventView';
+import {getShortEventId} from 'sentry/utils/events';
 import {useLocation} from 'sentry/utils/useLocation';
+import useProjects from 'sentry/utils/useProjects';
 import type {ReplayRecord} from 'sentry/views/replays/types';
 
 type Props = {
@@ -17,7 +19,18 @@ type Props = {
 function DetailsPageBreadcrumbs({orgSlug, replayRecord}: Props) {
   const location = useLocation();
   const eventView = EventView.fromLocation(location);
-  const labelTitle = replayRecord?.user.display_name;
+
+  const {projects} = useProjects();
+  const project = projects.find(p => p.id === replayRecord?.project_id);
+
+  const labelTitle = replayRecord ? (
+    <Fragment>
+      {getShortEventId(replayRecord?.id)}
+      <ReplaysFeatureBadge />
+    </Fragment>
+  ) : (
+    <HeaderPlaceholder width="100%" height="16px" />
+  );
 
   return (
     <Breadcrumbs
@@ -30,12 +43,10 @@ function DetailsPageBreadcrumbs({orgSlug, replayRecord}: Props) {
           label: t('Session Replay'),
         },
         {
-          label: labelTitle ? (
+          label: (
             <Fragment>
-              {labelTitle} <ReplaysFeatureBadge />
+              {<BaseBadge displayName={labelTitle} project={project} avatarSize={16} />}
             </Fragment>
-          ) : (
-            <HeaderPlaceholder width="500px" height="24px" />
           ),
         },
       ]}
@@ -43,10 +54,4 @@ function DetailsPageBreadcrumbs({orgSlug, replayRecord}: Props) {
   );
 }
 
-const HeaderPlaceholder = styled((props: ComponentProps<typeof Placeholder>) => (
-  <Placeholder width="100%" height="19px" {...props} />
-))`
-  background-color: ${p => p.theme.background};
-`;
-
 export default DetailsPageBreadcrumbs;

+ 28 - 0
static/app/components/replays/header/errorCount.tsx

@@ -0,0 +1,28 @@
+import styled from '@emotion/styled';
+
+import {IconFire} from 'sentry/icons';
+import {space} from 'sentry/styles/space';
+
+type Props = {
+  countErrors: number;
+  className?: string;
+};
+
+const ErrorCount = styled(({countErrors, className}: Props) =>
+  countErrors ? (
+    <span className={className}>
+      <IconFire />
+      {countErrors}
+    </span>
+  ) : (
+    <span className={className}>0</span>
+  )
+)`
+  display: flex;
+  align-items: center;
+  gap: ${space(0.5)};
+  color: ${p => (p.countErrors > 0 ? p.theme.red400 : 'inherit')};
+  font-variant-numeric: tabular-nums;
+`;
+
+export default ErrorCount;

+ 1 - 1
static/app/components/replays/header/feedbackButton.tsx

@@ -11,7 +11,7 @@ const FeedbackButtonHook = HookOrDefault({
 function FeedbackButton() {
   return (
     <FeedbackButtonHook>
-      <FeatureFeedback featureName="replay" buttonProps={{size: 'xs'}} />
+      <FeatureFeedback featureName="replay" buttonProps={{size: 'sm'}} />
     </FeedbackButtonHook>
   );
 }

+ 9 - 0
static/app/components/replays/header/headerPlaceholder.tsx

@@ -0,0 +1,9 @@
+import styled from '@emotion/styled';
+
+import Placeholder from 'sentry/components/placeholder';
+
+const HeaderPlaceholder = styled(Placeholder)`
+  background-color: ${p => p.theme.background};
+`;
+
+export default HeaderPlaceholder;

+ 2 - 2
static/app/components/replays/shareButton.tsx

@@ -70,8 +70,8 @@ function ShareButton() {
 
   return (
     <Button
-      size="xs"
-      icon={<IconUpload size="xs" />}
+      size="sm"
+      icon={<IconUpload size="sm" />}
       onClick={() =>
         openModal(deps => <ShareModal currentTimeSec={currentTimeSec} {...deps} />)
       }

Некоторые файлы не были показаны из-за большого количества измененных файлов