Browse Source

feat(starfish): init span summary page (#49862)

Pretty much a copy of the span summary panel @gggritso made but as a
page
We can work off this.


![image](https://github.com/getsentry/sentry/assets/44422760/d96cb1fb-fa4c-4573-9d94-6f0e897b6c43)
Dominik Buszowiecki 1 year ago
parent
commit
19a21ab311

+ 6 - 0
static/app/routes.tsx

@@ -1727,6 +1727,12 @@ function buildRoutes() {
         path="span/:groupId/"
         component={make(() => import('sentry/views/starfish/views/spanSummary'))}
       />
+      <Route
+        path="span-summary/:groupId/"
+        component={make(
+          () => import('sentry/views/starfish/views/spans/spanSummaryPage')
+        )}
+      />
     </Fragment>
   );
   const starfishRoutes = (

+ 227 - 0
static/app/views/starfish/views/spans/spanSummaryPage/index.tsx

@@ -0,0 +1,227 @@
+import {RouteComponentProps} from 'react-router';
+import {useTheme} from '@emotion/react';
+import styled from '@emotion/styled';
+
+import MarkLine from 'sentry/components/charts/components/markLine';
+import DatePageFilter from 'sentry/components/datePageFilter';
+import * as Layout from 'sentry/components/layouts/thirds';
+import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
+import TimeSince from 'sentry/components/timeSince';
+import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import {formatPercentage} from 'sentry/utils/formatters';
+import {
+  PageErrorAlert,
+  PageErrorProvider,
+} from 'sentry/utils/performance/contexts/pageError';
+import Chart from 'sentry/views/starfish/components/chart';
+import {Block, BlockContainer} from 'sentry/views/starfish/views/spans/spanSummaryPanel';
+import {ReleasePreview} from 'sentry/views/starfish/views/spans/spanSummaryPanel/releasePreview';
+import {SpanDescription} from 'sentry/views/starfish/views/spans/spanSummaryPanel/spanDescription';
+import {SpanTransactionsTable} from 'sentry/views/starfish/views/spans/spanSummaryPanel/spanTransactionsTable';
+import {useApplicationMetrics} from 'sentry/views/starfish/views/spans/spanSummaryPanel/useApplicationMetrics';
+import {useSpanById} from 'sentry/views/starfish/views/spans/spanSummaryPanel/useSpanById';
+import {useSpanMetrics} from 'sentry/views/starfish/views/spans/spanSummaryPanel/useSpanMetrics';
+import {useSpanMetricSeries} from 'sentry/views/starfish/views/spans/spanSummaryPanel/useSpanMetricSeries';
+import {
+  useSpanFirstSeenEvent,
+  useSpanLastSeenEvent,
+} from 'sentry/views/starfish/views/spans/spanSummaryPanel/useSpanSeenEvent';
+
+type Props = {
+  location: Location;
+} & RouteComponentProps<{groupId: string}, {}>;
+
+function SpanSummaryPage({params}: Props) {
+  const {groupId} = params;
+
+  const theme = useTheme();
+
+  const {data: span} = useSpanById(groupId, 'span-summary-page');
+  const {data: applicationMetrics} = useApplicationMetrics();
+  const {data: spanMetrics} = useSpanMetrics({group_id: groupId});
+  const {data: spanMetricSeries} = useSpanMetricSeries(span);
+  const {data: firstSeenSpanEvent} = useSpanFirstSeenEvent({group_id: groupId});
+  const {data: lastSeenSpanEvent} = useSpanLastSeenEvent({group_id: groupId});
+
+  return (
+    <Layout.Page>
+      <PageFiltersContainer>
+        <PageErrorProvider>
+          <Layout.Header>
+            <Layout.HeaderContent>
+              <Layout.Title> Span Summary </Layout.Title>
+            </Layout.HeaderContent>{' '}
+          </Layout.Header>
+          <Layout.Body>
+            <Layout.Main fullWidth>
+              <PageErrorAlert />
+              <FilterOptionsContainer>
+                <DatePageFilter alignDropdown="left" />
+              </FilterOptionsContainer>
+              <BlockContainer>
+                <Block
+                  title={t('First Seen')}
+                  description={t(
+                    'The first time this span was ever seen in the current retention window'
+                  )}
+                >
+                  <TimeSince date={spanMetrics?.first_seen} />
+                  {firstSeenSpanEvent?.release && (
+                    <ReleasePreview release={firstSeenSpanEvent?.release} />
+                  )}
+                </Block>
+
+                <Block
+                  title={t('Last Seen')}
+                  description={t('The most recent time this span was seen')}
+                >
+                  <TimeSince date={spanMetrics?.last_seen} />
+                  {lastSeenSpanEvent?.release && (
+                    <ReleasePreview release={lastSeenSpanEvent?.release} />
+                  )}
+                </Block>
+
+                <Block
+                  title={t('Total Spans')}
+                  description={t(
+                    'The total number of times this span was seen in all time'
+                  )}
+                >
+                  {spanMetrics?.count}
+                </Block>
+
+                <Block
+                  title={t('App Impact')}
+                  description={t(
+                    'The total exclusive time taken up by this span vs. entire application'
+                  )}
+                >
+                  {formatPercentage(
+                    spanMetrics?.total_time / applicationMetrics?.total_time
+                  )}
+                </Block>
+              </BlockContainer>
+              <BlockContainer>
+                {span && (
+                  <Block title={t('Description')}>
+                    <SpanDescription span={span} />
+                  </Block>
+                )}
+              </BlockContainer>
+              <BlockContainer>
+                <Block
+                  title={t('Span Throughput (SPM)')}
+                  description={t('Spans per minute')}
+                >
+                  <Chart
+                    statsPeriod="24h"
+                    height={140}
+                    data={[
+                      {
+                        ...spanMetricSeries.spm,
+                        markLine: spanMetrics?.p50
+                          ? MarkLine({
+                              silent: true,
+                              animation: false,
+                              lineStyle: {color: theme.blue300, type: 'dotted'},
+                              data: [
+                                {
+                                  yAxis: spanMetrics.spm,
+                                },
+                              ],
+                              label: {
+                                show: true,
+                                position: 'insideStart',
+                              },
+                            })
+                          : undefined,
+                      },
+                    ]}
+                    start=""
+                    end=""
+                    loading={false}
+                    utc={false}
+                    stacked
+                    isLineChart
+                    disableXAxis
+                    hideYAxisSplitLine
+                  />
+                </Block>
+
+                <Block title={t('Span Duration (p50)')} description={t('Exclusive time')}>
+                  <Chart
+                    statsPeriod="24h"
+                    height={140}
+                    data={[
+                      {
+                        ...spanMetricSeries.p50,
+                        markLine: spanMetrics?.p50
+                          ? MarkLine({
+                              silent: true,
+                              animation: false,
+                              lineStyle: {color: theme.blue300, type: 'dotted'},
+                              data: [
+                                {
+                                  yAxis: spanMetrics.p50,
+                                },
+                              ],
+                              label: {
+                                show: true,
+                                position: 'insideStart',
+                              },
+                            })
+                          : undefined,
+                      },
+                    ]}
+                    start=""
+                    end=""
+                    loading={false}
+                    chartColors={theme.charts.getColorPalette(4).slice(3, 5)}
+                    utc={false}
+                    stacked
+                    isLineChart
+                    disableXAxis
+                    hideYAxisSplitLine
+                  />
+                </Block>
+
+                {span?.span_operation === 'http.client' ? (
+                  <Block title={t('Failure Rate')} description={t('Non-200 HTTP status')}>
+                    <Chart
+                      statsPeriod="24h"
+                      height={140}
+                      data={[spanMetricSeries.failure_rate]}
+                      start=""
+                      end=""
+                      loading={false}
+                      chartColors={[theme.charts.getColorPalette(2)[2]]}
+                      utc={false}
+                      stacked
+                      isLineChart
+                      disableXAxis
+                      hideYAxisSplitLine
+                    />
+                  </Block>
+                ) : null}
+              </BlockContainer>
+              <BlockContainer>
+                {span && <SpanTransactionsTable span={span} />}
+              </BlockContainer>
+            </Layout.Main>
+          </Layout.Body>
+        </PageErrorProvider>
+      </PageFiltersContainer>
+    </Layout.Page>
+  );
+}
+
+const FilterOptionsContainer = styled('div')`
+  display: flex;
+  flex-direction: row;
+  gap: ${space(1)};
+  align-items: center;
+  margin-bottom: ${space(2)};
+`;
+
+export default SpanSummaryPage;

+ 2 - 2
static/app/views/starfish/views/spans/spanSummaryPanel/index.tsx

@@ -193,7 +193,7 @@ type BlockProps = {
   description?: React.ReactNode;
 };
 
-function Block({title, description, children}: BlockProps) {
+export function Block({title, description, children}: BlockProps) {
   return (
     <BlockWrapper>
       <BlockTitle>
@@ -227,7 +227,7 @@ const BlockTooltipContainer = styled('span')`
   margin-left: ${space(1)};
 `;
 
-const BlockContainer = styled('div')`
+export const BlockContainer = styled('div')`
   display: flex;
   & > div:last-child {
     padding-right: ${space(1)};

+ 1 - 0
static/app/views/starfish/views/spans/spanSummaryPanel/types.tsx

@@ -5,4 +5,5 @@ export type Span = {
   description?: string;
   domain?: string;
   formatted_desc?: string;
+  span_id?: string;
 };

+ 29 - 0
static/app/views/starfish/views/spans/spanSummaryPanel/useSpanById.ts

@@ -0,0 +1,29 @@
+import {useQuery} from 'sentry/utils/queryClient';
+import {HOST} from 'sentry/views/starfish/utils/constants';
+import type {Span} from 'sentry/views/starfish/views/spans/spanSummaryPanel/types';
+
+export const useSpanById = (groupId: string, referrer: string) => {
+  const query = `
+  SELECT
+    group_id,
+    action,
+    description,
+    span_operation,
+    FROM spans_experimental_starfish
+    WHERE group_id = '${groupId}'
+    LIMIT 1
+  `;
+
+  const result = useQuery<Span[]>({
+    queryKey: ['span-by-id', groupId],
+    queryFn: () =>
+      fetch(`${HOST}/?query=${query}&referrer=${referrer}&format=sql`).then(res =>
+        res.json()
+      ),
+    retry: false,
+    initialData: [],
+    enabled: Boolean(groupId),
+  });
+
+  return {...result, data: result.data[0]};
+};

+ 5 - 2
static/app/views/starfish/views/spans/spanSummaryPanel/useSpanMetrics.tsx

@@ -13,7 +13,10 @@ type Metrics = {
   total_time: number;
 };
 
-export const useSpanMetrics = (span?: Span, referrer = 'span-metrics') => {
+export const useSpanMetrics = (
+  span?: Pick<Span, 'group_id'>,
+  referrer = 'span-metrics'
+) => {
   const query = span ? getQuery(span) : '';
 
   const {isLoading, error, data} = useQuery<Metrics[]>({
@@ -28,7 +31,7 @@ export const useSpanMetrics = (span?: Span, referrer = 'span-metrics') => {
   return {isLoading, error, data: data[0]};
 };
 
-const getQuery = (span: Span) => {
+const getQuery = (span: Pick<Span, 'group_id'>) => {
   return `
     SELECT
     count() as count,

+ 7 - 4
static/app/views/starfish/views/spans/spanSummaryPanel/useSpanSeenEvent.tsx

@@ -7,7 +7,7 @@ type HasTransaction = {
   transaction_id: string;
 };
 
-function useFirstSeenSpan(span?: Span, referrer = 'first-seen-span') {
+function useFirstSeenSpan(span?: Pick<Span, 'group_id'>, referrer = 'first-seen-span') {
   const query = span
     ? `
     SELECT transaction_id
@@ -31,14 +31,14 @@ function useFirstSeenSpan(span?: Span, referrer = 'first-seen-span') {
 }
 
 export const useSpanFirstSeenEvent = (
-  span?: Span,
+  span?: Pick<Span, 'group_id'>,
   referrer = 'span-first-seen-event'
 ) => {
   const {data} = useFirstSeenSpan(span, referrer);
   return useQueryGetEvent(data?.transaction_id);
 };
 
-function useLastSeenSpan(span?: Span, referrer = 'last-seen-span') {
+function useLastSeenSpan(span?: Pick<Span, 'group_id'>, referrer = 'last-seen-span') {
   const query = span
     ? `
     SELECT transaction_id
@@ -61,7 +61,10 @@ function useLastSeenSpan(span?: Span, referrer = 'last-seen-span') {
   return {...result, data: result.data[0]};
 }
 
-export const useSpanLastSeenEvent = (span?: Span, referrer = 'span-last-seen-event') => {
+export const useSpanLastSeenEvent = (
+  span?: Pick<Span, 'group_id'>,
+  referrer = 'span-last-seen-event'
+) => {
   const {data} = useLastSeenSpan(span, referrer);
   return useQueryGetEvent(data?.transaction_id);
 };