Skip to content

chore: global settings endpoints v9#18893

Open
lassopicasso wants to merge 9 commits into
mainfrom
feat/global-settings-endpoints-v9
Open

chore: global settings endpoints v9#18893
lassopicasso wants to merge 9 commits into
mainfrom
feat/global-settings-endpoints-v9

Conversation

@lassopicasso
Copy link
Copy Markdown
Contributor

@lassopicasso lassopicasso commented May 20, 2026

Description

Verification

  • Related issues are connected (if applicable)
  • 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)

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced new API endpoints for managing global layout set validation on navigation settings, allowing users to retrieve current configurations, update them, and delete settings as needed.
    • Added new API endpoints for managing global task navigation group settings, enabling users to retrieve task navigation configurations and update task navigation group assignments.

Review Change Stack

@lassopicasso lassopicasso added squad/utforming Issues that belongs to the named squad. taskforce/next Issues that belongs to the named task-force labels May 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete HTTP API and backing service to manage global layout set settings (validation-on-navigation and task-navigation configuration) in Altinn Studio Designer. The changes persist settings to a global Settings.json file in the repository layouts folder, accessed through a new repository layer and coordinated by a dedicated service.

Changes

Global Layout Set Settings API

Layer / File(s) Summary
Data Persistence & Service Contract
src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs, src/Designer/backend/src/Designer/Services/Interfaces/ILayoutsetsService.cs
Repository introduces SettingsFilename constant, GetGlobalSettingsFile() and SaveGlobalSettingsFile() methods for JSON serialisation, and GetPathToGlobalSettingsFile() path helper. ILayoutsetsService interface defines async methods for retrieving and updating validation-on-navigation and task-navigation settings.
Service Implementation
src/Designer/backend/src/Designer/Services/LayoutsetsService.cs
LayoutsetsService implements ILayoutsetsService, orchestrating repository calls to load and save UiSettings, mapping task navigation groups to DTOs with task-type lookups, and handling null/default cases for missing configuration.
API Controller & Endpoints
src/Designer/backend/src/Designer/Controllers/LayoutsetsController.cs
LayoutsetsController exposes GET/POST/DELETE endpoints for validation-on-navigation settings and GET/POST endpoints for task-navigation settings. Returns Ok or NoContent on success; converts ArgumentException to BadRequest for task-navigation updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

skip-releasenotes, taskforce/next

Poem

🐰 A settings keeper, hops with grace,
Through validation paths and task-nav space,
JSON files saved in layouts' fold,
Navigation rules, both new and bold!
Controllers guide the flow with care,
Global layouts dance through the air. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete; it contains only the template with placeholders and an uncompleted verification checklist, with no actual details about the implementation, functionality, or testing. Fill in the Description section with details about what was added (LayoutsetsController, endpoints for validation and task navigation settings), why these changes were made, and complete the verification checklist items to confirm testing and build status.
Title check ❓ Inconclusive The title 'chore: global settings endpoints v9' is vague and generic; it lacks specificity about what functionality was actually added or changed. Consider using a more descriptive title that specifies the endpoints added, such as 'feat: add LayoutsetsController with global validation and task navigation endpoints' to better convey the primary changes.
✅ Passed checks (2 passed)
Check name Status Explanation
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/global-settings-endpoints-v9

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Designer/backend/src/Designer/Services/LayoutsetsService.cs (1)

28-32: 🏗️ Heavy lift

Avoid splitting AltinnRepoEditingContext into org/repo/developer repeatedly.

The service repeatedly unpacks context fields to call the factory. Prefer a factory overload that accepts AltinnRepoEditingContext directly to keep call sites consistent and reduce parameter fan-out.

As per coding guidelines, "Instead of passing the combination of org, repo/app and developer/user parameters into functions separately, look for suitable classes that join them into one, e.g. AltinnRepoEditingContext".

Also applies to: 45-49, 84-88, 100-104, 118-122

🤖 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/Designer/backend/src/Designer/Services/LayoutsetsService.cs` around lines
28 - 32, The code repeatedly unpacks AltinnRepoEditingContext into
org/repo/developer when calling
_altinnGitRepositoryFactory.GetAltinnAppGitRepository; add (or use) an overload
on the factory that takes AltinnRepoEditingContext directly and update all call
sites (the calls at the blocks creating AltinnAppGitRepository in
LayoutsetsService methods) to pass the AltinnRepoEditingContext instance instead
of individual fields, so callers use
_altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext);
keep existing behavior and tests by forwarding the new overload to the existing
implementation that extracts Org/Repo/Developer internally.
🤖 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/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs`:
- Around line 620-627: GetGlobalSettingsFile currently calls
ReadTextByRelativePathAsync and deserializes unconditionally which throws if
Settings.json is missing; update GetGlobalSettingsFile to first check for the
file (use GetPathToGlobalSettingsFile and the repository/file-exists helper or
catch the file-not-found error from ReadTextByRelativePathAsync), and if the
file does not exist return a new default UiSettings instance instead of
attempting to read/deserialize; ensure you still use s_jsonOptions when
deserializing existing content and preserve CancellationToken handling.

---

Nitpick comments:
In `@src/Designer/backend/src/Designer/Services/LayoutsetsService.cs`:
- Around line 28-32: The code repeatedly unpacks AltinnRepoEditingContext into
org/repo/developer when calling
_altinnGitRepositoryFactory.GetAltinnAppGitRepository; add (or use) an overload
on the factory that takes AltinnRepoEditingContext directly and update all call
sites (the calls at the blocks creating AltinnAppGitRepository in
LayoutsetsService methods) to pass the AltinnRepoEditingContext instance instead
of individual fields, so callers use
_altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext);
keep existing behavior and tests by forwarding the new overload to the existing
implementation that extracts Org/Repo/Developer internally.
🪄 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: 177770e3-6cec-4719-8569-784a76afb97c

📥 Commits

Reviewing files that changed from the base of the PR and between 9c991c7 and 06a146f.

📒 Files selected for processing (4)
  • src/Designer/backend/src/Designer/Controllers/LayoutsetsController.cs
  • src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs
  • src/Designer/backend/src/Designer/Services/Interfaces/ILayoutsetsService.cs
  • src/Designer/backend/src/Designer/Services/LayoutsetsService.cs

Comment on lines +620 to +627
public async Task<UiSettings> GetGlobalSettingsFile(CancellationToken cancellationToken = default)
{
string globalSettingsFilePath = GetPathToGlobalSettingsFile();
cancellationToken.ThrowIfCancellationRequested();
string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
UiSettings globalSettingsFile = JsonSerializer.Deserialize<UiSettings>(fileContent, s_jsonOptions);
return globalSettingsFile;
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle missing global settings file before reading.

GetGlobalSettingsFile reads Settings.json unconditionally. On first write scenario (file not created yet), this throws and breaks both save endpoints that expect default/empty settings to be possible.

Proposed fix
 public async Task<UiSettings> GetGlobalSettingsFile(CancellationToken cancellationToken = default)
 {
     string globalSettingsFilePath = GetPathToGlobalSettingsFile();
     cancellationToken.ThrowIfCancellationRequested();
+    if (!FileExistsByRelativePath(globalSettingsFilePath))
+    {
+        return new UiSettings();
+    }
     string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
     UiSettings globalSettingsFile = JsonSerializer.Deserialize<UiSettings>(fileContent, s_jsonOptions);
     return globalSettingsFile;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<UiSettings> GetGlobalSettingsFile(CancellationToken cancellationToken = default)
{
string globalSettingsFilePath = GetPathToGlobalSettingsFile();
cancellationToken.ThrowIfCancellationRequested();
string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
UiSettings globalSettingsFile = JsonSerializer.Deserialize<UiSettings>(fileContent, s_jsonOptions);
return globalSettingsFile;
}
public async Task<UiSettings> GetGlobalSettingsFile(CancellationToken cancellationToken = default)
{
string globalSettingsFilePath = GetPathToGlobalSettingsFile();
cancellationToken.ThrowIfCancellationRequested();
if (!FileExistsByRelativePath(globalSettingsFilePath))
{
return new UiSettings();
}
string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
UiSettings globalSettingsFile = JsonSerializer.Deserialize<UiSettings>(fileContent, s_jsonOptions);
return globalSettingsFile;
}
🤖 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/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs`
around lines 620 - 627, GetGlobalSettingsFile currently calls
ReadTextByRelativePathAsync and deserializes unconditionally which throws if
Settings.json is missing; update GetGlobalSettingsFile to first check for the
file (use GetPathToGlobalSettingsFile and the repository/file-exists helper or
catch the file-not-found error from ReadTextByRelativePathAsync), and if the
file does not exist return a new default UiSettings instance instead of
attempting to read/deserialize; ensure you still use s_jsonOptions when
deserializing existing content and preserve CancellationToken handling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

should i return null instead in this case?

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all usages of GetGlobalSettingsFile in the codebase
rg -n "GetGlobalSettingsFile" --type cs -C 5

Length of output: 6351


@lassopicasso Yes, returning null actually works equally well here — and arguably fits the existing caller patterns even better. Looking at all four call sites in LayoutsetsService:

Call site Handling of the returned value
GetGlobalValidationOnNavigationSettings globalSettingsFile?.ValidationOnNavigation — null-safe
SaveGlobalValidationOnNavigationSettings globalSettingsFile ??= new UiSettings(); — explicit null fallback
GetGlobalTaskNavigationSettings globalSettingsFile?.TaskNavigation?.ToList() ?? [] — null-safe
UpdateGlobalTaskNavigationSettings globalSettingsFile ??= new UiSettings(); — explicit null fallback

Every caller already guards against null with either the ?. operator or ??=, so returning null when the file doesn't exist is semantically clean — it accurately signals "the file doesn't exist yet" and lets the service layer decide what the default should be. The proposed fix would then be:

 public async Task<UiSettings> GetGlobalSettingsFile(CancellationToken cancellationToken = default)
 {
     string globalSettingsFilePath = GetPathToGlobalSettingsFile();
     cancellationToken.ThrowIfCancellationRequested();
+    if (!FileExistsByRelativePath(globalSettingsFilePath))
+    {
+        return null;
+    }
     string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
     UiSettings globalSettingsFile = JsonSerializer.Deserialize<UiSettings>(fileContent, s_jsonOptions);
     return globalSettingsFile;
 }

Either approach (null or new UiSettings()) will work, but null is the more idiomatic choice for a repository-layer method — it separates "no file" from "empty settings object" clearly.


🧠 Learnings used
Learnt from: mirkoSekulic
Repo: Altinn/altinn-studio PR: 17174
File: src/Runtime/StudioGateway/src/StudioGateway.Api/Clients/Designer/StudioEnvironmentConfig.cs:1-8
Timestamp: 2025-12-11T09:10:06.960Z
Learning: In C# 12, you can declare a class, interface, or struct with an empty body using a trailing semicolon as a shorthand (e.g., internal sealed class X : Y;). When reviewing StudioGateway.Api code under src/Runtime/StudioGateway/src/StudioGateway.Api/Clients/Designer/StudioEnvironmentConfig.cs and other C# files, verify the language version is set to at least 12.0+ and that any empty-body types use the single semicolon shorthand only when semantically appropriate (i.e., the type has no members). If a type previously had members, continue using a normal body.

Learnt from: danielskovli
Repo: Altinn/altinn-studio PR: 18623
File: src/Runtime/workflow-engine/src/WorkflowEngine.Models/Step.cs:47-57
Timestamp: 2026-04-29T10:40:01.618Z
Learning: When reviewing C# `record` types, do not require an `override` keyword on a manually declared `Equals(DerivedType? other)` method for a `sealed record` (e.g., `sealed record Step : PersistentItem`). In this case, the compiler uses the explicitly declared `Equals(DerivedType? other)` and does not synthesize a different one; it also is not overriding/inheriting an `Equals` method from the base record signature, so flagging “missing override” for this specific `Equals(DerivedType? other)` form is incorrect. Only flag override issues when the method actually overrides/inherits a base member with a matching signature.

@github-actions github-actions Bot added skip-releasenotes Issues that do not make sense to list in our release notes backend solution/studio/designer labels May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend skip-releasenotes Issues that do not make sense to list in our release notes solution/studio/designer squad/utforming Issues that belongs to the named squad. taskforce/next Issues that belongs to the named task-force

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant