Browse Source

ref(stacktrace): Create shared functions for hidden frames toggle logic (#53629)

Closes https://github.com/getsentry/sentry/issues/53341

After https://github.com/getsentry/sentry/issues/53300 and
https://github.com/getsentry/sentry/issues/52592 are done, we want to
ensure that the logic and styles do not diverge between the two stack
trace components. We can prevent this by extracting out some common
components and functions to be used in both.
Julia Hoge 1 year ago
parent
commit
32ae372065

+ 24 - 93
static/app/components/events/interfaces/crashContent/stackTrace/content.tsx

@@ -12,7 +12,14 @@ import {defined} from 'sentry/utils';
 import withOrganization from 'sentry/utils/withOrganization';
 
 import DeprecatedLine, {DeprecatedLineProps} from '../../frame/deprecatedLine';
-import {getImageRange, parseAddress, stackTracePlatformIcon} from '../../utils';
+import {
+  findImageForAddress,
+  getHiddenFrameIndices,
+  getLastFrameIndex,
+  isRepeatedFrame,
+  parseAddress,
+  stackTracePlatformIcon,
+} from '../../utils';
 
 import StacktracePlatformIcon from './platformIcon';
 
@@ -38,19 +45,6 @@ type Props = {
   threadId?: number;
 } & Partial<DefaultProps>;
 
-function isRepeatedFrame(frame: Frame, nextFrame?: Frame) {
-  if (!nextFrame) {
-    return false;
-  }
-  return (
-    frame.lineNo === nextFrame.lineNo &&
-    frame.instructionAddr === nextFrame.instructionAddr &&
-    frame.package === nextFrame.package &&
-    frame.module === nextFrame.module &&
-    frame.function === nextFrame.function
-  );
-}
-
 function Content({
   data,
   event,
@@ -114,63 +108,6 @@ function Content({
     return countMap;
   }
 
-  function getRepeatedFrameIndices() {
-    const repeats: number[] = [];
-    (data.frames ?? []).forEach((frame, frameIdx) => {
-      const nextFrame = (data.frames ?? [])[frameIdx + 1];
-      const repeatedFrame = isRepeatedFrame(frame, nextFrame);
-
-      if (repeatedFrame) {
-        repeats.push(frameIdx);
-      }
-    });
-    return repeats;
-  }
-
-  function getHiddenFrameIndices(frameCountMap: {[frameIndex: number]: number}) {
-    const repeatedIndeces = getRepeatedFrameIndices();
-    let hiddenFrameIndices: number[] = [];
-    Object.keys(toggleFrameMap)
-      .filter(frameIndex => toggleFrameMap[frameIndex] === true)
-      .forEach(indexString => {
-        const index = parseInt(indexString, 10);
-        const indicesToBeAdded: number[] = [];
-        let i = 1;
-        let numHidden = frameCountMap[index];
-        while (numHidden > 0) {
-          if (!repeatedIndeces.includes(index - i)) {
-            indicesToBeAdded.push(index - i);
-            numHidden -= 1;
-          }
-          i += 1;
-        }
-        hiddenFrameIndices = [...hiddenFrameIndices, ...indicesToBeAdded];
-      });
-    return hiddenFrameIndices;
-  }
-
-  function findImageForAddress(
-    address: Frame['instructionAddr'],
-    addrMode: Frame['addrMode']
-  ) {
-    const images = event.entries.find(entry => entry.type === 'debugmeta')?.data?.images;
-
-    if (!images || !address) {
-      return null;
-    }
-
-    const image = images.find((img, idx) => {
-      if (!addrMode || addrMode === 'abs') {
-        const [startAddress, endAddress] = getImageRange(img);
-        return address >= (startAddress as any) && address < (endAddress as any);
-      }
-
-      return addrMode === `rel:${idx}`;
-    });
-
-    return image;
-  }
-
   function isFrameAfterLastNonApp(): boolean {
     if (!frames.length || frames.length < 2) {
       return false;
@@ -204,21 +141,6 @@ function Content({
     }));
   };
 
-  function getLastFrameIndex() {
-    const inAppFrameIndexes = frames
-      .map((frame, frameIndex) => {
-        if (frame.inApp) {
-          return frameIndex;
-        }
-        return undefined;
-      })
-      .filter(frame => frame !== undefined);
-
-    return !inAppFrameIndexes.length
-      ? frames.length - 1
-      : inAppFrameIndexes[inAppFrameIndexes.length - 1];
-  }
-
   function renderOmittedFrames(firstFrameOmitted: any, lastFrameOmitted: any) {
     const props = {
       className: 'frame frames-omitted',
@@ -237,9 +159,13 @@ function Content({
 
   const firstFrameOmitted = framesOmitted?.[0] ?? null;
   const lastFrameOmitted = framesOmitted?.[1] ?? null;
-  const lastFrameIndex = getLastFrameIndex();
+  const lastFrameIndex = getLastFrameIndex(frames);
   const frameCountMap = getInitialFrameCounts();
-  const hiddenFrameIndices: number[] = getHiddenFrameIndices(frameCountMap);
+  const hiddenFrameIndices: number[] = getHiddenFrameIndices({
+    data,
+    toggleFrameMap,
+    frameCountMap,
+  });
 
   const mechanism =
     platform === 'java' && event.tags?.find(({key}) => key === 'mechanism')?.value;
@@ -249,10 +175,11 @@ function Content({
 
   const maxLengthOfAllRelativeAddresses = frames.reduce(
     (maxLengthUntilThisPoint, frame) => {
-      const correspondingImage = findImageForAddress(
-        frame.instructionAddr,
-        frame.addrMode
-      );
+      const correspondingImage = findImageForAddress({
+        event,
+        addrMode: frame.addrMode,
+        address: frame.instructionAddr,
+      });
 
       try {
         const relativeAddress = (
@@ -296,7 +223,11 @@ function Content({
           timesRepeated: nRepeats,
           showingAbsoluteAddress: showingAbsoluteAddresses,
           onAddressToggle: handleToggleAddresses,
-          image: findImageForAddress(frame.instructionAddr, frame.addrMode),
+          image: findImageForAddress({
+            event,
+            addrMode: frame.addrMode,
+            address: frame.instructionAddr,
+          }),
           maxLengthOfRelativeAddress: maxLengthOfAllRelativeAddresses,
           registers: {}, // TODO: Fix registers
           isFrameAfterLastNonApp: isFrameAfterLastNonApp(),

+ 23 - 93
static/app/components/events/interfaces/crashContent/stackTrace/nativeContent.tsx

@@ -10,7 +10,13 @@ import {StacktraceType} from 'sentry/types/stacktrace';
 import {defined} from 'sentry/utils';
 
 import NativeFrame from '../../nativeFrame';
-import {getImageRange, parseAddress} from '../../utils';
+import {
+  findImageForAddress,
+  getHiddenFrameIndices,
+  getLastFrameIndex,
+  isRepeatedFrame,
+  parseAddress,
+} from '../../utils';
 
 type Props = {
   data: StacktraceType;
@@ -29,19 +35,6 @@ type Props = {
   newestFirst?: boolean;
 };
 
-function isRepeatedFrame(frame: Frame, nextFrame?: Frame) {
-  if (!nextFrame) {
-    return false;
-  }
-  return (
-    frame.lineNo === nextFrame.lineNo &&
-    frame.instructionAddr === nextFrame.instructionAddr &&
-    frame.package === nextFrame.package &&
-    frame.module === nextFrame.module &&
-    frame.function === nextFrame.function
-  );
-}
-
 export function NativeContent({
   data,
   platform,
@@ -102,63 +95,6 @@ export function NativeContent({
     return countMap;
   }
 
-  function getRepeatedFrameIndices() {
-    const repeats: number[] = [];
-    (data.frames ?? []).forEach((frame, frameIdx) => {
-      const nextFrame = (data.frames ?? [])[frameIdx + 1];
-      const repeatedFrame = isRepeatedFrame(frame, nextFrame);
-
-      if (repeatedFrame) {
-        repeats.push(frameIdx);
-      }
-    });
-    return repeats;
-  }
-
-  function getHiddenFrameIndices(frameCountMap: {[frameIndex: number]: number}) {
-    const repeatedIndeces = getRepeatedFrameIndices();
-    let hiddenFrameIndices: number[] = [];
-    Object.keys(toggleFrameMap)
-      .filter(frameIndex => toggleFrameMap[frameIndex] === true)
-      .forEach(indexString => {
-        const index = parseInt(indexString, 10);
-        const indicesToBeAdded: number[] = [];
-        let i = 1;
-        let numHidden = frameCountMap[index];
-        while (numHidden > 0) {
-          if (!repeatedIndeces.includes(index - i)) {
-            indicesToBeAdded.push(index - i);
-            numHidden -= 1;
-          }
-          i += 1;
-        }
-        hiddenFrameIndices = [...hiddenFrameIndices, ...indicesToBeAdded];
-      });
-    return hiddenFrameIndices;
-  }
-
-  function findImageForAddress(
-    address: Frame['instructionAddr'],
-    addrMode: Frame['addrMode']
-  ) {
-    const images = event.entries.find(entry => entry.type === 'debugmeta')?.data?.images;
-
-    if (!images || !address) {
-      return null;
-    }
-
-    const image = images.find((img, idx) => {
-      if (!addrMode || addrMode === 'abs') {
-        const [startAddress, endAddress] = getImageRange(img);
-        return address >= (startAddress as any) && address < (endAddress as any);
-      }
-
-      return addrMode === `rel:${idx}`;
-    });
-
-    return image;
-  }
-
   function isFrameUsedForGrouping(frame: Frame) {
     const {minGroupingLevel} = frame;
 
@@ -191,21 +127,6 @@ export function NativeContent({
     }));
   };
 
-  function getLastFrameIndex() {
-    const inAppFrameIndexes = frames
-      .map((frame, frameIndex) => {
-        if (frame.inApp) {
-          return frameIndex;
-        }
-        return undefined;
-      })
-      .filter(frame => frame !== undefined);
-
-    return !inAppFrameIndexes.length
-      ? frames.length - 1
-      : inAppFrameIndexes[inAppFrameIndexes.length - 1];
-  }
-
   function renderOmittedFrames(firstFrameOmitted: any, lastFrameOmitted: any) {
     return t(
       'Frames %d until %d were omitted and not available.',
@@ -216,18 +137,23 @@ export function NativeContent({
 
   const firstFrameOmitted = framesOmitted?.[0] ?? null;
   const lastFrameOmitted = framesOmitted?.[1] ?? null;
-  const lastFrameIndex = getLastFrameIndex();
+  const lastFrameIndex = getLastFrameIndex(frames);
   const frameCountMap = getInitialFrameCounts();
-  const hiddenFrameIndices: number[] = getHiddenFrameIndices(frameCountMap);
+  const hiddenFrameIndices: number[] = getHiddenFrameIndices({
+    data,
+    toggleFrameMap,
+    frameCountMap,
+  });
 
   let nRepeats = 0;
 
   const maxLengthOfAllRelativeAddresses = frames.reduce(
     (maxLengthUntilThisPoint, frame) => {
-      const correspondingImage = findImageForAddress(
-        frame.instructionAddr,
-        frame.addrMode
-      );
+      const correspondingImage = findImageForAddress({
+        event,
+        addrMode: frame.addrMode,
+        address: frame.instructionAddr,
+      });
 
       try {
         const relativeAddress = (
@@ -277,7 +203,11 @@ export function NativeContent({
           onShowFramesToggle: (e: React.MouseEvent<HTMLElement>) => {
             handleToggleFrames(e, frameIndex);
           },
-          image: findImageForAddress(frame.instructionAddr, frame.addrMode),
+          image: findImageForAddress({
+            event,
+            addrMode: frame.addrMode,
+            address: frame.instructionAddr,
+          }),
           maxLengthOfRelativeAddress: maxLengthOfAllRelativeAddresses,
           registers: {},
           includeSystemFrames,

+ 98 - 1
static/app/components/events/interfaces/utils.tsx

@@ -7,7 +7,7 @@ import * as qs from 'query-string';
 import getThreadException from 'sentry/components/events/interfaces/threads/threadSelector/getThreadException';
 import {FILTER_MASK} from 'sentry/constants';
 import ConfigStore from 'sentry/stores/configStore';
-import {Frame, PlatformType} from 'sentry/types';
+import {Frame, PlatformType, StacktraceType} from 'sentry/types';
 import {Image} from 'sentry/types/debugImage';
 import {EntryRequest, EntryThreads, EntryType, Event, Thread} from 'sentry/types/event';
 import {defined} from 'sentry/utils';
@@ -19,6 +19,103 @@ import {fileExtensionToPlatform, getFileExtension} from 'sentry/utils/fileExtens
 function escapeBashString(v: string) {
   return v.replace(/(["$`\\])/g, '\\$1');
 }
+interface ImageForAddressProps {
+  addrMode: Frame['addrMode'];
+  address: Frame['instructionAddr'];
+  event: Event;
+}
+
+interface HiddenFrameIndicesProps {
+  data: StacktraceType;
+  frameCountMap: {[frameIndex: number]: number};
+  toggleFrameMap: {[frameIndex: number]: boolean};
+}
+
+export function findImageForAddress({event, addrMode, address}: ImageForAddressProps) {
+  const images = event.entries.find(entry => entry.type === 'debugmeta')?.data?.images;
+
+  if (!images || !address) {
+    return null;
+  }
+
+  const image = images.find((img, idx) => {
+    if (!addrMode || addrMode === 'abs') {
+      const [startAddress, endAddress] = getImageRange(img);
+      return address >= (startAddress as any) && address < (endAddress as any);
+    }
+
+    return addrMode === `rel:${idx}`;
+  });
+
+  return image;
+}
+
+export function isRepeatedFrame(frame: Frame, nextFrame?: Frame) {
+  if (!nextFrame) {
+    return false;
+  }
+  return (
+    frame.lineNo === nextFrame.lineNo &&
+    frame.instructionAddr === nextFrame.instructionAddr &&
+    frame.package === nextFrame.package &&
+    frame.module === nextFrame.module &&
+    frame.function === nextFrame.function
+  );
+}
+
+export function getRepeatedFrameIndices(data: StacktraceType) {
+  const repeats: number[] = [];
+  (data.frames ?? []).forEach((frame, frameIdx) => {
+    const nextFrame = (data.frames ?? [])[frameIdx + 1];
+    const repeatedFrame = isRepeatedFrame(frame, nextFrame);
+
+    if (repeatedFrame) {
+      repeats.push(frameIdx);
+    }
+  });
+  return repeats;
+}
+
+export function getHiddenFrameIndices({
+  data,
+  toggleFrameMap,
+  frameCountMap,
+}: HiddenFrameIndicesProps) {
+  const repeatedIndeces = getRepeatedFrameIndices(data);
+  let hiddenFrameIndices: number[] = [];
+  Object.keys(toggleFrameMap)
+    .filter(frameIndex => toggleFrameMap[frameIndex] === true)
+    .forEach(indexString => {
+      const index = parseInt(indexString, 10);
+      const indicesToBeAdded: number[] = [];
+      let i = 1;
+      let numHidden = frameCountMap[index];
+      while (numHidden > 0) {
+        if (!repeatedIndeces.includes(index - i)) {
+          indicesToBeAdded.push(index - i);
+          numHidden -= 1;
+        }
+        i += 1;
+      }
+      hiddenFrameIndices = [...hiddenFrameIndices, ...indicesToBeAdded];
+    });
+  return hiddenFrameIndices;
+}
+
+export function getLastFrameIndex(frames: Frame[]) {
+  const inAppFrameIndexes = frames
+    .map((frame, frameIndex) => {
+      if (frame.inApp) {
+        return frameIndex;
+      }
+      return undefined;
+    })
+    .filter(frame => frame !== undefined);
+
+  return !inAppFrameIndexes.length
+    ? frames.length - 1
+    : inAppFrameIndexes[inAppFrameIndexes.length - 1];
+}
 
 // TODO(dcramer): support cookies
 export function getCurlCommand(data: EntryRequest['data']) {