Browse Source

feat(graphql): Add alert below request when there were errors (#51846)

This adds context as to why a line has been highlighted and summarizes
errors in the response.
Malachi Willey 1 year ago
parent
commit
db3a08f560

+ 59 - 4
static/app/components/events/interfaces/request/graphQlRequestBody.tsx

@@ -1,21 +1,28 @@
 import {useEffect, useRef} from 'react';
+import styled from '@emotion/styled';
+import isEmpty from 'lodash/isEmpty';
 import omit from 'lodash/omit';
 import uniq from 'lodash/uniq';
 import Prism from 'prismjs';
 
+import Alert from 'sentry/components/alert';
 import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
+import List from 'sentry/components/list';
+import {t, tn} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
 import {EntryRequestDataGraphQl, Event} from 'sentry/types';
+import {defined} from 'sentry/utils';
 import {loadPrismLanguage} from 'sentry/utils/loadPrismLanguage';
 
 type GraphQlBodyProps = {data: EntryRequestDataGraphQl['data']; event: Event};
 
-type GraphQlErrors = Array<{
+type GraphQlError = {
   locations?: Array<{column: number; line: number}>;
   message?: string;
   path?: string[];
-}>;
+};
 
-function getGraphQlErrorsFromResponseContext(event: Event): GraphQlErrors {
+function getGraphQlErrorsFromResponseContext(event: Event): GraphQlError[] {
   const responseData = event.contexts?.response?.data;
 
   if (
@@ -31,7 +38,7 @@ function getGraphQlErrorsFromResponseContext(event: Event): GraphQlErrors {
   return [];
 }
 
-function getErrorLineNumbers(errors: GraphQlErrors): number[] {
+function getErrorLineNumbers(errors: GraphQlError[]): number[] {
   return uniq(
     errors.flatMap(
       error =>
@@ -41,6 +48,49 @@ function getErrorLineNumbers(errors: GraphQlErrors): number[] {
   );
 }
 
+function formatErrorAlertMessage(error: GraphQlError) {
+  const {locations, message} = error;
+
+  if (!locations || isEmpty(locations)) {
+    return message;
+  }
+
+  const prefix = locations
+    .filter(loc => defined(loc.line) && defined(loc.column))
+    .map(loc => t('Line %s Column %s', loc.line, loc.column))
+    .join(', ');
+
+  return `${prefix}: ${message}`;
+}
+
+function ErrorsAlert({errors}: {errors: GraphQlError[]}) {
+  const errorsWithMessage = errors.filter(error => !isEmpty(error.message));
+
+  if (isEmpty(errorsWithMessage)) {
+    return null;
+  }
+
+  return (
+    <StyledAlert
+      type="error"
+      showIcon
+      expand={
+        <List symbol="bullet">
+          {errorsWithMessage.map((error, i) => (
+            <li key={i}>{formatErrorAlertMessage(error)}</li>
+          ))}
+        </List>
+      }
+    >
+      {tn(
+        'There was %s GraphQL error raised during this request.',
+        'There were %s errors raised during this request.',
+        errorsWithMessage.length
+      )}
+    </StyledAlert>
+  );
+}
+
 export function GraphQlRequestBody({data, event}: GraphQlBodyProps) {
   const ref = useRef<HTMLElement | null>(null);
 
@@ -73,6 +123,7 @@ export function GraphQlRequestBody({data, event}: GraphQlBodyProps) {
           {data.query}
         </code>
       </pre>
+      <ErrorsAlert errors={errors} />
       <KeyValueList
         data={Object.entries(omit(data, 'query')).map(([key, value]) => ({
           key,
@@ -84,3 +135,7 @@ export function GraphQlRequestBody({data, event}: GraphQlBodyProps) {
     </div>
   );
 }
+
+const StyledAlert = styled(Alert)`
+  margin-top: -${space(1)};
+`;

+ 15 - 2
static/app/components/events/interfaces/request/index.spec.tsx

@@ -386,7 +386,7 @@ describe('Request entry', function () {
         ).toBeInTheDocument();
       });
 
-      it('highlights graphql query lines with errors', function () {
+      it('highlights graphql query lines with errors', async function () {
         const data: EntryRequest['data'] = {
           apiTarget: 'graphql',
           method: 'POST',
@@ -406,7 +406,13 @@ describe('Request entry', function () {
               data,
             },
           ],
-          contexts: {response: {data: {errors: [{locations: [{line: 1}]}]}}},
+          contexts: {
+            response: {
+              data: {
+                errors: [{message: 'Very bad error', locations: [{line: 1, column: 2}]}],
+              },
+            },
+          },
         };
 
         const {container} = render(
@@ -417,6 +423,13 @@ describe('Request entry', function () {
         expect(
           container.querySelector('.line-highlight')?.getAttribute('data-start')
         ).toBe('1');
+        expect(
+          screen.getByText('There was 1 GraphQL error raised during this request.')
+        ).toBeInTheDocument();
+
+        await userEvent.click(screen.getByText(/There was 1 GraphQL error/i));
+
+        expect(screen.getByText('Line 1 Column 2: Very bad error')).toBeInTheDocument();
       });
     });
   });