Skip to content

Commit

Permalink
Merge branch 'main' into renovate/nuget-minor-patch
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Oct 15, 2024
2 parents d3044aa + 0726885 commit c380de9
Show file tree
Hide file tree
Showing 29 changed files with 455 additions and 541 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,45 @@ public ProcessTaskIdChangedLayoutSetsHandler(IAltinnGitRepositoryFactory altinnG

public async Task Handle(ProcessTaskIdChangedEvent notification, CancellationToken cancellationToken)
{
bool hasChanges = false;
var repository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
notification.EditingContext.Org,
notification.EditingContext.Repo,
notification.EditingContext.Developer);

if (!repository.AppUsesLayoutSets())
{
return;
}

await _fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification(
notification.EditingContext,
SyncErrorCodes.LayoutSetsTaskIdSyncError,
"App/ui/layout-sets.json",
async () =>
{
var repository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
notification.EditingContext.Org,
notification.EditingContext.Repo,
notification.EditingContext.Developer);
if (!repository.AppUsesLayoutSets())
{
return hasChanges;
}
bool hasChanged = false;
var layoutSets = await repository.GetLayoutSetsFile(cancellationToken);
if (TryChangeTaskIds(layoutSets, notification.OldId, notification.NewId))
if (TryChangeLayoutSetTaskIds(layoutSets, notification.OldId, notification.NewId))
{
await repository.SaveLayoutSets(layoutSets);
hasChanges = true;
hasChanged = true;
}
return hasChanges;
return hasChanged;
});
}

private static bool TryChangeTaskIds(LayoutSets layoutSets, string oldId, string newId)
private static bool TryChangeLayoutSetTaskIds(LayoutSets layoutSets, string oldId, string newId)
{
bool changed = false;
foreach (var layoutSet in layoutSets.Sets.Where(layoutSet => layoutSet.Tasks.Contains(oldId)))
bool hasChanged = false;
foreach (var layoutSet in layoutSets.Sets.Where(layoutSet => layoutSet.Tasks != null && layoutSet.Tasks.Contains(oldId)))
{
layoutSet.Tasks.Remove(oldId);
layoutSet.Tasks.Add(newId);
changed = true;
layoutSet.Tasks!.Remove(oldId);
layoutSet.Tasks!.Add(newId);
hasChanged = true;
}

return changed;
return hasChanged;
}
}
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;
}
}
1 change: 1 addition & 0 deletions backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public static class SyncErrorCodes
{
public const string ApplicationMetadataTaskIdSyncError = nameof(ApplicationMetadataTaskIdSyncError);
public const string LayoutSetsTaskIdSyncError = nameof(LayoutSetsTaskIdSyncError);
public const string LayoutTaskIdSyncError = nameof(LayoutTaskIdSyncError);
public const string PolicyFileTaskIdSyncError = nameof(PolicyFileTaskIdSyncError);
public const string ApplicationMetadataDataTypeSyncError = nameof(ApplicationMetadataDataTypeSyncError);
public const string LayoutSetsDataTypeSyncError = nameof(LayoutSetsDataTypeSyncError);
Expand Down
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" } } };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ public async Task UpsertProcessDefinition_ShouldSyncLayoutSets(string org, strin

string processContent = SharedResourcesHelper.LoadTestDataAsString(bpmnFilePath);
processContent.Replace(metadata.TaskIdChange.OldId, metadata.TaskIdChange.NewId);
//processContent = metadata.TaskIdChange.Aggregate(processContent,
// (current, metadataTaskIdChange) => current.Replace(metadataTaskIdChange.OldId, metadataTaskIdChange.NewId));
using var processStream = new MemoryStream(Encoding.UTF8.GetBytes(processContent));

string url = VersionPrefix(org, targetRepository);
Expand Down
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"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { screen } from '@testing-library/react';
import type { LandingPagePanelProps } from './LandingPagePanel';
import { LandingPagePanel } from './LandingPagePanel';
import userEvent from '@testing-library/user-event';
import { fileSelectorInputId } from '@studio/testing/testids';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { renderWithProviders } from '../../../test/mocks';

Expand All @@ -23,7 +22,9 @@ describe('LandingPagePanel', () => {
expect(
screen.getByText(textMock('app_data_modelling.landing_dialog_paragraph')),
).toBeInTheDocument();
expect(screen.getByTestId(fileSelectorInputId)).toBeInTheDocument();
expect(
screen.getByLabelText(textMock('app_data_modelling.landing_dialog_upload')),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: textMock('app_data_modelling.landing_dialog_upload') }),
).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
}

.toolbar > *,
.toolbar > :global(.MuiGrid-item) {
.toolbar > {
margin-right: 1rem;
}

Expand Down
Loading

0 comments on commit c380de9

Please sign in to comment.