Skip to content

Add remaining Maskinporten consumer functionality - #2211

Merged
sonwit merged 10 commits into
mainfrom
feat/maskinporten-consumer-page
May 19, 2026
Merged

Add remaining Maskinporten consumer functionality#2211
sonwit merged 10 commits into
mainfrom
feat/maskinporten-consumer-page

Conversation

@sonwit

@sonwit sonwit commented May 13, 2026

Copy link
Copy Markdown
Contributor

Legger til en Consumer-side for Maskinporten og rydder opp i BFF-API-et slik at consumer/supplier-kontekst er tydelig adskilt.

  • Ny side for "Consumer" - viser delegerte ressurser per consumer med mulighet til å fjerne.

  • Felles DelegatedResourcesSection brukes av både Supplier og Consumer.

  • Endret BFF edepunkter til suppliers/... og consumers/...

  • Nye consumer-endepunkter: hent delegerte ressurser, fjern ressurs.

Description

Related Issue(s)

Altinn/altinn-authorization-tmp#3117

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • New Features

    • New Maskinporten consumer page and route to view/manage delegated resources from both consumer and supplier perspectives
    • Per-resource removal, revoke-only flow, and explicit add/remove actions for supplier and consumer views
  • Refactor

    • Delegation flows split into clear supplier vs consumer paths with updated UI wiring and resource lists
  • Tests

    • Expanded coverage for supplier/consumer operations and error cases
  • Localization

    • Added consumer page titles and "not found" messages (en/nb/nn)

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Splits Maskinporten resource delegation into supplier- and consumer-specific APIs and wiring: backend contracts, HTTP client and mocks, service calls, controller routes and tests, RTK Query endpoints/tags, and frontend hooks/components/pages, plus routing and localization changes.

Changes

Maskinporten Supplier/Consumer Delegation Refactor

Layer / File(s) Summary
Backend interface contracts
backend/.../IMaskinportenClient.cs, backend/.../IMaskinportenService.cs
Rename generic AddResource/GetResources/RemoveResource to AddSupplierResource/GetSupplierResources/RemoveSupplierResource; add GetConsumerResources and RemoveConsumerResource; extend GetConsumers with optional consumer filter and update XML docs.
HTTP client & mock implementation
backend/.../MaskinportenClient.cs, backend/.../MaskinportenClientMock.cs
Implement supplier-scoped suppliers/resources endpoints and new consumer-scoped consumers/resources endpoints; conditional consumer query param; update logging and mock behaviors to match new signatures.
Service layer
backend/.../MaskinportenService.cs
Add supplier- and consumer-specific service methods delegating to client endpoints and resolving ResourceDelegation via language-scoped resource lookups; forward consumer filter in GetConsumers.
Controller endpoints & routes
backend/.../MaskinportenController.cs
Move supplier resource routes to suppliers/resources, rename actions to supplier-specific names, add consumer resource endpoints under consumers/resources, validate inputs, and forward consumer query to service.
Controller tests
backend/.../MaskinportenControllerTest.cs
Add and adapt tests for supplier add/get/remove and consumer get/remove covering success, invalid-party, missing-parameter validation, and exception mapping (HttpStatusException→ProblemDetails, generic→500).
RTK Query endpoints
src/rtk/features/maskinportenApi.ts
Replace generic resource tag with maskinportenSupplierResources and maskinportenConsumerResources; move delegation-check to suppliers/resources/delegationcheck; add supplier and consumer endpoints and expose new hooks; remove old generic hooks.
Frontend hooks & components
src/features/amUI/maskinporten/hooks/*, ScopeInfo.tsx, ScopeSearch.tsx, ScopeSearchControls.tsx, SupplierPageContent.tsx, DelegatedResourcesSection.tsx, DelegationModal/EditModal.tsx
Refactor useMaskinportenResourceActions to accept delegate/remove actions; update components to use useGetMaskinportenSupplierResourcesQuery, supplier add/remove mutations, and inject .unwrap()-based delegate/remove handlers; support revoke-only flows calling consumer remove endpoint; replace inline delegated-resources UI with DelegatedResourcesSection.
Frontend pages, routing & locales
src/features/amUI/maskinporten/ConsumerPage.tsx, ConsumerPageContent.tsx, ConsumersTab.tsx, src/routes/*, src/localizations/*
Add consumer detail page and content, register route maskinporten/consumer/:id, adapt ConsumersTab to link into consumer page, and add consumer localization strings.
Delegation modal wiring
src/features/amUI/common/DelegationModal/EditModal.tsx
Pass availableActions into ScopeInfo for revoke-only / delegate modes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

Supplier and consumer split the chore,
Routes and hooks now ask for more.
Clients, services, controllers sing,
Tests and pages tied in a ring.
🌟 Delegation flows take wing.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: adding a Maskinporten consumer page with associated functionality. It is concise, clear, and directly reflects the main objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/maskinporten-consumer-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the kind/feature-request Used when issue is a new feature or request label May 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/features/amUI/maskinporten/ScopeSearch.tsx (1)

43-52: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty supplier string may bypass skip condition.

The supplier variable (line 32) defaults to an empty string when toParty?.orgNumber is undefined. The skip condition !supplier evaluates to false for "", allowing the query to execute with an empty supplier parameter.

🛡️ Proposed fix
  const { data: delegatedResources } = useGetMaskinportenSupplierResourcesQuery(
    {
      party: fromParty?.partyUuid,
      supplier,
    },
    {
-     skip: !fromParty?.partyUuid || !supplier,
+     skip: !fromParty?.partyUuid || !toParty?.orgNumber,
      refetchOnMountOrArgChange: true,
    },
  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/ScopeSearch.tsx` around lines 43 - 52, The
query is being triggered when supplier is an empty string because the skip test
uses !supplier which is false for ""—update the logic so an empty supplier does
not pass; either make the supplier variable undefined when missing (e.g., set
supplier = toParty?.orgNumber ?? undefined) or change the skip condition for
useGetMaskinportenSupplierResourcesQuery to explicitly check for a non-empty
string (e.g., skip when !supplier || supplier.length === 0); reference: supplier
and useGetMaskinportenSupplierResourcesQuery.
src/features/amUI/maskinporten/ScopeInfo.tsx (1)

59-70: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty supplier string may bypass skip condition.

The supplier variable (line 36) defaults to an empty string when toParty?.orgNumber is undefined. The skip condition checks !supplier, which evaluates to false for an empty string, allowing the query to execute with supplier="". Consider updating the skip condition to !fromParty?.partyUuid || !supplier || !resource.identifier or ensuring supplier is explicitly undefined/null when unavailable.

🛡️ Proposed fix
  const supplier = toParty?.orgNumber ?? '';
  ...
  const { data: delegatedResources, isFetching: isDelegatedResourcesLoading } =
    useGetMaskinportenSupplierResourcesQuery(
      {
        party: fromParty?.partyUuid,
        supplier,
        resource: resource.identifier,
      },
      {
-       skip: !fromParty?.partyUuid || !supplier || !resource.identifier,
+       skip: !fromParty?.partyUuid || !toParty?.orgNumber || !resource.identifier,
        refetchOnMountOrArgChange: true,
      },
    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/ScopeInfo.tsx` around lines 59 - 70, The skip
condition for useGetMaskinportenSupplierResourcesQuery allows execution when
supplier is an empty string; update the logic so the query is skipped when
supplier is missing or empty by either making supplier undefined/null when
unavailable or changing the skip expression to explicitly check emptiness (e.g.,
!fromParty?.partyUuid || supplier === "" || !resource.identifier). Locate the
supplier declaration and the useGetMaskinportenSupplierResourcesQuery call
(including the skip option) and apply the change so the query does not run with
supplier === "".
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/MaskinportenClient.cs (1)

109-115: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate cancellationToken to delete calls.

Both resource-delete methods accept a token but don’t pass it to the HTTP delete call, so request cancellation is not honored upstream.

Suggested fix
-HttpResponseMessage response = await _client.DeleteAsync(token, endpointUrl);
+HttpResponseMessage response = await _client.DeleteAsync(token, endpointUrl, cancellationToken);

Apply in both:

  • RemoveSupplierResource(...)
  • RemoveConsumerResource(...)

Also applies to: 205-211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/MaskinportenClient.cs`
around lines 109 - 115, The DeleteAsync calls in RemoveSupplierResource and
RemoveConsumerResource do not pass the provided CancellationToken, so
cancellations are ignored; update the calls to _client.DeleteAsync to include
the cancellationToken parameter (e.g., _client.DeleteAsync(token, endpointUrl,
cancellationToken)) in both methods (also update the other occurrence around the
205–211 block) so the incoming cancellationToken is propagated to the HTTP
request.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/MaskinportenController.cs (1)

121-125: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

[Required] on non-nullable Guid party is insufficient—add explicit Guid.Empty validation.

These actions accept Guid.Empty when party is omitted because [Required] validates the bound value (which is Guid.Empty by default), not whether the parameter was present in the request. Use [BindRequired] instead, or add a guard check.

Suggested fix
- if (!ModelState.IsValid)
+ if (!ModelState.IsValid || party == Guid.Empty)
 {
     return BadRequest(ModelState);
 }

Apply to: GetSupplierResources, AddSupplierResource, RemoveSupplierResource, GetConsumerResources, RemoveConsumerResource.

Alternatively, replace [Required] with [BindRequired] on all affected party parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/MaskinportenController.cs`
around lines 121 - 125, The action parameters decorated with [Required] accept
Guid.Empty (missing in request) so add explicit validation or use model-binding
to require presence: for GetSupplierResources, AddSupplierResource,
RemoveSupplierResource, GetConsumerResources, RemoveConsumerResource either
replace [Required] with [BindRequired] on the Guid party parameters or keep
[Required] and add a guard at the start of each method that throws
BadRequest/returns 400 when party == Guid.Empty; ensure the same approach is
applied consistently to all listed methods and include a clear error message
indicating the missing or empty party GUID.
🧹 Nitpick comments (2)
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs (2)

390-404: ⚡ Quick win

Add coverage for the new consumer filter on GET /maskinporten/consumers.

This PR adds optional consumer filtering across contracts/client/controller, but there’s no test asserting filtered results when consumer is supplied.

Also applies to: 785-815

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs`
around lines 390 - 404, The test GetConsumers_ReturnsConsumers lacks coverage
for the new optional consumer query filter on GET /maskinporten/consumers; add a
new test (or extend this one) that calls _client.GetAsync with both party and
consumer query params (e.g., ?party={party}&consumer={consumerId}), set up
expected filtered data from the same test JSON (or a new filtered fixture) and
assert HttpStatusCode.OK and that the deserialized List<MaskinportenConnection>
only contains connections matching the supplied consumer; use the existing
helper Util.GetMockData and SetAuthHeader and mirror assertions from
GetConsumers_ReturnsConsumers to validate the filtered behavior.

199-236: ⚡ Quick win

Add missing-party tests for the renamed/new resource endpoints.

Please add cases where party is omitted to lock in expected 400 behavior for supplier/consumer resource endpoints.

Also applies to: 804-815, 865-904

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs`
around lines 199 - 236, Add parallel test cases that assert a 400 when the party
query parameter is omitted for the supplier and consumer resource endpoints;
specifically, in MaskinportenControllerTest add tests similar to
GetSupplierResources_InvalidParty_ReturnsBadRequest,
AddSupplierResource_InvalidParty_ReturnsBadRequest, and
RemoveSupplierResource_InvalidParty_ReturnsBadRequest but call the same
endpoints without the "party=..." query string (e.g., GET
"accessmanagement/api/v1/maskinporten/suppliers/resources", POST
"accessmanagement/api/v1/maskinporten/suppliers/resources?supplier=...&resource=...",
DELETE
"accessmanagement/api/v1/maskinporten/suppliers/resources?supplier=...&resource=..."),
and likewise add corresponding tests for the consumer endpoints referenced
around lines 804-815 and 865-904 to ensure each returns
HttpStatusCode.BadRequest when party is missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/MaskinportenClient.cs`:
- Around line 109-115: The DeleteAsync calls in RemoveSupplierResource and
RemoveConsumerResource do not pass the provided CancellationToken, so
cancellations are ignored; update the calls to _client.DeleteAsync to include
the cancellationToken parameter (e.g., _client.DeleteAsync(token, endpointUrl,
cancellationToken)) in both methods (also update the other occurrence around the
205–211 block) so the incoming cancellationToken is propagated to the HTTP
request.

In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/MaskinportenController.cs`:
- Around line 121-125: The action parameters decorated with [Required] accept
Guid.Empty (missing in request) so add explicit validation or use model-binding
to require presence: for GetSupplierResources, AddSupplierResource,
RemoveSupplierResource, GetConsumerResources, RemoveConsumerResource either
replace [Required] with [BindRequired] on the Guid party parameters or keep
[Required] and add a guard at the start of each method that throws
BadRequest/returns 400 when party == Guid.Empty; ensure the same approach is
applied consistently to all listed methods and include a clear error message
indicating the missing or empty party GUID.

In `@src/features/amUI/maskinporten/ScopeInfo.tsx`:
- Around line 59-70: The skip condition for
useGetMaskinportenSupplierResourcesQuery allows execution when supplier is an
empty string; update the logic so the query is skipped when supplier is missing
or empty by either making supplier undefined/null when unavailable or changing
the skip expression to explicitly check emptiness (e.g., !fromParty?.partyUuid
|| supplier === "" || !resource.identifier). Locate the supplier declaration and
the useGetMaskinportenSupplierResourcesQuery call (including the skip option)
and apply the change so the query does not run with supplier === "".

In `@src/features/amUI/maskinporten/ScopeSearch.tsx`:
- Around line 43-52: The query is being triggered when supplier is an empty
string because the skip test uses !supplier which is false for ""—update the
logic so an empty supplier does not pass; either make the supplier variable
undefined when missing (e.g., set supplier = toParty?.orgNumber ?? undefined) or
change the skip condition for useGetMaskinportenSupplierResourcesQuery to
explicitly check for a non-empty string (e.g., skip when !supplier ||
supplier.length === 0); reference: supplier and
useGetMaskinportenSupplierResourcesQuery.

---

Nitpick comments:
In
`@backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs`:
- Around line 390-404: The test GetConsumers_ReturnsConsumers lacks coverage for
the new optional consumer query filter on GET /maskinporten/consumers; add a new
test (or extend this one) that calls _client.GetAsync with both party and
consumer query params (e.g., ?party={party}&consumer={consumerId}), set up
expected filtered data from the same test JSON (or a new filtered fixture) and
assert HttpStatusCode.OK and that the deserialized List<MaskinportenConnection>
only contains connections matching the supplied consumer; use the existing
helper Util.GetMockData and SetAuthHeader and mirror assertions from
GetConsumers_ReturnsConsumers to validate the filtered behavior.
- Around line 199-236: Add parallel test cases that assert a 400 when the party
query parameter is omitted for the supplier and consumer resource endpoints;
specifically, in MaskinportenControllerTest add tests similar to
GetSupplierResources_InvalidParty_ReturnsBadRequest,
AddSupplierResource_InvalidParty_ReturnsBadRequest, and
RemoveSupplierResource_InvalidParty_ReturnsBadRequest but call the same
endpoints without the "party=..." query string (e.g., GET
"accessmanagement/api/v1/maskinporten/suppliers/resources", POST
"accessmanagement/api/v1/maskinporten/suppliers/resources?supplier=...&resource=...",
DELETE
"accessmanagement/api/v1/maskinporten/suppliers/resources?supplier=...&resource=..."),
and likewise add corresponding tests for the consumer endpoints referenced
around lines 804-815 and 865-904 to ensure each returns
HttpStatusCode.BadRequest when party is missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c72b68b2-8602-4f9b-a981-651bcecc46d6

📥 Commits

Reviewing files that changed from the base of the PR and between a8c0bb5 and 8b46ca8.

📒 Files selected for processing (12)
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/ClientInterfaces/IMaskinportenClient.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/Services/Interfaces/IMaskinportenService.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/Services/MaskinportenService.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/MaskinportenClient.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Mocks/Mocks/MaskinportenClientMock.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs
  • backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/MaskinportenController.cs
  • src/features/amUI/maskinporten/ScopeInfo.tsx
  • src/features/amUI/maskinporten/ScopeSearch.tsx
  • src/features/amUI/maskinporten/SupplierPageContent.tsx
  • src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts
  • src/rtk/features/maskinportenApi.ts

@sonwit
sonwit marked this pull request as ready for review May 13, 2026 13:14
Copilot AI review requested due to automatic review settings May 13, 2026 13:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the Maskinporten “consumer” BFF surface while refactoring existing “supplier” resource delegation endpoints to use explicit supplier/consumer paths, improving clarity and enabling optional filtering.

Changes:

  • Refactors supplier resource delegation endpoints to suppliers/resources (frontend RTK + backend controller/service/client) and updates all call sites/tests accordingly.
  • Adds consumer-oriented endpoints for listing delegated resources and removing a delegated resource via consumers/resources.
  • Extends consumers query support with optional consumer filter parameter across frontend + backend + mocks/tests.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/rtk/features/maskinportenApi.ts Splits resource tags/endpoints into supplier vs consumer, adds consumer resources endpoints and optional consumer filtering.
src/features/amUI/maskinporten/SupplierPageContent.tsx Updates supplier page to use the renamed supplier resources query hook.
src/features/amUI/maskinporten/ScopeSearch.tsx Updates delegated resources lookup to use supplier-specific resources endpoint.
src/features/amUI/maskinporten/ScopeInfo.tsx Updates delegated resources lookup to use supplier-specific resources endpoint.
src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts Updates resource delegation actions to use supplier-specific add/remove mutations.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/MaskinportenController.cs Renames supplier resource routes and adds consumers/resources GET/DELETE endpoints.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Tests/Controllers/MaskinportenControllerTest.cs Updates supplier resource route tests and adds coverage for new consumer resources endpoints.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Mocks/Mocks/MaskinportenClientMock.cs Aligns mock client with renamed supplier methods and adds consumer resources mock methods + optional consumer filter.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/MaskinportenClient.cs Renames supplier resource client methods and adds consumer resources client methods + optional consumer query param.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/Services/MaskinportenService.cs Renames supplier resource service methods and adds consumer resource mapping method.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/Services/Interfaces/IMaskinportenService.cs Updates service interface to reflect renamed supplier methods and new consumer resources operations.
backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Core/ClientInterfaces/IMaskinportenClient.cs Updates client interface to reflect renamed supplier methods and new consumer resources operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rtk/features/maskinportenApi.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/amUI/maskinporten/ScopeInfo.tsx (1)

259-268: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use aria-disabled instead of disabled on DsButton.

Per codebase conventions, DsButton should use aria-disabled to remain focusable for accessibility. The click handler should guard against the disabled state.

♿ Proposed fix
               <DsButton
                 data-size='sm'
                 data-color='danger'
-                disabled={isActionLoading}
-                onClick={handleRemoveResource}
+                aria-disabled={isActionLoading}
+                onClick={() => {
+                  if (isActionLoading) return;
+                  handleRemoveResource();
+                }}
                 variant='secondary'
               >

Based on learnings: "when using the DsButton component, it is intentional to use aria-disabled (not the native disabled attribute) so the button remains focusable for accessibility."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/ScopeInfo.tsx` around lines 259 - 268, Replace
the native disabled prop on the DsButton with aria-disabled and ensure the click
handler early-exits when the button should be disabled: change the DsButton prop
from disabled={isActionLoading} to aria-disabled={isActionLoading}, and update
handleRemoveResource (or add a small wrapper onClick) to check isActionLoading
and return immediately if true so clicks are ignored while aria-disabled is set;
keep the existing variant, icon, and label intact.
🧹 Nitpick comments (3)
src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts (1)

47-55: 💤 Low value

Inconsistent return types from delegate and remove.

When the action is missing, you return undefined (sync). When present, you return run(...) which is a Promise<void>. Callers awaiting these might get unexpected behavior.

Consider making the return type explicit and consistent:

♻️ Proposed fix
-  const delegate = (resource: ServiceResource, callbacks: ActionCallbacks = {}) => {
-    if (!delegateAction) return;
+  const delegate = async (resource: ServiceResource, callbacks: ActionCallbacks = {}): Promise<void> => {
+    if (!delegateAction) return;
     return run(resource, delegateAction, callbacks);
   };

-  const remove = (resource: ServiceResource, callbacks: ActionCallbacks = {}) => {
-    if (!removeAction) return;
+  const remove = async (resource: ServiceResource, callbacks: ActionCallbacks = {}): Promise<void> => {
+    if (!removeAction) return;
     return run(resource, removeAction, callbacks);
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts`
around lines 47 - 55, The functions delegate and remove return inconsistent
types: when the corresponding action (delegateAction/removeAction) is absent
they return undefined synchronously, but when present they return the Promise
from run(...). Make the return type consistent by always returning a
Promise<void>; update delegate and remove to return Promise.resolve() when the
action is missing (or explicitly return Promise<void>), ensuring callers can
always await the result; reference the delegate and remove functions and the
run(resource, delegateAction/removeAction, callbacks) call sites when making the
change.
src/features/amUI/maskinporten/SupplierPageContent.tsx (1)

64-71: 💤 Low value

Non-null assertions on party and supplier in mutation callback.

The party! and supplier! assertions assume these values are defined when the remove action executes. While the query skips when undefined (line 63), the mutation callback is defined unconditionally.

If user triggers removal before data loads (edge case), this could throw. The risk is low since UI guards exist, but explicit guards would be safer.

🛡️ Defensive alternative
   const { remove, isLoading } = useMaskinportenResourceActions({
     remove: (resource) =>
+      party && supplier
+        ? removeSupplierResource({
+            party,
+            supplier,
+            resource: resource.identifier,
+          }).unwrap()
+        : Promise.reject(new Error('Missing party or supplier')),
-      removeSupplierResource({
-        party: party!,
-        supplier: supplier!,
-        resource: resource.identifier,
-      }).unwrap(),
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/SupplierPageContent.tsx` around lines 64 - 71,
The mutation callback passed to useMaskinportenResourceActions uses non-null
assertions on party and supplier (party!, supplier!) which can throw if the user
triggers remove before data loads; update the remove callback (the remove
property in the useMaskinportenResourceActions call) to guard that party and
supplier are defined before calling removeSupplierResource — if either is
missing, reject/return an appropriate error or no-op (e.g., return
Promise.reject/new Error or resolve) so the mutation never dereferences
undefined; reference the remove property, useMaskinportenResourceActions hook,
and removeSupplierResource function when making this change.
src/features/amUI/maskinporten/ConsumersTab.tsx (1)

38-42: 💤 Low value

getUserLink performs lookup on each call.

The find() iterates through consumers for each user. If the search component calls this frequently or with many users, consider memoizing a lookup map.

Current implementation is acceptable for typical list sizes; flagging as optional.

♻️ Optional: Pre-compute lookup map
+ const consumerOrgNrById = React.useMemo(() => {
+   const map = new Map<string, string>();
+   consumers?.forEach((c) => {
+     if (c.party.organizationIdentifier) {
+       map.set(c.party.id, c.party.organizationIdentifier);
+     }
+   });
+   return map;
+ }, [consumers]);
+
  <MaskinportenUserSearch
    connections={consumers}
    isLoading={isLoading}
    error={error}
    emptyText={t('maskinporten_page.no_consumers')}
    canDelegate={false}
-   getUserLink={(user) => {
-     const orgNr = consumers?.find((c) => c.party.id === user.id)?.party
-       .organizationIdentifier;
-     return orgNr ? `/maskinporten/consumer/${orgNr}` : '';
-   }}
+   getUserLink={(user) => {
+     const orgNr = consumerOrgNrById.get(user.id);
+     return orgNr ? `/maskinporten/consumer/${orgNr}` : '';
+   }}
  />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/ConsumersTab.tsx` around lines 38 - 42,
getUserLink currently scans consumers on every call by using consumers.find(...)
which is inefficient if invoked frequently; precompute a lookup map from user id
to party.organizationIdentifier (e.g., build a Map or object from the consumers
array) once in the ConsumersTab component (using useMemo if React hooks are
available) and then have getUserLink read orgNr from that map (falling back to
'' when missing), referencing the existing getUserLink function, consumers
array, and party.organizationIdentifier field to locate where to replace the
per-call find with a constant-time lookup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts`:
- Line 35: The early return in useMaskinportenResourceActions that does "if
(!resource.identifier) return;" silently skips execution; instead, detect the
missing identifier and call the provided onError callback (e.g., onError(new
Error("Missing resource.identifier") or a descriptive Error/Message) so callers
are notified, then return or abort; update the block in
useMaskinportenResourceActions where resource.identifier is checked to invoke
onError with a clear message referencing the resource and identifier before
exiting.

---

Outside diff comments:
In `@src/features/amUI/maskinporten/ScopeInfo.tsx`:
- Around line 259-268: Replace the native disabled prop on the DsButton with
aria-disabled and ensure the click handler early-exits when the button should be
disabled: change the DsButton prop from disabled={isActionLoading} to
aria-disabled={isActionLoading}, and update handleRemoveResource (or add a small
wrapper onClick) to check isActionLoading and return immediately if true so
clicks are ignored while aria-disabled is set; keep the existing variant, icon,
and label intact.

---

Nitpick comments:
In `@src/features/amUI/maskinporten/ConsumersTab.tsx`:
- Around line 38-42: getUserLink currently scans consumers on every call by
using consumers.find(...) which is inefficient if invoked frequently; precompute
a lookup map from user id to party.organizationIdentifier (e.g., build a Map or
object from the consumers array) once in the ConsumersTab component (using
useMemo if React hooks are available) and then have getUserLink read orgNr from
that map (falling back to '' when missing), referencing the existing getUserLink
function, consumers array, and party.organizationIdentifier field to locate
where to replace the per-call find with a constant-time lookup.

In `@src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts`:
- Around line 47-55: The functions delegate and remove return inconsistent
types: when the corresponding action (delegateAction/removeAction) is absent
they return undefined synchronously, but when present they return the Promise
from run(...). Make the return type consistent by always returning a
Promise<void>; update delegate and remove to return Promise.resolve() when the
action is missing (or explicitly return Promise<void>), ensuring callers can
always await the result; reference the delegate and remove functions and the
run(resource, delegateAction/removeAction, callbacks) call sites when making the
change.

In `@src/features/amUI/maskinporten/SupplierPageContent.tsx`:
- Around line 64-71: The mutation callback passed to
useMaskinportenResourceActions uses non-null assertions on party and supplier
(party!, supplier!) which can throw if the user triggers remove before data
loads; update the remove callback (the remove property in the
useMaskinportenResourceActions call) to guard that party and supplier are
defined before calling removeSupplierResource — if either is missing,
reject/return an appropriate error or no-op (e.g., return Promise.reject/new
Error or resolve) so the mutation never dereferences undefined; reference the
remove property, useMaskinportenResourceActions hook, and removeSupplierResource
function when making this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0a06212-e7d5-44a5-a380-0d4a1647fdca

📥 Commits

Reviewing files that changed from the base of the PR and between b7817d1 and ddde19e.

📒 Files selected for processing (13)
  • src/features/amUI/common/DelegationModal/EditModal.tsx
  • src/features/amUI/maskinporten/ConsumerPage.tsx
  • src/features/amUI/maskinporten/ConsumerPageContent.tsx
  • src/features/amUI/maskinporten/ConsumersTab.tsx
  • src/features/amUI/maskinporten/ScopeInfo.tsx
  • src/features/amUI/maskinporten/ScopeSearchControls.tsx
  • src/features/amUI/maskinporten/SupplierPageContent.tsx
  • src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts
  • src/localizations/en.json
  • src/localizations/no_nb.json
  • src/localizations/no_nn.json
  • src/routes/Router/Router.tsx
  • src/routes/paths/amUIPath.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/routes/paths/amUIPath.tsx

Comment thread src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts Outdated
@sonwit sonwit changed the title Add remaining consumer BFF endpoints & refactor Supplier endpoints Add remaining Maskinporten consumer functionality May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/amUI/maskinporten/SupplierPageContent.tsx (1)

85-96: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the supplier page back link on the suppliers tab.

Line 85 returns to ?tab=suppliers, but Line 96 still points PageContainer at the bare Maskinporten route. That makes the page header/back action land on a different tab than the delete flow.

Suggested fix
-      backUrl={`/${amUIPath.Maskinporten}`}
+      backUrl={`/${amUIPath.Maskinporten}?tab=suppliers`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/amUI/maskinporten/SupplierPageContent.tsx` around lines 85 - 96,
The PageContainer back link currently uses backUrl={`/${amUIPath.Maskinporten}`}
which conflicts with the delete flow that navigates to
`/${amUIPath.Maskinporten}?tab=suppliers`; update the PageContainer backUrl prop
to include the suppliers tab query so it matches the navigation used in the
remove handler (e.g., set backUrl to `/${amUIPath.Maskinporten}?tab=suppliers`),
ensuring PageContainer, its backUrl prop, and amUIPath.Maskinporten use the same
destination as the navigate call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/features/amUI/maskinporten/SupplierPageContent.tsx`:
- Around line 85-96: The PageContainer back link currently uses
backUrl={`/${amUIPath.Maskinporten}`} which conflicts with the delete flow that
navigates to `/${amUIPath.Maskinporten}?tab=suppliers`; update the PageContainer
backUrl prop to include the suppliers tab query so it matches the navigation
used in the remove handler (e.g., set backUrl to
`/${amUIPath.Maskinporten}?tab=suppliers`), ensuring PageContainer, its backUrl
prop, and amUIPath.Maskinporten use the same destination as the navigate call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b3520412-48b7-4d6f-9778-50a6cc1f4055

📥 Commits

Reviewing files that changed from the base of the PR and between ddde19e and 37fecb1.

📒 Files selected for processing (3)
  • src/features/amUI/maskinporten/ConsumerPageContent.tsx
  • src/features/amUI/maskinporten/DelegatedResourcesSection.tsx
  • src/features/amUI/maskinporten/SupplierPageContent.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/features/amUI/maskinporten/DelegatedResourcesSection.tsx

sonwit added 3 commits May 19, 2026 08:37
…error state management; add DelegatedResourcesSection styles.
…ctions; refactor DelegatedResourcesSection to accept editModal as a prop

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/amUI/maskinporten/ScopeInfo.tsx`:
- Around line 54-67: supplierOrgNumber is set to '' when toParty.orgNumber is
missing, but the delegate path (delegate in useMaskinportenResourceActions) and
addSupplierResource calls are not guarded, allowing delegation attempts with
supplier: ''. Fix by early-checking supplierOrgNumber wherever delegation or
addSupplierResource is wired (the delegate callback passed into
useMaskinportenResourceActions, the handleAddResource flow, and the button
enablement logic referenced around the other occurrences), and prevent or
short-circuit the action if supplierOrgNumber is falsy: disable the "give POA"
button when supplierOrgNumber is empty, and have delegate/handleAddResource
return/fail fast before calling addSupplierResource({ supplier:
supplierOrgNumber, ... }) when supplierOrgNumber === ''.

In `@src/features/amUI/maskinporten/SupplierPage.tsx`:
- Around line 62-63: The current notFound logic (const notFound = !!error ||
(!isLoading && !data?.length)) treats transport/server/auth errors as "not
found"; change it so notFound is true only when the request completed
successfully with an empty result or when the error is explicitly a 404.
Concretely, update the notFound calculation in SupplierPage (and the rendering
branches around lines where notFound is used) to check: notFound = (!isLoading
&& !error && Array.isArray(data) && data.length === 0) || (error?.status === 404
|| error?.response?.status === 404); and ensure all other error cases render a
generic error state (use the existing error handling path rather than the
notFound UI).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0e95575-3c9c-4d45-8855-f23c448b708b

📥 Commits

Reviewing files that changed from the base of the PR and between 37fecb1 and fe93e11.

📒 Files selected for processing (9)
  • src/features/amUI/maskinporten/ConsumerPage.tsx
  • src/features/amUI/maskinporten/ConsumerPageContent.tsx
  • src/features/amUI/maskinporten/DelegatedResourcesSection.module.css
  • src/features/amUI/maskinporten/DelegatedResourcesSection.tsx
  • src/features/amUI/maskinporten/MaskinportenPage.module.css
  • src/features/amUI/maskinporten/ScopeInfo.tsx
  • src/features/amUI/maskinporten/SupplierPage.tsx
  • src/features/amUI/maskinporten/SupplierPageContent.tsx
  • src/features/amUI/maskinporten/hooks/useMaskinportenResourceActions.ts
💤 Files with no reviewable changes (1)
  • src/features/amUI/maskinporten/MaskinportenPage.module.css
✅ Files skipped from review due to trivial changes (1)
  • src/features/amUI/maskinporten/DelegatedResourcesSection.module.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/features/amUI/maskinporten/ConsumerPageContent.tsx
  • src/features/amUI/maskinporten/ConsumerPage.tsx

Comment thread src/features/amUI/maskinporten/ScopeInfo.tsx
Comment thread src/features/amUI/maskinporten/SupplierPage.tsx
@sonwit
sonwit requested a review from allinox May 19, 2026 09:53

@allinox allinox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tested it and looked through the code, and I only have a couple of small comments.

Great work bringing this feature to completion! 😄🙌

Comment thread src/features/amUI/maskinporten/ScopeSearchControls.tsx
@sonarqubecloud

Copy link
Copy Markdown

@sonwit
sonwit merged commit f616e01 into main May 19, 2026
10 checks passed
@sonwit
sonwit deleted the feat/maskinporten-consumer-page branch May 19, 2026 15:39
@coderabbitai coderabbitai Bot mentioned this pull request May 20, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature-request Used when issue is a new feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants