diff --git a/src/Designer/backend/src/Designer/Controllers/UiFoldersController.cs b/src/Designer/backend/src/Designer/Controllers/UiFoldersController.cs
new file mode 100644
index 00000000000..52c32e6ef20
--- /dev/null
+++ b/src/Designer/backend/src/Designer/Controllers/UiFoldersController.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.Studio.Designer.Filters;
+using Altinn.Studio.Designer.Helpers;
+using Altinn.Studio.Designer.Models;
+using Altinn.Studio.Designer.Models.Dto;
+using Altinn.Studio.Designer.Services.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Altinn.Studio.Designer.Controllers;
+
+///
+/// Controller for handling UI folder related operations for v9 and newer, such as fetching and saving settings for validation on navigation and task navigation.
+///
+[ApiController]
+[Authorize]
+[AutoValidateAntiforgeryToken]
+[Route("designer/api/{org}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/ui-folders")]
+public class UiFoldersController : Controller
+{
+ private readonly IUiFoldersService _uiFoldersService;
+
+ public UiFoldersController(IUiFoldersService uiFoldersService)
+ {
+ _uiFoldersService = uiFoldersService;
+ }
+
+ private AltinnRepoEditingContext CreateContext(string org, string app)
+ {
+ string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
+ return AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer);
+ }
+
+ [HttpGet("settings/validation-on-navigation")]
+ [UseSystemTextJson]
+ public async Task GetGlobalValidationOnNavigation(
+ string org,
+ string app,
+ CancellationToken cancellationToken
+ )
+ {
+ AltinnRepoEditingContext editingContext = CreateContext(org, app);
+ ValidationOnNavigation? config = await _uiFoldersService.GetGlobalValidationOnNavigation(
+ editingContext,
+ cancellationToken
+ );
+ return Ok(config);
+ }
+
+ [HttpPost("settings/validation-on-navigation")]
+ [UseSystemTextJson]
+ public async Task SaveGlobalValidationOnNavigation(
+ string org,
+ string app,
+ [FromBody] ValidationOnNavigation config,
+ CancellationToken cancellationToken
+ )
+ {
+ AltinnRepoEditingContext editingContext = CreateContext(org, app);
+ await _uiFoldersService.SaveGlobalValidationOnNavigation(editingContext, config, cancellationToken);
+ return Ok();
+ }
+
+ [HttpDelete("settings/validation-on-navigation")]
+ public async Task DeleteGlobalValidationOnNavigation(
+ string org,
+ string app,
+ CancellationToken cancellationToken
+ )
+ {
+ AltinnRepoEditingContext editingContext = CreateContext(org, app);
+ await _uiFoldersService.SaveGlobalValidationOnNavigation(editingContext, null, cancellationToken);
+ return Ok();
+ }
+
+ [HttpGet("settings/task-navigation")]
+ [UseSystemTextJson]
+ public async Task GetGlobalTaskNavigation(
+ string org,
+ string app,
+ CancellationToken cancellationToken
+ )
+ {
+ AltinnRepoEditingContext editingContext = CreateContext(org, app);
+ IEnumerable result = await _uiFoldersService.GetGlobalTaskNavigationDto(
+ editingContext,
+ cancellationToken
+ );
+
+ return Ok(result);
+ }
+
+ [HttpPost("settings/task-navigation")]
+ [UseSystemTextJson]
+ public async Task UpdateGlobalTaskNavigation(
+ string org,
+ string app,
+ [FromBody] IEnumerable taskNavigationGroupDtoList,
+ CancellationToken cancellationToken
+ )
+ {
+ try
+ {
+ AltinnRepoEditingContext editingContext = CreateContext(org, app);
+ await _uiFoldersService.UpdateGlobalTaskNavigation(
+ editingContext,
+ taskNavigationGroupDtoList,
+ cancellationToken
+ );
+
+ return NoContent();
+ }
+ catch (ArgumentException exception)
+ {
+ return BadRequest(exception.Message);
+ }
+ }
+}
diff --git a/src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs b/src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs
index e731a94cfef..c207746688e 100644
--- a/src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs
+++ b/src/Designer/backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs
@@ -42,7 +42,7 @@ public class AltinnAppGitRepository : AltinnGitRepository
private const string CshtmlPath = "App/views/Home/Index.cshtml";
private const string ServiceConfigFilename = "config.json";
- private const string LayoutSettingsFilename = "Settings.json";
+ private const string SettingsFilename = "Settings.json";
private const string AppMetadataFilename = "applicationmetadata.json";
private const string LayoutSetsFilename = "layout-sets.json";
private const string FooterFilename = "footer.json";
@@ -617,6 +617,26 @@ public async Task SaveLayoutSets(LayoutSets layoutSets)
}
}
+ public async Task GetGlobalSettingsFile(CancellationToken cancellationToken = default)
+ {
+ string globalSettingsFilePath = GetPathToGlobalSettingsFile();
+ cancellationToken.ThrowIfCancellationRequested();
+ if (!FileExistsByRelativePath(globalSettingsFilePath))
+ {
+ return null;
+ }
+ string fileContent = await ReadTextByRelativePathAsync(globalSettingsFilePath, cancellationToken);
+ UiSettings globalSettingsFile = JsonSerializer.Deserialize(fileContent, s_jsonOptions);
+ return globalSettingsFile;
+ }
+
+ public async Task SaveGlobalSettingsFile(UiSettings globalSettings)
+ {
+ string globalSettingsFilePath = GetPathToGlobalSettingsFile();
+ string globalSettingsString = JsonSerializer.Serialize(globalSettings, s_jsonOptions);
+ await WriteTextByRelativePathAsync(globalSettingsFilePath, globalSettingsString);
+ }
+
public async Task GetFooter(CancellationToken cancellationToken = default)
{
string footerFilePath = GetPathToFooterFile();
@@ -1022,8 +1042,8 @@ private static string GetPathToLayoutFile(string layoutSetName, string layoutNam
private static string GetPathToLayoutSettings(string layoutSetName)
{
return string.IsNullOrEmpty(layoutSetName)
- ? Path.Combine(LayoutsFolderName, LayoutSettingsFilename)
- : Path.Combine(LayoutsFolderName, layoutSetName, LayoutSettingsFilename);
+ ? Path.Combine(LayoutsFolderName, SettingsFilename)
+ : Path.Combine(LayoutsFolderName, layoutSetName, SettingsFilename);
}
private static string GetPathToLayoutSetsFile()
@@ -1031,6 +1051,11 @@ private static string GetPathToLayoutSetsFile()
return Path.Combine(LayoutsFolderName, LayoutSetsFilename);
}
+ private static string GetPathToGlobalSettingsFile()
+ {
+ return Path.Combine(LayoutsFolderName, SettingsFilename);
+ }
+
private static string GetPathToFooterFile()
{
return Path.Combine(LayoutsFolderName, FooterFilename);
diff --git a/src/Designer/backend/src/Designer/Infrastructure/ServiceRegistration.cs b/src/Designer/backend/src/Designer/Infrastructure/ServiceRegistration.cs
index 81435799c4c..0c827103802 100644
--- a/src/Designer/backend/src/Designer/Infrastructure/ServiceRegistration.cs
+++ b/src/Designer/backend/src/Designer/Infrastructure/ServiceRegistration.cs
@@ -110,6 +110,7 @@ IConfiguration configuration
services.AddHttpClient();
services.AddHttpClient();
services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/src/Designer/backend/src/Designer/Services/Implementation/UiFoldersService.cs b/src/Designer/backend/src/Designer/Services/Implementation/UiFoldersService.cs
new file mode 100644
index 00000000000..617d58d4434
--- /dev/null
+++ b/src/Designer/backend/src/Designer/Services/Implementation/UiFoldersService.cs
@@ -0,0 +1,133 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.App.Core.Internal.Process.Elements;
+using Altinn.Studio.Designer.Infrastructure.GitRepository;
+using Altinn.Studio.Designer.Mappers;
+using Altinn.Studio.Designer.Models;
+using Altinn.Studio.Designer.Models.Dto;
+using Altinn.Studio.Designer.Services.Interfaces;
+
+public class UiFoldersService : IUiFoldersService
+{
+ private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory;
+
+ public UiFoldersService(IAltinnGitRepositoryFactory altinnGitRepositoryFactory)
+ {
+ _altinnGitRepositoryFactory = altinnGitRepositoryFactory;
+ }
+
+ public async Task GetGlobalValidationOnNavigation(
+ AltinnRepoEditingContext altinnRepoEditingContext,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
+ altinnRepoEditingContext.Org,
+ altinnRepoEditingContext.Repo,
+ altinnRepoEditingContext.Developer
+ );
+
+ UiSettings globalSettingsFile = await altinnAppGitRepository.GetGlobalSettingsFile(cancellationToken);
+ return globalSettingsFile?.ValidationOnNavigation;
+ }
+
+ public async Task SaveGlobalValidationOnNavigation(
+ AltinnRepoEditingContext altinnRepoEditingContext,
+ ValidationOnNavigation? config,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
+ altinnRepoEditingContext.Org,
+ altinnRepoEditingContext.Repo,
+ altinnRepoEditingContext.Developer
+ );
+
+ UiSettings globalSettingsFile =
+ await altinnAppGitRepository.GetGlobalSettingsFile(cancellationToken) ?? new UiSettings();
+
+ globalSettingsFile.ValidationOnNavigation = config;
+ await altinnAppGitRepository.SaveGlobalSettingsFile(globalSettingsFile);
+ }
+
+ public async Task> GetGlobalTaskNavigationDto(
+ AltinnRepoEditingContext editingContext,
+ CancellationToken cancellationToken
+ )
+ {
+ IEnumerable taskNavigationGroups = await GetGlobalTaskNavigation(
+ editingContext,
+ cancellationToken
+ );
+
+ IEnumerable tasks = GetTasks(editingContext, cancellationToken);
+
+ Dictionary taskTypesById = tasks.ToDictionary(
+ task => task.Id,
+ task => task.ExtensionElements?.TaskExtension?.TaskType
+ );
+
+ return taskNavigationGroups.Select(group => group.ToDto(taskId => taskTypesById.GetValueOrDefault(taskId)));
+ }
+
+ public async Task> GetGlobalTaskNavigation(
+ AltinnRepoEditingContext altinnRepoEditingContext,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
+ altinnRepoEditingContext.Org,
+ altinnRepoEditingContext.Repo,
+ altinnRepoEditingContext.Developer
+ );
+
+ UiSettings globalSettingsFile = await altinnAppGitRepository.GetGlobalSettingsFile(cancellationToken);
+ return globalSettingsFile?.TaskNavigation?.ToList() ?? [];
+ }
+
+ public IEnumerable GetTasks(
+ AltinnRepoEditingContext altinnRepoEditingContext,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
+ altinnRepoEditingContext.Org,
+ altinnRepoEditingContext.Repo,
+ altinnRepoEditingContext.Developer
+ );
+
+ Definitions definitions = altinnAppGitRepository.GetProcessDefinitions();
+ return definitions.Process.Tasks;
+ }
+
+ public async Task UpdateGlobalTaskNavigation(
+ AltinnRepoEditingContext altinnRepoEditingContext,
+ IEnumerable taskNavigationGroupDtoList,
+ CancellationToken cancellationToken
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
+ altinnRepoEditingContext.Org,
+ altinnRepoEditingContext.Repo,
+ altinnRepoEditingContext.Developer
+ );
+
+ IEnumerable taskNavigationGroupList = taskNavigationGroupDtoList.Select(x => x.ToDomain());
+
+ UiSettings globalSettingsFile =
+ await altinnAppGitRepository.GetGlobalSettingsFile(cancellationToken) ?? new UiSettings();
+
+ globalSettingsFile.TaskNavigation = taskNavigationGroupList.Any() ? taskNavigationGroupList : null;
+
+ await altinnAppGitRepository.SaveGlobalSettingsFile(globalSettingsFile);
+ }
+}
diff --git a/src/Designer/backend/src/Designer/Services/Interfaces/IUiFoldersService.cs b/src/Designer/backend/src/Designer/Services/Interfaces/IUiFoldersService.cs
new file mode 100644
index 00000000000..ef5b6985774
--- /dev/null
+++ b/src/Designer/backend/src/Designer/Services/Interfaces/IUiFoldersService.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.Studio.Designer.Models;
+using Altinn.Studio.Designer.Models.Dto;
+
+namespace Altinn.Studio.Designer.Services.Interfaces;
+
+public interface IUiFoldersService
+{
+ public Task GetGlobalValidationOnNavigation(
+ AltinnRepoEditingContext context,
+ CancellationToken cancellationToken
+ );
+
+ public Task SaveGlobalValidationOnNavigation(
+ AltinnRepoEditingContext editingContext,
+ ValidationOnNavigation? validationOnNavigation,
+ CancellationToken cancellationToken
+ );
+
+ public Task> GetGlobalTaskNavigationDto(
+ AltinnRepoEditingContext editingContext,
+ CancellationToken cancellationToken
+ );
+
+ public Task UpdateGlobalTaskNavigation(
+ AltinnRepoEditingContext editingContext,
+ IEnumerable taskNavigationGroupDtoList,
+ CancellationToken cancellationToken
+ );
+}