|
@@ -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)};
|
|
|
+`;
|