mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
console: Avoid tracing some GraphiQL errors in Sentry
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6603 GitOrigin-RevId: 779a6d31298ec2152d7088b31f02f00002ece226
This commit is contained in:
parent
2bc05c1697
commit
41d31fba10
@ -0,0 +1,71 @@
|
|||||||
|
import { blockServerRequests } from './utils/blockServerRequests';
|
||||||
|
import {
|
||||||
|
stubInitialServerRequests,
|
||||||
|
waitForInitialServerRequests,
|
||||||
|
} from './fixtures/initialRequests/stubInitialServerRequests';
|
||||||
|
|
||||||
|
describe('Sentry', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.log('**--- Start controlling the server**');
|
||||||
|
blockServerRequests();
|
||||||
|
stubInitialServerRequests();
|
||||||
|
|
||||||
|
cy.log('**--- Load the Console**');
|
||||||
|
cy.visit('/', {
|
||||||
|
onBeforeLoad: window => {
|
||||||
|
Cypress.log({
|
||||||
|
message: '**--- Fake the `consoleSentryDsn` env variable**',
|
||||||
|
});
|
||||||
|
|
||||||
|
function recursivelyTryToSetConsoleSentryDsn() {
|
||||||
|
if (!window.__env) {
|
||||||
|
// The page has not been loaded yet and the window.__env is not available yet
|
||||||
|
setTimeout(recursivelyTryToSetConsoleSentryDsn, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const consoleSentryDsnAlreadyExists =
|
||||||
|
!!window.__env.consoleSentryDsn &&
|
||||||
|
window.__env.consoleSentryDsn !== 'undefined';
|
||||||
|
|
||||||
|
if (consoleSentryDsnAlreadyExists) {
|
||||||
|
Cypress.log({
|
||||||
|
message: '**--- A `consoleSentryDsn` env var already exists**',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without a consoleSentryDsn env variable, we do not start Sentry tracing.
|
||||||
|
// Here we set a fake consoleSentryDsn (usually it's not defined in the dev local machine)
|
||||||
|
// before the Console starts
|
||||||
|
window.__env.consoleSentryDsn =
|
||||||
|
// This Sentry DSN does not exist, it's a real one modified to avoid exposing the original
|
||||||
|
// one publicly
|
||||||
|
'https://99942022c9cc4306aa4084ef90f307ff@o417608.ingest.sentry.io/6684052';
|
||||||
|
|
||||||
|
Cypress.log({
|
||||||
|
message: '**--- A Fake `consoleSentryDsn` env var has been set**',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(recursivelyTryToSetConsoleSentryDsn, 10);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
waitForInitialServerRequests();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that we do not mess up with the Sentry integration/configuration
|
||||||
|
it('When HASURA_CONSOLE_SENTRY_DSN is set, then Sentry should start tracing', () => {
|
||||||
|
// Sentry sends requests to URLs like this
|
||||||
|
// https://o417608.ingest.sentry.io/api/6684052/envelope/?sentry_key=99942022c9cc4306aa4084ef90f307ff&sentry_version=7&sentry_client=sentry.javascript.react%2F7.11.1
|
||||||
|
cy.intercept('https://**.ingest.sentry.io/**').as('sentryRequest');
|
||||||
|
|
||||||
|
// The only fact that a request has been performed to ingest.sentry.io is enough to safely
|
||||||
|
// say that Sentry is up and running
|
||||||
|
cy.log(
|
||||||
|
'**--- Check that Sentry called the Sentry server to start tracing errors**'
|
||||||
|
);
|
||||||
|
cy.wait('@sentryRequest');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,92 @@
|
|||||||
|
import { errorMustBeBlocked } from './errorMustBeBlocked';
|
||||||
|
|
||||||
|
describe('errorMustBeBlocked', () => {
|
||||||
|
test('When the error is null, then should return false', () => {
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error: null,
|
||||||
|
urlPrefix: '/console',
|
||||||
|
pathname: '/console',
|
||||||
|
})
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('When the error is en empty string, then should return false', () => {
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error: '',
|
||||||
|
urlPrefix: '/console',
|
||||||
|
pathname: '/console',
|
||||||
|
})
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('When the error is a string, then should be threated as an error', () => {
|
||||||
|
const errorToBlock =
|
||||||
|
'"__twoDashesColumn" must not begin with "__", which is reserved by GraphQL introspection';
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error: errorToBlock,
|
||||||
|
urlPrefix: '/console',
|
||||||
|
pathname: '/console',
|
||||||
|
})
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('When the path name is /, then should always return false', () => {
|
||||||
|
const errorToBlock =
|
||||||
|
'"__twoDashesColumn" must not begin with "__", which is reserved by GraphQL introspection';
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error: errorToBlock,
|
||||||
|
urlPrefix: '/console',
|
||||||
|
pathname: '/',
|
||||||
|
})
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each`
|
||||||
|
urlPrefix | pathname
|
||||||
|
${'/console'} | ${'/console'}
|
||||||
|
${'/console'} | ${'/console/'}
|
||||||
|
${'/console'} | ${'/api/api-explorer'}
|
||||||
|
${'/console'} | ${'/api/api-explorer/'}
|
||||||
|
${'/console'} | ${'/console/api/api-explorer'}
|
||||||
|
${'/customConsolePrefix'} | ${'/customConsolePrefix'}
|
||||||
|
${'/customConsolePrefix'} | ${'/customConsolePrefix/'}
|
||||||
|
${'/customConsolePrefix'} | ${'/api/api-explorer'}
|
||||||
|
${'/customConsolePrefix'} | ${'/api/api-explorer/'}
|
||||||
|
${'/customConsolePrefix'} | ${'/customConsolePrefix/api/api-explorer'}
|
||||||
|
`(
|
||||||
|
`When the urlPrefix is '$urlPrefix' and the pathname is '$pathname', then should return true`,
|
||||||
|
({ urlPrefix, pathname }) => {
|
||||||
|
const errorToBlock = new Error(
|
||||||
|
'"__twoDashesColumn" must not begin with "__", which is reserved by GraphQL introspection'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error: errorToBlock,
|
||||||
|
urlPrefix,
|
||||||
|
pathname,
|
||||||
|
})
|
||||||
|
).toEqual(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each`
|
||||||
|
error
|
||||||
|
${new Error('"__twoDashesColumn" must not begin with "__", which is reserved by GraphQL introspection')}
|
||||||
|
${new Error('Input Object type msgbox_UserInbox_stream_cursor_value_input must define one or more fields.')}
|
||||||
|
${new Error(`Cannot read properties of undefined (reading 'variableDefinitions')`)}
|
||||||
|
${new Error(`Cannot read properties of undefined (reading 'getFields')`)}
|
||||||
|
`(`When the error is '$error', then should return true`, ({ error }) => {
|
||||||
|
expect(
|
||||||
|
errorMustBeBlocked({
|
||||||
|
error,
|
||||||
|
urlPrefix: '/console',
|
||||||
|
pathname: '/console',
|
||||||
|
})
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,20 @@
|
|||||||
|
import type { EventHint } from '@sentry/react';
|
||||||
|
import { isGraphiQlError } from './isGraphiQlError';
|
||||||
|
|
||||||
|
export interface ErrorInfo {
|
||||||
|
error: EventHint['originalException'];
|
||||||
|
pathname: string; // window.location.pathname
|
||||||
|
urlPrefix: string; // globals.urlPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify the errors that must be blocked from hitting Sentry.
|
||||||
|
*
|
||||||
|
* Please note that we must be extremely careful when blocking errors, false positives are better
|
||||||
|
* then not being notified at all!
|
||||||
|
*/
|
||||||
|
export function errorMustBeBlocked(info: ErrorInfo) {
|
||||||
|
if (isGraphiQlError(info)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { errorMustBeBlocked } from './errorMustBeBlocked';
|
@ -0,0 +1,70 @@
|
|||||||
|
import type { ErrorInfo } from './errorMustBeBlocked';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify the GraphiQl errors we do not want to hit Sentry.
|
||||||
|
* Unfortunately, the 1.0.0-alpha.0 version of GraphiQL (the one we use at the time of writing) does
|
||||||
|
* not expose APIs to manage the errors. The best thing that we can do is trying to identify
|
||||||
|
* and block them downstream.
|
||||||
|
*
|
||||||
|
* Please note that we must be extremely careful when blocking errors, false positives are better
|
||||||
|
* then not being notified at all!
|
||||||
|
*/
|
||||||
|
export function isGraphiQlError(info: ErrorInfo) {
|
||||||
|
const { error, urlPrefix, pathname } = info;
|
||||||
|
const errorMessage = typeof error === 'string' ? error : error?.message;
|
||||||
|
|
||||||
|
if (!errorMessage) return false;
|
||||||
|
|
||||||
|
// Please note that the "/" pathname should not be blocked, because only while working locally,
|
||||||
|
// the console is served on "/" despite of the URL_PREFIX env variable.
|
||||||
|
const isApiExplorer =
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3671973903/tags/url/?project=6684052
|
||||||
|
pathname === urlPrefix ||
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3672788747/tags/url/?project=6684052
|
||||||
|
pathname === `${urlPrefix}/` ||
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3671973903/tags/url/?project=6684052
|
||||||
|
pathname.includes('/api/api-explorer');
|
||||||
|
|
||||||
|
if (!isApiExplorer) return false;
|
||||||
|
|
||||||
|
// An example of the GraphiQL error we want to block:
|
||||||
|
// Name "__twoDashesColumn" must not begin with "__", which is reserved by GraphQL introspection.
|
||||||
|
//
|
||||||
|
// If you want to reproduce it locally:
|
||||||
|
// 1. Create a table named "__twoSlashes"
|
||||||
|
// 2. Run the following query in GraphiQL
|
||||||
|
// query MyQuery {
|
||||||
|
// __twoSlashes
|
||||||
|
// }
|
||||||
|
// 3. Look at the error in the browser console
|
||||||
|
if (errorMessage.includes('which is reserved by GraphQL introspection')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An example of the GraphiQL error we want to block:
|
||||||
|
// Input Object type msgbox_UserInbox_stream_cursor_value_input must define one or more fields.
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3699191288/
|
||||||
|
if (
|
||||||
|
errorMessage.includes('Input Object type') &&
|
||||||
|
errorMessage.includes('must define one or more fields.')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3671973903
|
||||||
|
if (
|
||||||
|
errorMessage ===
|
||||||
|
`Cannot read properties of undefined (reading 'variableDefinitions')`
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: https://sentry.io/organizations/hasura-nn/issues/3703083666/
|
||||||
|
if (
|
||||||
|
errorMessage === `Cannot read properties of undefined (reading 'getFields')`
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -5,6 +5,7 @@ import { BrowserTracing } from '@sentry/tracing';
|
|||||||
|
|
||||||
import globals from '@/Globals';
|
import globals from '@/Globals';
|
||||||
import { getSentryTags } from './getSentryTags';
|
import { getSentryTags } from './getSentryTags';
|
||||||
|
import { errorMustBeBlocked } from './errorMustBeBlocked';
|
||||||
import { getSentryEnvironment } from './getSentryEnvironment';
|
import { getSentryEnvironment } from './getSentryEnvironment';
|
||||||
import { isSentryAlreadyStarted } from './isSentryAlreadyStarted';
|
import { isSentryAlreadyStarted } from './isSentryAlreadyStarted';
|
||||||
import { logSentryEnabled, logSentryDisabled } from './logSentryInfo';
|
import { logSentryEnabled, logSentryDisabled } from './logSentryInfo';
|
||||||
@ -63,6 +64,17 @@ export function startSentryTracing(globalVars: Globals, envVars: EnvVars) {
|
|||||||
initialScope: {
|
initialScope: {
|
||||||
tags,
|
tags,
|
||||||
},
|
},
|
||||||
|
beforeSend(event, hint) {
|
||||||
|
const blockError = errorMustBeBlocked({
|
||||||
|
error: hint.originalException,
|
||||||
|
urlPrefix: globalVars.urlPrefix,
|
||||||
|
pathname: window.location.pathname,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (blockError) return null;
|
||||||
|
|
||||||
|
return event;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Sentry.setUser({
|
Sentry.setUser({
|
||||||
|
Loading…
Reference in New Issue
Block a user