-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Auto-update taskId in layout files
...when taskId is updated from process editor (#13648)
- Loading branch information
1 parent
0a51994
commit 0726885
Showing
6 changed files
with
269 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
...end/src/Designer/EventHandlers/ProcessTaskIdChanged/ProcessTaskIdChangedLayoutsHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text.Json.Nodes; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Altinn.Studio.Designer.Events; | ||
using Altinn.Studio.Designer.Hubs.SyncHub; | ||
using Altinn.Studio.Designer.Services.Interfaces; | ||
using MediatR; | ||
|
||
namespace Altinn.Studio.Designer.EventHandlers.ProcessTaskIdChanged; | ||
|
||
public class ProcessTaskIdChangedLayoutsHandler : INotificationHandler<ProcessTaskIdChangedEvent> | ||
{ | ||
private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory; | ||
private readonly IFileSyncHandlerExecutor _fileSyncHandlerExecutor; | ||
|
||
public ProcessTaskIdChangedLayoutsHandler(IAltinnGitRepositoryFactory altinnGitRepositoryFactory, | ||
IFileSyncHandlerExecutor fileSyncHandlerExecutor) | ||
{ | ||
_altinnGitRepositoryFactory = altinnGitRepositoryFactory; | ||
_fileSyncHandlerExecutor = fileSyncHandlerExecutor; | ||
} | ||
|
||
public async Task Handle(ProcessTaskIdChangedEvent notification, CancellationToken cancellationToken) | ||
{ | ||
var repository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository( | ||
notification.EditingContext.Org, | ||
notification.EditingContext.Repo, | ||
notification.EditingContext.Developer); | ||
|
||
if (!repository.AppUsesLayoutSets()) | ||
{ | ||
return; | ||
} | ||
|
||
var layoutSetsFile = await repository.GetLayoutSetsFile(cancellationToken); | ||
|
||
foreach (string layoutSetName in layoutSetsFile.Sets.Select(layoutSet => layoutSet.Id)) | ||
{ | ||
string[] layoutNames; | ||
try | ||
{ | ||
layoutNames = repository.GetLayoutNames(layoutSetName); | ||
} | ||
catch (FileNotFoundException) | ||
{ | ||
continue; | ||
} | ||
|
||
await _fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification( | ||
notification.EditingContext, | ||
SyncErrorCodes.LayoutTaskIdSyncError, | ||
$"App/ui/{layoutSetName}/layouts", | ||
async () => | ||
{ | ||
bool hasChanged = false; | ||
|
||
foreach (string layoutName in layoutNames) | ||
{ | ||
var layout = await repository.GetLayout(layoutSetName, layoutName, cancellationToken); | ||
if (TryChangeLayoutTaskIds(layout, notification.OldId, notification.NewId)) | ||
{ | ||
await repository.SaveLayout(layoutSetName, layoutName, layout, cancellationToken); | ||
hasChanged = true; | ||
} | ||
} | ||
|
||
return hasChanged; | ||
}); | ||
} | ||
} | ||
|
||
private static bool TryChangeLayoutTaskIds(JsonNode node, string oldId, string newId) | ||
{ | ||
bool hasChanged = false; | ||
|
||
if (node is JsonObject jsonObject) | ||
{ | ||
foreach (var property in jsonObject.ToList()) | ||
{ | ||
if (property.Key == "taskId" && property.Value?.ToString() == oldId) | ||
{ | ||
jsonObject["taskId"] = newId; | ||
hasChanged = true; | ||
} | ||
|
||
hasChanged |= TryChangeLayoutTaskIds(property.Value, oldId, newId); | ||
} | ||
} | ||
else if (node is JsonArray jsonArray) | ||
{ | ||
foreach (var element in jsonArray) | ||
{ | ||
hasChanged |= TryChangeLayoutTaskIds(element, oldId, newId); | ||
} | ||
} | ||
|
||
return hasChanged; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
...rollers/ProcessModelingController/FileSync/TaskIdChangeTests/LayoutFileSyncTaskIdTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Mime; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Text.Json.Nodes; | ||
using System.Threading.Tasks; | ||
using Altinn.Studio.Designer.Models.Dto; | ||
using Designer.Tests.Controllers.ApiTests; | ||
using Designer.Tests.Utils; | ||
using FluentAssertions; | ||
using Microsoft.AspNetCore.Mvc.Testing; | ||
using SharedResources.Tests; | ||
using Xunit; | ||
|
||
namespace Designer.Tests.Controllers.ProcessModelingController.FileSync.TaskIdChangeTests; | ||
|
||
public class LayoutFileSyncTaskIdTests : DesignerEndpointsTestsBase<LayoutFileSyncTaskIdTests>, IClassFixture<WebApplicationFactory<Program>> | ||
{ | ||
public LayoutFileSyncTaskIdTests(WebApplicationFactory<Program> factory) : base(factory) | ||
{ | ||
} | ||
|
||
private static string GetVersionPrefix(string org, string repository) | ||
{ | ||
return $"/designer/api/{org}/{repository}/process-modelling/process-definition-latest"; | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(GetReferencedTaskIdTestData))] | ||
public async Task UpsertProcessDefinitionAndNotify_ShouldUpdateLayout_WhenReferencedTaskIdIsChanged( | ||
string org, | ||
string app, | ||
string developer, | ||
string bpmnFilePath, | ||
ProcessDefinitionMetadata metadata) | ||
{ | ||
// Arrange | ||
string targetRepository = TestDataHelper.GenerateTestRepoName(); | ||
await CopyRepositoryForTest(org, app, developer, targetRepository); | ||
|
||
string processContent = SharedResourcesHelper.LoadTestDataAsString(bpmnFilePath) | ||
.Replace(metadata.TaskIdChange.OldId, metadata.TaskIdChange.NewId); | ||
|
||
using var processStream = new MemoryStream(Encoding.UTF8.GetBytes(processContent)); | ||
|
||
string url = GetVersionPrefix(org, targetRepository); | ||
|
||
using var form = new MultipartFormDataContent(); | ||
form.Add(new StreamContent(processStream), "content", "process.bpmn"); | ||
form.Add(new StringContent( | ||
JsonSerializer.Serialize(metadata, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }), | ||
Encoding.UTF8, | ||
MediaTypeNames.Application.Json), "metadata"); | ||
|
||
// Act | ||
using var response = await HttpClient.PutAsync(url, form); | ||
|
||
// Assert | ||
response.StatusCode.Should().Be(HttpStatusCode.Accepted); | ||
|
||
string layoutFilePath = "App/ui/layoutSet2/layouts/layoutFile2InSet2.json"; | ||
string layoutContent = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, layoutFilePath); | ||
|
||
JsonNode layout = JsonSerializer.Deserialize<JsonNode>(layoutContent); | ||
string newTaskId = layout["data"]?["layout"]?[0]?["target"]?["taskId"]?.ToString(); | ||
|
||
newTaskId.Should().Be(metadata.TaskIdChange.NewId); | ||
newTaskId.Should().NotBe(metadata.TaskIdChange.OldId); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(GetUnreferencedTaskIdTestData))] | ||
public async Task UpsertProcessDefinitionAndNotify_ShouldNotUpdateLayout_WhenUnreferencedTaskIdIsChanged( | ||
string org, | ||
string app, | ||
string developer, | ||
string bpmnFilePath, | ||
ProcessDefinitionMetadata metadata) | ||
{ | ||
// Arrange | ||
string targetRepository = TestDataHelper.GenerateTestRepoName(); | ||
await CopyRepositoryForTest(org, app, developer, targetRepository); | ||
|
||
string layoutPath = "App/ui/layoutSet2/layouts/layoutFile2InSet2.json"; | ||
string layoutBeforeUpdate = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, layoutPath); | ||
|
||
string processContent = SharedResourcesHelper.LoadTestDataAsString(bpmnFilePath) | ||
.Replace(metadata.TaskIdChange.OldId, metadata.TaskIdChange.NewId); | ||
|
||
using var processStream = new MemoryStream(Encoding.UTF8.GetBytes(processContent)); | ||
|
||
string url = GetVersionPrefix(org, targetRepository); | ||
|
||
using var form = new MultipartFormDataContent(); | ||
form.Add(new StreamContent(processStream), "content", "process.bpmn"); | ||
form.Add(new StringContent( | ||
JsonSerializer.Serialize(metadata, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }), | ||
Encoding.UTF8, | ||
MediaTypeNames.Application.Json), "metadata"); | ||
|
||
// Act | ||
using var response = await HttpClient.PutAsync(url, form); | ||
|
||
// Assert | ||
response.StatusCode.Should().Be(HttpStatusCode.Accepted); | ||
|
||
string layoutAfterUpdate = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, layoutPath); | ||
layoutAfterUpdate.Should().Be(layoutBeforeUpdate); | ||
} | ||
|
||
public static IEnumerable<object[]> GetReferencedTaskIdTestData() | ||
{ | ||
// "Task_1" is targeted by Summary2 component in "app-with-layoutsets" | ||
yield return new object[] | ||
{ | ||
"ttd", | ||
"app-with-layoutsets", | ||
"testUser", "App/config/process/process.bpmn", | ||
new ProcessDefinitionMetadata { TaskIdChange = new TaskIdChange { OldId = "Task_1", NewId = "SomeNewId" } } | ||
}; | ||
} | ||
|
||
public static IEnumerable<object[]> GetUnreferencedTaskIdTestData() | ||
{ | ||
// "Task_2" is not targeted by Summary2 component in "app-with-layoutsets" | ||
yield return new object[] { "ttd", | ||
"app-with-layoutsets", | ||
"testUser", | ||
"App/config/process/process.bpmn", | ||
new ProcessDefinitionMetadata { TaskIdChange = new TaskIdChange { OldId = "Task_2", NewId = "SomeNewId" } } }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 11 additions & 1 deletion
12
...itories/testUser/ttd/app-with-layoutsets/App/ui/layoutSet2/layouts/layoutFile2InSet2.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,16 @@ | ||
{ | ||
"schema":"https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", | ||
"data": { | ||
"layout": [] | ||
"layout": [ | ||
{ | ||
"target": { | ||
"type": "page", | ||
"id": "layoutFile1inSet1", | ||
"taskId": "Task_1" | ||
}, | ||
"id": "Summary2-B5VMK2", | ||
"type": "Summary2" | ||
} | ||
] | ||
} | ||
} |