Skip to content

Standardize Party/Entity Type Representation Across Frontend and BFF #1659

@sonwit

Description

@sonwit

📋 Problem Summary

The application currently has inconsistent representations of party/entity types across the codebase, leading to:

  • Type confusion and potential bugs
  • Mixing of Norwegian and English terminology
  • Multiple string literals and enum types representing the same concept
  • Complex conversion logic scattered throughout the frontend

Current Inconsistencies

1. Multiple Enum Definitions

// Frontend - Numeric enum (from BFF)
export enum PartyType {
  Person = 1,
  Organization = 2,
  SelfIdentified = 3,
  SubUnit = 4,
}

// Frontend - String enum with Norwegian! ❌
export enum ConnectionUserType {
  Person = 'Person',
  Organization = 'Organisasjon',  // Norwegian mixed with English
  Systemuser = 'Systembruker',
}
// Backend - Multiple enums for same concept
public enum PartyType { Person = 1, Organization = 2, ... }
public enum AuthorizedPartyType { Person = 1, Organization = 2, ... }
public enum ResourcePartyType { PrivatePerson = 0, Company = 2, ... }

2. Inconsistent String Representations

Throughout the codebase, we see:

  • 'Person' (Pascal case)
  • 'person' (lowercase)
  • 'Organisasjon' (Norwegian)
  • 'Organization' (English)
  • 'organization' (lowercase English)
  • 'org' (abbreviated)
  • 'company' (UI component variant)
  • 'system' (for system users)

3. Multiple Conversion Points

Currently, type conversion happens in:

  • UserItem.tsx - Maps ConnectionUserType'person'/'company'/'system'
  • UserPageHeader.tsx - Maps PartyType'company'/'person'
  • PageLayoutWrapper.tsx - getAccountType() helper
  • PermissionBadge.tsx - Compares 'organisasjon' (lowercase Norwegian!)
  • useSelfConnection.ts - Converts partyTypeName.toString()
  • Various test files with mixed mock data

Affected Files (Sample)

  • /src/rtk/features/userInfoApi.ts - PartyType & ConnectionUserType enums
  • /src/rtk/features/lookupApi.ts - Party interface with partyTypeName
  • /src/features/amUI/common/UserPageHeader/UserPageHeader.tsx
  • /src/features/amUI/common/UserList/UserItem.tsx
  • /src/features/amUI/common/AccessPackageList/PermissionBadge.tsx
  • /src/features/amUI/users/NewUserModal/* - Uses 'person'/'org' for tabs
  • Multiple test files with inconsistent mock data

🎯 Proposed Solution: BFF-Level Normalization

Strategy: Normalize at the Backend Boundary

Key Principle: All party type normalization should happen in the BFF layer, providing a single, consistent contract to the frontend.

Phase 1: Backend (BFF) Standardization

1.1 Create Canonical DTO Contract

// Altinn.AccessManagement.UI.Core/Models/Common/PartyTypeDto.cs
namespace Altinn.AccessManagement.UI.Core.Models.Common
{
    /// <summary>
    /// Standardized party type for all frontend responses.
    /// This is the ONLY party type representation sent to frontend.
    /// </summary>
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public enum PartyTypeDto
    {
        Person = 1,
        Organization = 2,
        SelfIdentified = 3,
        SubUnit = 4
    }
}

1.2 Create Conversion Extensions

// Altinn.AccessManagement.UI.Core/Extensions/PartyTypeExtensions.cs
public static class PartyTypeExtensions
{
    // Convert Register PartyType → PartyTypeDto
    public static PartyTypeDto ToDto(this PartyType partyType) { ... }
    
    // Convert AuthorizedPartyType → PartyTypeDto
    public static PartyTypeDto ToDto(this AuthorizedPartyType authorizedType) { ... }
    
    // Convert ResourcePartyType → PartyTypeDto
    public static PartyTypeDto ToDto(this ResourcePartyType resourceType) { ... }
    
    // Handle legacy string values (including Norwegian)
    public static PartyTypeDto? FromString(string? partyTypeString)
    {
        var normalized = partyTypeString?.Trim().ToLowerInvariant();
        return normalized switch
        {
            "person" => PartyTypeDto.Person,
            "organization" => PartyTypeDto.Organization,
            "organisasjon" => PartyTypeDto.Organization, // ✅ Handle Norwegian
            "selfidentified" => PartyTypeDto.SelfIdentified,
            "subunit" => PartyTypeDto.SubUnit,
            _ => null
        };
    }
}

1.3 Update BFF Response Models

public class PartyDto
{
    public int PartyId { get; set; }
    public string PartyUuid { get; set; }
    public string Name { get; set; }
    public PartyTypeDto PartyType { get; set; }  // ✅ Standardized
    // ... other fields
}

public class UserDto
{
    public string Id { get; set; }
    public string Name { get; set; }
    public PartyTypeDto PartyType { get; set; }  // ✅ Standardized
    // ... other fields
}

1.4 Apply Conversions in Controllers

All controllers returning party data should convert to PartyTypeDto:

  • LookupController
  • UserController
  • RightHolderController
  • Any other endpoints returning party/user data

Phase 2: Frontend Simplification

2.1 Update TypeScript Interfaces

// src/rtk/features/userInfoApi.ts

// ✅ Keep this enum (matches BFF exactly)
export enum PartyType {
  Person = 1,
  Organization = 2,
  SelfIdentified = 3,
  SubUnit = 4,
}

// ❌ REMOVE ConnectionUserType entirely
// No longer needed since BFF provides normalized data

export interface User {
  id: string;
  name: string;
  partyType: PartyType;  // ✅ Now comes normalized from BFF
  variant?: string;
  children: (User | ExtendedUser)[] | null;
  keyValues: UserKeyValues | null;
}

export interface Party {
  partyId: number;
  partyUuid: string;
  name: string;
  partyType: PartyType;  // ✅ Renamed from partyTypeName
  // ... other fields
}

2.2 Create Simple UI Utility

// src/utils/partyTypeUtils.ts

/**
 * Convert PartyType to UI component icon type.
 * This is the ONLY conversion needed in frontend.
 */
export const partyTypeToUIType = (
  partyType: PartyType
): 'person' | 'company' | 'system' => {
  switch (partyType) {
    case PartyType.Person:
      return 'person';
    case PartyType.Organization:
    case PartyType.SubUnit:
      return 'company';
    case PartyType.SelfIdentified:
      return 'system';
    default:
      return 'person';
  }
};

export const isPersonType = (partyType: PartyType): boolean => 
  partyType === PartyType.Person;

export const isOrganizationType = (partyType: PartyType): boolean => 
  partyType === PartyType.Organization || partyType === PartyType.SubUnit;

2.3 Update Components

Replace all scattered conversion logic with the utility:

// Before ❌
type: user?.partyTypeName === PartyType.Organization ? 'company' : 'person'

// After ✅
type: partyTypeToUIType(user.partyType)

📝 Implementation Checklist

Backend Tasks (BFF)

  • Create PartyTypeDto enum in Core/Models/Common
  • Create PartyTypeExtensions with all conversion methods
  • Update PartyDto model to use PartyType field
  • Update UserDto/ConnectionDto models to use PartyType field
  • Update LookupController to apply conversions
  • Update UserController to apply conversions
  • Update RightHolderController to apply conversions
  • Update any other controllers returning party data
  • Add unit tests for conversion logic
  • Update integration tests
  • Deploy BFF changes

Frontend Tasks

  • Create src/utils/partyTypeUtils.ts with conversion utilities
  • Update Party interface: rename partyTypeNamepartyType
  • Update User/ExtendedUser interfaces to use partyType: PartyType
  • Remove ConnectionUserType enum from userInfoApi.ts
  • Update UserItem.tsx to use partyTypeToUIType()
  • Update UserPageHeader.tsx to use partyTypeToUIType()
  • Update PageLayoutWrapper.tsx to use utility functions
  • Update PermissionBadge.tsx to use partyType enum
  • Update AccessPackageSection.tsx to use partyType enum
  • Update NewUserModal components
  • Update useSelfConnection.ts to use partyType directly
  • Remove all string comparisons ('organisasjon', 'Organization', etc.)
  • Update all test files with correct mock data
  • Remove deprecated code and helpers

Testing & Validation

  • Test all party type conversions end-to-end
  • Verify UI components render correct icons/types
  • Test with Person, Organization, SubUnit, and SelfIdentified types
  • Verify backwards compatibility during migration
  • Test all affected pages (Users, Rights, Settings, etc.)

🎁 Benefits

Single Source of Truth: All normalization happens in BFF
Type Safety: Strong typing from backend to UI components
Consistency: No more Norwegian/English mixing
Maintainability: Centralized conversion logic
Future-Proof: Easy to add new source systems or types
Better DX: Clear API contract, better autocomplete
Fewer Bugs: Eliminates string comparison errors

🚨 Migration Considerations

Backwards Compatibility Strategy

  1. Phase 1: BFF changes with dual fields

    • Add new partyType field alongside existing fields
    • Keep deprecated fields for transition period
  2. Phase 2: Frontend migration

    • Update to use new partyType field
    • Remove references to old string types
  3. Phase 3: Cleanup

    • Remove deprecated BFF fields
    • Final testing and validation

Breaking Changes

  • API response structure changes (mitigated by dual-field strategy)
  • Frontend interface renames (partyTypeNamepartyType)
  • Removal of ConnectionUserType enum

Risk Mitigation

  • Deploy BFF changes first (backwards compatible)
  • Frontend can migrate incrementally
  • Comprehensive testing at each phase
  • Feature flag for gradual rollout if needed

📚 Related Files to Review

Backend:

  • Altinn.AccessManagement.UI.Core/Models/Register/PartyType.cs
  • Altinn.AccessManagement.UI.Core/Enums/AuthorizedPartyType.cs
  • Altinn.AccessManagement.UI.Core/Enums/ResourcePartyType.cs
  • Controllers/LookupController.cs
  • Controllers/UserController.cs

Frontend:

  • src/rtk/features/userInfoApi.ts
  • src/rtk/features/lookupApi.ts
  • src/features/amUI/common/UserList/UserItem.tsx
  • src/features/amUI/common/UserPageHeader/UserPageHeader.tsx
  • src/features/amUI/common/PageLayoutWrapper/PageLayoutWrapper.tsx

💡 Questions to Address

  1. Should we introduce feature flags for gradual rollout?
  2. What's the timeline for backwards compatibility support?
  3. Do we need data migration for any stored party types?
  4. Should we add linting rules to prevent string literal party types?

Priority: High
Effort: Medium (2-3 sprints)
Type: Technical Debt / Refactoring
Labels: backend, frontend, api-contract, type-safety, refactoring

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrontendThis is a frontend task

    Type

    Projects

    Status

    Sprint backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions