console: Add Sentry

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5699
Co-authored-by: Rishichandra Wawhal <27274869+wawhal@users.noreply.github.com>
Co-authored-by: Daniel Harvey <4729125+danieljharvey@users.noreply.github.com>
GitOrigin-RevId: 00f2c5d25012b21f4e8763ef578598a3a11896e4
This commit is contained in:
Stefano Magni 2022-09-15 18:59:00 +02:00 committed by hasura-bot
parent 913f3f12e4
commit a0f4f00bfd
23 changed files with 635 additions and 20 deletions

View File

@ -18,6 +18,11 @@ interface Env {
serverVersion: string;
telemetryTopic: string;
urlPrefix: string;
/**
* Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
*/
consoleSentryDsn: string;
}
interface Window {

View File

@ -11,7 +11,7 @@ export {
export { fetchConsoleNotifications } from '../src/components/Main/Actions';
export { default as NotificationSection } from '../src/components/Main/NotificationSection';
export { default as Onboarding } from '../src/components/Common/Onboarding';
export { analyticsToolsUtils } from '../src/features/AnalyticsToolsUtils';
export { tracingTools } from '../src/features/TracingTools';
export { OnboardingWizard } from '../src/features/OnboardingWizard';
export { makeGrowthExperimentsClient } from '../src/features/GrowthExperiments';
export { default as PageNotFound } from '../src/components/Error/PageNotFound';

View File

@ -21,6 +21,8 @@
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"@reduxjs/toolkit": "^1.5.1",
"@sentry/react": "7.11.1",
"@sentry/tracing": "7.11.1",
"@types/lodash.get": "^4.4.6",
"@xstate/react": "^2.0.0",
"ace-builds": "^1.4.11",
@ -5507,6 +5509,129 @@
}
}
},
"node_modules/@sentry/browser": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.11.1.tgz",
"integrity": "sha512-k2XHuzPfnm8VJPK5eWd1+Y5VCgN42sLveb8Qxc3prb5PSL416NWMLZaoB7RMIhy430fKrSFiosnm6QDk2M6pbA==",
"dependencies": {
"@sentry/core": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/core": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.11.1.tgz",
"integrity": "sha512-kaDSZ6VNuO4ZZdqUOOX6XM6x+kjo2bMnDQ3IJG51FPvVjr8lXYhXj1Ccxcot3pBYAIWPPby2+vNDOXllmXqoBA==",
"dependencies": {
"@sentry/hub": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/core/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/hub": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.11.1.tgz",
"integrity": "sha512-M6ClgdXdptS0lUBKB5KpXXe2qMQhsoiEN2pEGRI6+auqhfHCUQB1ZXsfjiOYexKC9fwx7TyFyZ9Jcaf2DTxEhw==",
"dependencies": {
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/hub/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/react": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.11.1.tgz",
"integrity": "sha512-kp/vBgwNrlFEtW3e6DY9T4s3di9peL66n5UIY5n6dYkiN7A7D6/Kz1WJ/ZCL82DvaCMEY577wNyr2C+442l7fw==",
"dependencies": {
"@sentry/browser": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": "15.x || 16.x || 17.x || 18.x"
}
},
"node_modules/@sentry/react/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/tracing": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.11.1.tgz",
"integrity": "sha512-ilgnHfpdYUWKG/5yAXIfIbPVsCfrC4ONFBR/wN25/hdAyVfXMa3AJx7NCCXxZBOPDWH3hMW8rl4La5yuDbXofg==",
"dependencies": {
"@sentry/hub": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/tracing/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sentry/types": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.11.1.tgz",
"integrity": "sha512-gIEhOPxC2cjrxQ0+K2SFJ1P6e/an5osSxVc9OOtekN28eHtVsXFCLB8XVWeNQnS7N2VkrVrkqORMBz1kvIcvVQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.11.1.tgz",
"integrity": "sha512-tRVXNT5O9ilkV31pyHeTqA1PcPQfMV/2OR6yUYM4ah+QVISovC0f0ybhByuH5nYg6x/Gsnx1o7pc8L1GE3+O7A==",
"dependencies": {
"@sentry/types": "7.11.1",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@sinonjs/commons": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
@ -6022,6 +6147,21 @@
"lodash": "^4.17.15"
}
},
"node_modules/@storybook/addon-interactions/node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"extraneous": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@storybook/addon-links": {
"version": "6.5.10",
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-6.5.10.tgz",
@ -45705,6 +45845,117 @@
"any-observable": "^0.3.0"
}
},
"@sentry/browser": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.11.1.tgz",
"integrity": "sha512-k2XHuzPfnm8VJPK5eWd1+Y5VCgN42sLveb8Qxc3prb5PSL416NWMLZaoB7RMIhy430fKrSFiosnm6QDk2M6pbA==",
"requires": {
"@sentry/core": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/core": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.11.1.tgz",
"integrity": "sha512-kaDSZ6VNuO4ZZdqUOOX6XM6x+kjo2bMnDQ3IJG51FPvVjr8lXYhXj1Ccxcot3pBYAIWPPby2+vNDOXllmXqoBA==",
"requires": {
"@sentry/hub": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/hub": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.11.1.tgz",
"integrity": "sha512-M6ClgdXdptS0lUBKB5KpXXe2qMQhsoiEN2pEGRI6+auqhfHCUQB1ZXsfjiOYexKC9fwx7TyFyZ9Jcaf2DTxEhw==",
"requires": {
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/react": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.11.1.tgz",
"integrity": "sha512-kp/vBgwNrlFEtW3e6DY9T4s3di9peL66n5UIY5n6dYkiN7A7D6/Kz1WJ/ZCL82DvaCMEY577wNyr2C+442l7fw==",
"requires": {
"@sentry/browser": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/tracing": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.11.1.tgz",
"integrity": "sha512-ilgnHfpdYUWKG/5yAXIfIbPVsCfrC4ONFBR/wN25/hdAyVfXMa3AJx7NCCXxZBOPDWH3hMW8rl4La5yuDbXofg==",
"requires": {
"@sentry/hub": "7.11.1",
"@sentry/types": "7.11.1",
"@sentry/utils": "7.11.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/types": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.11.1.tgz",
"integrity": "sha512-gIEhOPxC2cjrxQ0+K2SFJ1P6e/an5osSxVc9OOtekN28eHtVsXFCLB8XVWeNQnS7N2VkrVrkqORMBz1kvIcvVQ=="
},
"@sentry/utils": {
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.11.1.tgz",
"integrity": "sha512-tRVXNT5O9ilkV31pyHeTqA1PcPQfMV/2OR6yUYM4ah+QVISovC0f0ybhByuH5nYg6x/Gsnx1o7pc8L1GE3+O7A==",
"requires": {
"@sentry/types": "7.11.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sinonjs/commons": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",

View File

@ -89,6 +89,8 @@
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"@reduxjs/toolkit": "^1.5.1",
"@sentry/react": "7.11.1",
"@sentry/tracing": "7.11.1",
"@types/lodash.get": "^4.4.6",
"@xstate/react": "^2.0.0",
"ace-builds": "^1.4.11",

View File

@ -1,8 +1,11 @@
/* eslint no-underscore-dangle: 0 */
import { SERVER_CONSOLE_MODE } from './constants';
import { getFeaturesCompatibility } from './helpers/versionUtils';
import { stripTrailingSlash } from './components/Common/utils/urlUtils';
import { sentry } from './features/TracingTools/sentry';
import { isEmpty } from './components/Common/utils/jsUtils';
import { stripTrailingSlash } from './components/Common/utils/urlUtils';
import { SERVER_CONSOLE_MODE } from './constants';
type ConsoleType = 'oss' | 'cloud' | 'pro' | 'pro-lite';
@ -18,6 +21,7 @@ type OSSServerEnv = {
serverVersion: string; // e.g. "v2.7.0"
urlPrefix: string; // e.g. "/console"
cdnAssets: boolean;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
type ProServerEnv = {
@ -30,6 +34,7 @@ type ProServerEnv = {
isAdminSecretSet: boolean;
serverVersion: string;
urlPrefix: string;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
type ProLiteServerEnv = {
@ -42,6 +47,7 @@ type ProLiteServerEnv = {
isAdminSecretSet: boolean;
serverVersion: string;
urlPrefix: string;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
type CloudUserRole = 'owner' | 'user';
@ -64,6 +70,8 @@ type CloudServerEnv = {
tenantID: UUID;
urlPrefix: string;
userRole: CloudUserRole;
userId?: string;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
type OSSCliEnv = {
@ -78,6 +86,7 @@ type OSSCliEnv = {
enableTelemetry: boolean;
serverVersion: string;
urlPrefix: string;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
export type CloudCliEnv = {
@ -100,6 +109,7 @@ export type CloudCliEnv = {
pro: true;
projectId: UUID;
isAdminSecretSet: boolean;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
};
type ProCliEnv = CloudCliEnv;
@ -124,6 +134,9 @@ export type EnvVars = {
eeMode?: string;
consoleId?: string;
userRole?: string;
userId?: string;
cdnAssets?: boolean;
consoleSentryDsn?: string; // Corresponds to the HASURA_CONSOLE_SENTRY_DSN environment variable
} & (
| OSSServerEnv
| CloudServerEnv
@ -138,6 +151,11 @@ export type EnvVars = {
declare global {
interface Window {
__env: EnvVars;
/**
* Consuming Heap is allowed only through the TracingTools/heap module, never directly.
* @deprecated (when marked as deprecated, the IDE shows it as strikethrough'ed, helping the
* developers realize that they should not use it)
*/
heap?: {
addUserProperties: (properties: Record<string, string>) => void;
};
@ -154,6 +172,7 @@ const globals = {
apiPort: window.__env?.apiPort,
dataApiUrl: stripTrailingSlash(window.__env?.dataApiUrl || ''), // overridden below if server mode
urlPrefix: stripTrailingSlash(window.__env?.urlPrefix || '/'), // overridden below if server mode in production
consoleSentryDsn: sentry.parseSentryDsn(window.__env?.consoleSentryDsn),
adminSecret: window.__env?.adminSecret || null, // gets updated after login/logout in server mode
isAdminSecretSet:
window.__env?.isAdminSecretSet ||
@ -178,6 +197,7 @@ const globals = {
cloudDataApiUrl: `${window.location?.protocol}//data.${window.__env?.cloudRootDomain}`,
luxDataHost: window.__env?.luxDataHost,
userRole: window.__env?.userRole || undefined,
userId: window.__env?.userId || undefined,
consoleType: window.__env?.consoleType || '',
eeMode: window.__env?.eeMode === 'true',
};

View File

@ -4,20 +4,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { useBasename } from 'history';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { useBasename } from 'history';
import { ReactQueryProvider } from './lib/reactQuery';
import './theme/tailwind.css';
import './theme/legacy-boostrap.css';
import getRoutes from './routes';
import { tracingTools } from './features/TracingTools';
import { ReactQueryProvider } from './lib/reactQuery';
import globals from './Globals';
import { store } from './store';
import getRoutes from './routes';
tracingTools.sentry.startTracing(globals, window.__env);
const hashLinkScroll = () => {
const { hash } = window.location;

View File

@ -1,8 +0,0 @@
import { heap } from './heap';
/**
* Common utils for analytics tools, could be extended to export functions for other tools like Sentry, etc.
*/
export const analyticsToolsUtils = {
heap,
};

View File

@ -1,6 +1,5 @@
/**
* A heap object that attempts to mirror the actual heap API, while handling the nullability check
* Currently only implements `addUserProperties`. More functions: identify, track etc can be added
* A heap object that attempts to mirror the actual heap API, while handling the nullability check.
*/
export const heap = {
addUserProperties: (props: Record<string, string>) => {

View File

@ -0,0 +1,10 @@
import { heap } from './heap';
import { sentry } from './sentry';
/**
* Common utils for tracing tools.
*/
export const tracingTools = {
heap,
sentry,
};

View File

@ -0,0 +1,16 @@
import { getSentryEnvironment } from './getSentryEnvironment';
describe('getSentryEnvironment', () => {
it.each`
hostname | expectedEnvironment
${'localhost'} | ${'local'}
${'stagingHostname'} | ${'stagingHostname'}
${'unmanagedHostname'} | ${'unmanagedHostname'}
${'hge-mono-pr-3792.herokuapp.com'} | ${'hge-mono-pr.herokuapp.com'}
`(
`When invoked with '$hostname', then should return '$expectedEnvironment'`,
({ hostname, expectedEnvironment }) => {
expect(getSentryEnvironment(hostname)).toEqual(expectedEnvironment);
}
);
});

View File

@ -0,0 +1,17 @@
export function getSentryEnvironment(windowLocationHostname: string) {
if (windowLocationHostname === 'localhost') {
return 'local';
}
if (windowLocationHostname.startsWith('hge-mono-pr')) {
// Allow grouping all the PRs under the same environment in Sentry
return 'hge-mono-pr.herokuapp.com';
}
/*
Please note that returning the hostname allows
1. Not to expose the staging URL in the OSS repo
2. Easily detect unmanaged hosts in Sentry
*/
return windowLocationHostname;
}

View File

@ -0,0 +1,104 @@
import type { EnvVars } from '../../../../Globals';
/**
* Return the tags to be used in Sentry.
*
* ATTENTION: To avoid leaking sensitive data, it's better to whitelist vars instead of
* blacklisting them. It would be easier to filter out the adminSecret and tracking whatever
* else, but what happens if in the future some more secret-like vars will be added? They would
* accidentally be sent to Sentry, something that we should avoid.
*/
export function getSentryTags(envVars: EnvVars) {
if (envVars.consoleMode === 'cli') {
if ('pro' in envVars) {
return {
pro: envVars.pro,
apiHost: envVars.apiHost,
apiPort: envVars.apiPort,
cliUUID: envVars.cliUUID,
urlPrefix: envVars.urlPrefix,
projectId: envVars.projectId,
assetsPath: envVars.assetsPath,
dataApiUrl: envVars.dataApiUrl,
consoleMode: envVars.consoleMode,
adminSecret: envVars.adminSecret,
consolePath: envVars.consolePath,
serverVersion: envVars.serverVersion,
enableTelemetry: envVars.enableTelemetry,
isAdminSecretSet: envVars.isAdminSecretSet,
};
}
return {
apiHost: envVars.apiHost,
apiPort: envVars.apiPort,
cliUUID: envVars.cliUUID,
urlPrefix: envVars.urlPrefix,
assetsPath: envVars.assetsPath,
dataApiUrl: envVars.dataApiUrl,
consoleMode: envVars.consoleMode,
consolePath: envVars.consolePath,
serverVersion: envVars.serverVersion,
enableTelemetry: envVars.enableTelemetry,
};
}
switch (envVars.consoleType) {
case 'oss':
return {
urlPrefix: envVars.urlPrefix,
cdnAssets: envVars.cdnAssets,
assetsPath: envVars.assetsPath,
consoleMode: envVars.consoleMode,
consoleType: envVars.consoleType,
consolePath: envVars.consolePath,
serverVersion: envVars.serverVersion,
enableTelemetry: envVars.enableTelemetry,
isAdminSecretSet: envVars.isAdminSecretSet,
};
case 'pro':
return {
consoleId: envVars.consoleId,
urlPrefix: envVars.urlPrefix,
assetsPath: envVars.assetsPath,
consoleType: envVars.consoleType,
consoleMode: envVars.consoleMode,
consolePath: envVars.consolePath,
serverVersion: envVars.serverVersion,
enableTelemetry: envVars.enableTelemetry,
isAdminSecretSet: envVars.isAdminSecretSet,
};
case 'cloud':
return {
eeMode: envVars.eeMode,
tenantID: envVars.tenantID,
userRole: envVars.userRole,
consoleId: envVars.consoleId,
projectID: envVars.projectID,
urlPrefix: envVars.urlPrefix,
assetsPath: envVars.assetsPath,
dataApiUrl: envVars.dataApiUrl,
consoleMode: envVars.consoleMode,
consoleType: envVars.consoleType,
consolePath: envVars.consolePath,
luxDataHost: envVars.luxDataHost,
serverVersion: envVars.serverVersion,
cloudRootDomain: envVars.cloudRootDomain,
isAdminSecretSet: envVars.isAdminSecretSet,
herokuOAuthClientId: envVars.herokuOAuthClientId,
};
default:
console.warn('Unknown Console version');
return {
// This is a fallback it should never happen. If it happens, the above cases should be extended.
unknownConsole: true,
consoleMode: envVars.consoleMode,
consoleType: envVars.consoleType,
};
}
}

View File

@ -0,0 +1,8 @@
import * as Sentry from '@sentry/react';
export function isSentryAlreadyStarted() {
// See https://github.com/getsentry/sentry-go/issues/9#issuecomment-619615289
const tracingAlreadyStarted = !!Sentry.getCurrentHub().getClient();
return tracingAlreadyStarted;
}

View File

@ -0,0 +1,31 @@
export function logSentryEnabled(environment: string) {
console.group();
console.log(
'%c Sentry Tracing Enabled ',
'background: #A0D7D1; color: black; display: block;'
);
console.log(
`%c Sentry Environment: ${environment} `,
'background: #A0D7D1; color: black; display: block;'
);
console.groupEnd();
}
export function logSentryDisabled() {
// Agree with https://github.com/hasura/graphql-engine-mono/pull/5699#issuecomment-1234205492
// we are not going to log anything to the user until we have a user-facing doc that speaks about
// Sentry
// TODO: log the missing/invalid status
return;
console.group();
console.log(
'%c Sentry Tracing Disabled ',
'background: #A0D7D1; color: black; display: block;'
);
console.log(
'%c Provide HASURA_CONSOLE_SENTRY_DSN env variable to enable it ',
'background: #A0D7D1; color: black; display: block;'
);
console.groupEnd();
}

View File

@ -0,0 +1,29 @@
import { parseSentryDsn } from './parseSentryDsn';
describe('parseSentryDsn', () => {
it.each`
value | expected
${''} | ${{ status: 'missing' }}
${null} | ${{ status: 'missing' }}
${undefined} | ${{ status: 'missing' }}
${0} | ${{ status: 'invalid', value: 0 }}
${1} | ${{ status: 'invalid', value: 1 }}
${{}} | ${{ status: 'invalid', value: {} }}
${[]} | ${{ status: 'invalid', value: [] }}
${true} | ${{ status: 'invalid', value: true }}
${false} | ${{ status: 'invalid', value: false }}
${'https://sentry.io'} | ${{ status: 'invalid', value: 'https://sentry.io' }}
${'https://ingest.sentry.io'} | ${{ status: 'invalid', value: 'https://ingest.sentry.io' }}
${'https://ingest.sentry.io'} | ${{ status: 'invalid', value: 'https://ingest.sentry.io' }}
${'https://foo.ingest.sentry.io'} | ${{ status: 'invalid', value: 'https://foo.ingest.sentry.io' }}
${'https://foo.ingest.sentry.io/'} | ${{ status: 'invalid', value: 'https://foo.ingest.sentry.io/' }}
${'https://.ingest.sentry.io/bar'} | ${{ status: 'invalid', value: 'https://.ingest.sentry.io/bar' }}
${'http://foo.ingest.sentry.io/bar'} | ${{ status: 'invalid', value: 'http://foo.ingest.sentry.io/bar' }}
${'https://foo.ingest.sentry.io/bar'} | ${{ status: 'valid', value: 'https://foo.ingest.sentry.io/bar' }}
`(
`When invoked with '$value', then should return '$expected'`,
({ value, expected }) => {
expect(parseSentryDsn(value)).toEqual(expected);
}
);
});

View File

@ -0,0 +1,38 @@
import type { SentryDsn } from '../types';
type ParseSentryDsnResult =
| { status: 'missing' }
| { status: 'invalid'; value: unknown }
| { status: 'valid'; value: SentryDsn };
export function parseSentryDsn(value: unknown): ParseSentryDsnResult {
if (value === '' || value === undefined || value === null) {
return { status: 'missing' };
}
if (typeof value !== 'string') {
return { status: 'invalid', value };
}
if (!isSentryDsn(value)) {
return { status: 'invalid', value };
}
return { status: 'valid', value };
}
function isSentryDsn(url: unknown): url is SentryDsn {
if (typeof url !== 'string') return false;
if (!url.startsWith('https://')) return false;
if (!url.includes('.ingest.sentry.io/')) return false;
const missSentryProjectId = url.endsWith('.ingest.sentry.io/');
if (missSentryProjectId) return false;
const missSentryPublicKey = url.includes('https://.ingest.sentry.io');
if (missSentryPublicKey) return false;
return true;
}

View File

@ -0,0 +1,70 @@
import type { EnvVars } from '@/Globals';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import globals from '@/Globals';
import { getSentryTags } from './getSentryTags';
import { getSentryEnvironment } from './getSentryEnvironment';
import { isSentryAlreadyStarted } from './isSentryAlreadyStarted';
import { logSentryEnabled, logSentryDisabled } from './logSentryInfo';
type Globals = typeof globals;
/**
* Start Sentry idempotently.
*
* Please note that Sentry automatically tracks also the React errors, there is no need to manually track them
* from the various React error boundaries.
*
* ATTENTION: This function expects the `window.__envVars` because I think
* using the server-driven vars instead of the client-parsed ones (since they could
* differ in some details) as tags would be better.
*/
export function startTracing(globalVars: Globals, envVars: EnvVars) {
if (isSentryAlreadyStarted()) return 'enabled';
const consoleSentryDsn = globalVars.consoleSentryDsn;
if (
consoleSentryDsn.status === 'missing' ||
consoleSentryDsn.status === 'invalid'
) {
logSentryDisabled();
return 'disabled';
}
const tags = getSentryTags(envVars);
const environment = getSentryEnvironment(window.location.hostname);
logSentryEnabled(environment);
Sentry.init({
dsn: consoleSentryDsn.value,
tracesSampleRate: 1.0,
integrations: [
new BrowserTracing(),
new Sentry.Integrations.Breadcrumbs({
// Disable tracking console.logs
console: false,
// Disable tracking clicks
dom: false,
}),
],
// Allow grouping logs by environment
environment,
release: tags.serverVersion,
initialScope: {
tags,
},
});
Sentry.setUser({
id: globalVars.userId,
ip_address: '{{auto}}',
});
return 'enabled';
}

View File

@ -0,0 +1 @@
export { sentry } from './sentry';

View File

@ -0,0 +1,10 @@
import { startTracing } from './core/startTracing';
import { parseSentryDsn } from './core/parseSentryDsn';
/**
* A sentry object that attempts to mirror the actual sentry API.
*/
export const sentry = {
startTracing,
parseSentryDsn,
};

View File

@ -0,0 +1,6 @@
type SentryPublicKey = string;
type SentryProjectId = string;
// see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
export type SentryDsn =
`https://${SentryPublicKey}.ingest.sentry.io/${SentryProjectId}`;

View File

@ -16,6 +16,7 @@ const serverEnvVars = `
projectID: '${process.env.HASURA_CLOUD_PROJECT_ID || ''}',
cloudRootDomain: '${process.env.HASURA_CLOUD_ROOT_DOMAIN}',
consoleType: '${process.env.HASURA_CONSOLE_TYPE}',
consoleSentryDsn: '${process.env.HASURA_CONSOLE_SENTRY_DSN}'
`;
const cliEnvVars = `
@ -33,7 +34,8 @@ const cliEnvVars = `
herokuOAuthClientId: '${process.env.HEROKU_OAUTH_CLIENT_ID || ''}',
tenantID: '${process.env.HASURA_CLOUD_TENANT_ID || ''}',
projectID: '${process.env.HASURA_CLOUD_PROJECT_ID || ''}',
cloudRootDomain: '${process.env.HASURA_CLOUD_ROOT_DOMAIN}'
cloudRootDomain: '${process.env.HASURA_CLOUD_ROOT_DOMAIN}',
consoleSentryDsn: '${process.env.HASURA_CONSOLE_SENTRY_DSN}'
`;
const envVars = process.env.CONSOLE_MODE === 'cli' ? cliEnvVars : serverEnvVars;

View File

@ -1207,7 +1207,8 @@ mkConsoleHTML path authMode enableTelemetry consoleAssetsDir =
"enableTelemetry" A..= boolToText enableTelemetry,
"cdnAssets" A..= boolToText (isNothing consoleAssetsDir),
"assetsVersion" A..= consoleAssetsVersion,
"serverVersion" A..= currentVersion
"serverVersion" A..= currentVersion,
"consoleSentryDsn" A..= ("" :: Text)
]
where
consolePath = case path of

View File

@ -13,6 +13,7 @@
cdnAssets: {{cdnAssets}},
serverVersion: "{{serverVersion}}",
consoleType: "oss",
consoleSentryDsn: "{{consoleSentryDsn}}"
};
window.__env.versionedAssetsPath = window.__env.assetsPath;
</script>