From aa367fe014743922608a5d61470da7729e4e3e16 Mon Sep 17 00:00:00 2001 From: MikelAlejoBR Date: Mon, 20 Apr 2026 10:39:54 -0400 Subject: [PATCH 1/3] feature: disable integrations in the UI There are times when we want to disable certain faulty integrations in the UI, so that we can prevent customers from accessing them and having a bad experience. These changes read the new "disabledIntegrations" from the registration service to hide the integrations that are disabled. Assisted-by: Cursor with claude-4.6-opus-max-thinking SANDBOX-1769 --- .../src/api/RegistrationBackendClient.tsx | 1 + .../RegistrationBackendClient.test.tsx | 23 +++++++ .../SandboxCatalog/SandboxCatalogGrid.tsx | 23 ++++++- .../__tests__/SandboxCatalogGrid.test.tsx | 60 ++++++++++++++++++- .../sandbox/src/hooks/useSandboxContext.tsx | 9 ++- 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/plugins/sandbox/src/api/RegistrationBackendClient.tsx b/plugins/sandbox/src/api/RegistrationBackendClient.tsx index 7713ad8..df0c270 100644 --- a/plugins/sandbox/src/api/RegistrationBackendClient.tsx +++ b/plugins/sandbox/src/api/RegistrationBackendClient.tsx @@ -28,6 +28,7 @@ export type RegistrationBackendClientOptions = { export interface UIConfig { workatoWebHookURL?: string; + disabledIntegrations?: string[]; } export interface RegistrationService { diff --git a/plugins/sandbox/src/api/__tests__/RegistrationBackendClient.test.tsx b/plugins/sandbox/src/api/__tests__/RegistrationBackendClient.test.tsx index 7a47fb3..4ae269d 100644 --- a/plugins/sandbox/src/api/__tests__/RegistrationBackendClient.test.tsx +++ b/plugins/sandbox/src/api/__tests__/RegistrationBackendClient.test.tsx @@ -289,6 +289,28 @@ describe('RegistrationBackendClient', () => { ); }); + it('should return UI config with disabledIntegrations', async () => { + const mockUIConfig = { + workatoWebHookURL: 'https://webhooks.test', + disabledIntegrations: ['openshift-console', 'devspaces'], + }; + + mockSecureFetchApi.fetch.mockResolvedValue( + createMockResponse({ + ok: true, + json: () => Promise.resolve(mockUIConfig), + }), + ); + + const result = await client.getUIConfig(); + + expect(result).toEqual(mockUIConfig); + expect(result.disabledIntegrations).toEqual([ + 'openshift-console', + 'devspaces', + ]); + }); + it('should return empty config if workatoWebHookURL is not present', async () => { const mockUIConfig = {}; @@ -303,6 +325,7 @@ describe('RegistrationBackendClient', () => { expect(result).toEqual({}); expect(result.workatoWebHookURL).toBeUndefined(); + expect(result.disabledIntegrations).toBeUndefined(); }); it('should return empty config on unsuccessful response', async () => { diff --git a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx index acefafb..bc676ff 100644 --- a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx +++ b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx @@ -13,16 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import { Product, productData } from './productData'; import useGreenCorners from '../../hooks/useGreenCorners'; import { SandboxCatalogCard } from './SandboxCatalogCard'; import useProductURLs from '../../hooks/useProductURLs'; +import { useSandboxContext } from '../../hooks/useSandboxContext'; export const SandboxCatalogGrid: React.FC = () => { - const { greenCorners, setGreenCorners } = useGreenCorners(productData); + const { disabledIntegrations } = useSandboxContext(); + const enabledProducts = useMemo( + () => + disabledIntegrations === undefined + ? [] + : productData.filter(p => !disabledIntegrations.includes(p.id)), + [disabledIntegrations], + ); + const { greenCorners, setGreenCorners } = useGreenCorners(enabledProducts); const productURLs = useProductURLs(); const showGreenCorner = (id: Product) => { @@ -31,9 +40,17 @@ export const SandboxCatalogGrid: React.FC = () => { ); }; + // Do not load the grid until we have a list of which integrations are + // disabled. Otherwise what happens is that the whole integration catalog + // is loaded, and once the UI configuration is fetched, the disabled + // integrations disappear. + if (disabledIntegrations === undefined) { + return null; + } + return ( - {productData?.map(product => ( + {enabledProducts.map(product => ( ({ @@ -80,6 +82,10 @@ describe('SandboxCatalogGrid', () => { beforeEach(() => { jest.clearAllMocks(); + (useSandboxContext as jest.Mock).mockReturnValue({ + disabledIntegrations: [], + }); + // Setup mock return values for hooks (useGreenCorners as jest.Mock).mockReturnValue({ greenCorners: mockGreenCorners, @@ -107,7 +113,7 @@ describe('SandboxCatalogGrid', () => { expect(cards).toHaveLength(productData.length); }); - it('calls useGreenCorners hook with productData', () => { + it('calls useGreenCorners hook with enabled products', () => { renderGrid(); expect(useGreenCorners).toHaveBeenCalledWith(productData); }); @@ -170,4 +176,56 @@ describe('SandboxCatalogGrid', () => { { id: 'openshift-ai', show: true }, // Already true, unchanged ]); }); + + it('renders nothing while UI config is loading', () => { + (useSandboxContext as jest.Mock).mockReturnValue({ + disabledIntegrations: undefined, + }); + + (useGreenCorners as jest.Mock).mockReturnValue({ + greenCorners: [], + setGreenCorners: mockSetGreenCorners, + }); + + const { container } = renderGrid(); + + const cards = screen.queryAllByTestId('catalog-card'); + expect(cards).toHaveLength(0); + expect(container.querySelector('.MuiGrid-container')).toBeNull(); + }); + + it('hides cards for disabled integrations', () => { + (useSandboxContext as jest.Mock).mockReturnValue({ + disabledIntegrations: ['openshift-ai'], + }); + + (useGreenCorners as jest.Mock).mockReturnValue({ + greenCorners: [{ id: 'openshift-console', show: false }], + setGreenCorners: mockSetGreenCorners, + }); + + renderGrid(); + + const cards = screen.getAllByTestId('catalog-card'); + expect(cards).toHaveLength(1); + + const firstCallProps = (SandboxCatalogCard as jest.Mock).mock.calls[0][0]; + expect(firstCallProps.id).toBe('openshift-console'); + }); + + it('hides all cards when all integrations are disabled', () => { + (useSandboxContext as jest.Mock).mockReturnValue({ + disabledIntegrations: ['openshift-console', 'openshift-ai'], + }); + + (useGreenCorners as jest.Mock).mockReturnValue({ + greenCorners: [], + setGreenCorners: mockSetGreenCorners, + }); + + renderGrid(); + + const cards = screen.queryAllByTestId('catalog-card'); + expect(cards).toHaveLength(0); + }); }); diff --git a/plugins/sandbox/src/hooks/useSandboxContext.tsx b/plugins/sandbox/src/hooks/useSandboxContext.tsx index 747b196..251f703 100644 --- a/plugins/sandbox/src/hooks/useSandboxContext.tsx +++ b/plugins/sandbox/src/hooks/useSandboxContext.tsx @@ -48,6 +48,7 @@ interface SandboxContextType { ansibleStatus: AnsibleStatus; segmentTrackClick?: (data: SegmentTrackingData) => Promise; marketoWebhookURL?: string; + disabledIntegrations?: string[]; } const SandboxContext = createContext(undefined); @@ -73,6 +74,9 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ const registerApi = useApi(registerApiRef); const [segmentWriteKey, setSegmentWriteKey] = useState(); const [marketoWebhookURL, setMarketoWebhookURL] = useState(); + const [disabledIntegrations, setDisabledIntegrations] = useState< + string[] | undefined + >(); const [statusUnknown, setStatusUnknown] = React.useState(true); const [userFound, setUserFound] = useState(false); const [userData, setData] = useState(undefined); @@ -228,13 +232,15 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ fetchSegmentWriteKey(); }, [registerApi, isProd]); - // Fetch Marketo webhook URL from UI config + // Fetch the marketo URL and the disabled integrations from the registration + // service. useEffect(() => { const fetchUIConfig = async () => { const uiConfig = await registerApi.getUIConfig(); if (uiConfig.workatoWebHookURL) { setMarketoWebhookURL(uiConfig.workatoWebHookURL); } + setDisabledIntegrations(uiConfig.disabledIntegrations ?? []); }; fetchUIConfig(); }, [registerApi]); @@ -291,6 +297,7 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ ansibleStatus, segmentTrackClick: segmentAnalytics.trackClick, marketoWebhookURL, + disabledIntegrations, }} > {children} From 4d51a1049d9fc0ca62fd803091df3a5eb6b439ae Mon Sep 17 00:00:00 2001 From: MikelAlejoBR Date: Mon, 20 Apr 2026 14:47:25 -0400 Subject: [PATCH 2/3] refactor: implement feedback These changes prevent wiping the "green corner" cookie which stored the users' active trials by accident introduced by the new changes, and it also introduces a type check to make sure that we guard the UI from any unexpected array structures that might come from the back end. SANDBOX-1769 --- .../SandboxCatalog/SandboxCatalogGrid.tsx | 6 +++--- .../__tests__/SandboxCatalogGrid.test.tsx | 3 ++- .../sandbox/src/hooks/useSandboxContext.tsx | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx index bc676ff..1ab5c0c 100644 --- a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx +++ b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx @@ -26,9 +26,9 @@ export const SandboxCatalogGrid: React.FC = () => { const { disabledIntegrations } = useSandboxContext(); const enabledProducts = useMemo( () => - disabledIntegrations === undefined - ? [] - : productData.filter(p => !disabledIntegrations.includes(p.id)), + productData.filter( + p => !(disabledIntegrations ?? []).includes(p.id), + ), [disabledIntegrations], ); const { greenCorners, setGreenCorners } = useGreenCorners(enabledProducts); diff --git a/plugins/sandbox/src/components/SandboxCatalog/__tests__/SandboxCatalogGrid.test.tsx b/plugins/sandbox/src/components/SandboxCatalog/__tests__/SandboxCatalogGrid.test.tsx index 1b8c0da..dd1677c 100644 --- a/plugins/sandbox/src/components/SandboxCatalog/__tests__/SandboxCatalogGrid.test.tsx +++ b/plugins/sandbox/src/components/SandboxCatalog/__tests__/SandboxCatalogGrid.test.tsx @@ -183,7 +183,7 @@ describe('SandboxCatalogGrid', () => { }); (useGreenCorners as jest.Mock).mockReturnValue({ - greenCorners: [], + greenCorners: mockGreenCorners, setGreenCorners: mockSetGreenCorners, }); @@ -192,6 +192,7 @@ describe('SandboxCatalogGrid', () => { const cards = screen.queryAllByTestId('catalog-card'); expect(cards).toHaveLength(0); expect(container.querySelector('.MuiGrid-container')).toBeNull(); + expect(useGreenCorners).toHaveBeenCalledWith(productData); }); it('hides cards for disabled integrations', () => { diff --git a/plugins/sandbox/src/hooks/useSandboxContext.tsx b/plugins/sandbox/src/hooks/useSandboxContext.tsx index 251f703..590aded 100644 --- a/plugins/sandbox/src/hooks/useSandboxContext.tsx +++ b/plugins/sandbox/src/hooks/useSandboxContext.tsx @@ -236,11 +236,21 @@ export const SandboxProvider: React.FC<{ children: React.ReactNode }> = ({ // service. useEffect(() => { const fetchUIConfig = async () => { - const uiConfig = await registerApi.getUIConfig(); - if (uiConfig.workatoWebHookURL) { - setMarketoWebhookURL(uiConfig.workatoWebHookURL); + try { + const uiConfig = await registerApi.getUIConfig(); + if (uiConfig.workatoWebHookURL) { + setMarketoWebhookURL(uiConfig.workatoWebHookURL); + } + setDisabledIntegrations( + Array.isArray(uiConfig.disabledIntegrations) + ? uiConfig.disabledIntegrations + : [], + ); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error fetching UI config:', err); + setDisabledIntegrations([]); } - setDisabledIntegrations(uiConfig.disabledIntegrations ?? []); }; fetchUIConfig(); }, [registerApi]); From bb2d9002ed7f407b922dcbc427dd01e22a16c31f Mon Sep 17 00:00:00 2001 From: MikelAlejoBR Date: Mon, 20 Apr 2026 15:00:39 -0400 Subject: [PATCH 3/3] fix: linting errors SANDBOX-1769 --- .../src/components/SandboxCatalog/SandboxCatalogGrid.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx index 1ab5c0c..b14f469 100644 --- a/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx +++ b/plugins/sandbox/src/components/SandboxCatalog/SandboxCatalogGrid.tsx @@ -25,10 +25,7 @@ import { useSandboxContext } from '../../hooks/useSandboxContext'; export const SandboxCatalogGrid: React.FC = () => { const { disabledIntegrations } = useSandboxContext(); const enabledProducts = useMemo( - () => - productData.filter( - p => !(disabledIntegrations ?? []).includes(p.id), - ), + () => productData.filter(p => !(disabledIntegrations ?? []).includes(p.id)), [disabledIntegrations], ); const { greenCorners, setGreenCorners } = useGreenCorners(enabledProducts);