Repository: Altinn/app-frontend-react
Pull request #3232: add expression support for organisation instance owner name
Author: login: HauklandJ
Labels: name: kind/product-feature
description: Pull requests containing new features
name: backport-ignore
description: This PR is a new feature and should not be cherry-picked onto release branches
Created: 2025-04-03T12:59:01Z | Merged: 2025-04-08T10:27:39Z

## Description

<!---
  Provide a general summary of your changes in the title above.
  Describe your change(s) in detail here.
  Remember that the title and description should include a non-technical summary readable
  for service owners browsing our release notes.
-->

## Related Issue(s)

- closes #{issue number}

## Verification/QA

- Manual functionality testing
  - [x] I have tested these changes manually
  - [ ] Creator of the original issue (or service owner) has been contacted for manual testing (or will be contacted when released in alpha)
  - [ ] No testing done/necessary
- Automated tests
  - [x] Unit test(s) have been added/updated
  - [ ] Cypress E2E test(s) have been added/updated
  - [ ] No automatic tests are needed here (no functional changes/additions)
  - [ ] I want someone to help me make some tests
- UU/WCAG ([follow these guidelines](https://aksel.nav.no/god-praksis/artikler/utvikling?tema=universell-utforming) until we have our own)
  - [ ] I have tested with a screen reader/keyboard navigation/automated wcag validator
  - [x] No testing done/necessary (no DOM/visual changes)
  - [ ] I want someone to help me perform accessibility testing
- User documentation @ [altinn-studio-docs](https://github.com/Altinn/altinn-studio-docs)
  - [ ] Has been added/updated
  <!--- insert link to PR here -->
  - [ ] No functionality has been changed/added, so no documentation is needed
  - [x] I will do that later/have created an issue
  <!--- insert link to issue here -->
- Support in Altinn Studio
  - [ ] Issue(s) created for support in Studio
  <!--- insert link to issue(s) here -->
  - [x] This change/feature does not require any changes to Altinn Studio
- Sprint board
  - [ ] The original issue (or this PR itself) has been added to the Team Apps project and to the current sprint board
  - [ ] I don't have permissions to do that, please help me out
- Labels
  - [x] I have added a `kind/*` label to this PR for proper release notes grouping
  - [ ] I don't have permissions to add labels, please help me out
  <!---
    Breaking changes:       kind/breaking-change
    New features:               kind/product-feature
    Bug fixes:                      kind/bug
    Dependencies:             kind/dependencies
    Other changes:            kind/other
    Not in release notes:  ignore-for-release
  --->



Diff:
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,
   };
 }


Reviews:
user: login: martinothamar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:02:42Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:03:19Z
user: login: bjosttveit
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:12:04Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:15:01Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:15:56Z
user: login: ivarne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:17:42Z
user: login: ivarne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:23:42Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:41:01Z
user: login: ivarne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:47:29Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T12:44:27Z
user: login: ivarne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T13:02:04Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T13:08:40Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T13:11:19Z
user: login: ivarne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T13:14:01Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T14:22:02Z
user: login: martinothamar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T08:17:31Z
user: login: olemartinorg
body: Nice! :raised_hands: This will also affect text resource variables, so the docs for that should be updated: https://docs.altinn.studio/nb/altinn-studio/reference/ux/texts/#datakilder
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T13:37:26Z
user: login: HauklandJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:21:41Z

Review comments:
diff_hunk: @@ -96,6 +96,7 @@ export interface IInstanceOwner {
   personNumber?: string;
   organisationNumber?: string | null;
   username?: string;
+  party?: IParty | null;
path: src/types/shared.ts
user: login: martinothamar
body: I'm assuming `IParty` here is modelled after the platform register model and not the new response/dto? Some fields will always be null but maybe that's good enough
created_at: 2025-04-03T13:02:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,6 +96,7 @@ export interface IInstanceOwner {
   personNumber?: string;
   organisationNumber?: string | null;
   username?: string;
+  party?: IParty | null;
path: src/types/shared.ts
user: login: HauklandJ
body: correct
created_at: 2025-04-03T13:03:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,10 +12,14 @@ export function buildInstanceDataSources(instance?: IInstance | null | undefined
         ? 'selfIdentified'
         : 'unknown';
 
+  // instanceOwnerName is set to 'unknown' if the instanceOwner is not an organisation
+  const instanceOwnerName = instance.instanceOwner.organisationNumber ? instance.instanceOwner?.party?.name : 'unknown';
+
path: src/utils/instanceDataSources.ts
user: login: bjosttveit
body: Why should it be set to `unknown`?
created_at: 2025-04-04T08:12:04Z
author_association: MEMBER
diff_hunk: @@ -12,10 +12,14 @@ export function buildInstanceDataSources(instance?: IInstance | null | undefined
         ? 'selfIdentified'
         : 'unknown';
 
+  // instanceOwnerName is set to 'unknown' if the instanceOwner is not an organisation
+  const instanceOwnerName = instance.instanceOwner.organisationNumber ? instance.instanceOwner?.party?.name : 'unknown';
+
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: I did not want to exposed more than necessary. The instanceContext expressions are limited "to ensure that information not (yet) exposed via a key here is not attempted to be retrieved from the instance."

Could expose it or have a different placeholder
created_at: 2025-04-04T08:15:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,10 +12,14 @@ export function buildInstanceDataSources(instance?: IInstance | null | undefined
         ? 'selfIdentified'
         : 'unknown';
 
+  // instanceOwnerName is set to 'unknown' if the instanceOwner is not an organisation
+  const instanceOwnerName = instance.instanceOwner.organisationNumber ? instance.instanceOwner?.party?.name : 'unknown';
+
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: Open to change it, if you don't see any issues with exposing names for person instance owners
created_at: 2025-04-04T08:15:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,10 +12,14 @@ export function buildInstanceDataSources(instance?: IInstance | null | undefined
         ? 'selfIdentified'
         : 'unknown';
 
+  // instanceOwnerName is set to 'unknown' if the instanceOwner is not an organisation
+  const instanceOwnerName = instance.instanceOwner.organisationNumber ? instance.instanceOwner?.party?.name : 'unknown';
+
path: src/utils/instanceDataSources.ts
user: login: ivarne
body: As long as backend exposes it in the API, I don't really see the reason for this logic.

created_at: 2025-04-04T08:17:42Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: ivarne
body: The instance returned from PATCH, actions, process/next ... does not contain the instance owner party. Should we do something to ensure that an instance from the correct API is used?

PS: when we add `["instanceContext", "dataElementCount", dataType]`, or something similar, we need to use the most recent instance to resolve.

created_at: 2025-04-04T08:23:30Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: We probably do not want to extend all the endpoints to include the instance owner party. 
For the instance owner party we do not need the most recent instance as it is not goingto change. 
Hmm.. I do not immediately see a solution that I like
created_at: 2025-04-04T08:41:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: ivarne
body: Me neither. My preference would be a new `org/app/instances/instanceOwnerPartyId/instanceGuid/party` endpoint instead of partially modifying the instance endpoint and having to deal with multiple instance document layouts, but that would mean an extra request until v9 where we will likely create a new frontend root object that contains all the basic info you need to draw an instance.


created_at: 2025-04-04T08:47:28Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: > The instance returned from PATCH, actions, process/next ... does not contain the instance owner party. 

Looking at the [instaceContext store](https://github.com/Altinn/app-frontend-react/blob/main/src/features/instance/InstanceContext.tsx) I thought so too, seeing how `mutateDataElement`/`changeData` etc calls `buildInstanceDataSources` with `next`. 

However, I set up an example app with `resource.nb.json`:

```
    {
      "id": "singing.summary.title.override",
      "value": "Disse har signert på vegne av {0}",
      "variables": [
        {
          "key": "instanceOwnerName",
          "dataSource": "instanceContext"
        }
      ]
    }
```
Which is used to override the title of a summary2 component in a custom pdf-layout `pdf.json`:

```
{
  "$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json",
  "data": {
    "layout": [
      {
        "id": "signeeListSummary",
        "type": "Summary2",
        "target": {
          "type": "component",
          "id": "signee-list"
        },
        "overrides": [
          {
            "componentId": "signee-list",
            "title": "singing.summary.title.override"
          }
        ]
      }
    ]
  }
}
```

Which is used as `pdfLayoutName` in `Settings.json`:

```
{
  "$schema": "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json",
  "pages": {
    "order": ["Side1"],
    "pdfLayoutName": "pdf"
  }
}
```

And this results in the instance owner name being used in the title.. (ignore timestamp)
![image](https://github.com/user-attachments/assets/3b2659c6-e35c-4e00-92ed-524b5ec648c0)

There are plenty of PATCHes and a process next between the instance creation and the pdf generation, so why would this work if the hypothesis is correct?
created_at: 2025-04-04T12:44:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: ivarne
body: I'm not sure I have an hypothesis, or what it might be. I just pointed out that we currently have two different instance documents around, and your example seems to indicate that we currently use the right one (meaning we don't update), but it will be very confusing when we start adding more context keys that require us to use the latest instance document.

created_at: 2025-04-04T13:02:03Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: [The only available keys](https://docs.altinn.studio/altinn-studio/reference/logic/expressions/#lookup-components-and-data) for `instanceContext` at the moment are `instanceId`, `instanceOwnerPartyId`, `instanceOwnerPartyType` and `appId`. None of these require an update, so that makes sense, right. If we need keys for the latest instance document we would have to rethink the approach.
created_at: 2025-04-04T13:08:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: This PR does not change that fact
created_at: 2025-04-04T13:11:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: ivarne
body: Yes, there is no bug yet. The question is if there is something easy we can do to prevent this from happening.
created_at: 2025-04-04T13:14:01Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: HauklandJ
body: I am open to suggestions
created_at: 2025-04-04T14:22:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,7 @@
 import type { IInstance, IInstanceDataSources } from 'src/types/shared';
 
 export function buildInstanceDataSources(instance?: IInstance | null | undefined): IInstanceDataSources | null {
path: src/utils/instanceDataSources.ts
user: login: martinothamar
body: Made an issue for it: https://github.com/Altinn/app-lib-dotnet/issues/1236
I'm open to any solution. We could also update the frontend to have two instance models to match the new OpenAPI spec. If we were to move to OpenAPI spec driven codegen for models then this would probably not be an issue, and we would just realize that we needed to keep instance owner information separate, which IMO makes sense any way since it never changes
created_at: 2025-04-07T08:17:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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);
+}
path: src/features/instance/InstanceContext.tsx
user: login: olemartinorg
body: This seems to select everything, and then reduce it later. Doing it that way will cause every component using this to re-render whenever instance data changes (and it changes every time we do a PATCH request to save form data). Building the data sources and then memoizing that result will cause fewer re-renders:

```suggestion
export function useLaxInstanceDataSources(): IInstanceDataSources | null {
  const instanceOwnerParty = useInstanceOwnerParty();
  return useLaxInstanceData((data) => buildInstanceDataSources(data, instanceOwnerParty));
}
```
created_at: 2025-04-08T13:35:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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);
+}
path: src/features/instance/InstanceContext.tsx
user: login: HauklandJ
body: Nice! Did this on the target branch of this pr: https://github.com/Altinn/app-frontend-react/pull/2871/commits/821cb60574a22891ea633cef77864c89e9443a1c
created_at: 2025-04-08T14:21:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: ivarne
created_at: 2025-04-03T13:52:40Z
body: A (few) shared test(s) https://github.com/Altinn/app-frontend-react/tree/main/src/features/expressions/shared-tests/functions/instanceContext would be nice :)
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-04-08T08:01:14Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Altinn_app-frontend-react&pullRequest=3232) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=Altinn_app-frontend-react&pullRequest=3232&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Altinn_app-frontend-react&pullRequest=3232&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Altinn_app-frontend-react&pullRequest=3232&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [95.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=Altinn_app-frontend-react&pullRequest=3232&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Altinn_app-frontend-react&pullRequest=3232&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Altinn_app-frontend-react&pullRequest=3232)


author_association: NONE