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

chore(contexts): Simplify platform contexts (#80765)

See https://github.com/getsentry/sentry/pull/80420

16/13 🎉

This PR puts the platform-specific contexts into their own directory
making them more straight-forward to update for SDK changes. It also
moves `unity` into that area, and adds a simple `react` one since those
are the only other platforms I noticed having a unique context. Lastly,
makes sure that platform icons render with a minimum width so they are
visible.

<img width="426" alt="image"
src="https://github.com/user-attachments/assets/e5be761f-e438-42a4-843d-f40430c13750">


<img width="573" alt="image"
src="https://github.com/user-attachments/assets/79093ce6-01f9-4eb2-8a42-ff4bc0281147">


<img width="463" alt="image"
src="https://github.com/user-attachments/assets/98e0c85e-c323-4ff0-9479-6d39bd33bea3">
Leander Rodrigues 3 месяцев назад
Родитель
Сommit
d79a850a99

+ 1 - 1
static/app/components/events/contexts/contextCard.tsx

@@ -112,7 +112,7 @@ export default function ContextCard({
       title={
         <Title>
           <div>{getContextTitle({alias, type, value})}</div>
-          <div>
+          <div style={{minWidth: 14}}>
             <ErrorBoundary customComponent={null}>
               {getContextIcon({
                 alias,

+ 59 - 53
static/app/components/events/contexts/knownContext/user.tsx

@@ -65,57 +65,63 @@ export function getUserContextData({
   data: UserContext;
   meta?: Record<keyof UserContext, any>;
 }): KeyValueListData {
-  return getContextKeys({data}).map(ctxKey => {
-    switch (ctxKey) {
-      case UserContextKeys.NAME:
-        return {
-          key: ctxKey,
-          subject: t('Name'),
-          value: data.name,
-        };
-      case UserContextKeys.USERNAME:
-        return {
-          key: ctxKey,
-          subject: t('Username'),
-          value: data.username,
-        };
-      case UserContextKeys.ID:
-        return {
-          key: ctxKey,
-          subject: t('ID'),
-          value: data.id,
-        };
-      case UserContextKeys.IP_ADDRESS:
-        return {
-          key: ctxKey,
-          subject: t('IP Address'),
-          value: data.ip_address,
-        };
-      case UserContextKeys.EMAIL:
-        return {
-          key: ctxKey,
-          subject: t('Email'),
-          value: data.email,
-          action: {
-            link:
-              defined(data.email) && EMAIL_REGEX.test(data.email)
-                ? `mailto:${data.email}`
-                : undefined,
-          },
-        };
-      case UserContextKeys.GEO:
-        return {
-          key: ctxKey,
-          subject: t('Geography'),
-          value: formatGeo(data.geo),
-        };
-      default:
-        return {
-          key: ctxKey,
-          subject: ctxKey,
-          value: data[ctxKey],
-          meta: meta?.[ctxKey]?.[''],
-        };
-    }
-  });
+  return (
+    getContextKeys({data})
+      .map(ctxKey => {
+        switch (ctxKey) {
+          case UserContextKeys.NAME:
+            return {
+              key: ctxKey,
+              subject: t('Name'),
+              value: data.name,
+            };
+          case UserContextKeys.USERNAME:
+            return {
+              key: ctxKey,
+              subject: t('Username'),
+              value: data.username,
+            };
+          case UserContextKeys.ID:
+            return {
+              key: ctxKey,
+              subject: t('ID'),
+              value: data.id,
+            };
+          case UserContextKeys.IP_ADDRESS:
+            return {
+              key: ctxKey,
+              subject: t('IP Address'),
+              value: data.ip_address,
+            };
+          case UserContextKeys.EMAIL:
+            return {
+              key: ctxKey,
+              subject: t('Email'),
+              value: data.email,
+              action: {
+                link:
+                  defined(data.email) && EMAIL_REGEX.test(data.email)
+                    ? `mailto:${data.email}`
+                    : undefined,
+              },
+            };
+          case UserContextKeys.GEO:
+            return {
+              key: ctxKey,
+              subject: t('Geography'),
+              value: formatGeo(data.geo),
+            };
+          default:
+            return {
+              key: ctxKey,
+              subject: ctxKey,
+              value: data[ctxKey],
+              meta: meta?.[ctxKey]?.[''],
+            };
+        }
+      })
+      // Since user context is generated separately from the rest, it has all known keys with those
+      // unset appearing as `null`. We want to omit those unless they have annotations.
+      .filter(item => defined(item.value) || defined(meta?.[item.key]))
+  );
 }

+ 0 - 40
static/app/components/events/contexts/platform/index.spec.tsx

@@ -1,40 +0,0 @@
-import {EventFixture} from 'sentry-fixture/event';
-import {GroupFixture} from 'sentry-fixture/group';
-import {OrganizationFixture} from 'sentry-fixture/organization';
-import {ProjectFixture} from 'sentry-fixture/project';
-
-import {render, screen} from 'sentry-test/reactTestingLibrary';
-
-import ContextCard from 'sentry/components/events/contexts/contextCard';
-
-describe('platform event context', function () {
-  const platformContexts = {
-    laravel: {
-      type: 'default',
-      some: 'value',
-      number: 123,
-    },
-  };
-  const organization = OrganizationFixture();
-  const event = EventFixture({contexts: platformContexts});
-  const group = GroupFixture();
-  const project = ProjectFixture();
-
-  it('renders laravel context', function () {
-    const alias = 'laravel';
-    render(
-      <ContextCard
-        type={platformContexts[alias].type}
-        alias={alias}
-        value={platformContexts[alias]}
-        event={event}
-        group={group}
-        project={project}
-      />,
-      {organization}
-    );
-
-    expect(screen.getByText('Laravel Context')).toBeInTheDocument();
-    expect(screen.getByTestId(`${alias}-context-icon`)).toBeInTheDocument();
-  });
-});

+ 0 - 78
static/app/components/events/contexts/platform/index.tsx

@@ -1,78 +0,0 @@
-import {PlatformIcon} from 'platformicons';
-
-import {getKnownData, getUnknownData} from 'sentry/components/events/contexts/utils';
-import type {IconSize} from 'sentry/utils/theme';
-
-/**
- * Mapping of platform to known context keys for platform-specific context.
- */
-const KNOWN_PLATFORM_CONTEXT_KEYS: Record<string, string[]> = {
-  laravel: [],
-};
-
-export const KNOWN_PLATFORM_CONTEXTS = new Set(Object.keys(KNOWN_PLATFORM_CONTEXT_KEYS));
-
-interface PlatformContextProps {
-  data: Record<string, any>;
-  platform: string;
-  meta?: Record<string, any>;
-}
-
-enum PlatformContextKeys {}
-
-export function getKnownPlatformContextData({
-  platform,
-  data,
-  meta,
-}: PlatformContextProps) {
-  return getKnownData<PlatformContextProps['data'], PlatformContextKeys>({
-    data,
-    meta,
-    knownDataTypes: KNOWN_PLATFORM_CONTEXT_KEYS[platform] ?? [],
-    onGetKnownDataDetails: () => {
-      switch (platform) {
-        default:
-          return undefined;
-      }
-    },
-  });
-}
-
-export function getUnknownPlatformContextData({
-  platform,
-  data,
-  meta,
-}: PlatformContextProps) {
-  return getUnknownData({
-    allData: data,
-    knownKeys: KNOWN_PLATFORM_CONTEXT_KEYS[platform] ?? [],
-    meta,
-  });
-}
-
-export function getPlatformContextIcon({
-  platform,
-  size = 'sm',
-}: Pick<PlatformContextProps, 'platform'> & {
-  size?: IconSize;
-}) {
-  let platformIconName = '';
-  switch (platform) {
-    case 'laravel':
-      platformIconName = 'php-laravel';
-      break;
-    default:
-      break;
-  }
-
-  if (platformIconName.length === 0) {
-    return null;
-  }
-  return (
-    <PlatformIcon
-      size={size}
-      platform={platformIconName}
-      data-test-id={`${platform}-context-icon`}
-    />
-  );
-}

+ 65 - 0
static/app/components/events/contexts/platformContext/laravel.spec.tsx

@@ -0,0 +1,65 @@
+import {EventFixture} from 'sentry-fixture/event';
+
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import ContextCard from 'sentry/components/events/contexts/contextCard';
+import {
+  getLaravelContextData,
+  type LaravelContext,
+} from 'sentry/components/events/contexts/platformContext/laravel';
+
+const MOCK_LARAVEL_CONTEXT: LaravelContext = {
+  type: 'default',
+  // No known keys, but extra data is still valid and preserved
+  extra_data: 'something',
+  unknown_key: 123,
+};
+
+const MOCK_REDACTION = {
+  extra_data: {
+    '': {
+      rem: [['organization:0', 's', 0, 0]],
+      len: 5,
+    },
+  },
+};
+
+describe('LaravelContext', function () {
+  it('returns values and according to the parameters', function () {
+    expect(getLaravelContextData({data: MOCK_LARAVEL_CONTEXT})).toEqual([
+      {
+        key: 'extra_data',
+        subject: 'extra_data',
+        value: 'something',
+        meta: undefined,
+      },
+      {
+        key: 'unknown_key',
+        subject: 'unknown_key',
+        value: 123,
+        meta: undefined,
+      },
+    ]);
+  });
+
+  it('renders with meta annotations correctly', function () {
+    const event = EventFixture({
+      _meta: {contexts: {laravel: MOCK_REDACTION}},
+    });
+
+    render(
+      <ContextCard
+        event={event}
+        type={'default'}
+        alias={'laravel'}
+        value={{...MOCK_LARAVEL_CONTEXT, extra_data: ''}}
+      />
+    );
+
+    expect(screen.getByText('Laravel Context')).toBeInTheDocument();
+    expect(screen.getByText('unknown_key')).toBeInTheDocument();
+    expect(screen.getByText('123')).toBeInTheDocument();
+    expect(screen.getByText('extra_data')).toBeInTheDocument();
+    expect(screen.getByText(/redacted/)).toBeInTheDocument();
+  });
+});

+ 15 - 0
static/app/components/events/contexts/platformContext/laravel.tsx

@@ -0,0 +1,15 @@
+import {getContextKeys} from 'sentry/components/events/contexts/utils';
+import type {KeyValueListData} from 'sentry/types/group';
+
+export interface LaravelContext {
+  // Any custom keys users may set
+  [key: string]: any;
+}
+
+export function getLaravelContextData({data}: {data: LaravelContext}): KeyValueListData {
+  return getContextKeys({data}).map(ctxKey => ({
+    key: ctxKey,
+    subject: ctxKey,
+    value: data[ctxKey],
+  }));
+}

+ 71 - 0
static/app/components/events/contexts/platformContext/react.spec.tsx

@@ -0,0 +1,71 @@
+import {EventFixture} from 'sentry-fixture/event';
+
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import ContextCard from 'sentry/components/events/contexts/contextCard';
+import {
+  getReactContextData,
+  type ReactContext,
+} from 'sentry/components/events/contexts/platformContext/react';
+
+const MOCK_REACT_CONTEXT: ReactContext = {
+  type: 'default',
+  version: '17.0.2',
+  // Extra data is still valid and preserved
+  extra_data: 'something',
+  unknown_key: 123,
+};
+
+const MOCK_REDACTION = {
+  version: {
+    '': {
+      rem: [['organization:0', 's', 0, 0]],
+      len: 5,
+    },
+  },
+};
+
+describe('ReactContext', function () {
+  it('returns values and according to the parameters', function () {
+    expect(getReactContextData({data: MOCK_REACT_CONTEXT})).toEqual([
+      {
+        key: 'version',
+        subject: 'Version',
+        value: '17.0.2',
+      },
+      {
+        key: 'extra_data',
+        subject: 'extra_data',
+        value: 'something',
+        meta: undefined,
+      },
+      {
+        key: 'unknown_key',
+        subject: 'unknown_key',
+        value: 123,
+        meta: undefined,
+      },
+    ]);
+  });
+
+  it('renders with meta annotations correctly', function () {
+    const event = EventFixture({
+      _meta: {contexts: {react: MOCK_REDACTION}},
+    });
+
+    render(
+      <ContextCard
+        event={event}
+        type={'default'}
+        alias={'react'}
+        value={{...MOCK_REACT_CONTEXT, version: ''}}
+      />
+    );
+
+    expect(screen.getByText('React')).toBeInTheDocument();
+    expect(screen.getByText('unknown_key')).toBeInTheDocument();
+    expect(screen.getByText('123')).toBeInTheDocument();
+    expect(screen.getByText('Version')).toBeInTheDocument();
+    expect(screen.getByText(/redacted/)).toBeInTheDocument();
+  });
+});

+ 32 - 0
static/app/components/events/contexts/platformContext/react.tsx

@@ -0,0 +1,32 @@
+import {getContextKeys} from 'sentry/components/events/contexts/utils';
+import {t} from 'sentry/locale';
+import type {KeyValueListData} from 'sentry/types/group';
+
+enum ReactContextKeys {
+  VERSION = 'version',
+}
+
+export interface ReactContext {
+  // Any custom keys users may set
+  [key: string]: any;
+  [ReactContextKeys.VERSION]: string;
+}
+
+export function getReactContextData({data}: {data: ReactContext}): KeyValueListData {
+  return getContextKeys({data}).map(ctxKey => {
+    switch (ctxKey) {
+      case ReactContextKeys.VERSION:
+        return {
+          key: ctxKey,
+          subject: t('Version'),
+          value: data.version,
+        };
+      default:
+        return {
+          key: ctxKey,
+          subject: ctxKey,
+          value: data[ctxKey],
+        };
+    }
+  });
+}

+ 77 - 0
static/app/components/events/contexts/platformContext/unity.spec.tsx

@@ -0,0 +1,77 @@
+import {EventFixture} from 'sentry-fixture/event';
+
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import ContextCard from 'sentry/components/events/contexts/contextCard';
+import {getUnityContextData} from 'sentry/components/events/contexts/platformContext/unity';
+
+export const MOCK_UNITY_CONTEXT = {
+  type: 'unity' as const,
+  copy_texture_support: 'Basic, Copy3D, DifferentTypes, TextureToRT, RTToTexture',
+  editor_version: '2022.1.23f1',
+  install_mode: 'Store',
+  rendering_threading_mode: 'LegacyJobified',
+  target_frame_rate: '-1',
+  // Extra data is still valid and preserved
+  extra_data: 'something',
+  unknown_key: 123,
+};
+
+export const MOCK_REDACTION = {
+  install_mode: {
+    '': {
+      rem: [['organization:0', 'x']],
+    },
+  },
+};
+
+describe('UnityContext', function () {
+  it('returns values and according to the parameters', function () {
+    expect(getUnityContextData({data: MOCK_UNITY_CONTEXT})).toEqual([
+      {
+        key: 'copy_texture_support',
+        subject: 'Copy Texture Support',
+        value: 'Basic, Copy3D, DifferentTypes, TextureToRT, RTToTexture',
+      },
+      {
+        key: 'editor_version',
+        subject: 'Editor Version',
+        value: '2022.1.23f1',
+      },
+      {key: 'install_mode', subject: 'Install Mode', value: 'Store'},
+      {
+        key: 'rendering_threading_mode',
+        subject: 'Rendering Threading Mode',
+        value: 'LegacyJobified',
+      },
+      {
+        key: 'target_frame_rate',
+        subject: 'Target Frame Rate',
+        value: '-1',
+      },
+      {key: 'extra_data', subject: 'extra_data', value: 'something'},
+      {key: 'unknown_key', subject: 'unknown_key', value: 123},
+    ]);
+  });
+
+  it('renders with meta annotations correctly', function () {
+    const event = EventFixture({
+      _meta: {contexts: {unity: MOCK_REDACTION}},
+    });
+
+    render(
+      <ContextCard
+        event={event}
+        type={'unity'}
+        alias={'unity'}
+        value={{...MOCK_UNITY_CONTEXT, install_mode: ''}}
+      />
+    );
+
+    expect(screen.getByText('Unity')).toBeInTheDocument();
+    expect(screen.getByText('Editor Version')).toBeInTheDocument();
+    expect(screen.getByText('2022.1.23f1')).toBeInTheDocument();
+    expect(screen.getByText('Install Mode')).toBeInTheDocument();
+    expect(screen.getByText(/redacted/)).toBeInTheDocument();
+  });
+});

+ 47 - 0
static/app/components/events/contexts/platformContext/unity.tsx

@@ -0,0 +1,47 @@
+import {getContextKeys} from 'sentry/components/events/contexts/utils';
+import {t} from 'sentry/locale';
+import {type UnityContext, UnityContextKey} from 'sentry/types/event';
+import type {KeyValueListData} from 'sentry/types/group';
+
+export function getUnityContextData({data}: {data: UnityContext}): KeyValueListData {
+  return getContextKeys({data}).map(ctxKey => {
+    switch (ctxKey) {
+      case UnityContextKey.COPY_TEXTURE_SUPPORT:
+        return {
+          key: ctxKey,
+          subject: t('Copy Texture Support'),
+          value: data.copy_texture_support,
+        };
+      case UnityContextKey.EDITOR_VERSION:
+        return {
+          key: ctxKey,
+          subject: t('Editor Version'),
+          value: data.editor_version,
+        };
+      case UnityContextKey.INSTALL_MODE:
+        return {
+          key: ctxKey,
+          subject: t('Install Mode'),
+          value: data.install_mode,
+        };
+      case UnityContextKey.RENDERING_THREADING_MODE:
+        return {
+          key: ctxKey,
+          subject: t('Rendering Threading Mode'),
+          value: data.rendering_threading_mode,
+        };
+      case UnityContextKey.TARGET_FRAME_RATE:
+        return {
+          key: ctxKey,
+          subject: t('Target Frame Rate'),
+          value: data.target_frame_rate,
+        };
+      default:
+        return {
+          key: ctxKey,
+          subject: ctxKey,
+          value: data[ctxKey],
+        };
+    }
+  });
+}

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