diff --git a/src/features/applicationMetadata/types.ts b/src/features/applicationMetadata/types.ts index 58c8d84f5a..a3a6e80b12 100644 --- a/src/features/applicationMetadata/types.ts +++ b/src/features/applicationMetadata/types.ts @@ -52,4 +52,5 @@ export interface IPartyTypesAllowed { export interface IBackendFeaturesState { jsonObjectInDataResponse: boolean; // Extended attachment validation + addInstanceIdentifierToLayoutRequests: boolean; // Add instance identifier to layout requests } diff --git a/src/features/form/layout/LayoutsContext.tsx b/src/features/form/layout/LayoutsContext.tsx index 5af44595dd..45646e6945 100644 --- a/src/features/form/layout/LayoutsContext.tsx +++ b/src/features/form/layout/LayoutsContext.tsx @@ -6,15 +6,17 @@ import { useAppQueries } from 'src/core/contexts/AppQueriesProvider'; import { ContextNotProvided } from 'src/core/contexts/context'; import { delayedContext } from 'src/core/contexts/delayedContext'; import { createQueryContext } from 'src/core/contexts/queryContext'; +import { useApplicationMetadata } from 'src/features/applicationMetadata/ApplicationMetadataProvider'; import { useCurrentDataModelName } from 'src/features/datamodel/useBindingSchema'; import { cleanLayout } from 'src/features/form/layout/cleanLayout'; import { makeLayoutLookups } from 'src/features/form/layout/makeLayoutLookups'; import { applyLayoutQuirks } from 'src/features/form/layout/quirks'; import { useLayoutSets } from 'src/features/form/layoutSets/LayoutSetsProvider'; import { useLayoutSetIdFromUrl } from 'src/features/form/layoutSets/useCurrentLayoutSet'; -import { useInstanceDataQuery } from 'src/features/instance/InstanceContext'; +import { useInstanceDataQuery, useLaxInstanceId } from 'src/features/instance/InstanceContext'; import { useProcessQuery } from 'src/features/instance/useProcessQuery'; import { makeLikertChildId } from 'src/layout/Likert/Generator/makeLikertChildId'; +import { fetchLayoutsForInstance } from 'src/queries/queries'; import type { QueryDefinition } from 'src/core/queries/usePrefetchQuery'; import type { CompExternal, ILayoutCollection, ILayouts } from 'src/layout/layout'; import type { IExpandedWidthLayouts, IHiddenLayoutsExternal } from 'src/types'; @@ -32,10 +34,20 @@ export function useLayoutQueryDef( layoutSetId?: string, ): QueryDefinition { const { fetchLayouts } = useAppQueries(); + const instanceId = useLaxInstanceId(); + const features = useApplicationMetadata().features ?? {}; + return { queryKey: ['formLayouts', layoutSetId, enabled], queryFn: layoutSetId - ? () => fetchLayouts(layoutSetId).then((layouts) => processLayouts(layouts, layoutSetId, defaultDataModelType)) + ? async () => { + const shouldUseInstanceEndpoint = features.addInstanceIdentifierToLayoutRequests && instanceId; + const layouts = shouldUseInstanceEndpoint + ? await fetchLayoutsForInstance(layoutSetId, instanceId) + : await fetchLayouts(layoutSetId); + + return processLayouts(layouts, layoutSetId, defaultDataModelType); + } : skipToken, enabled: enabled && !!layoutSetId, }; diff --git a/src/queries/queries.ts b/src/queries/queries.ts index d847a751ba..8198a10785 100644 --- a/src/queries/queries.ts +++ b/src/queries/queries.ts @@ -27,6 +27,7 @@ import { getFileUploadUrl, getFileUploadUrlOld, getFooterLayoutUrl, + getInstanceLayoutsUrl, getInstantiateUrl, getJsonSchemaUrl, getLayoutSetsUrl, @@ -292,6 +293,9 @@ export const fetchLayoutSets = (): Promise => httpGet(getLayoutSets export const fetchLayouts = (layoutSetId: string): Promise => httpGet(getLayoutsUrl(layoutSetId)); +export const fetchLayoutsForInstance = (layoutSetId: string, instanceId: string): Promise => + httpGet(getInstanceLayoutsUrl(layoutSetId, instanceId)); + export const fetchLayoutSettings = (layoutSetId: string): Promise => httpGet(getLayoutSettingsUrl(layoutSetId)); diff --git a/src/test/renderWithProviders.tsx b/src/test/renderWithProviders.tsx index 9ca88f1e28..39664bb8a1 100644 --- a/src/test/renderWithProviders.tsx +++ b/src/test/renderWithProviders.tsx @@ -152,6 +152,7 @@ const defaultQueryMocks: AppQueries = { fetchPostPlace: async () => ({ valid: true, result: 'OSLO' }), fetchLayoutSettings: async () => ({ pages: { order: [] } }), fetchLayouts: () => Promise.reject(new Error('fetchLayouts not mocked')), + fetchLayoutsForInstance: () => Promise.reject(new Error('fetchLayoutsForInstance not mocked')), fetchBackendValidations: async () => [], fetchBackendValidationsForDataElement: async () => [], fetchPaymentInformation: async () => paymentResponsePayload, diff --git a/src/utils/urls/appUrlHelper.test.ts b/src/utils/urls/appUrlHelper.test.ts index 14abe83565..9d7f9de492 100644 --- a/src/utils/urls/appUrlHelper.test.ts +++ b/src/utils/urls/appUrlHelper.test.ts @@ -5,6 +5,7 @@ import { getEnvironmentLoginUrl, getFetchFormDynamicsUrl, getHostname, + getInstanceLayoutsUrl, getInstantiateUrl, getLayoutSettingsUrl, getLayoutsUrl, @@ -379,6 +380,13 @@ describe('Frontend urlHelper.ts', () => { }); }); + describe('getInstanceLayoutsUrl', () => { + it('should include instance ID in layout URL when provided', () => { + const result = getInstanceLayoutsUrl('custom-layout.json', 'instanceId-1234'); + expect(result).toBe('https://local.altinn.cloud/ttd/test/instances/instanceId-1234/layouts/custom-layout.json'); + }); + }); + describe('getLayoutSettingsUrl', () => { it('should return layout as passed argument', () => { const result = getLayoutSettingsUrl('custom-layout.json'); diff --git a/src/utils/urls/appUrlHelper.ts b/src/utils/urls/appUrlHelper.ts index 279d749ff5..66d642d028 100644 --- a/src/utils/urls/appUrlHelper.ts +++ b/src/utils/urls/appUrlHelper.ts @@ -186,6 +186,8 @@ export const getLayoutSetsUrl = () => `${appPath}/api/layoutsets`; export const getFooterLayoutUrl = () => `${appPath}/api/v1/footer`; export const getFetchFormDynamicsUrl = (layoutSetId: string) => `${appPath}/api/ruleconfiguration/${layoutSetId}`; export const getLayoutsUrl = (layoutSetId: string) => `${appPath}/api/layouts/${layoutSetId}`; +export const getInstanceLayoutsUrl = (layoutSetId: string, instanceId: string) => + `${appPath}/instances/${instanceId}/layouts/${layoutSetId}`; export const getRulehandlerUrl = (layoutSet: string) => `${appPath}/api/rulehandler/${layoutSet}`; export const getActiveInstancesUrl = (partyId: number) => `${appPath}/instances/${partyId}/active`; export const getInstanceUiUrl = (instanceId: string) => `${appPath}#/instance/${instanceId}`;