Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion src/Altinn.App.Api/Controllers/ResourceController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Altinn.App.Core.Features;
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Internal.App;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -11,14 +12,17 @@ namespace Altinn.App.Api.Controllers;
public class ResourceController : ControllerBase
{
private readonly IAppResources _appResourceService;
private readonly AppImplementationFactory _appImplementationFactory;

/// <summary>
/// Initializes a new instance of the <see cref="ResourceController"/> class
/// </summary>
/// <param name="appResourcesService">The execution service</param>
public ResourceController(IAppResources appResourcesService)
/// <param name="serviceProvider">The service provider</param>
public ResourceController(IAppResources appResourcesService, IServiceProvider serviceProvider)
{
_appResourceService = appResourcesService;
_appImplementationFactory = serviceProvider.GetRequiredService<AppImplementationFactory>();
}

/// <summary>
Expand Down Expand Up @@ -71,6 +75,41 @@ public ActionResult GetLayouts(string org, string app, string id)
return Ok(layouts);
}

/// <summary>
/// Endpoint for layouts with instance context.
/// Uses ICustomLayoutForInstance if implemented with IAppResources as fallback.
/// </summary>
/// <param name="org">The application owner short name</param>
/// <param name="app">The application name</param>
/// <param name="instanceOwnerPartyId">The instance owner party id</param>
/// <param name="instanceId">The instance id</param>
/// <param name="layoutSetId">The layout set id</param>
/// <returns>A collection of FormLayout objects in JSON format.</returns>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "application/json")]
[HttpGet]
[Route("{org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceId}/layouts/{layoutSetId}")]
public async Task<ActionResult> GetInstanceLayouts(
string org,
string app,
int instanceOwnerPartyId,
string instanceId,
string layoutSetId
)
{
ICustomLayoutForInstance? customLayoutService = _appImplementationFactory.Get<ICustomLayoutForInstance>();
if (customLayoutService is not null)
{
string? customLayout = await customLayoutService.GetCustomLayoutForInstance(
layoutSetId,
instanceOwnerPartyId,
Guid.Parse(instanceId)
);
return Ok(customLayout);
}
string layouts = _appResourceService.GetLayoutsForSet(layoutSetId);
return Ok(layouts);
}

/// <summary>
/// Get the layout settings.
/// </summary>
Expand All @@ -86,6 +125,41 @@ public ActionResult GetLayoutSettings(string org, string app)
return Ok(settings);
}

/// <summary>
/// Endpoint for layout settings with instance context.
/// Uses ICustomLayoutForInstance if implemented with IAppResources as fallback.
/// </summary>
/// <param name="org">The application owner short name</param>
/// <param name="app">The application name</param>
/// <param name="instanceOwnerPartyId">The instance owner party id</param>
/// <param name="instanceId">The instance id</param>
/// <param name="layoutSetId">The layout set id</param>
/// <returns>The settings in the form of a string.</returns>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "application/json")]
[HttpGet]
[Route("{org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceId}/layoutsettings/{layoutSetId}")]
public async Task<ActionResult> GetInstanceLayoutSettings(
string org,
string app,
int instanceOwnerPartyId,
string instanceId,
string layoutSetId
)
{
ICustomLayoutForInstance? customLayoutService = _appImplementationFactory.Get<ICustomLayoutForInstance>();
if (customLayoutService is not null)
{
string? customLayoutSettings = await customLayoutService.GetCustomLayoutSettingsForInstance(
layoutSetId,
instanceOwnerPartyId,
Guid.Parse(instanceId)
);
return Ok(customLayoutSettings);
}
string? settings = _appResourceService.GetLayoutSettingsStringForSet(layoutSetId);
return Ok(settings);
}

/// <summary>
/// Get the layout settings.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Altinn.App.Core/Features/FeatureFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ public static class FeatureFlags
/// return validation errors in the response body instead of a string.
/// </summary>
public const string JsonObjectInDataResponse = "JsonObjectInDataResponse";

// TODO: write a better summary here
/// <summary>
/// Enabling this feature changes backend endpoint used for layouts to
/// add instance identifier.
/// </summary>
public const string AddInstanceIdentifierToLayoutRequests = "AddInstanceIdentifierToLayoutRequests";
}
9 changes: 9 additions & 0 deletions src/Altinn.App.Core/Internal/App/FrontendFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ public FrontendFeatures(IFeatureManager featureManager)
{
_features.Add("jsonObjectInDataResponse", false);
}

if (featureManager.IsEnabledAsync(FeatureFlags.AddInstanceIdentifierToLayoutRequests).Result)
{
_features.Add("addInstanceIdentifierToLayoutRequests", true);
}
else
{
_features.Add("addInstanceIdentifierToLayoutRequests", false);
}
}

/// <inheritdoc />
Expand Down
26 changes: 26 additions & 0 deletions src/Altinn.App.Core/Internal/App/ICustomLayoutForInstance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Altinn.App.Core.Features;

namespace Altinn.App.Core.Internal.App;

/// <summary>
/// Interface for getting custom layouts for an instance.
/// </summary>
[ImplementableByApps]
public interface ICustomLayoutForInstance
{
/// <summary>
/// Gets the custom layout
/// </summary>
/// <param name="layoutSetId">The layout set ID</param>
/// <param name="instanceOwnerPartyId">The instance owner party ID</param>
/// <param name="instanceGuid">The instance GUID</param>
Task<string?> GetCustomLayoutForInstance(string layoutSetId, int instanceOwnerPartyId, Guid instanceGuid);

/// <summary>
/// Gets the custom layout settings
/// </summary>
/// <param name="layoutSetId">The layout set ID</param>
/// <param name="instanceOwnerPartyId">The instance owner party ID</param>
/// <param name="instanceGuid">The instance GUID</param>
Task<string?> GetCustomLayoutSettingsForInstance(string layoutSetId, int instanceOwnerPartyId, Guid instanceGuid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System.Net;
using System.Text.Json;
using Altinn.App.Api.Tests.Data;
using Altinn.App.Core.Internal.App;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;

namespace Altinn.App.Api.Tests.Controllers;

public class ResourceController_CustomLayoutTests : ApiTestBase, IClassFixture<WebApplicationFactory<Program>>
{
public ResourceController_CustomLayoutTests(WebApplicationFactory<Program> factory, ITestOutputHelper outputHelper)
: base(factory, outputHelper) { }

private class CustomLayoutForInstance : ICustomLayoutForInstance
{
public Task<string?> GetCustomLayoutForInstance(string layoutSetId, int instanceOwnerPartyId, Guid instanceId)
{
return Task.FromResult<string?>(instanceId.ToString());
}

public Task<string?> GetCustomLayoutSettingsForInstance(
string layoutSetId,
int instanceOwnerPartyId,
Guid instanceId
)
{
return Task.FromResult<string?>(instanceId.ToString());
}
}

[Fact]
public async Task GetLayoutsForSet_WithCustomLayoutForInstanceService_ReturnsOk()
{
OverrideServicesForThisTest = (services) =>
{
services.AddSingleton<ICustomLayoutForInstance, CustomLayoutForInstance>();
};

string org = "tdd";
string app = "contributer-restriction";
int instanceOwnerPartyId = 500600;
Guid instanceGuid = Guid.Parse("cff1cb24-5bc1-4888-8e06-c634753c5144");
string layoutSetId = "default";
using HttpClient client = GetRootedUserClient(org, app, 1337, instanceOwnerPartyId);

TestData.PrepareInstance(org, app, instanceOwnerPartyId, instanceGuid);
var response = await client.GetAsync(
$"/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/layouts/{layoutSetId}"
);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(instanceGuid.ToString(), content);
}

[Fact]
public async Task GetLayoutsForSet_WithoutCustomLayoutForInstanceService_ReturnsOk()
{
string org = "tdd";
string app = "contributer-restriction";
int instanceOwnerPartyId = 500600;
Guid instanceGuid = Guid.Parse("cff1cb24-5bc1-4888-8e06-c634753c5144");
string layoutSetId = "default";
using HttpClient client = GetRootedUserClient(org, app, 1337, instanceOwnerPartyId);

TestData.PrepareInstance(org, app, instanceOwnerPartyId, instanceGuid);
var response = await client.GetAsync(
$"/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/layouts/{layoutSetId}"
);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
using var jsonDoc = JsonDocument.Parse(content);
var root = jsonDoc.RootElement;
Assert.Equal(JsonValueKind.Object, root.ValueKind);
Assert.True(root.TryGetProperty("page", out var pageLayout));
Assert.True(pageLayout.TryGetProperty("data", out var data));
Assert.True(data.TryGetProperty("layout", out var layout));
Assert.Equal(JsonValueKind.Array, layout.ValueKind);
Assert.True(layout.GetArrayLength() > 0);
}

[Fact]
public async Task GetLayoutSettingsForSet_WithCustomLayoutForInstanceService_ReturnsOk()
{
OverrideServicesForThisTest = (services) =>
{
services.AddSingleton<ICustomLayoutForInstance, CustomLayoutForInstance>();
};

string org = "tdd";
string app = "contributer-restriction";
int instanceOwnerPartyId = 500600;
Guid instanceGuid = Guid.Parse("cff1cb24-5bc1-4888-8e06-c634753c5144");
string layoutSetId = "default";
using HttpClient client = GetRootedUserClient(org, app, 1337, instanceOwnerPartyId);

TestData.PrepareInstance(org, app, instanceOwnerPartyId, instanceGuid);
var response = await client.GetAsync(
$"/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/layoutsettings/{layoutSetId}"
);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Equal(instanceGuid.ToString(), content);
}

[Fact]
public async Task GetLayoutSettingsForSet_WithoutCustomLayoutForInstanceService_ReturnsOk()
{
string org = "tdd";
string app = "contributer-restriction";
int instanceOwnerPartyId = 500600;
Guid instanceGuid = Guid.Parse("cff1cb24-5bc1-4888-8e06-c634753c5144");
string layoutSetId = "default";
using HttpClient client = GetRootedUserClient(org, app, 1337, instanceOwnerPartyId);

TestData.PrepareInstance(org, app, instanceOwnerPartyId, instanceGuid);
var response = await client.GetAsync(
$"/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/layoutsettings/{layoutSetId}"
);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
using var jsonDoc = JsonDocument.Parse(content);
var root = jsonDoc.RootElement;
Assert.Equal(JsonValueKind.Object, root.ValueKind);
}
}
Loading
Loading