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/api/RegistrationBackendClient.tsx b/plugins/sandbox/src/api/RegistrationBackendClient.tsx index 5aabac9..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; @@ -111,18 +112,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') ?? + SandboxEnvironment.PROD) === SandboxEnvironment.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, }); }; diff --git a/plugins/sandbox/src/components/SandboxHeader.tsx b/plugins/sandbox/src/components/SandboxHeader.tsx index 58320f7..bf4b7ce 100644 --- a/plugins/sandbox/src/components/SandboxHeader.tsx +++ b/plugins/sandbox/src/components/SandboxHeader.tsx @@ -21,7 +21,9 @@ 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'; +import { SandboxEnvironment } from '../const'; interface SandboxHeaderProps { pageTitle: string; @@ -29,27 +31,30 @@ interface SandboxHeaderProps { export const SandboxHeader: React.FC = ({ pageTitle }) => { const trackAnalytics = useTrackAnalytics(); + const configApi = useApi(configApiRef); + const environment = + configApi.getOptionalString('sandbox.environment') ?? + SandboxEnvironment.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 === SandboxEnvironment.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..8a307d3 100644 --- a/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx +++ b/plugins/sandbox/src/components/__tests__/SandboxHeader.test.tsx @@ -17,7 +17,13 @@ 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 { SandboxEnvironment } from '../../const'; import * as eddlUtils from '../../utils/eddl-utils'; // Mock the useTrackAnalytics hook @@ -26,24 +32,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 +150,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', 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', SandboxEnvironment.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/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/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..747b196 100644 --- a/plugins/sandbox/src/hooks/useSandboxContext.tsx +++ b/plugins/sandbox/src/hooks/useSandboxContext.tsx @@ -16,10 +16,10 @@ 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'; +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'; @@ -63,7 +63,11 @@ export const useSandboxContext = (): SandboxContextType => { export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { - useRecaptcha(); + const configApi = useApi(configApiRef); + const isProd = + (configApi.getOptionalString('sandbox.environment') ?? + SandboxEnvironment.PROD) !== SandboxEnvironment.DEV; + useRecaptcha(isProd); const aapApi = useApi(aapApiRef); const kubeApi = useApi(kubeApiRef); const registerApi = useApi(registerApiRef); @@ -210,6 +214,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 +226,7 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ } }; fetchSegmentWriteKey(); - }, [registerApi]); + }, [registerApi, isProd]); // Fetch Marketo webhook URL from UI config useEffect(() => {