Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Delete all Summary 2.0 components that has a reference to deleted component #14126

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
25 changes: 19 additions & 6 deletions backend/src/Designer/Controllers/AppDevelopmentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,29 @@

if (formLayoutPayload.ComponentIdsChange is not null && !string.IsNullOrEmpty(layoutSetName))
{
foreach (var componentIdChange in formLayoutPayload.ComponentIdsChange)
{
await _mediator.Publish(new ComponentIdChangedEvent
if (componentIdChange.OldComponentId != componentIdChange.NewComponentId)
{
OldComponentId = componentIdChange.OldComponentId,
NewComponentId = componentIdChange.NewComponentId,
LayoutSetName = layoutSetName,
EditingContext = editingContext
}, cancellationToken);
if (componentIdChange.NewComponentId == null)
{
await _mediator.Publish(new ComponentDeletedEvent
{
ComponentId = componentIdChange.OldComponentId,
LayoutSetName = layoutSetName,
EditingContext = editingContext
}, cancellationToken);
}

await _mediator.Publish(new ComponentIdChangedEvent
{
OldComponentId = componentIdChange.OldComponentId,
NewComponentId = componentIdChange.NewComponentId,
LayoutSetName = layoutSetName,
EditingContext = editingContext
}, cancellationToken);
}
}

Check notice

Code scanning / CodeQL

Missed opportunity to use Where Note

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.
}
if (!formLayouts.ContainsKey(layoutName))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Altinn.App.Core.Helpers;
using Altinn.Studio.Designer.Events;
using Altinn.Studio.Designer.Hubs.SyncHub;
using Altinn.Studio.Designer.Infrastructure.GitRepository;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Services.Interfaces;
using MediatR;

namespace Altinn.Studio.Designer.EventHandlers.ComponentDeleted;

public class ComponentDeletedSummaryRefHandler : INotificationHandler<ComponentDeletedEvent>
{
private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory;
private readonly IFileSyncHandlerExecutor _fileSyncHandlerExecutor;

public ComponentDeletedSummaryRefHandler(IAltinnGitRepositoryFactory altinnGitRepositoryFactory, IFileSyncHandlerExecutor fileSyncHandlerExecutor)
{
_altinnGitRepositoryFactory = altinnGitRepositoryFactory;
_fileSyncHandlerExecutor = fileSyncHandlerExecutor;
}

public async Task Handle(ComponentDeletedEvent notification, CancellationToken cancellationToken)
{
bool hasChanges = false;
await _fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification(
notification.EditingContext,
SyncErrorCodes.ComponentDeletedSummaryRefSyncError,
"layouts",
async () =>
{
AltinnAppGitRepository repository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
notification.EditingContext.Org,
notification.EditingContext.Repo,
notification.EditingContext.Developer);

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

return await DeleteSummaryComponents(repository, notification.LayoutSetName, notification.ComponentId, cancellationToken);
});

}

private async Task<bool> DeleteSummaryComponents(AltinnAppGitRepository altinnAppGitRepository, string deletedComponentLayoutSetId, string deletedComponentId, CancellationToken cancellationToken)
{
bool hasChanged = false;

LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken);

foreach (LayoutSetConfig layoutSetConfig in layoutSets.Sets)
{
string[] layoutNames = altinnAppGitRepository.GetLayoutNames(layoutSetConfig.Id);
foreach (string layoutName in layoutNames)
{
JsonNode layout = await altinnAppGitRepository.GetLayout(layoutSetConfig.Id, layoutName);

if (layout?["data"]?["layout"] is not JsonArray layoutArray)
{
continue;
}

int initialCount = layoutArray.Count;
layoutArray.RemoveAll(layoutObject =>
{
if (layoutObject["type"]?.GetValue<string>() != "Summary2" || layoutObject["target"] is not JsonObject targetObject)
{
return false;
}

string summaryType = targetObject["type"]?.GetValue<string>();
string id = targetObject["id"]?.GetValue<string>();
string taskId = targetObject["taskId"]?.GetValue<string>();
string layouSetId = string.IsNullOrEmpty(taskId) ? layoutSetConfig.Id : layoutSets.Sets.FirstOrDefault(item => item.Tasks.Contains(taskId))?.Id;

return summaryType == "component" && layouSetId == deletedComponentLayoutSetId && id == deletedComponentId;
});

if (layoutArray.Count != initialCount)
{
await altinnAppGitRepository.SaveLayout(layoutSetConfig.Id, layoutName, layout, cancellationToken);
hasChanged = true;
}
}
}

return hasChanged;
}
}
11 changes: 11 additions & 0 deletions backend/src/Designer/Events/ComponentDeletedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Altinn.Studio.Designer.Models;
using MediatR;

namespace Altinn.Studio.Designer.Events;

public class ComponentDeletedEvent : INotification
{
public string ComponentId { get; set; }
public string LayoutSetName { get; set; }
public AltinnRepoEditingContext EditingContext { get; set; }
}
2 changes: 2 additions & 0 deletions backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public static class SyncErrorCodes
public const string LayoutSetSubFormButtonSyncError = nameof(LayoutSetSubFormButtonSyncError);
public const string SettingsComponentIdSyncError = nameof(SettingsComponentIdSyncError);
public const string LayoutPageAddSyncError = nameof(LayoutPageAddSyncError);
public const string ComponentDeletedSummaryRefSyncError = nameof(ComponentDeletedSummaryRefSyncError);

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
Expand Down Expand Up @@ -93,6 +94,52 @@
JsonUtils.DeepEquals(layout, savedLayout).Should().BeTrue();
}

[Theory]
[InlineData("ttd", "testUser", "Side1", "form")]
public async Task SaveFormLayoutWithDeletedComponent_DeletesAssociatedSummaryComponents_ReturnsOk(string org, string developer, string layoutName, string layoutSetName)
{
string actualApp = "deleted-component-before-delete";
string app = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(org, actualApp, developer, app);

string layout = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{layoutSetName}/layouts/{layoutName}.json");
JsonNode layoutWithDeletedComponent = JsonNode.Parse(layout);
JsonArray layoutArray = layoutWithDeletedComponent["data"]["layout"] as JsonArray;
layoutArray.RemoveAt(0);

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning test

Variable
layoutArray
may be null at this access because of
this
assignment.

string url = $"{VersionPrefix(org, app)}/form-layout/{layoutName}?layoutSetName={layoutSetName}";
var payload = new JsonObject
{
["componentIdsChange"] = new JsonArray() {
new JsonObject
{
["oldComponentId"] = "Input-XDDxRp",
}
},
["layout"] = layoutWithDeletedComponent
};
HttpResponseMessage response = await SendHttpRequest(url, payload);
response.StatusCode.Should().Be(HttpStatusCode.OK);

string expectedApp = "deleted-component-after-delete";

string[] layoutPaths = [
"form/layouts/Side1.json",
"form/layouts/Side2.json",
"form/layouts/Side3.json",
"Activity_0z4cgvm/layouts/Side1.json",
"Activity_0z4cgvm/layouts/Side2.json",
"Activity_0z4cgvm/layouts/Side3.json"
];

layoutPaths.ToList().ForEach(file =>
{
string actual = TestDataHelper.GetFileFromRepo(org, app, developer, $"App/ui/{file}");
string expected = TestDataHelper.GetFileFromRepo(org, expectedApp, developer, $"App/ui/{file}");
JsonUtils.DeepEquals(actual, expected).Should().BeTrue();
});
}

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "testLayout", "layoutSet1", "TestData/App/ui/layoutWithUnknownProperties.json")]
public async Task SaveFormLayoutWithNewPageLanguageUpdate_ReturnsOk(string org, string app, string developer, string layoutName, string layoutSetName, string layoutPath)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"id": "NavigationButtons-DfcNol",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"id": "NavigationButtons-GAW8Dx",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"target": {
"type": "component",
"id": "Input-1GAVAE",
"taskId": "Task_1"
},
"id": "Summary2-PBrjWH",
"type": "Summary2"
},
{
"target": {
"type": "component",
"id": "",
"taskId": ""
},
"id": "Summary2-WokR5T",
"type": "Summary2"
},
{
"id": "NavigationButtons-Knkbt3",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"hidden": false,
"layout": [
{
"dataModelBindings": {
"simpleBinding": ""
},
"id": "Input-1GAVAE",
"type": "Input"
},
{
"id": "NavigationButtons-C2O3bE",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"id": "NavigationButtons-TFHw9D",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"target": {
"type": "component",
"id": "Input-1GAVAE",
"taskId": ""
},
"id": "Summary2-GcK8zp",
"type": "Summary2"
},
{
"target": {
"type": "component",
"id": "",
"taskId": ""
},
"id": "Summary2-3TdVTp",
"type": "Summary2"
},
{
"id": "NavigationButtons-tIAlCU",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"target": {
"type": "component",
"id": "Input-XDDxRp",
"taskId": "Task_1"
},
"id": "Summary2-QN78Y8",
"type": "Summary2"
},
{
"id": "NavigationButtons-DfcNol",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"target": {
"type": "component",
"id": "Input-XDDxRp",
"taskId": "Task_1"
},
"id": "Summary2-9OFVzC",
"type": "Summary2"
},
{
"id": "NavigationButtons-GAW8Dx",
"showBackButton": true,
"textResourceBindings": {},
"type": "NavigationButtons"
}
]
}
}
Loading
Loading