Browse Source

feat(starfish): Single endpoint scoped span summary page (#50770)

Updates the span summary link from the endpoint overview page to be
scoped to the selected endpoint.
Adds View More Endpoints button to expand scope from single endpoint to
all endpoints.
Adds sps and p95 deltas.
Cleans up ui a bit to match design.
edwardgou-sentry 1 year ago
parent
commit
911744ff57

+ 4 - 0
static/app/views/starfish/queries/useSpanTransactionMetrics.tsx

@@ -13,7 +13,9 @@ import {useSpansQuery} from 'sentry/views/starfish/utils/useSpansQuery';
 export type SpanTransactionMetrics = {
   'p50(span.duration)': number;
   'p95(span.duration)': number;
+  'percentile_percent_change(span.duration, 0.95)': number;
   'sps()': number;
+  'sps_percent_change()': number;
   'sum(span.self_time)': number;
   'time_spent_percentage(local)': number;
   transaction: string;
@@ -80,8 +82,10 @@ function getEventView(span: {group: string}, location: Location, transactions: s
       fields: [
         'transaction',
         'sps()',
+        'sps_percent_change()',
         'sum(span.duration)',
         'p95(span.duration)',
+        'percentile_percent_change(span.duration, 0.95)',
         'time_spent_percentage(local)',
       ],
       orderby: '-time_spent_percentage_local',

+ 49 - 32
static/app/views/starfish/views/spanSummaryPage/index.tsx

@@ -1,6 +1,8 @@
 import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
+import omit from 'lodash/omit';
 
+import {Button} from 'sentry/components/button';
 import DatePageFilter from 'sentry/components/datePageFilter';
 import * as Layout from 'sentry/components/layouts/thirds';
 import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
@@ -32,11 +34,13 @@ type Props = {
 
 function SpanSummaryPage({params, location}: Props) {
   const {groupId} = params;
-  const {transaction} = location.query;
+  const {transaction, endpoint} = location.query;
+
+  const queryFilter = endpoint ? {transactionName: endpoint} : undefined;
 
   const {data: spanMetas} = useSpanMeta(
     groupId,
-    undefined,
+    queryFilter,
     'span-summary-page-span-meta'
   );
   // TODO: Span meta might in theory return more than one row! In that case, we
@@ -49,7 +53,7 @@ function SpanSummaryPage({params, location}: Props) {
 
   const {data: spanMetrics} = useSpanMetrics(
     {group: groupId},
-    undefined,
+    queryFilter,
     ['sps()', 'sum(span.duration)', 'p95(span.duration)', 'time_spent_percentage()'],
     'span-summary-page-metrics'
   );
@@ -57,7 +61,7 @@ function SpanSummaryPage({params, location}: Props) {
   const {isLoading: areSpanMetricsSeriesLoading, data: spanMetricsSeriesData} =
     useSpanMetricsSeries(
       {group: groupId},
-      undefined,
+      queryFilter,
       ['p95(span.duration)', 'sps()'],
       'sidebar-span-metrics'
     );
@@ -70,37 +74,39 @@ function SpanSummaryPage({params, location}: Props) {
         <PageErrorProvider>
           <Layout.Header>
             <Layout.HeaderContent>
-              <Layout.Title> Span Summary </Layout.Title>
-            </Layout.HeaderContent>{' '}
+              <Layout.Title>{t('Span Summary')}</Layout.Title>
+            </Layout.HeaderContent>
           </Layout.Header>
           <Layout.Body>
             <Layout.Main fullWidth>
               <PageErrorAlert />
-              <FilterOptionsContainer>
-                <DatePageFilter alignDropdown="left" />
-              </FilterOptionsContainer>
               <BlockContainer>
-                <Block title={t('Operation')}>{span?.['span.op']}</Block>
-                <Block
-                  title={t('Throughput')}
-                  description={t('Throughput of this span per second')}
-                >
-                  <ThroughputCell throughputPerSecond={spanMetrics?.['sps()']} />
-                </Block>
-                <Block title={t('Duration')} description={t('Time spent in this span')}>
-                  <DurationCell milliseconds={spanMetrics?.['p95(span.duration)']} />
-                </Block>
-                <Block
-                  title={t('Time Spent')}
-                  description={t(
-                    'Time spent in this span as a proportion of total application time'
-                  )}
-                >
-                  <TimeSpentCell
-                    timeSpentPercentage={spanMetrics?.['time_spent_percentage()']}
-                    totalSpanTime={spanMetrics?.['sum(span.duration)']}
-                  />
-                </Block>
+                <FilterOptionsContainer>
+                  <DatePageFilter alignDropdown="left" />
+                </FilterOptionsContainer>
+                <BlockContainer>
+                  <Block title={t('Operation')}>{span?.['span.op']}</Block>
+                  <Block
+                    title={t('Throughput')}
+                    description={t('Throughput of this span per second')}
+                  >
+                    <ThroughputCell throughputPerSecond={spanMetrics?.['sps()']} />
+                  </Block>
+                  <Block title={t('Duration')} description={t('Time spent in this span')}>
+                    <DurationCell milliseconds={spanMetrics?.['p95(span.duration)']} />
+                  </Block>
+                  <Block
+                    title={t('Time Spent')}
+                    description={t(
+                      'Time spent in this span as a proportion of total application time'
+                    )}
+                  >
+                    <TimeSpentCell
+                      timeSpentPercentage={spanMetrics?.['time_spent_percentage()']}
+                      totalSpanTime={spanMetrics?.['sum(span.duration)']}
+                    />
+                  </Block>
+                </BlockContainer>
               </BlockContainer>
 
               {span?.['span.description'] && (
@@ -151,7 +157,17 @@ function SpanSummaryPage({params, location}: Props) {
                 </BlockContainer>
               )}
 
-              {span && <SpanTransactionsTable span={span} />}
+              {span && <SpanTransactionsTable span={span} endpoint={endpoint} />}
+              {endpoint && (
+                <Button
+                  to={{
+                    pathname: location.pathname,
+                    query: omit(location.query, 'endpoint'),
+                  }}
+                >
+                  {t('View More Endpoints')}
+                </Button>
+              )}
 
               {transaction && span?.group && (
                 <SampleList groupId={span.group} transactionName={transaction} />
@@ -169,7 +185,7 @@ const FilterOptionsContainer = styled('div')`
   flex-direction: row;
   gap: ${space(1)};
   align-items: center;
-  margin-bottom: ${space(2)};
+  flex: 1;
 `;
 
 type BlockProps = {
@@ -199,6 +215,7 @@ const BlockTitle = styled('h3')`
   font-size: ${p => p.theme.fontSizeMedium};
   margin: 0;
   margin-bottom: ${space(1)};
+  white-space: nowrap;
 `;
 
 const BlockContent = styled('h4')`

+ 40 - 10
static/app/views/starfish/views/spanSummaryPage/spanTransactionsTable.tsx

@@ -25,6 +25,7 @@ type Row = {
 
 type Props = {
   span: Pick<IndexedSpan, 'group'>;
+  endpoint?: string;
   onClickTransaction?: (row: Row) => void;
   openSidebar?: boolean;
 };
@@ -36,10 +37,18 @@ export type Keys =
   | 'sps()';
 export type TableColumnHeader = GridColumnHeader<Keys>;
 
-export function SpanTransactionsTable({span, openSidebar, onClickTransaction}: Props) {
+export function SpanTransactionsTable({
+  span,
+  openSidebar,
+  onClickTransaction,
+  endpoint,
+}: Props) {
   const location = useLocation();
 
-  const {data: spanTransactionMetrics, isLoading} = useSpanTransactionMetrics(span);
+  const {data: spanTransactionMetrics, isLoading} = useSpanTransactionMetrics(
+    span,
+    endpoint ? [endpoint] : undefined
+  );
 
   const spanTransactionsWithMetrics = spanTransactionMetrics.map(row => {
     return {
@@ -60,6 +69,7 @@ export function SpanTransactionsTable({span, openSidebar, onClickTransaction}: P
         row={row}
         openSidebar={openSidebar}
         onClickTransactionName={onClickTransaction}
+        endpoint={endpoint}
       />
     );
   };
@@ -83,14 +93,23 @@ type CellProps = {
   column: TableColumnHeader;
   row: Row;
   span: Pick<IndexedSpan, 'group'>;
+  endpoint?: string;
   onClickTransactionName?: (row: Row) => void;
   openSidebar?: boolean;
 };
 
-function BodyCell({span, column, row, openSidebar, onClickTransactionName}: CellProps) {
+function BodyCell({
+  span,
+  column,
+  row,
+  openSidebar,
+  onClickTransactionName,
+  endpoint,
+}: CellProps) {
   if (column.key === 'transaction') {
     return (
       <TransactionCell
+        endpoint={endpoint}
         span={span}
         row={row}
         column={column}
@@ -101,11 +120,21 @@ function BodyCell({span, column, row, openSidebar, onClickTransactionName}: Cell
   }
 
   if (column.key === 'p95(transaction.duration)') {
-    return <DurationCell milliseconds={row.metrics?.['p95(span.duration)']} />;
+    return (
+      <DurationCell
+        milliseconds={row.metrics?.['p95(span.duration)']}
+        delta={row.metrics?.['percentile_percent_change(span.duration, 0.95)']}
+      />
+    );
   }
 
   if (column.key === 'sps()') {
-    return <ThroughputCell throughputPerSecond={row.metrics?.['sps()']} />;
+    return (
+      <ThroughputCell
+        throughputPerSecond={row.metrics?.['sps()']}
+        delta={row.metrics?.['sps_percent_change()']}
+      />
+    );
   }
 
   if (column.key === 'time_spent_percentage(local)') {
@@ -120,11 +149,12 @@ function BodyCell({span, column, row, openSidebar, onClickTransactionName}: Cell
   return <span>{row[column.key]}</span>;
 }
 
-function TransactionCell({span, column, row}: CellProps) {
+function TransactionCell({span, column, row, endpoint}: CellProps) {
   return (
     <Fragment>
       <Link
         to={`/starfish/span/${encodeURIComponent(span.group)}?${qs.stringify({
+          endpoint,
           transaction: row.transaction,
         })}`}
       >
@@ -137,18 +167,18 @@ function TransactionCell({span, column, row}: CellProps) {
 const COLUMN_ORDER: TableColumnHeader[] = [
   {
     key: 'transaction',
-    name: 'In Endpoint',
-    width: 500,
+    name: 'Found In Endpoints',
+    width: COL_WIDTH_UNDEFINED,
   },
   {
     key: 'sps()',
     name: DataTitles.throughput,
-    width: COL_WIDTH_UNDEFINED,
+    width: 175,
   },
   {
     key: 'p95(transaction.duration)',
     name: DataTitles.p95,
-    width: COL_WIDTH_UNDEFINED,
+    width: 175,
   },
   {
     key: 'time_spent_percentage(local)',

+ 14 - 3
static/app/views/starfish/views/spans/spansTable.tsx

@@ -1,4 +1,5 @@
 import styled from '@emotion/styled';
+import {urlEncode} from '@sentry/utils';
 
 import GridEditable, {
   COL_WIDTH_UNDEFINED,
@@ -21,6 +22,7 @@ type Props = {
   orderBy: string;
   spansData: SpanDataRow[];
   columnOrder?: TableColumnHeader[];
+  endpoint?: string;
 };
 
 export type SpanDataRow = {
@@ -54,6 +56,7 @@ export default function SpansTable({
   onSetOrderBy,
   isLoading,
   columnOrder,
+  endpoint,
 }: Props) {
   const location = useLocation();
 
@@ -67,7 +70,7 @@ export default function SpansTable({
       }
       grid={{
         renderHeadCell: getRenderHeadCell(orderBy, onSetOrderBy),
-        renderBodyCell: (column, row) => renderBodyCell(column, row),
+        renderBodyCell: (column, row) => renderBodyCell(column, row, endpoint),
       }}
       location={location}
     />
@@ -97,12 +100,20 @@ function getRenderHeadCell(orderBy: string, onSetOrderBy: (orderBy: string) => v
   return renderHeadCell;
 }
 
-function renderBodyCell(column: TableColumnHeader, row: SpanDataRow): React.ReactNode {
+function renderBodyCell(
+  column: TableColumnHeader,
+  row: SpanDataRow,
+  endpoint?: string
+): React.ReactNode {
   if (column.key === 'span.description') {
     return (
       <OverflowEllipsisTextContainer>
         {row['span.group'] ? (
-          <Link to={`/starfish/span/${row['span.group']}`}>
+          <Link
+            to={`/starfish/span/${row['span.group']}${
+              endpoint ? `?${urlEncode({endpoint})}` : ''
+            }`}
+          >
             {row['span.description'] || '<null>'}
           </Link>
         ) : (

+ 1 - 0
static/app/views/starfish/views/webServiceView/endpointOverview/index.tsx

@@ -312,6 +312,7 @@ function SpanMetricsTable({
       spansData={spansData}
       orderBy="-time_spent_percentage"
       onSetOrderBy={() => undefined}
+      endpoint={transaction}
     />
   );
 }