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 { getSentryTags } from './getSentryTags';
|
||||
import { errorMustBeBlocked } from './errorMustBeBlocked';
|
||||
import { getSentryEnvironment } from './getSentryEnvironment';
|
||||
import { isSentryAlreadyStarted } from './isSentryAlreadyStarted';
|
||||
import { logSentryEnabled, logSentryDisabled } from './logSentryInfo';
|
||||
@ -63,6 +64,17 @@ export function startSentryTracing(globalVars: Globals, envVars: EnvVars) {
|
||||
initialScope: {
|
||||
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({
|
||||
|
Loading…
Reference in New Issue
Block a user