Browse Source

feat(starfish): Add 5XX samples to endpoint overview (#51696)

![Screenshot 2023-06-27 at 11 05 21
AM](https://github.com/getsentry/sentry/assets/63818634/55506ba1-c558-45b4-b6a1-bd018044c6b5)
Shruthi 1 year ago
parent
commit
635405d32f

+ 71 - 39
static/app/views/starfish/components/samplesTable/transactionSamplesTable.tsx

@@ -3,7 +3,10 @@ import styled from '@emotion/styled';
 
 import DateTime from 'sentry/components/dateTime';
 import Duration from 'sentry/components/duration';
-import GridEditable, {GridColumnHeader} from 'sentry/components/gridEditable';
+import GridEditable, {
+  COL_WIDTH_UNDEFINED,
+  GridColumnHeader,
+} from 'sentry/components/gridEditable';
 import Link from 'sentry/components/links/link';
 import QuestionTooltip from 'sentry/components/questionTooltip';
 import {t} from 'sentry/locale';
@@ -16,6 +19,7 @@ import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import {DurationComparisonCell} from 'sentry/views/starfish/components/samplesTable/common';
+import useErrorSamples from 'sentry/views/starfish/components/samplesTable/useErrorSamples';
 import useSlowMedianFastSamplesQuery from 'sentry/views/starfish/components/samplesTable/useSlowMedianFastSamplesQuery';
 import {
   OverflowEllipsisTextContainer,
@@ -23,6 +27,7 @@ import {
   TextAlignRight,
 } from 'sentry/views/starfish/components/textAlign';
 import {DataTitles} from 'sentry/views/starfish/views/spans/types';
+import {SampleFilter} from 'sentry/views/starfish/views/webServiceView/endpointOverview';
 
 type Keys =
   | 'id'
@@ -30,47 +35,18 @@ type Keys =
   | 'timestamp'
   | 'transaction.duration'
   | 'p95_comparison'
-  | 'span_ops_breakdown.relative';
+  | 'span_ops_breakdown.relative'
+  | 'http.status_code'
+  | 'transaction.status';
 type TableColumnHeader = GridColumnHeader<Keys>;
 
-const COLUMN_ORDER: TableColumnHeader[] = [
-  {
-    key: 'id',
-    name: 'Event ID',
-    width: 100,
-  },
-  {
-    key: 'profile_id',
-    name: 'Profile ID',
-    width: 140,
-  },
-  {
-    key: SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
-    name: 'Operation Duration',
-    width: 180,
-  },
-  {
-    key: 'timestamp',
-    name: 'Timestamp',
-    width: 230,
-  },
-  {
-    key: 'transaction.duration',
-    name: DataTitles.duration,
-    width: 100,
-  },
-  {
-    key: 'p95_comparison',
-    name: 'Compared to P95',
-    width: 100,
-  },
-];
-
 type Props = {
   queryConditions: string[];
+  sampleFilter: SampleFilter;
 };
 
 type DataRow = {
+  'http.status_code': number;
   id: string;
   profile_id: string;
   'spans.browser': number;
@@ -80,9 +56,10 @@ type DataRow = {
   'spans.ui': number;
   timestamp: string;
   'transaction.duration': number;
+  'transaction.status': string;
 };
 
-export function TransactionSamplesTable({queryConditions}: Props) {
+export function TransactionSamplesTable({queryConditions, sampleFilter}: Props) {
   const location = useLocation();
   const organization = useOrganization();
   const query = new MutableSearch(queryConditions);
@@ -97,8 +74,63 @@ export function TransactionSamplesTable({queryConditions}: Props) {
     version: 2,
   };
 
+  const columnOrder: TableColumnHeader[] = [
+    {
+      key: 'id',
+      name: 'Event ID',
+      width: 100,
+    },
+    ...(sampleFilter === 'ALL'
+      ? [
+          {
+            key: 'profile_id',
+            name: 'Profile ID',
+            width: 140,
+          } as TableColumnHeader,
+          {
+            key: SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
+            name: 'Operation Duration',
+            width: 180,
+          } as TableColumnHeader,
+          {
+            key: 'timestamp',
+            name: 'Timestamp',
+            width: 230,
+          } as TableColumnHeader,
+          {
+            key: 'transaction.duration',
+            name: DataTitles.duration,
+            width: 100,
+          } as TableColumnHeader,
+          {
+            key: 'p95_comparison',
+            name: 'Compared to P95',
+            width: 100,
+          } as TableColumnHeader,
+        ]
+      : [
+          {
+            key: 'http.status_code',
+            name: 'Response Code',
+            width: COL_WIDTH_UNDEFINED,
+          } as TableColumnHeader,
+          {
+            key: 'transaction.status',
+            name: 'Status',
+            width: COL_WIDTH_UNDEFINED,
+          } as TableColumnHeader,
+          {
+            key: 'timestamp',
+            name: 'Timestamp',
+            width: 230,
+          } as TableColumnHeader,
+        ]),
+  ];
+
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
   const {isLoading, data, aggregatesData} = useSlowMedianFastSamplesQuery(eventView);
+  const {isLoading: isErrorSamplesLoading, data: errorSamples} =
+    useErrorSamples(eventView);
 
   function renderHeadCell(column: GridColumnHeader): React.ReactNode {
     if (column.key === 'p95_comparison') {
@@ -184,9 +216,9 @@ export function TransactionSamplesTable({queryConditions}: Props) {
 
   return (
     <GridEditable
-      isLoading={isLoading}
-      data={data as DataRow[]}
-      columnOrder={COLUMN_ORDER}
+      isLoading={sampleFilter === 'ALL' ? isLoading : isErrorSamplesLoading}
+      data={sampleFilter === 'ALL' ? (data as DataRow[]) : (errorSamples as DataRow[])}
+      columnOrder={columnOrder}
       columnSortBy={[]}
       location={location}
       grid={{

+ 47 - 0
static/app/views/starfish/components/samplesTable/useErrorSamples.tsx

@@ -0,0 +1,47 @@
+import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
+import EventView from 'sentry/utils/discover/eventView';
+import {QueryFieldValue} from 'sentry/utils/discover/fields';
+import {MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {useLocation} from 'sentry/utils/useLocation';
+import useOrganization from 'sentry/utils/useOrganization';
+
+export default function useErrorSamples(eventView: EventView) {
+  const location = useLocation();
+  const organization = useOrganization();
+
+  const columns: QueryFieldValue[] = [
+    {
+      field: 'timestamp',
+      kind: 'field',
+    },
+    {
+      field: 'http.status_code',
+      kind: 'field',
+    },
+    {
+      field: 'transaction.status',
+      kind: 'field',
+    },
+  ];
+
+  let errorSamplesEventView = eventView.clone();
+  errorSamplesEventView.additionalConditions = new MutableSearch(
+    'http.status_code:[500,501,502,503,504,505,506,507,508,510,511]'
+  );
+  errorSamplesEventView = errorSamplesEventView.withColumns(columns).withSorts([
+    {
+      field: 'timestamp',
+      kind: 'desc',
+    },
+  ]);
+
+  const {isLoading, data} = useDiscoverQuery({
+    eventView: errorSamplesEventView,
+    referrer: 'starfish-transaction-summary-sample-events',
+    location,
+    orgSlug: organization.slug,
+    limit: 6,
+  });
+
+  return {isLoading, data: data ? data.data : []};
+}

+ 24 - 10
static/app/views/starfish/views/webServiceView/endpointOverview/index.tsx

@@ -44,7 +44,10 @@ const SPANS_TABLE_LIMIT = 5;
 
 const EventsRequest = withApi(_EventsRequest);
 
+export type SampleFilter = 'ALL' | '500s';
+
 type State = {
+  samplesFilter: SampleFilter;
   spansFilter: ModuleName;
 };
 
@@ -65,7 +68,10 @@ export default function EndpointOverview() {
     : undefined;
   const pageFilter = usePageFilters();
 
-  const [state, setState] = useState<State>({spansFilter: ModuleName.ALL});
+  const [state, setState] = useState<State>({
+    spansFilter: ModuleName.ALL,
+    samplesFilter: 'ALL',
+  });
   const [issueFilter, setIssueFilter] = useState<IssueCategory | 'ALL'>('ALL');
 
   const queryConditions = [
@@ -299,8 +305,23 @@ export default function EndpointOverview() {
               transaction={transaction}
               method={method}
             />
-            <SubHeader>{t('Sample Events')}</SubHeader>
-            <TransactionSamplesTable queryConditions={queryConditions} />
+            <SegmentedControlContainer>
+              <SegmentedControl
+                size="xs"
+                aria-label={t('Filter events')}
+                value={state.samplesFilter}
+                onChange={key => setState({...state, samplesFilter: key})}
+              >
+                <SegmentedControl.Item key="ALL">
+                  {t('Sample Events')}
+                </SegmentedControl.Item>
+                <SegmentedControl.Item key="500s">{t('5XXs')}</SegmentedControl.Item>
+              </SegmentedControl>
+            </SegmentedControlContainer>
+            <TransactionSamplesTable
+              queryConditions={queryConditions}
+              sampleFilter={state.samplesFilter}
+            />
             <SegmentedControlContainer>
               <SegmentedControl
                 size="xs"
@@ -376,13 +397,6 @@ const ChartValue = styled('div')`
   font-size: ${p => p.theme.fontSizeExtraLarge};
 `;
 
-const SubHeader = styled('h3')`
-  color: ${p => p.theme.gray300};
-  font-size: ${p => p.theme.fontSizeLarge};
-  margin: 0;
-  margin-bottom: ${space(1)};
-`;
-
 const SearchContainerWithFilterAndMetrics = styled('div')`
   display: grid;
   grid-template-rows: auto auto auto;