diff --git a/src/features/expressions/expression-functions.ts b/src/features/expressions/expression-functions.ts index 997724f7fd..f703eca2f5 100644 --- a/src/features/expressions/expression-functions.ts +++ b/src/features/expressions/expression-functions.ts @@ -390,6 +390,7 @@ export const ExprFunctionImplementations: { [K in ExprFunctionName]: Implementat appId: true, instanceOwnerPartyId: true, instanceOwnerPartyType: true, + instanceOwnerName: true, }; if (key === null || instanceDataSourcesKeys[key] !== true) { diff --git a/src/features/expressions/shared-tests/functions/instanceContext/instanceOwnerNameOrg.json b/src/features/expressions/shared-tests/functions/instanceContext/instanceOwnerNameOrg.json new file mode 100644 index 0000000000..03b6c84904 --- /dev/null +++ b/src/features/expressions/shared-tests/functions/instanceContext/instanceOwnerNameOrg.json @@ -0,0 +1,18 @@ +{ + "name": "Instance owner name when instance owner is Person", + "expression": [ + "instanceContext", + "instanceOwnerName" + ], + "expects": "My Org AS", + "instance": { + "id": "d00ce51c-800b-416a-a906-ccab55f597e9", + "appId": "org/app-name", + "instanceOwner": { + "organisationNumber": "1234567", + "party": { + "name": "My Org AS" + } + } + } +} diff --git a/src/features/expressions/shared-tests/functions/instanceContext/instanseOwnerNamePerson.json b/src/features/expressions/shared-tests/functions/instanceContext/instanseOwnerNamePerson.json new file mode 100644 index 0000000000..8d136f8ba0 --- /dev/null +++ b/src/features/expressions/shared-tests/functions/instanceContext/instanseOwnerNamePerson.json @@ -0,0 +1,17 @@ +{ + "name": "Instance owner name when instance owner is Person", + "expression": [ + "instanceContext", + "instanceOwnerName" + ], + "expects": "Firstname Lastname", + "instance": { + "id": "d00ce51c-800b-416a-a906-ccab55f597e9", + "appId": "org/app-name", + "instanceOwner": { + "party": { + "name": "Firstname Lastname" + } + } + } +} diff --git a/src/features/instance/InstanceContext.tsx b/src/features/instance/InstanceContext.tsx index 298a94fc48..9f42faad98 100644 --- a/src/features/instance/InstanceContext.tsx +++ b/src/features/instance/InstanceContext.tsx @@ -14,6 +14,7 @@ import { Loader } from 'src/core/loading/Loader'; import { cleanUpInstanceData } from 'src/features/instance/instanceUtils'; import { ProcessProvider } from 'src/features/instance/ProcessContext'; import { useInstantiation } from 'src/features/instantiate/InstantiationContext'; +import { useInstanceOwnerParty } from 'src/features/party/PartiesProvider'; import { useNavigationParam } from 'src/features/routing/AppRoutingContext'; import { buildInstanceDataSources } from 'src/utils/instanceDataSources'; import type { QueryDefinition } from 'src/core/queries/usePrefetchQuery'; @@ -22,7 +23,6 @@ import type { IData, IInstance, IInstanceDataSources } from 'src/types/shared'; export interface InstanceContext { // Data data: IInstance | undefined; - dataSources: IInstanceDataSources | null; // Methods/utilities appendDataElements: (element: IData[]) => void; @@ -51,14 +51,13 @@ const { initialCreateStore: () => createStore<InstanceContext>((set) => ({ data: undefined, - dataSources: null, appendDataElements: (elements) => set((state) => { if (!state.data) { throw new Error('Cannot append data element when instance data is not set'); } const next = { ...state.data, data: [...state.data.data, ...elements] }; - return { ...state, data: next, dataSources: buildInstanceDataSources(next) }; + return { ...state, data: next }; }), mutateDataElement: (elementId, mutator) => set((state) => { @@ -69,7 +68,7 @@ const { ...state.data, data: state.data.data.map((element) => (element.id === elementId ? mutator(element) : element)), }; - return { ...state, data: next, dataSources: buildInstanceDataSources(next) }; + return { ...state, data: next }; }), removeDataElement: (elementId) => set((state) => { @@ -77,14 +76,14 @@ const { throw new Error('Cannot remove data element when instance data is not set'); } const next = { ...state.data, data: state.data.data.filter((element) => element.id !== elementId) }; - return { ...state, data: next, dataSources: buildInstanceDataSources(next) }; + return { ...state, data: next }; }), changeData: (callback) => set((state) => { const next = callback(state.data); const clean = cleanUpInstanceData(next); if (clean && !deepEqual(state.data, clean)) { - return { ...state, data: next, dataSources: buildInstanceDataSources(next) }; + return { ...state, data: next }; } return {}; }), @@ -95,13 +94,21 @@ const { set({ reFetch: async () => { const result = await reFetch(); - set((state) => ({ ...state, data: result.data, dataSources: buildInstanceDataSources(result.data) })); + set((state) => ({ ...state, data: result.data })); return result; }, }), })), }); +export const instanceQueryKeys = { + instanceData: (instanceOwnerPartyId: string | undefined, instanceGuid: string | undefined) => [ + 'instanceData', + instanceOwnerPartyId, + instanceGuid, + ], +}; + // Also used for prefetching @see appPrefetcher.ts export function useInstanceDataQueryDef( hasResultFromInstantiation: boolean, @@ -110,7 +117,7 @@ export function useInstanceDataQueryDef( ): QueryDefinition<IInstance> { const { fetchInstanceData } = useAppQueries(); return { - queryKey: ['fetchInstanceData', partyId, instanceGuid], + queryKey: instanceQueryKeys.instanceData(partyId, instanceGuid), queryFn: partyId && instanceGuid ? () => fetchInstanceData(partyId, instanceGuid) : skipToken, enabled: !!partyId && !!instanceGuid && !hasResultFromInstantiation, }; @@ -206,10 +213,16 @@ export const useLaxInstanceStatus = () => useLaxInstance((state) => state.data?. export const useLaxAppendDataElements = () => useLaxInstance((state) => state.appendDataElements); export const useLaxMutateDataElement = () => useLaxInstance((state) => state.mutateDataElement); export const useLaxRemoveDataElement = () => useLaxInstance((state) => state.removeDataElement); -export const useLaxInstanceDataSources = () => useLaxInstance((state) => state.dataSources) ?? null; export const useLaxChangeInstance = (): ChangeInstanceData | undefined => useLaxInstance((state) => state.changeData); export const useHasInstance = () => useHasProvider(); +export function useLaxInstanceDataSources(): IInstanceDataSources | null { + const instance = useLaxInstanceData((i) => i); + const instanceOwnerParty = useInstanceOwnerParty(); + + return buildInstanceDataSources(instance, instanceOwnerParty); +} + /** Beware that in later versions, this will re-render your component after every save, as * the backend sends us updated instance data */ export const useLaxInstanceDataElements = (dataType: string | undefined) => diff --git a/src/features/party/PartiesProvider.tsx b/src/features/party/PartiesProvider.tsx index 9aa67919d8..3d10f1bc8e 100644 --- a/src/features/party/PartiesProvider.tsx +++ b/src/features/party/PartiesProvider.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router'; import type { PropsWithChildren } from 'react'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useAppMutations, useAppQueries } from 'src/core/contexts/AppQueriesProvider'; import { createContext } from 'src/core/contexts/context'; @@ -9,10 +10,10 @@ import { delayedContext } from 'src/core/contexts/delayedContext'; import { createQueryContext } from 'src/core/contexts/queryContext'; import { DisplayError } from 'src/core/errorHandling/DisplayError'; import { Loader } from 'src/core/loading/Loader'; -import { useLaxInstanceData } from 'src/features/instance/InstanceContext'; +import { instanceQueryKeys } from 'src/features/instance/InstanceContext'; import { NoValidPartiesError } from 'src/features/instantiate/containers/NoValidPartiesError'; import { useShouldFetchProfile } from 'src/features/profile/ProfileProvider'; -import type { IParty } from 'src/types/shared'; +import type { IInstance, IParty } from 'src/types/shared'; import type { HttpClientError } from 'src/utils/network/sharedNetworking'; const partyQueryKeys = { @@ -197,7 +198,11 @@ export const useSetHasSelectedParty = () => useCurrentPartyCtx().setUserHasSelec export function useInstanceOwnerParty(): IParty | null { const parties = usePartiesAllowedToInstantiate() ?? []; - const instanceOwner = useLaxInstanceData((i) => i.instanceOwner); + const queryClient = useQueryClient(); + const { instanceOwnerPartyId, instanceGuid } = useParams(); + const instanceOwner = queryClient.getQueryData<IInstance>( + instanceQueryKeys.instanceData(instanceOwnerPartyId, instanceGuid), + )?.instanceOwner; if (!instanceOwner) { return null; diff --git a/src/types/shared.ts b/src/types/shared.ts index 49c7ced3aa..dd63f6ce80 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -293,6 +293,7 @@ export interface IInstanceDataSources { appId: string; instanceOwnerPartyId: string; instanceOwnerPartyType: InstanceOwnerPartyType; + instanceOwnerName?: string; } export type IActionType = 'instantiate' | 'confirm' | 'sign' | 'reject'; diff --git a/src/utils/instanceDataSources.test.ts b/src/utils/instanceDataSources.test.ts index ad35179c43..84680b0cc8 100644 --- a/src/utils/instanceDataSources.test.ts +++ b/src/utils/instanceDataSources.test.ts @@ -11,6 +11,9 @@ describe('instanceDataSources/instanceContext', () => { appId, instanceOwner: { partyId, + party: { + name: 'Firstname Lastname', + }, }, } as IInstance; @@ -19,6 +22,35 @@ describe('instanceDataSources/instanceContext', () => { instanceId: instaceId, instanceOwnerPartyId: partyId, instanceOwnerPartyType: 'unknown', + instanceOwnerName: 'Firstname Lastname', + }; + const actual = buildInstanceDataSources(mockInstance); + + expect(actual).toEqual(expected); + }); + + it('should build a valid instance context with organisation', () => { + const partyId = '1337'; + const appId = 'tdd/enapp'; + const instaceId = `${partyId}/super-secret-uuid-000`; + const mockInstance: IInstance = { + id: instaceId, + appId, + instanceOwner: { + partyId, + organisationNumber: '123456789', + party: { + name: 'My Organisation AS', + }, + }, + } as IInstance; + + const expected: IInstanceDataSources = { + appId, + instanceId: instaceId, + instanceOwnerPartyId: partyId, + instanceOwnerPartyType: 'org', + instanceOwnerName: 'My Organisation AS', }; const actual = buildInstanceDataSources(mockInstance); diff --git a/src/utils/instanceDataSources.ts b/src/utils/instanceDataSources.ts index 320f2469b9..3098f92b02 100644 --- a/src/utils/instanceDataSources.ts +++ b/src/utils/instanceDataSources.ts @@ -1,7 +1,10 @@ -import type { IInstance, IInstanceDataSources } from 'src/types/shared'; +import type { IInstance, IInstanceDataSources, IParty } from 'src/types/shared'; -export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null { - if (!instance || !instance.instanceOwner) { +export function buildInstanceDataSources( + instance: IInstance | null | undefined, + instanceOwnerParty: IParty | null | undefined = instance?.instanceOwner?.party, +): IInstanceDataSources | null { + if (!instance?.instanceOwner) { return null; } const instanceOwnerPartyType = instance.instanceOwner.organisationNumber @@ -17,5 +20,6 @@ export function buildInstanceDataSources(instance?: IInstance | null | undefined instanceId: instance.id, instanceOwnerPartyId: instance.instanceOwner?.partyId, instanceOwnerPartyType, + instanceOwnerName: instanceOwnerParty?.name, }; }