From a7b097d98b29a21a44540d81b30742191b87d3b7 Mon Sep 17 00:00:00 2001 From: fmuntean Date: Wed, 14 Jan 2026 14:52:16 +0100 Subject: [PATCH 1/4] updatede qs package --- package.json | 3 ++- yarn.lock | 17 +++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e907709..0c7360b 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ }, "resolutions": { "@types/react": "^18", - "@types/react-dom": "^18" + "@types/react-dom": "^18", + "qs": "^6.14.0" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 4441f1f..0c287b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15297,17 +15297,10 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -qs@^6.12.3, qs@^6.9.4: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== +qs@6.13.0, qs@^6.12.3, qs@^6.14.0, qs@^6.9.4: + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== dependencies: side-channel "^1.1.0" @@ -16552,7 +16545,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0: +side-channel@^1.0.4, side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== From 7e7d53df95eeb8aa5c9f7e1b2ec9222b1014077a Mon Sep 17 00:00:00 2001 From: fmuntean Date: Wed, 25 Mar 2026 13:00:56 +0100 Subject: [PATCH 2/4] disable recaptcha and segment in dev --- app-config.production.yaml | 1 + deploy/base/app-config.yaml | 1 + plugins/sandbox/config.d.ts | 7 ++ .../sandbox/src/components/SandboxHeader.tsx | 39 ++++++----- .../__tests__/SandboxHeader.test.tsx | 67 +++++++++++++++++-- plugins/sandbox/src/hooks/useRecaptcha.ts | 8 ++- .../sandbox/src/hooks/useSandboxContext.tsx | 12 +++- 7 files changed, 106 insertions(+), 29 deletions(-) diff --git a/app-config.production.yaml b/app-config.production.yaml index 94f8f27..4bf5267 100644 --- a/app-config.production.yaml +++ b/app-config.production.yaml @@ -61,6 +61,7 @@ auth: signInPage: oidc sandbox: + environment: PROD signupAPI: ${SIGNUP_API} kubeAPI: ${KUBE_API} recaptcha: diff --git a/deploy/base/app-config.yaml b/deploy/base/app-config.yaml index 1e28fa4..abbb0d4 100644 --- a/deploy/base/app-config.yaml +++ b/deploy/base/app-config.yaml @@ -76,6 +76,7 @@ auth: dangerouslyAllowSignInWithoutUserInCatalog: true signInPage: oidc sandbox: + environment: DEV signupAPI: https://registration-service-toolchain-host-operator.apps.sandbox.x8i5.p1.openshiftapps.com/api/v1 kubeAPI: https://api-toolchain-host-operator.apps.sandbox.x8i5.p1.openshiftapps.com recaptcha: diff --git a/plugins/sandbox/config.d.ts b/plugins/sandbox/config.d.ts index 948cda8..010f7e8 100644 --- a/plugins/sandbox/config.d.ts +++ b/plugins/sandbox/config.d.ts @@ -40,5 +40,12 @@ export interface Config { */ siteKey: string; }; + /** + * Controls the runtime environment (DEV or PROD). + * When set to DEV, analytics scripts (trustarc, dpal) are not loaded. + * Defaults to PROD if not specified. + * @visibility frontend + */ + environment?: string; }; } diff --git a/plugins/sandbox/src/components/SandboxHeader.tsx b/plugins/sandbox/src/components/SandboxHeader.tsx index 58320f7..9b368e6 100644 --- a/plugins/sandbox/src/components/SandboxHeader.tsx +++ b/plugins/sandbox/src/components/SandboxHeader.tsx @@ -21,6 +21,7 @@ import { useTheme } from '@mui/material/styles'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import SupportAgentIcon from '@mui/icons-material/SupportAgent'; import { Header, Link } from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { useTrackAnalytics } from '../utils/eddl-utils'; interface SandboxHeaderProps { @@ -29,27 +30,29 @@ interface SandboxHeaderProps { export const SandboxHeader: React.FC = ({ pageTitle }) => { const trackAnalytics = useTrackAnalytics(); + const configApi = useApi(configApiRef); + const environment = + configApi.getOptionalString('sandbox.environment') ?? 'PROD'; useEffect(() => { - const initializeAnalytics = async () => { - // Check if script is already loaded - if (!document.getElementById('trustarc')) { - const script = document.createElement('script'); - script.id = 'trustarc'; - script.src = - '//static.redhat.com/libs/redhat/marketing/latest/trustarc/trustarc.js'; - document.body.appendChild(script); - } - if (!document.getElementById('dpal')) { - const script = document.createElement('script'); - script.id = 'dpal'; - script.src = 'https://www.redhat.com/ma/dpal.js'; - document.body.appendChild(script); - } - }; + if (environment === 'DEV') { + return; + } - initializeAnalytics(); - }, []); + if (!document.getElementById('trustarc')) { + const script = document.createElement('script'); + script.id = 'trustarc'; + script.src = + '//static.redhat.com/libs/redhat/marketing/latest/trustarc/trustarc.js'; + document.body.appendChild(script); + } + if (!document.getElementById('dpal')) { + const script = document.createElement('script'); + script.id = 'dpal'; + script.src = 'https://www.redhat.com/ma/dpal.js'; + document.body.appendChild(script); + } + }, [environment]); const theme = useTheme(); diff --git a/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx b/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx index f4c0a0e..27b895e 100644 --- a/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx +++ b/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx @@ -17,7 +17,12 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { SandboxHeader } from '../SandboxHeader'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { + MockConfigApi, + TestApiProvider, + wrapInTestApp, +} from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; import * as eddlUtils from '../../utils/eddl-utils'; // Mock the useTrackAnalytics hook @@ -26,24 +31,50 @@ jest.mock('../../utils/eddl-utils', () => ({ useTrackAnalytics: jest.fn(), })); +const removeAnalyticsScripts = () => { + document.getElementById('trustarc')?.remove(); + document.getElementById('dpal')?.remove(); +}; + describe('SandboxHeader', () => { const mockTrackAnalytics = jest.fn(); beforeEach(() => { jest.clearAllMocks(); + removeAnalyticsScripts(); // Mock the useTrackAnalytics hook to return a mock function (eddlUtils.useTrackAnalytics as jest.Mock).mockReturnValue( mockTrackAnalytics, ); }); - const renderComponent = (pageTitle = 'My Page Title') => { + afterEach(() => { + removeAnalyticsScripts(); + }); + + const renderComponent = ( + pageTitle = 'My Page Title', + environment?: string, + ) => { const theme = createTheme(); + const content = ( + + + + ); return render( wrapInTestApp( - - - , + environment ? ( + + {content} + + ) : ( + content + ), ), ); }; @@ -118,4 +149,30 @@ describe('SandboxHeader', () => { windowOpenSpy.mockRestore(); }); + + describe('analytics scripts loading', () => { + test('does not load trustarc and dpal scripts when environment is DEV', () => { + renderComponent('My Page Title', 'DEV'); + expect(document.getElementById('trustarc')).toBeNull(); + expect(document.getElementById('dpal')).toBeNull(); + }); + + test('loads trustarc and dpal scripts when environment is PROD', () => { + renderComponent('My Page Title', 'PROD'); + const trustarcScript = document.getElementById( + 'trustarc', + ) as HTMLScriptElement; + const dpalScript = document.getElementById('dpal') as HTMLScriptElement; + expect(trustarcScript).not.toBeNull(); + expect(trustarcScript.src).toContain('trustarc.js'); + expect(dpalScript).not.toBeNull(); + expect(dpalScript.src).toContain('dpal.js'); + }); + + test('loads analytics scripts by default when environment is not set', () => { + renderComponent(); + expect(document.getElementById('trustarc')).not.toBeNull(); + expect(document.getElementById('dpal')).not.toBeNull(); + }); + }); }); diff --git a/plugins/sandbox/src/hooks/useRecaptcha.ts b/plugins/sandbox/src/hooks/useRecaptcha.ts index 9743e27..477f8a0 100644 --- a/plugins/sandbox/src/hooks/useRecaptcha.ts +++ b/plugins/sandbox/src/hooks/useRecaptcha.ts @@ -18,10 +18,12 @@ import { useApi } from '@backstage/core-plugin-api'; import { loadRecaptchaScript } from '../utils/recaptcha'; import { registerApiRef } from '../api'; -export const useRecaptcha = () => { +export const useRecaptcha = (enabled = true) => { const registerApi = useApi(registerApiRef); React.useEffect(() => { - loadRecaptchaScript(registerApi.getRecaptchaAPIKey()); + if (enabled) { + loadRecaptchaScript(registerApi.getRecaptchaAPIKey()); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [enabled]); }; diff --git a/plugins/sandbox/src/hooks/useSandboxContext.tsx b/plugins/sandbox/src/hooks/useSandboxContext.tsx index 0b2dd42..486a31f 100644 --- a/plugins/sandbox/src/hooks/useSandboxContext.tsx +++ b/plugins/sandbox/src/hooks/useSandboxContext.tsx @@ -16,7 +16,7 @@ import { isEqual } from 'lodash'; import React, { createContext, useContext, useEffect, useState } from 'react'; import { AAPData, SignupData } from '../types'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { aapApiRef, kubeApiRef, registerApiRef } from '../api'; import { useRecaptcha } from './useRecaptcha'; import { LONG_INTERVAL, SHORT_INTERVAL } from '../const'; @@ -63,7 +63,10 @@ export const useSandboxContext = (): SandboxContextType => { export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { - useRecaptcha(); + const configApi = useApi(configApiRef); + const isProd = + (configApi.getOptionalString('sandbox.environment') ?? 'PROD') !== 'DEV'; + useRecaptcha(isProd); const aapApi = useApi(aapApiRef); const kubeApi = useApi(kubeApiRef); const registerApi = useApi(registerApiRef); @@ -210,6 +213,9 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ // Initialize Segment Analytics useEffect(() => { + if (!isProd) { + return; + } const fetchSegmentWriteKey = async () => { try { const writeKey = await registerApi.getSegmentWriteKey(); @@ -219,7 +225,7 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ } }; fetchSegmentWriteKey(); - }, [registerApi]); + }, [registerApi, isProd]); // Fetch Marketo webhook URL from UI config useEffect(() => { From fb000fb2e463897aa8415fc68a7f952671a2ec42 Mon Sep 17 00:00:00 2001 From: fmuntean Date: Wed, 25 Mar 2026 17:52:35 +0100 Subject: [PATCH 3/4] avoid passing the recaptcha token when in DEV mode --- .../src/api/RegistrationBackendClient.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/sandbox/src/api/RegistrationBackendClient.tsx b/plugins/sandbox/src/api/RegistrationBackendClient.tsx index 5aabac9..4664134 100644 --- a/plugins/sandbox/src/api/RegistrationBackendClient.tsx +++ b/plugins/sandbox/src/api/RegistrationBackendClient.tsx @@ -111,18 +111,23 @@ export class RegistrationBackendClient implements RegistrationService { }; signup = async (): Promise => { - let token = ''; - try { - token = await this.getRecaptchaToken(); - } catch (err) { - throw new Error(`Error getting recaptcha token: ${err}`); + const isDev = + (this.configApi.getOptionalString('sandbox.environment') ?? 'PROD') === + 'DEV'; + const headers: Record = {}; + + if (!isDev) { + try { + headers['Recaptcha-Token'] = await this.getRecaptchaToken(); + } catch (err) { + throw new Error(`Error getting recaptcha token: ${err}`); + } } + const signupURL = await this.signupAPI(); await this.secureFetchApi.fetch(signupURL, { method: 'POST', - headers: { - 'Recaptcha-Token': token, - }, + headers, body: null, }); }; From e33db944a8bfdf283841d436aedc8d2917b73406 Mon Sep 17 00:00:00 2001 From: fmuntean Date: Wed, 25 Mar 2026 17:59:47 +0100 Subject: [PATCH 4/4] use enum for constants --- plugins/sandbox/src/api/RegistrationBackendClient.tsx | 5 +++-- plugins/sandbox/src/components/SandboxHeader.tsx | 6 ++++-- .../sandbox/src/components/__tests__/SandboxHeader.test.tsx | 5 +++-- plugins/sandbox/src/const.ts | 5 +++++ plugins/sandbox/src/hooks/useSandboxContext.tsx | 5 +++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plugins/sandbox/src/api/RegistrationBackendClient.tsx b/plugins/sandbox/src/api/RegistrationBackendClient.tsx index 4664134..7713ad8 100644 --- a/plugins/sandbox/src/api/RegistrationBackendClient.tsx +++ b/plugins/sandbox/src/api/RegistrationBackendClient.tsx @@ -19,6 +19,7 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { isValidCountryCode, isValidPhoneNumber } from '../utils/phone-utils'; import { CommonResponse, SignupData } from '../types'; import { SecureFetchApi } from './SecureFetchClient'; +import { SandboxEnvironment } from '../const'; export type RegistrationBackendClientOptions = { configApi: ConfigApi; @@ -112,8 +113,8 @@ export class RegistrationBackendClient implements RegistrationService { signup = async (): Promise => { const isDev = - (this.configApi.getOptionalString('sandbox.environment') ?? 'PROD') === - 'DEV'; + (this.configApi.getOptionalString('sandbox.environment') ?? + SandboxEnvironment.PROD) === SandboxEnvironment.DEV; const headers: Record = {}; if (!isDev) { diff --git a/plugins/sandbox/src/components/SandboxHeader.tsx b/plugins/sandbox/src/components/SandboxHeader.tsx index 9b368e6..bf4b7ce 100644 --- a/plugins/sandbox/src/components/SandboxHeader.tsx +++ b/plugins/sandbox/src/components/SandboxHeader.tsx @@ -23,6 +23,7 @@ import SupportAgentIcon from '@mui/icons-material/SupportAgent'; import { Header, Link } from '@backstage/core-components'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { useTrackAnalytics } from '../utils/eddl-utils'; +import { SandboxEnvironment } from '../const'; interface SandboxHeaderProps { pageTitle: string; @@ -32,10 +33,11 @@ export const SandboxHeader: React.FC = ({ pageTitle }) => { const trackAnalytics = useTrackAnalytics(); const configApi = useApi(configApiRef); const environment = - configApi.getOptionalString('sandbox.environment') ?? 'PROD'; + configApi.getOptionalString('sandbox.environment') ?? + SandboxEnvironment.PROD; useEffect(() => { - if (environment === 'DEV') { + if (environment === SandboxEnvironment.DEV) { return; } diff --git a/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx b/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx index 27b895e..8a307d3 100644 --- a/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx +++ b/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx @@ -23,6 +23,7 @@ import { wrapInTestApp, } from '@backstage/test-utils'; import { configApiRef } from '@backstage/core-plugin-api'; +import { SandboxEnvironment } from '../../const'; import * as eddlUtils from '../../utils/eddl-utils'; // Mock the useTrackAnalytics hook @@ -152,13 +153,13 @@ describe('SandboxHeader', () => { describe('analytics scripts loading', () => { test('does not load trustarc and dpal scripts when environment is DEV', () => { - renderComponent('My Page Title', 'DEV'); + renderComponent('My Page Title', SandboxEnvironment.DEV); expect(document.getElementById('trustarc')).toBeNull(); expect(document.getElementById('dpal')).toBeNull(); }); test('loads trustarc and dpal scripts when environment is PROD', () => { - renderComponent('My Page Title', 'PROD'); + renderComponent('My Page Title', SandboxEnvironment.PROD); const trustarcScript = document.getElementById( 'trustarc', ) as HTMLScriptElement; diff --git a/plugins/sandbox/src/const.ts b/plugins/sandbox/src/const.ts index 3af06b1..c30f279 100644 --- a/plugins/sandbox/src/const.ts +++ b/plugins/sandbox/src/const.ts @@ -15,3 +15,8 @@ */ export const SHORT_INTERVAL = 2000; export const LONG_INTERVAL = 20000; + +export enum SandboxEnvironment { + DEV = 'DEV', + PROD = 'PROD', +} diff --git a/plugins/sandbox/src/hooks/useSandboxContext.tsx b/plugins/sandbox/src/hooks/useSandboxContext.tsx index 486a31f..747b196 100644 --- a/plugins/sandbox/src/hooks/useSandboxContext.tsx +++ b/plugins/sandbox/src/hooks/useSandboxContext.tsx @@ -19,7 +19,7 @@ import { AAPData, SignupData } from '../types'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { aapApiRef, kubeApiRef, registerApiRef } from '../api'; import { useRecaptcha } from './useRecaptcha'; -import { LONG_INTERVAL, SHORT_INTERVAL } from '../const'; +import { LONG_INTERVAL, SandboxEnvironment, SHORT_INTERVAL } from '../const'; import { signupDataToStatus } from '../utils/register-utils'; import { AnsibleStatus, decode, getReadyCondition } from '../utils/aap-utils'; import { errorMessage } from '../utils/common'; @@ -65,7 +65,8 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ }) => { const configApi = useApi(configApiRef); const isProd = - (configApi.getOptionalString('sandbox.environment') ?? 'PROD') !== 'DEV'; + (configApi.getOptionalString('sandbox.environment') ?? + SandboxEnvironment.PROD) !== SandboxEnvironment.DEV; useRecaptcha(isProd); const aapApi = useApi(aapApiRef); const kubeApi = useApi(kubeApiRef);