Browse Source

feat(suspect-spans): Add show more button for more examples (#30377)

In order to compact the information on the span view, only 2 examples per span
will be visible by default with up to 10 once the user clicks show more
transactions examples.
Tony Xiao 3 years ago
parent
commit
9d8d22ea7e

+ 2 - 0
static/app/views/performance/transactionSummary/transactionSpans/content.tsx

@@ -129,6 +129,7 @@ function SpansContent(props: Props) {
               location={location}
               orgSlug={organization.slug}
               eventView={eventView}
+              perSuspect={10}
               spanOps={defined(spanOp) ? [spanOp] : []}
               spanGroups={defined(spanGroup) ? [spanGroup] : []}
             >
@@ -164,6 +165,7 @@ function SpansContent(props: Props) {
                         generateTransactionLink={generateTransactionLink(transactionName)}
                         eventView={eventView}
                         totals={totals}
+                        preview={2}
                       />
                     ))}
                     <Pagination pageLinks={pageLinks} />

+ 17 - 1
static/app/views/performance/transactionSummary/transactionSpans/styles.tsx

@@ -40,13 +40,29 @@ export const UpperPanel = styled(Panel)`
   }
 `;
 
-export const LowerPanel = styled('div')`
+export const LowerPanel = styled('div')<{expandable: boolean}>`
   > div {
     border-top-left-radius: 0;
     border-top-right-radius: 0;
+
+    ${p =>
+      p.expandable &&
+      `
+      margin-bottom: 0;
+      border-bottom-left-radius: 0;
+      border-bottom-right-radius: 0;
+      `}
   }
 `;
 
+export const FooterPanel = styled(Panel)`
+  font-size: ${p => p.theme.fontSizeMedium};
+  padding: ${space(1)} ${space(0)} ${space(1)} ${space(3)};
+  border-top: 0;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+`;
+
 type HeaderItemProps = {
   label: string;
   value: React.ReactNode;

+ 24 - 2
static/app/views/performance/transactionSummary/transactionSpans/suspectSpanCard.tsx

@@ -1,5 +1,7 @@
+import {useReducer} from 'react';
 import {Location, LocationDescriptor, Query} from 'history';
 
+import Button from 'sentry/components/button';
 import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
 import SortLink from 'sentry/components/gridEditable/sortLink';
 import Link from 'sentry/components/links/link';
@@ -18,6 +20,7 @@ import {PerformanceDuration} from '../../utils';
 
 import {
   emptyValue,
+  FooterPanel,
   HeaderItem,
   LowerPanel,
   SpanDurationBar,
@@ -86,6 +89,7 @@ type Props = {
   ) => LocationDescriptor;
   eventView: EventView;
   totals: SpansTotalValues | null;
+  preview: number;
 };
 
 export default function SuspectSpanEntry(props: Props) {
@@ -96,9 +100,18 @@ export default function SuspectSpanEntry(props: Props) {
     generateTransactionLink,
     eventView,
     totals,
+    preview,
   } = props;
 
-  const examples = suspectSpan.examples.map(example => ({
+  const expandable = suspectSpan.examples.length > preview;
+
+  const [collapsed, toggleCollapsed] = useReducer(state => !state, true);
+
+  const visibileExamples = collapsed
+    ? suspectSpan.examples.slice(0, preview)
+    : suspectSpan.examples;
+
+  const examples = visibileExamples.map(example => ({
     id: example.id,
     project: suspectSpan.project,
     // timestamps are in seconds but want them in milliseconds
@@ -127,7 +140,7 @@ export default function SuspectSpanEntry(props: Props) {
         <SpanCount sort={sort} suspectSpan={suspectSpan} totals={totals} />
         <TotalCumulativeDuration sort={sort} suspectSpan={suspectSpan} totals={totals} />
       </UpperPanel>
-      <LowerPanel data-test-id="suspect-card-lower">
+      <LowerPanel expandable={expandable} data-test-id="suspect-card-lower">
         <GridEditable
           data={examples}
           columnOrder={SPANS_TABLE_COLUMN_ORDER}
@@ -144,6 +157,15 @@ export default function SuspectSpanEntry(props: Props) {
           location={location}
         />
       </LowerPanel>
+      {expandable && (
+        <FooterPanel>
+          <Button priority="link" onClick={toggleCollapsed}>
+            {collapsed
+              ? t('Show More Transaction Examples')
+              : t('Hide Transaction Examples')}
+          </Button>
+        </FooterPanel>
+      )}
     </div>
   );
 }

+ 12 - 2
tests/js/sentry-test/performance/initializePerformanceData.ts

@@ -61,6 +61,11 @@ export const SAMPLE_SPANS = [
         description: 'span-2',
         spans: [{id: 'acacac11'}, {id: 'acacac22'}],
       },
+      {
+        id: 'adadadadadadadad',
+        description: 'span-3',
+        spans: [{id: 'adadad11'}, {id: 'adadad22'}],
+      },
     ],
   },
   {
@@ -69,14 +74,19 @@ export const SAMPLE_SPANS = [
     examples: [
       {
         id: 'bcbcbcbcbcbcbcbc',
-        description: 'span-3',
+        description: 'span-4',
         spans: [{id: 'bcbcbc11'}, {id: 'bcbcbc11'}],
       },
       {
         id: 'bdbdbdbdbdbdbdbd',
-        description: 'span-4',
+        description: 'span-5',
         spans: [{id: 'bdbdbd11'}, {id: 'bdbdbd22'}],
       },
+      {
+        id: 'bebebebebebebebe',
+        description: 'span-6',
+        spans: [{id: 'bebebe11'}, {id: 'bebebe22'}],
+      },
     ],
   },
 ];

+ 57 - 2
tests/js/spec/views/performance/transactionSpans/index.spec.tsx

@@ -3,7 +3,13 @@ import {
   generateSuspectSpansResponse,
   SAMPLE_SPANS,
 } from 'sentry-test/performance/initializePerformanceData';
-import {act, mountWithTheme, screen, within} from 'sentry-test/reactTestingLibrary';
+import {
+  act,
+  fireEvent,
+  mountWithTheme,
+  screen,
+  within,
+} from 'sentry-test/reactTestingLibrary';
 
 import ProjectsStore from 'sentry/stores/projectsStore';
 import {getShortEventId} from 'sentry/utils/events';
@@ -130,7 +136,8 @@ describe('Performance > Transaction Spans', function () {
           await within(upper).findByText('Total Exclusive Time')
         ).toBeInTheDocument();
 
-        for (const example of SAMPLE_SPANS[i].examples) {
+        // only 2 examples show by default
+        for (const example of SAMPLE_SPANS[i].examples.slice(0, 2)) {
           expect(
             await within(card).findByText(getShortEventId(example.id))
           ).toBeInTheDocument();
@@ -271,5 +278,53 @@ describe('Performance > Transaction Spans', function () {
         expect(await within(lower).findByText('Cumulative Duration')).toBeInTheDocument();
       }
     });
+
+    it('allows toggling of more examples', async function () {
+      const initialData = initializeData();
+      mountWithTheme(
+        <TransactionSpans
+          organization={initialData.organization}
+          location={initialData.router.location}
+        />,
+        {context: initialData.routerContext}
+      );
+
+      const cards = await screen.findAllByTestId('suspect-card');
+      expect(cards).toHaveLength(2);
+      for (let i = 0; i < cards.length; i++) {
+        const card = cards[i];
+
+        expect(
+          within(card).queryByText('Show More Transaction Examples')
+        ).toBeInTheDocument();
+        expect(
+          within(card).queryByText('Hide Transaction Examples')
+        ).not.toBeInTheDocument();
+
+        // only 2 examples show by default
+        for (const example of SAMPLE_SPANS[i].examples.slice(0, 2)) {
+          expect(
+            await within(card).findByText(getShortEventId(example.id))
+          ).toBeInTheDocument();
+        }
+
+        fireEvent.click(await within(card).findByText('Show More Transaction Examples'));
+
+        expect(within(card).queryByText('Hide Transaction Examples')).toBeInTheDocument();
+        expect(
+          within(card).queryByText('Show More Transaction Examples')
+        ).not.toBeInTheDocument();
+
+        // each span has 3 examples
+        expect(SAMPLE_SPANS[i].examples).toHaveLength(3);
+
+        // all the examples should be shown now
+        for (const example of SAMPLE_SPANS[i].examples) {
+          expect(
+            await within(card).findByText(getShortEventId(example.id))
+          ).toBeInTheDocument();
+        }
+      }
+    });
   });
 });