From 165af4663dd151e594b147dece712c12493b9063 Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Thu, 3 Oct 2024 22:39:31 +0200 Subject: [PATCH] Reorganize ProcessTaskFinalizer to use CachedInstanceDataAccessor --- .../Common/ProcessTaskFinalizer.cs | 139 ++++-------------- ...sNext_PdfFails_DataIsUnlocked.verified.txt | 18 +++ .../Common/ProcessTaskFinalizerTests.cs | 1 + 3 files changed, 49 insertions(+), 109 deletions(-) diff --git a/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs b/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs index e2c0f6db4..d3c4dcffe 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs @@ -1,12 +1,10 @@ -using System.Globalization; -using System.Reflection; using System.Text.Json; using Altinn.App.Core.Configuration; -using Altinn.App.Core.Features; using Altinn.App.Core.Helpers; using Altinn.App.Core.Helpers.DataModel; using Altinn.App.Core.Helpers.Serialization; using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Expressions; using Altinn.App.Core.Models; @@ -20,6 +18,7 @@ public class ProcessTaskFinalizer : IProcessTaskFinalizer { private readonly IAppMetadata _appMetadata; private readonly IDataClient _dataClient; + private readonly IAppModel _appModel; private readonly ILayoutEvaluatorStateInitializer _layoutEvaluatorStateInitializer; private readonly IOptions _appSettings; private readonly ModelSerializationService _modelSerializer; @@ -30,6 +29,7 @@ public class ProcessTaskFinalizer : IProcessTaskFinalizer public ProcessTaskFinalizer( IAppMetadata appMetadata, IDataClient dataClient, + IAppModel appModel, ModelSerializationService modelSerializer, ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer, IOptions appSettings @@ -39,100 +39,49 @@ IOptions appSettings _dataClient = dataClient; _layoutEvaluatorStateInitializer = layoutEvaluatorStateInitializer; _appSettings = appSettings; + _appModel = appModel; _modelSerializer = modelSerializer; } /// public async Task Finalize(string taskId, Instance instance) { - ApplicationMetadata applicationMetadata = await _appMetadata.GetApplicationMetadata(); - List connectedDataTypes = applicationMetadata.DataTypes.FindAll(dt => dt.TaskId == taskId); - var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _modelSerializer); - var changedDataElements = await RunRemoveFieldsInModelOnTaskComplete( - instance, - dataAccessor, - taskId, - connectedDataTypes, - language: null - ); - // Save changes to the data elements with app logic that was changed. - await Task.WhenAll( - changedDataElements.Select(async dataElement => - { - var data = await dataAccessor.GetFormData(dataElement); - return _dataClient.UpdateData( - data, - Guid.Parse(instance.Id.Split('/')[1]), - data.GetType(), - instance.Org, - instance.AppId.Split('/')[1], - int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture), - Guid.Parse(dataElement.Id) - ); - }) - ); - } + ApplicationMetadata applicationMetadata = await _appMetadata.GetApplicationMetadata(); - private async Task> RunRemoveFieldsInModelOnTaskComplete( - Instance instance, - IInstanceDataAccessor dataAccessor, - string taskId, - List dataTypesToLock, - string? language = null - ) - { - ArgumentNullException.ThrowIfNull(instance.Data); - HashSet modifiedDataElements = []; + List tasks = []; + foreach ( + var dataType in applicationMetadata.DataTypes.Where(dt => + dt.TaskId == taskId && dt.AppLogic?.ClassRef is not null + ) + ) + { + foreach (var dataElement in instance.Data.Where(de => de.DataType == dataType.Id)) + { + tasks.Add(RemoveFieldsOnTaskComplete(dataAccessor, taskId, applicationMetadata, dataElement, dataType)); + } + } + await Task.WhenAll(tasks); - var dataTypesWithLogic = dataTypesToLock.Where(d => !string.IsNullOrEmpty(d.AppLogic?.ClassRef)).ToList(); - await Task.WhenAll( - instance - .Data.Join( - dataTypesWithLogic, - de => de.DataType, - dt => dt.Id, - (de, dt) => (dataElement: de, dataType: dt) - ) - .Select( - async (d) => - { - if ( - await RemoveFieldsOnTaskComplete( - instance, - dataAccessor, - taskId, - dataTypesWithLogic, - d.dataElement, - d.dataType, - language - ) - ) - { - modifiedDataElements.Add(d.dataElement); - } - } - ) - ); - return modifiedDataElements; + var changes = dataAccessor.GetDataElementChanges(initializeAltinnRowId: false); + await dataAccessor.UpdateInstanceData(); + await dataAccessor.SaveChanges(changes); } - private async Task RemoveFieldsOnTaskComplete( - Instance instance, - IInstanceDataAccessor dataAccessor, + private async Task RemoveFieldsOnTaskComplete( + CachedInstanceDataAccessor dataAccessor, string taskId, - List dataTypesWithLogic, + ApplicationMetadata applicationMetadata, DataElement dataElement, DataType dataType, string? language = null ) { - bool isModified = false; var data = await dataAccessor.GetFormData(dataElement); // remove AltinnRowIds - isModified |= ObjectUtils.RemoveAltinnRowId(data); + ObjectUtils.RemoveAltinnRowId(data); // Remove hidden data before validation, ignore hidden rows. if (_appSettings.Value?.RemoveHiddenData == true) @@ -147,20 +96,17 @@ private async Task RemoveFieldsOnTaskComplete( language ); await LayoutEvaluator.RemoveHiddenData(evaluationState, RowRemovalOption.DeleteRow); - // TODO: Make RemoveHiddenData return a bool indicating if data was removed - isModified = true; } // Remove shadow fields // TODO: Use reflection or code generation instead of JsonSerializer if (dataType.AppLogic?.ShadowFields?.Prefix != null) { - Type saveToModelType = data.GetType(); string serializedData = JsonSerializerIgnorePrefix.Serialize(data, dataType.AppLogic.ShadowFields.Prefix); if (dataType.AppLogic.ShadowFields.SaveToDataType != null) { // Save the shadow fields to another data type - DataType? saveToDataType = dataTypesWithLogic.Find(dt => + DataType? saveToDataType = applicationMetadata.DataTypes.Find(dt => dt.Id == dataType.AppLogic.ShadowFields.SaveToDataType ); if (saveToDataType == null) @@ -169,6 +115,7 @@ private async Task RemoveFieldsOnTaskComplete( $"SaveToDataType {dataType.AppLogic.ShadowFields.SaveToDataType} not found" ); } + Type saveToModelType = _appModel.GetModelType(saveToDataType.AppLogic.ClassRef); object updatedData = JsonSerializer.Deserialize(serializedData, saveToModelType) @@ -176,44 +123,18 @@ private async Task RemoveFieldsOnTaskComplete( "Could not deserialize back datamodel after removing shadow fields. Data was \"null\"" ); // Save a new data element with the cleaned data without shadow fields. - Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); - string app = instance.AppId.Split("/")[1]; - int instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture); - var newDataElement = await _dataClient.InsertFormData( - updatedData, - instanceGuid, - saveToModelType, - instance.Org, - app, - instanceOwnerPartyId, - saveToDataType.Id - ); - instance.Data.Add(newDataElement); + dataAccessor.AddFormDataElement(saveToDataType.Id, updatedData); } else { // Remove the shadow fields from the data using JsonSerializer var newData = - JsonSerializer.Deserialize(serializedData, saveToModelType) + JsonSerializer.Deserialize(serializedData, data.GetType()) ?? throw new JsonException( "Could not deserialize back datamodel after removing shadow fields. Data was \"null\"" ); - // Copy all properties with a public setter from newData to data - foreach ( - var propertyInfo in saveToModelType - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(p => p.CanWrite) - ) - { - object? value = propertyInfo.GetValue(newData); - propertyInfo.SetValue(data, value); - } - - isModified = true; // TODO: Detect if modifications were made + dataAccessor.SetFormData(dataElement, newData); } } - - // Save the updated data - return isModified; } } diff --git a/test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_PdfFails_DataIsUnlocked.verified.txt b/test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_PdfFails_DataIsUnlocked.verified.txt index bcad7a72b..0c8966a7f 100644 --- a/test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_PdfFails_DataIsUnlocked.verified.txt +++ b/test/Altinn.App.Api.Tests/Controllers/ProcessControllerTests.RunProcessNext_PdfFails_DataIsUnlocked.verified.txt @@ -242,6 +242,24 @@ ], IdFormat: W3C }, + { + ActivityName: SerializationService.DeserializeXml, + Tags: [ + { + Type: Altinn.App.Api.Tests.Data.apps.tdd.contributer_restriction.models.Skjema + } + ], + IdFormat: W3C + }, + { + ActivityName: SerializationService.SerializeXml, + Tags: [ + { + Type: Altinn.App.Api.Tests.Data.apps.tdd.contributer_restriction.models.Skjema + } + ], + IdFormat: W3C + }, { ActivityName: Validation.RunValidator, Tags: [ diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizerTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizerTests.cs index f03c5fc28..3a6b63928 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizerTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizerTests.cs @@ -27,6 +27,7 @@ public ProcessTaskFinalizerTests() _processTaskFinalizer = new ProcessTaskFinalizer( _appMetadataMock.Object, _dataClientMock.Object, + _appModelMock.Object, new ModelSerializationService(_appModelMock.Object), _layoutEvaluatorStateInitializerMock.Object, _appSettings