From 5939401ba5e55d3ce20808170caaf178a7f25399 Mon Sep 17 00:00:00 2001 From: Jared Jolton Date: Thu, 21 May 2026 09:37:15 -0600 Subject: [PATCH 1/2] fix(deep-clone): remove org-scoped app installations query from useInstallationParameters [EXT-7458] The hook was calling appInstallation.getForOrganization() on every sidebar mount to fetch ALL installations across the org, just to find the current space's parameters. In large orgs this triggers O(N-spaces) fan-out to CIA, contributing to the INC-1276 cascading failure. sdk.parameters.installation already provides the parameters synchronously via the App SDK. Co-Authored-By: Claude Opus 4.6 --- .../src/utils/useInstallationParameters.ts | 41 +------------ apps/deep-clone/test/mocks/mockCma.ts | 9 --- .../utils/useInstallationParameters.spec.ts | 60 +------------------ 3 files changed, 5 insertions(+), 105 deletions(-) diff --git a/apps/deep-clone/src/utils/useInstallationParameters.ts b/apps/deep-clone/src/utils/useInstallationParameters.ts index 0a8a4533f5..0c33037075 100644 --- a/apps/deep-clone/src/utils/useInstallationParameters.ts +++ b/apps/deep-clone/src/utils/useInstallationParameters.ts @@ -1,43 +1,6 @@ -import { useEffect, useState } from 'react'; import { BaseAppSDK } from '@contentful/app-sdk'; -import { AppInstallationProps, KeyValueMap } from 'contentful-management'; - -export const useInstallationParameters = (sdk: BaseAppSDK) => { - const [parameters, setParameters] = useState(sdk.parameters.installation); - - useEffect(() => { - const fetch = async () => { - const newParameters = await fetchParameters(sdk); - setParameters(newParameters); - }; - fetch(); - }, [sdk]); - - return parameters; -}; - -const fetchParameters = async (sdk: BaseAppSDK): Promise => { - try { - if (!sdk.ids.organization || !sdk.ids.app || !sdk.ids.space) { - throw new Error('Required SDK IDs not available'); - } - - const appInstallation = await sdk.cma.appInstallation.getForOrganization({ - appDefinitionId: sdk.ids.app, - organizationId: sdk.ids.organization, - }); - - const currentInstallation = appInstallation.items.find( - (installation: AppInstallationProps) => installation.sys.space.sys.id === sdk.ids.space - ); - - if (currentInstallation?.parameters) { - return currentInstallation.parameters as KeyValueMap; - } - return sdk.parameters.installation; - } catch (error) { - console.warn('Failed to fetch fresh parameters from CMA:', error); - } +import { KeyValueMap } from 'contentful-management'; +export const useInstallationParameters = (sdk: BaseAppSDK): KeyValueMap => { return sdk.parameters.installation; }; diff --git a/apps/deep-clone/test/mocks/mockCma.ts b/apps/deep-clone/test/mocks/mockCma.ts index bf44451d77..cc57e66a53 100644 --- a/apps/deep-clone/test/mocks/mockCma.ts +++ b/apps/deep-clone/test/mocks/mockCma.ts @@ -10,15 +10,6 @@ const mockCma = { create: vi.fn(), update: vi.fn(), }, - appInstallation: { - getForOrganization: vi.fn().mockReturnValue({ - items: [ - { - sys: { space: { sys: { id: 'test-space' } } }, - }, - ], - }), - }, }; export { mockCma }; diff --git a/apps/deep-clone/test/utils/useInstallationParameters.spec.ts b/apps/deep-clone/test/utils/useInstallationParameters.spec.ts index 9375534f0e..2feef5c157 100644 --- a/apps/deep-clone/test/utils/useInstallationParameters.spec.ts +++ b/apps/deep-clone/test/utils/useInstallationParameters.spec.ts @@ -1,31 +1,11 @@ -import { renderHook, waitFor } from '@testing-library/react'; -import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { renderHook } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; import { useInstallationParameters } from '../../src/utils/useInstallationParameters'; import { mockSdk } from '../mocks/mockSdk'; -import { mockCma } from '../mocks/mockCma'; import { BaseAppSDK } from '@contentful/app-sdk'; describe('useInstallationParameters', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return parameters from appInstallation.getForOrganization when successful', async () => { - const mockAppInstallationResponse = { - items: [ - { - sys: { space: { sys: { id: 'test-space' } } }, - parameters: { - cloneText: 'Custom Copy', - cloneTextBefore: false, - automaticRedirect: false, - }, - }, - ], - }; - - mockCma.appInstallation.getForOrganization.mockResolvedValue(mockAppInstallationResponse); - + it('should return sdk.parameters.installation directly', () => { const { result } = renderHook(() => useInstallationParameters(mockSdk as unknown as BaseAppSDK) ); @@ -35,39 +15,5 @@ describe('useInstallationParameters', () => { cloneTextBefore: true, automaticRedirect: true, }); - - await waitFor(() => { - expect(result.current).toEqual({ - cloneText: 'Custom Copy', - cloneTextBefore: false, - automaticRedirect: false, - }); - }); - - expect(mockCma.appInstallation.getForOrganization).toHaveBeenCalledWith({ - appDefinitionId: 'test-app', - organizationId: 'test-organization', - }); - }); - - it('should return sdk.parameters.installation when appInstallation.getForOrganization throws an error', async () => { - mockCma.appInstallation.getForOrganization.mockRejectedValue(new Error('Network error')); - - const { result } = renderHook(() => - useInstallationParameters(mockSdk as unknown as BaseAppSDK) - ); - - await waitFor(() => { - expect(result.current).toEqual({ - cloneText: 'Copy', - cloneTextBefore: true, - automaticRedirect: true, - }); - }); - - expect(mockCma.appInstallation.getForOrganization).toHaveBeenCalledWith({ - appDefinitionId: 'test-app', - organizationId: 'test-organization', - }); }); }); From 87e34319981d6e1b72481661f1eb91138f9f80e6 Mon Sep 17 00:00:00 2001 From: Jared Jolton Date: Thu, 21 May 2026 10:11:47 -0600 Subject: [PATCH 2/2] fix(deep-clone): add defaults fallback for empty installation parameters [EXT-7458] When installation parameters are not yet saved (e.g., fresh install before config screen is submitted), sdk.parameters.installation is empty. Fall back to sensible defaults matching the ConfigScreen initial state. Co-Authored-By: Claude Opus 4.6 --- .../src/utils/useInstallationParameters.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/deep-clone/src/utils/useInstallationParameters.ts b/apps/deep-clone/src/utils/useInstallationParameters.ts index 0c33037075..dda4ebcc02 100644 --- a/apps/deep-clone/src/utils/useInstallationParameters.ts +++ b/apps/deep-clone/src/utils/useInstallationParameters.ts @@ -1,6 +1,16 @@ import { BaseAppSDK } from '@contentful/app-sdk'; import { KeyValueMap } from 'contentful-management'; +const DEFAULTS: KeyValueMap = { + cloneText: 'Copy', + cloneTextBefore: true, + automaticRedirect: true, +}; + export const useInstallationParameters = (sdk: BaseAppSDK): KeyValueMap => { - return sdk.parameters.installation; + const params = sdk.parameters.installation; + if (!params || Object.keys(params).length === 0) { + return DEFAULTS; + } + return params; };