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

Add draft support for adding/removing/modifying other data elements in dataProcessWrite #809

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 10 additions & 8 deletions src/Altinn.App.Api/Controllers/ActionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Altinn.App.Core.Features;
using Altinn.App.Core.Features.Action;
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Helpers.Serialization;
using Altinn.App.Core.Internal.App;
using Altinn.App.Core.Internal.AppModel;
using Altinn.App.Core.Internal.Data;
Expand Down Expand Up @@ -35,6 +36,7 @@ public class ActionsController : ControllerBase
private readonly IDataClient _dataClient;
private readonly IAppMetadata _appMetadata;
private readonly IAppModel _appModel;
private readonly ModelSerializationService _modelSerialization;

/// <summary>
/// Create new instance of the <see cref="ActionsController"/> class
Expand All @@ -46,7 +48,8 @@ public ActionsController(
IValidationService validationService,
IDataClient dataClient,
IAppMetadata appMetadata,
IAppModel appModel
IAppModel appModel,
ModelSerializationService modelSerialization
)
{
_authorization = authorization;
Expand All @@ -56,6 +59,7 @@ IAppModel appModel
_dataClient = dataClient;
_appMetadata = appMetadata;
_appModel = appModel;
_modelSerialization = modelSerialization;
}

/// <summary>
Expand Down Expand Up @@ -160,7 +164,7 @@ public async Task<ActionResult<UserActionResponse>> Perform(
);
}

var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _appModel);
var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _modelSerialization);
Dictionary<string, Dictionary<string, List<ValidationIssueWithSource>>>? validationIssues = null;

if (result.UpdatedDataModels is { Count: > 0 })
Expand Down Expand Up @@ -202,7 +206,7 @@ Dictionary<string, object> resultUpdatedDataModels
continue;
}
var dataElement = instance.Data.First(d => d.Id.Equals(elementId, StringComparison.OrdinalIgnoreCase));
var previousData = await dataAccessor.GetData(dataElement);
var previousData = await dataAccessor.GetFormData(dataElement);

ObjectUtils.InitializeAltinnRowId(newModel);
ObjectUtils.PrepareModelForXmlStorage(newModel);
Expand All @@ -217,16 +221,14 @@ await _dataClient.UpdateData(
Guid.Parse(dataElement.Id)
);
// update dataAccessor to use the changed data
dataAccessor.Set(dataElement, newModel);
dataAccessor.SetFormData(dataElement, newModel);
// add change to list
changes.Add(
new DataElementChange
{
HasAppLogic = true,
ChangeType = DataElementChangeType.Update,
DataElement = dataElement,
PreviousValue = previousData,
CurrentValue = newModel,
PreviousFormData = previousData,
CurrentFormData = newModel,
}
);
}
Expand Down
19 changes: 9 additions & 10 deletions src/Altinn.App.Api/Controllers/DataController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -577,23 +577,22 @@ public async Task<ActionResult<DataPatchResponseMultiple>> PatchFormDataMultiple

if (res.Success)
{
// TODO: handle added and deleted data elements
foreach (var change in res.Ok.ChangedDataElements)
{
if (change.HasAppLogic)
{
await UpdateDataValuesOnInstance(instance, change.DataElement.DataType, change.CurrentValue);
await UpdatePresentationTextsOnInstance(
instance,
change.DataElement.DataType,
change.CurrentValue
);
}
await UpdateDataValuesOnInstance(instance, change.DataElement.DataType, change.CurrentFormData);
await UpdatePresentationTextsOnInstance(
instance,
change.DataElement.DataType,
change.CurrentFormData
);
}

return Ok(
new DataPatchResponseMultiple()
{
NewDataModels = res.Ok.GetUpdatedData(),
Instance = res.Ok.Instance,
NewDataModels = res.Ok.UpdatedData,
ValidationIssues = res.Ok.ValidationIssues
}
);
Expand Down
12 changes: 6 additions & 6 deletions src/Altinn.App.Api/Controllers/ProcessController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using Altinn.App.Api.Models;
using Altinn.App.Core.Constants;
using Altinn.App.Core.Helpers;
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.Instances;
using Altinn.App.Core.Internal.Process;
Expand Down Expand Up @@ -43,7 +43,7 @@ public class ProcessController : ControllerBase
private readonly IProcessReader _processReader;
private readonly IDataClient _dataClient;
private readonly IAppMetadata _appMetadata;
private readonly IAppModel _appModel;
private readonly ModelSerializationService _modelSerialization;

/// <summary>
/// Initializes a new instance of the <see cref="ProcessController"/>
Expand All @@ -58,7 +58,7 @@ public ProcessController(
IProcessEngine processEngine,
IDataClient dataClient,
IAppMetadata appMetadata,
IAppModel appModel
ModelSerializationService modelSerialization
)
{
_logger = logger;
Expand All @@ -70,7 +70,7 @@ IAppModel appModel
_processEngine = processEngine;
_dataClient = dataClient;
_appMetadata = appMetadata;
_appModel = appModel;
_modelSerialization = modelSerialization;
}

/// <summary>
Expand Down Expand Up @@ -249,10 +249,10 @@ [FromRoute] Guid instanceGuid
string? language
)
{
var dataAcceesor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _appModel);
var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _modelSerialization);
var validationIssues = await _validationService.ValidateInstanceAtTask(
instance,
dataAcceesor,
dataAccessor,
currentTaskId, // run full validation
ignoredValidators: null,
onlyIncrementalValidators: null,
Expand Down
12 changes: 6 additions & 6 deletions src/Altinn.App.Api/Controllers/ValidateController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Altinn.App.Core.Helpers;
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.Instances;
using Altinn.App.Core.Internal.Validation;
Expand All @@ -20,7 +20,7 @@ public class ValidateController : ControllerBase
{
private readonly IInstanceClient _instanceClient;
private readonly IDataClient _dataClient;
private readonly IAppModel _appModel;
private readonly ModelSerializationService _modelSerialization;
private readonly IAppMetadata _appMetadata;
private readonly IValidationService _validationService;

Expand All @@ -32,14 +32,14 @@ public ValidateController(
IValidationService validationService,
IAppMetadata appMetadata,
IDataClient dataClient,
IAppModel appModel
ModelSerializationService modelSerialization
)
{
_instanceClient = instanceClient;
_validationService = validationService;
_appMetadata = appMetadata;
_dataClient = dataClient;
_appModel = appModel;
_modelSerialization = modelSerialization;
}

/// <summary>
Expand Down Expand Up @@ -80,7 +80,7 @@ public async Task<IActionResult> ValidateInstance(

try
{
var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _appModel);
var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _modelSerialization);
var ignoredSources = ignoredValidators?.Split(',').ToList();
List<ValidationIssueWithSource> messages = await _validationService.ValidateInstanceAtTask(
instance,
Expand Down Expand Up @@ -155,7 +155,7 @@ public async Task<IActionResult> ValidateData(
throw new ValidationException("Unknown element type.");
}

var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _appModel);
var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _modelSerialization);

// Run validations for all data elements, but only return the issues for the specific data element
var issues = await _validationService.ValidateInstanceAtTask(
Expand Down
6 changes: 6 additions & 0 deletions src/Altinn.App.Api/Models/DataPatchResponseMultiple.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Altinn.App.Api.Controllers;
using Altinn.App.Core.Models.Validation;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.App.Api.Models;

Expand All @@ -17,4 +18,9 @@ public class DataPatchResponseMultiple
/// The current data in all data models updated by the patch operation.
/// </summary>
public required Dictionary<Guid, object> NewDataModels { get; init; }

/// <summary>
/// The instance with updated dataElement list.
/// </summary>
public required Instance Instance { get; init; }
}
2 changes: 2 additions & 0 deletions src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Altinn.App.Core.Features.Pdf;
using Altinn.App.Core.Features.Validation;
using Altinn.App.Core.Features.Validation.Default;
using Altinn.App.Core.Helpers.Serialization;
using Altinn.App.Core.Implementation;
using Altinn.App.Core.Infrastructure.Clients.Authentication;
using Altinn.App.Core.Infrastructure.Clients.Authorization;
Expand Down Expand Up @@ -179,6 +180,7 @@ IWebHostEnvironment env
services.Configure<AccessTokenSettings>(configuration.GetSection("AccessTokenSettings"));
services.Configure<FrontEndSettings>(configuration.GetSection(nameof(FrontEndSettings)));
services.Configure<PdfGeneratorSettings>(configuration.GetSection(nameof(PdfGeneratorSettings)));
services.AddSingleton<ModelSerializationService>();

AddAppOptions(services);
AddExternalApis(services);
Expand Down
7 changes: 7 additions & 0 deletions src/Altinn.App.Core/Features/IDataProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ public interface IDataProcessor
/// <param name="previousData">The previous data model (for running comparisons)</param>
/// <param name="language">The currently selected language of the user (if available)</param>
public Task ProcessDataWrite(Instance instance, Guid? dataId, object data, object? previousData, string? language);

public Task ProcessDataWrite(
IInstanceDataAccessor instanceDataAccessor,
string taskId,
List<DataElementChange> changes,
string? language
) => Task.CompletedTask;
}
41 changes: 37 additions & 4 deletions src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,44 @@ public interface IInstanceDataAccessor
/// <summary>
/// Get the actual data represented in the data element.
/// </summary>
/// <returns>The deserialized data model for this data element or a stream for binary elements</returns>
Task<object> GetData(DataElementId dataElementId);
/// <returns>The deserialized data model for this data element or an exception for non-form data elements</returns>
Task<object> GetFormData(DataElementId dataElementId);

/// <summary>
/// Get actual data represented, when there should only be a single element of this type.
/// Gets the raw binary data from a DataElement.
/// </summary>´
/// <remarks>Form data elements (with appLogic) will get json serialized UTF-8</remarks>
/// <throws>Throws an InvalidOperationException if the data element is not found on the instance</throws>
/// <returns></returns>
Task<ReadOnlyMemory<byte>> GetBinaryData(DataElementId dataElementId);

/// <summary>
/// Get a data element from an instance by id,
/// </summary>
/// <throws>Throws an InvalidOperationException if the data element is not found on the instance</throws>
DataElement GetDataElement(DataElementId dataElementId);

/// <summary>
/// Add a new data element with app logic to the instance of this accessor
/// </summary>
/// <remarks>
/// Serialization of data is done immediately, so the data object should be in a valid state.
/// </remarks>
/// <throws>Throws an InvalidOperationException if the dataType is not found in applicationmetadata</throws>
void AddFormDataElement(string dataType, object data);

/// <summary>
/// Add a new data element without app logic to the instance.
/// </summary>
/// <remarks>
/// Saving to storage is not done until the instance is saved, so mutations to data might or might not be sendt to storage.
/// </remarks>
void AddAttachmentDataElement(string dataType, string contentType, string? filename, ReadOnlyMemory<byte> bytes);

/// <summary>
/// Remove a data element from the instance.
///
/// Actual removal from storage is not done until the instance is saved.
/// </summary>
Task<object?> GetSingleDataByType(string dataType);
void RemoveDataElement(DataElementId dataElementId);
}
40 changes: 4 additions & 36 deletions src/Altinn.App.Core/Features/IValidator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Altinn.App.Core.Models;
using Altinn.App.Core.Models.Validation;
using Altinn.Platform.Storage.Interface.Models;

Expand Down Expand Up @@ -67,50 +67,18 @@ List<DataElementChange> changes
/// </summary>
public class DataElementChange
{
/// <summary>
/// If the data element has app logic you can expect <see cref="CurrentValue"/> and <see cref="PreviousValue"/> to be available
/// </summary>
[MemberNotNullWhen(true, nameof(CurrentValue), nameof(PreviousValue))]
public required bool HasAppLogic { get; init; }

/// <summary>
/// The data element the change is related to
/// </summary>
public required DataElement DataElement { get; init; }

/// <summary>
/// The type of change that has occurred
/// </summary>
public required DataElementChangeType ChangeType { get; init; }
public required DataElementId DataElement { get; init; }

/// <summary>
/// The state of the data element before the change
/// </summary>
public required object? PreviousValue { get; init; }
public required object PreviousFormData { get; init; }

/// <summary>
/// The state of the data element after the change
/// </summary>
public required object? CurrentValue { get; init; }
}

/// <summary>
/// Enum specifying the type of changes that can occur to a data element
/// </summary>
public enum DataElementChangeType
{
/// <summary>
/// The data element has appLogic and was updated
/// </summary>
Update,

/// <summary>
/// The data element was added
/// </summary>
Add,

/// <summary>
/// The data element was removed
/// </summary>
Delete,
public required object CurrentFormData { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Diagnostics;

namespace Altinn.App.Core.Features;

partial class Telemetry
{
internal Activity? StartSerializeToXmlActivity(Type typeToSerialize)
{
var activity = ActivitySource.StartActivity("SerializationService.SerializeXml");
activity?.SetTag("Type", typeToSerialize.FullName);
return activity;
}

internal Activity? StartSerializeToJsonActivity(Type typeToSerialize)
{
var activity = ActivitySource.StartActivity("SerializationService.SerializeXml");
activity?.SetTag("Type", typeToSerialize.FullName);
return activity;
}

internal Activity? StartDeserializeFromXmlActivity(Type typeToDeserialize)
{
var activity = ActivitySource.StartActivity("SerializationService.DeserializeXml");
activity?.SetTag("Type", typeToDeserialize.FullName);
return activity;
}

internal Activity? StartDeserializeFromJsonActivity(Type typeToDeserialize)
{
var activity = ActivitySource.StartActivity("SerializationService.DeserializeJson");
activity?.SetTag("Type", typeToDeserialize.FullName);
return activity;
}
}
Loading