mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-22 18:00:25 +01:00
Compare commits
1 Commits
chore/migr
...
boot-setti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7807a6df44 |
@@ -94,12 +94,15 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
window.signozBootData = { settings: [[.BootSettings]] };
|
||||
</script>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
|
||||
if (PYLON_APP_ID) {
|
||||
var pylonAppId = (window.signozBootData?.settings?.pylon || {}).appId || '';
|
||||
if (pylonAppId) {
|
||||
(function () {
|
||||
var e = window;
|
||||
var t = document;
|
||||
@@ -115,10 +118,7 @@
|
||||
var e = t.createElement('script');
|
||||
e.setAttribute('type', 'text/javascript');
|
||||
e.setAttribute('async', 'true');
|
||||
e.setAttribute(
|
||||
'src',
|
||||
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
|
||||
);
|
||||
e.setAttribute('src', 'https://widget.usepylon.com/widget/' + pylonAppId);
|
||||
var n = t.getElementsByTagName('script')[0];
|
||||
n.parentNode.insertBefore(e, n);
|
||||
};
|
||||
@@ -130,16 +130,15 @@
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
window.AppcuesSettings = { enableURLDetection: true };
|
||||
</script>
|
||||
<script>
|
||||
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
|
||||
if (APPCUES_APP_ID) {
|
||||
var appcuesAppId =
|
||||
(window.signozBootData?.settings?.appcues || {}).appId || '';
|
||||
if (appcuesAppId) {
|
||||
window.AppcuesSettings = { enableURLDetection: true };
|
||||
(function (d, t) {
|
||||
var a = d.createElement(t);
|
||||
a.async = 1;
|
||||
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
|
||||
a.src = '//fast.appcues.com/' + appcuesAppId + '.js';
|
||||
var s = d.getElementsByTagName(t)[0];
|
||||
s.parentNode.insertBefore(a, s);
|
||||
})(document, 'script');
|
||||
|
||||
@@ -35,6 +35,7 @@ import { PreferenceContextProvider } from 'providers/preferences/context/Prefere
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||
import { extractDomain } from 'utils/app';
|
||||
import { bootSettings } from 'utils/bootData';
|
||||
|
||||
import { Home } from './pageComponents';
|
||||
import PrivateRoute from './Private';
|
||||
@@ -293,7 +294,7 @@ function App(): JSX.Element {
|
||||
(isCloudUser || isEnterpriseSelfHostedUser)
|
||||
) {
|
||||
const email = user.email || '';
|
||||
const secret = process.env.PYLON_IDENTITY_SECRET || '';
|
||||
const secret = bootSettings.pylon.identSecret ?? '';
|
||||
let emailHash = '';
|
||||
|
||||
if (email && secret) {
|
||||
@@ -302,7 +303,7 @@ function App(): JSX.Element {
|
||||
|
||||
window.pylon = {
|
||||
chat_settings: {
|
||||
app_id: process.env.PYLON_APP_ID,
|
||||
app_id: bootSettings.pylon.appId,
|
||||
email: user.email,
|
||||
name: user.displayName || user.email,
|
||||
email_hash: emailHash,
|
||||
@@ -332,8 +333,8 @@ function App(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
if (process.env.POSTHOG_KEY) {
|
||||
posthog.init(process.env.POSTHOG_KEY, {
|
||||
if (bootSettings.posthog.key) {
|
||||
posthog.init(bootSettings.posthog.key, {
|
||||
api_host: 'https://us.i.posthog.com',
|
||||
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
|
||||
});
|
||||
@@ -341,8 +342,8 @@ function App(): JSX.Element {
|
||||
|
||||
if (!isSentryInitialized) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tunnel: process.env.TUNNEL_URL,
|
||||
dsn: bootSettings.sentry.dsn,
|
||||
tunnel: bootSettings.sentry.tunnelUrl,
|
||||
environment: 'production',
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { compose, Store } from 'redux';
|
||||
import type { SignozBootSettings } from 'utils/bootData';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -7,6 +8,7 @@ declare global {
|
||||
pylon: any;
|
||||
Appcues: Record<string, any>;
|
||||
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose;
|
||||
signozBootData?: { settings?: Partial<SignozBootSettings> };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
99
frontend/src/utils/__tests__/bootData.test.ts
Normal file
99
frontend/src/utils/__tests__/bootData.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
export {};
|
||||
|
||||
type BootData = typeof import('../bootData');
|
||||
|
||||
function loadModule(settings?: object): BootData {
|
||||
(window as any).signozBootData =
|
||||
settings !== undefined ? { settings } : undefined;
|
||||
let mod!: BootData;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../bootData');
|
||||
});
|
||||
return mod;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete (window as any).signozBootData;
|
||||
});
|
||||
|
||||
describe('when window.signozBootData is absent', () => {
|
||||
it('all sub-objects are defined and empty', () => {
|
||||
const { bootSettings } = loadModule();
|
||||
expect(bootSettings.sentry).toStrictEqual({});
|
||||
expect(bootSettings.posthog).toStrictEqual({});
|
||||
expect(bootSettings.pylon).toStrictEqual({});
|
||||
expect(bootSettings.appcues).toStrictEqual({});
|
||||
expect(bootSettings.roles).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('optional fields are undefined', () => {
|
||||
const { bootSettings } = loadModule();
|
||||
expect(bootSettings.sentry.dsn).toBeUndefined();
|
||||
expect(bootSettings.sentry.tunnelUrl).toBeUndefined();
|
||||
expect(bootSettings.posthog.key).toBeUndefined();
|
||||
expect(bootSettings.pylon.appId).toBeUndefined();
|
||||
expect(bootSettings.pylon.identSecret).toBeUndefined();
|
||||
expect(bootSettings.appcues.appId).toBeUndefined();
|
||||
expect(bootSettings.roles.isRolesDetailEnabled).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when window.signozBootData.settings is populated', () => {
|
||||
it('reads sentry config', () => {
|
||||
const { bootSettings } = loadModule({
|
||||
sentry: { dsn: 'https://abc@sentry.io/1', tunnelUrl: '/tunnel' },
|
||||
});
|
||||
expect(bootSettings.sentry.dsn).toBe('https://abc@sentry.io/1');
|
||||
expect(bootSettings.sentry.tunnelUrl).toBe('/tunnel');
|
||||
});
|
||||
|
||||
it('reads posthog config', () => {
|
||||
const { bootSettings } = loadModule({ posthog: { key: 'phk_xxx' } });
|
||||
expect(bootSettings.posthog.key).toBe('phk_xxx');
|
||||
});
|
||||
|
||||
it('reads pylon config', () => {
|
||||
const { bootSettings } = loadModule({
|
||||
pylon: { appId: 'pylon-abc', identSecret: 'secret-xyz' },
|
||||
});
|
||||
expect(bootSettings.pylon.appId).toBe('pylon-abc');
|
||||
expect(bootSettings.pylon.identSecret).toBe('secret-xyz');
|
||||
});
|
||||
|
||||
it('reads appcues config', () => {
|
||||
const { bootSettings } = loadModule({ appcues: { appId: 'appcues-123' } });
|
||||
expect(bootSettings.appcues.appId).toBe('appcues-123');
|
||||
});
|
||||
|
||||
it('reads roles config', () => {
|
||||
const { bootSettings } = loadModule({
|
||||
roles: { isRolesDetailEnabled: true },
|
||||
});
|
||||
expect(bootSettings.roles.isRolesDetailEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('missing sub-namespaces fall back to empty objects', () => {
|
||||
const { bootSettings } = loadModule({
|
||||
sentry: { dsn: 'https://abc@sentry.io/1' },
|
||||
});
|
||||
expect(bootSettings.posthog).toStrictEqual({});
|
||||
expect(bootSettings.posthog.key).toBeUndefined();
|
||||
expect(bootSettings.pylon).toStrictEqual({});
|
||||
expect(bootSettings.appcues).toStrictEqual({});
|
||||
expect(bootSettings.roles).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when window.signozBootData exists but settings is undefined', () => {
|
||||
it('all sub-objects are empty', () => {
|
||||
(window as any).signozBootData = {};
|
||||
let mod!: BootData;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../bootData');
|
||||
});
|
||||
expect(mod.bootSettings.sentry).toStrictEqual({});
|
||||
expect(mod.bootSettings.posthog).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
35
frontend/src/utils/bootData.ts
Normal file
35
frontend/src/utils/bootData.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface SentryConfig {
|
||||
dsn?: string;
|
||||
tunnelUrl?: string;
|
||||
}
|
||||
export interface PosthogConfig {
|
||||
key?: string;
|
||||
}
|
||||
export interface PylonConfig {
|
||||
appId?: string;
|
||||
identSecret?: string;
|
||||
}
|
||||
export interface AppcuesConfig {
|
||||
appId?: string;
|
||||
}
|
||||
export interface RolesConfig {
|
||||
isRolesDetailEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface SignozBootSettings {
|
||||
sentry: SentryConfig;
|
||||
posthog: PosthogConfig;
|
||||
pylon: PylonConfig;
|
||||
appcues: AppcuesConfig;
|
||||
roles: RolesConfig;
|
||||
}
|
||||
|
||||
const raw = window.signozBootData?.settings;
|
||||
|
||||
export const bootSettings: Readonly<SignozBootSettings> = {
|
||||
sentry: raw?.sentry ?? {},
|
||||
posthog: raw?.posthog ?? {},
|
||||
pylon: raw?.pylon ?? {},
|
||||
appcues: raw?.appcues ?? {},
|
||||
roles: raw?.roles ?? {},
|
||||
};
|
||||
7
frontend/src/vite-env.d.ts
vendored
7
frontend/src/vite-env.d.ts
vendored
@@ -13,16 +13,9 @@ declare module '*.md?raw' {
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_FRONTEND_API_ENDPOINT: string;
|
||||
readonly VITE_WEBSOCKET_API_ENDPOINT: string;
|
||||
readonly VITE_PYLON_APP_ID: string;
|
||||
readonly VITE_PYLON_IDENTITY_SECRET: string;
|
||||
readonly VITE_APPCUES_APP_ID: string;
|
||||
readonly VITE_POSTHOG_KEY: string;
|
||||
readonly VITE_SENTRY_AUTH_TOKEN: string;
|
||||
readonly VITE_SENTRY_ORG: string;
|
||||
readonly VITE_SENTRY_PROJECT_ID: string;
|
||||
readonly VITE_SENTRY_DSN: string;
|
||||
readonly VITE_TUNNEL_URL: string;
|
||||
readonly VITE_TUNNEL_DOMAIN: string;
|
||||
readonly VITE_DOCS_BASE_URL: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { Plugin, TransformResult, UserConfig } from 'vite';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vitePluginChecker from 'vite-plugin-checker';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
@@ -23,6 +22,29 @@ function devBasePathPlugin(basePath: string): Plugin {
|
||||
};
|
||||
}
|
||||
|
||||
function devBootDataPlugin(env: Record<string, string>): Plugin {
|
||||
return {
|
||||
name: 'dev-boot-data',
|
||||
apply: 'serve',
|
||||
transformIndexHtml(html): string {
|
||||
const bootSettings = {
|
||||
sentry: {
|
||||
dsn: env.VITE_SENTRY_DSN || undefined,
|
||||
tunnelUrl: env.VITE_TUNNEL_URL || undefined,
|
||||
},
|
||||
posthog: { key: env.VITE_POSTHOG_KEY || undefined },
|
||||
pylon: {
|
||||
appId: env.VITE_PYLON_APP_ID || undefined,
|
||||
identSecret: env.VITE_PYLON_IDENTITY_SECRET || undefined,
|
||||
},
|
||||
appcues: { appId: env.VITE_APPCUES_APP_ID || undefined },
|
||||
roles: {},
|
||||
};
|
||||
return html.replaceAll('[[.BootSettings]]', JSON.stringify(bootSettings));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function rawMarkdownPlugin(): Plugin {
|
||||
return {
|
||||
name: 'raw-markdown',
|
||||
@@ -47,15 +69,8 @@ export default defineConfig(({ mode }): UserConfig => {
|
||||
tsconfigPaths(),
|
||||
rawMarkdownPlugin(),
|
||||
devBasePathPlugin(basePath),
|
||||
devBootDataPlugin(env),
|
||||
react(),
|
||||
createHtmlPlugin({
|
||||
inject: {
|
||||
data: {
|
||||
PYLON_APP_ID: env.VITE_PYLON_APP_ID || '',
|
||||
APPCUES_APP_ID: env.VITE_APPCUES_APP_ID || '',
|
||||
},
|
||||
},
|
||||
}),
|
||||
vitePluginChecker({
|
||||
typescript: true,
|
||||
// this doubles the build tim
|
||||
@@ -126,17 +141,8 @@ export default defineConfig(({ mode }): UserConfig => {
|
||||
'process.env.WEBSOCKET_API_ENDPOINT': JSON.stringify(
|
||||
env.VITE_WEBSOCKET_API_ENDPOINT,
|
||||
),
|
||||
'process.env.PYLON_APP_ID': JSON.stringify(env.VITE_PYLON_APP_ID),
|
||||
'process.env.PYLON_IDENTITY_SECRET': JSON.stringify(
|
||||
env.VITE_PYLON_IDENTITY_SECRET,
|
||||
),
|
||||
'process.env.APPCUES_APP_ID': JSON.stringify(env.VITE_APPCUES_APP_ID),
|
||||
'process.env.POSTHOG_KEY': JSON.stringify(env.VITE_POSTHOG_KEY),
|
||||
'process.env.SENTRY_ORG': JSON.stringify(env.VITE_SENTRY_ORG),
|
||||
'process.env.SENTRY_PROJECT_ID': JSON.stringify(env.VITE_SENTRY_PROJECT_ID),
|
||||
'process.env.SENTRY_DSN': JSON.stringify(env.VITE_SENTRY_DSN),
|
||||
'process.env.TUNNEL_URL': JSON.stringify(env.VITE_TUNNEL_URL),
|
||||
'process.env.TUNNEL_DOMAIN': JSON.stringify(env.VITE_TUNNEL_DOMAIN),
|
||||
'process.env.DOCS_BASE_URL': JSON.stringify(env.VITE_DOCS_BASE_URL),
|
||||
},
|
||||
// In production, use relative paths so assets work with any base path injected by the backend.
|
||||
|
||||
Reference in New Issue
Block a user