Browse Source

feat(spans): Be able to regroup nested spans (#27716)

Alberto Leal 3 years ago
parent
commit
c6afa49005

+ 36 - 11
static/app/components/events/interfaces/spans/spanBar.tsx

@@ -119,6 +119,7 @@ type SpanBarProps = {
     | undefined;
   fetchEmbeddedChildrenState: FetchEmbeddedChildrenState;
   hasCollapsedSpanGroup: boolean;
+  toggleSpanGroup: (() => void) | undefined;
 };
 
 type SpanBarState = {
@@ -445,16 +446,38 @@ class SpanBar extends React.Component<SpanBarProps, SpanBarState> {
     errors: TraceError[] | null
   ) {
     const {generateContentSpanBarRef} = scrollbarManagerChildrenProps;
-    const {span, treeDepth} = this.props;
-
-    const operationName = getSpanOperation(span) ? (
-      <strong>
-        {getSpanOperation(span)}
-        {' \u2014 '}
-      </strong>
-    ) : (
-      ''
-    );
+    const {span, treeDepth, toggleSpanGroup} = this.props;
+
+    let titleFragments: React.ReactNode[] = [];
+
+    if (typeof toggleSpanGroup === 'function') {
+      titleFragments.push(
+        <Regroup
+          onClick={event => {
+            event.stopPropagation();
+            event.preventDefault();
+            toggleSpanGroup();
+          }}
+        >
+          <a
+            href="#regroup"
+            onClick={event => {
+              event.preventDefault();
+            }}
+          >
+            {t('Regroup')}
+          </a>
+        </Regroup>
+      );
+    }
+
+    const spanOperationName = getSpanOperation(span);
+    if (spanOperationName) {
+      titleFragments.push(spanOperationName);
+    }
+
+    titleFragments = titleFragments.flatMap(current => [current, ' \u2014 ']);
+
     const description = span?.description ?? getSpanID(span);
 
     const left = treeDepth * (TOGGLE_BORDER_BOX / 2) + MARGIN_LEFT;
@@ -473,7 +496,7 @@ class SpanBar extends React.Component<SpanBarProps, SpanBarState> {
           }}
         >
           <RowTitleContent errored={errored}>
-            {operationName}
+            <strong>{titleFragments}</strong>
             {description}
           </RowTitleContent>
         </RowTitle>
@@ -978,4 +1001,6 @@ const StyledIconWarning = styled(IconWarning)`
   margin-bottom: ${space(0.25)};
 `;
 
+const Regroup = styled('span')``;
+
 export default SpanBar;

+ 9 - 0
static/app/components/events/interfaces/spans/spanTree.tsx

@@ -232,6 +232,14 @@ class SpanTree extends React.Component<PropType> {
           spanNumber = spanNumber + 1;
         }
 
+        let toggleSpanGroup: (() => void) | undefined = undefined;
+        if (
+          (payload.type === 'span' || payload.type === 'root_span') &&
+          !payload.spanGrouping
+        ) {
+          toggleSpanGroup = payload.toggleSpanGroup;
+        }
+
         acc.spanTree.push(
           <SpanBar
             key={key}
@@ -254,6 +262,7 @@ class SpanTree extends React.Component<PropType> {
             toggleEmbeddedChildren={payload.toggleEmbeddedChildren}
             fetchEmbeddedChildrenState={payload.fetchEmbeddedChildrenState}
             hasCollapsedSpanGroup={hasCollapsedSpanGroup}
+            toggleSpanGroup={toggleSpanGroup}
           />
         );
 

+ 31 - 9
static/app/components/events/interfaces/spans/spanTreeModel.tsx

@@ -211,8 +211,13 @@ class SpanTreeModel {
     const isNotLastSpanOfGroup =
       isOnlySibling && !this.isRoot && descendantsSource.length === 1;
     const shouldGroup = isNotLastSpanOfGroup;
+    const hideSpanTree = hiddenSpanGroups.has(parentSpanID);
     const isLastSpanOfGroup =
-      isOnlySibling && !this.isRoot && descendantsSource.length !== 1;
+      isOnlySibling && !this.isRoot && (descendantsSource.length !== 1 || hideSpanTree);
+    const isFirstSpanOfGroup =
+      shouldGroup &&
+      (spanGrouping === undefined ||
+        (Array.isArray(spanGrouping) && spanGrouping.length === 0));
 
     // For a collapsed span group chain to be useful, we prefer span groupings
     // that are two or more spans.
@@ -252,7 +257,11 @@ class SpanTreeModel {
       toggleEmbeddedChildren: this.toggleEmbeddedChildren,
       spanGrouping: spanGroupingCriteria ? spanGrouping : undefined,
       toggleSpanGroup:
-        spanGroupingCriteria && toggleSpanGroup ? toggleSpanGroup : undefined,
+        spanGroupingCriteria && toggleSpanGroup
+          ? toggleSpanGroup
+          : isFirstSpanOfGroup && this.showSpanGroup && !hideSpanTree
+          ? this.toggleSpanGroup
+          : undefined,
       showSpanGroup:
         (spanGroupingCriteria && toggleSpanGroup === undefined && this.showSpanGroup) ||
         (spanGroupingCriteria && toggleSpanGroup !== undefined && showSpanGroup),
@@ -264,6 +273,7 @@ class SpanTreeModel {
 
     const shouldHideSpanOfGroup =
       shouldGroup &&
+      !isLastSpanOfGroup &&
       ((toggleSpanGroup === undefined && !this.showSpanGroup) ||
         (toggleSpanGroup !== undefined && !showSpanGroup));
 
@@ -272,7 +282,14 @@ class SpanTreeModel {
         ? continuingTreeDepths
         : [...continuingTreeDepths, treeDepthEntry];
 
-    const {descendants} = descendantsSource.reduce(
+    for (const hiddenSpanGroup of hiddenSpanGroups) {
+      if (spanGroups.has(hiddenSpanGroup)) {
+        // If this span is hidden, then all the descendants are hidden as well
+        return [];
+      }
+    }
+
+    const {descendants} = (hideSpanTree ? [] : descendantsSource).reduce(
       (
         acc: {
           descendants: EnhancedProcessedSpanType[];
@@ -320,12 +337,6 @@ class SpanTreeModel {
       }
     );
 
-    for (const hiddenSpanGroup of hiddenSpanGroups) {
-      if (spanGroups.has(hiddenSpanGroup)) {
-        return descendants;
-      }
-    }
-
     if (this.isSpanFilteredOut(props)) {
       return [
         {
@@ -356,6 +367,17 @@ class SpanTreeModel {
       return [...descendants];
     }
 
+    if (
+      isFirstSpanOfGroup &&
+      this.showSpanGroup &&
+      !hideSpanTree &&
+      descendants.length <= 1
+    ) {
+      // If we know the descendants will be one span or less, we remove the "regroup" feature (therefore hide it)
+      // by setting toggleSpanGroup to be undefined for the first span of the group chain.
+      wrappedSpan.toggleSpanGroup = undefined;
+    }
+
     if (isLastSpanOfGroup && Array.isArray(spanGrouping) && spanGrouping.length === 1) {
       if (toggleSpanGroup !== undefined && !showSpanGroup) {
         toggleSpanGroup();

+ 18 - 4
tests/js/spec/components/events/interfaces/spans/waterfallModel.spec.tsx

@@ -651,7 +651,7 @@ describe('WaterfallModel', () => {
     // expect 1 or more spans are grouped
     expect(spans).toHaveLength(2);
 
-    expect(spans).toEqual([
+    const collapsedWaterfallExpected = [
       {
         ...fullWaterfall[0],
         numOfSpanChildren: 1,
@@ -695,7 +695,9 @@ describe('WaterfallModel', () => {
         showSpanGroup: false,
         toggleSpanGroup: expect.any(Function),
       },
-    ]);
+    ];
+
+    expect(spans).toEqual(collapsedWaterfallExpected);
 
     // Expand span group
     assert(spans[1].type === 'span' && spans[1].toggleSpanGroup);
@@ -725,7 +727,7 @@ describe('WaterfallModel', () => {
         treeDepth: 1,
         spanGrouping: undefined,
         showSpanGroup: false,
-        toggleSpanGroup: undefined,
+        toggleSpanGroup: expect.any(Function),
       },
       {
         ...fullWaterfall[1],
@@ -758,7 +760,7 @@ describe('WaterfallModel', () => {
             numOfSpanChildren: 1,
             spanGrouping: undefined,
             showSpanGroup: false,
-            toggleSpanGroup: undefined,
+            toggleSpanGroup: expect.any(Function),
           },
           {
             ...fullWaterfall[1],
@@ -779,5 +781,17 @@ describe('WaterfallModel', () => {
         toggleSpanGroup: expect.any(Function),
       },
     ]);
+
+    // Collapse span group
+    assert(spans[1].type === 'span' && spans[1].toggleSpanGroup);
+    spans[1].toggleSpanGroup();
+
+    spans = waterfallModel.getWaterfall({
+      viewStart: 0,
+      viewEnd: 1,
+    });
+
+    expect(spans).toHaveLength(2);
+    expect(spans).toEqual(collapsedWaterfallExpected);
   });
 });