Sitelet https://github.com/Altinn/app-lib-dotnet/commit/127cb9b77c70979443ea41874a72d030f06449b8
Skip to content

Commit 127cb9b

Browse files
committed
Change the schema for calculations to a list of calculations, instead of a dictionary
1 parent 45d0389 commit 127cb9b

19 files changed

Lines changed: 135 additions & 200 deletions
Lines changed: 11 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System.Text.Json;
21
using Altinn.App.Core.Internal.App;
32
using Altinn.App.Core.Internal.Data;
43
using Altinn.App.Core.Internal.Expressions;
54
using Altinn.App.Core.Models;
5+
using Altinn.App.Core.Models.Calculation;
6+
using Altinn.App.Core.Models.Expressions;
67
using Altinn.App.Core.Models.Layout;
78
using Altinn.Platform.Storage.Interface.Models;
89
using Microsoft.Extensions.Logging;
@@ -12,12 +13,6 @@ namespace Altinn.App.Core.Features.DataProcessing;
1213

1314
internal sealed class DataModelFieldCalculator
1415
{
15-
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
16-
{
17-
ReadCommentHandling = JsonCommentHandling.Skip,
18-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
19-
};
20-
2116
private readonly ILogger<DataModelFieldCalculator> _logger;
2217
private readonly IAppResources _appResourceService;
2318
private readonly IDataElementAccessChecker _dataElementAccessChecker;
@@ -46,27 +41,26 @@ public async Task Calculate(IInstanceDataAccessor dataAccessor, string taskId)
4641
continue;
4742
}
4843

49-
var calculationConfig = _appResourceService.GetCalculationConfiguration(dataType.Id);
50-
if (!string.IsNullOrEmpty(calculationConfig))
44+
var calculationSchema = _appResourceService.GetCalculationConfiguration(dataType.Id);
45+
if (calculationSchema is not null)
5146
{
52-
await CalculateFormData(dataAccessor, dataElement, calculationConfig);
47+
await CalculateFormData(dataAccessor, dataElement, calculationSchema);
5348
}
5449
}
5550
}
5651

5752
private async Task CalculateFormData(
5853
IInstanceDataAccessor dataAccessor,
5954
DataElement dataElement,
60-
string rawCalculationConfig
55+
CalculationSchema calculationSchema
6156
)
6257
{
6358
DataElementIdentifier dataElementIdentifier = dataElement;
64-
var dataModelFieldCalculations = ParseDataModelFieldCalculationConfig(rawCalculationConfig);
6559
var formDataWrapper = await dataAccessor.GetFormDataWrapper(dataElement);
6660

67-
foreach (var (baseField, calculation) in dataModelFieldCalculations)
61+
foreach (var calculation in calculationSchema.Calculations)
6862
{
69-
var resolvedFields = formDataWrapper.GetResolvedKeys(baseField);
63+
var resolvedFields = formDataWrapper.GetResolvedKeys(calculation.Field);
7064
foreach (var resolvedField in resolvedFields)
7165
{
7266
var resolvedFieldReference = new DataReference()
@@ -88,7 +82,7 @@ await RunCalculation(
8882
formDataWrapper,
8983
resolvedFieldReference,
9084
positionalArguments,
91-
calculation
85+
calculation.Expression
9286
);
9387
}
9488
}
@@ -100,14 +94,14 @@ private async Task RunCalculation(
10094
IFormDataWrapper formDataWrapper,
10195
DataReference resolvedField,
10296
ExpressionValue[] positionalArguments,
103-
DataModelFieldCalculation calculation
97+
Expression calculation
10498
)
10599
{
106100
try
107101
{
108102
var calculationResult = await ExpressionEvaluator.EvaluateExpressionToExpressionValue(
109103
dataAccessor,
110-
calculation.Expression,
104+
calculation,
111105
context,
112106
positionalArguments
113107
);
@@ -127,72 +121,4 @@ DataModelFieldCalculation calculation
127121
throw;
128122
}
129123
}
130-
131-
private Dictionary<string, DataModelFieldCalculation> ParseDataModelFieldCalculationConfig(
132-
string rawCalculationConfig
133-
)
134-
{
135-
JsonDocument calculationConfigDocument;
136-
try
137-
{
138-
calculationConfigDocument = JsonDocument.Parse(
139-
rawCalculationConfig,
140-
new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }
141-
);
142-
}
143-
catch (JsonException e)
144-
{
145-
_logger.LogError(e, "Failed to parse calculation configuration JSON");
146-
return new Dictionary<string, DataModelFieldCalculation>();
147-
}
148-
using (calculationConfigDocument)
149-
{
150-
var dataModelFieldCalculations = new Dictionary<string, DataModelFieldCalculation>();
151-
var hasCalculations = calculationConfigDocument.RootElement.TryGetProperty(
152-
"calculations",
153-
out JsonElement calculationsObject
154-
);
155-
if (hasCalculations)
156-
{
157-
foreach (var calculationArray in calculationsObject.EnumerateObject())
158-
{
159-
var field = calculationArray.Name;
160-
var calculation = calculationArray.Value;
161-
var resolvedDataModelFieldCalculation = ResolveDataModelFieldCalculation(field, calculation);
162-
if (resolvedDataModelFieldCalculation == null)
163-
{
164-
_logger.LogError("Calculation for field {Field} could not be resolved", field);
165-
continue;
166-
}
167-
dataModelFieldCalculations[field] = resolvedDataModelFieldCalculation;
168-
}
169-
}
170-
return dataModelFieldCalculations;
171-
}
172-
}
173-
174-
private DataModelFieldCalculation? ResolveDataModelFieldCalculation(string field, JsonElement definition)
175-
{
176-
var dataModelFieldCalculationDefinition = definition.Deserialize<RawDataModelFieldCalculation>(
177-
_jsonSerializerOptions
178-
);
179-
if (dataModelFieldCalculationDefinition == null)
180-
{
181-
_logger.LogError("Calculation for field {Field} could not be parsed", field);
182-
return null;
183-
}
184-
185-
if (dataModelFieldCalculationDefinition.Expression == null)
186-
{
187-
_logger.LogError("Calculation for field {Field} is missing expression", field);
188-
return null;
189-
}
190-
191-
var dataModelFieldCalculation = new DataModelFieldCalculation
192-
{
193-
Expression = dataModelFieldCalculationDefinition.Expression.Value,
194-
};
195-
196-
return dataModelFieldCalculation;
197-
}
198124
}

‎src/Altinn.App.Core/Implementation/AppResourcesSI.cs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Altinn.App.Core.Helpers;
66
using Altinn.App.Core.Internal.App;
77
using Altinn.App.Core.Models;
8+
using Altinn.App.Core.Models.Calculation;
89
using Altinn.App.Core.Models.Layout;
910
using Altinn.App.Core.Models.Layout.Components;
1011
using Altinn.Platform.Storage.Interface.Models;
@@ -541,19 +542,20 @@ private static byte[] ReadFileContentsFromLegalPath(string legalPath, string fil
541542
}
542543

543544
/// <inheritdoc />
544-
public string? GetCalculationConfiguration(string dataTypeId)
545+
public CalculationSchema? GetCalculationConfiguration(string dataTypeId)
545546
{
546547
using var activity = _telemetry?.StartGetCalculationConfigurationActivity();
547548
string legalPath = Path.Join(_settings.AppBasePath, _settings.ModelsFolder);
548549
string filename = Path.Join(legalPath, $"{dataTypeId}.{_settings.CalculationConfigurationFileName}");
549550
PathHelper.EnsureLegalPath(legalPath, filename);
550551

551-
string? fileData = null;
552-
if (File.Exists(filename))
552+
if (!File.Exists(filename))
553553
{
554-
fileData = File.ReadAllText(filename, Encoding.UTF8);
554+
return null;
555555
}
556556

557-
return fileData;
557+
return System.Text.Json.JsonSerializer.Deserialize<CalculationSchema>(
558+
File.ReadAllText(filename, Encoding.UTF8)
559+
);
558560
}
559561
}

‎src/Altinn.App.Core/Internal/App/IAppResources.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Altinn.App.Core.Models;
2+
using Altinn.App.Core.Models.Calculation;
23
using Altinn.App.Core.Models.Layout;
34
using Altinn.Platform.Storage.Interface.Models;
45

@@ -177,5 +178,5 @@ public interface IAppResources
177178
/// Gets the calculation configuration for a given data type
178179
/// </summary>
179180
/// <returns>The calculation configuration in JSON format represented as string</returns>
180-
string? GetCalculationConfiguration(string dataTypeId);
181+
CalculationSchema? GetCalculationConfiguration(string dataTypeId);
181182
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Text.Json.Serialization;
2+
using Altinn.App.Core.Models.Expressions;
3+
4+
namespace Altinn.App.Core.Models.Calculation;
5+
6+
/// <summary>
7+
/// Calculation item in the calculation configuration
8+
/// </summary>
9+
public class CalculationItem
10+
{
11+
/// <summary>
12+
/// The base field to be calculated.
13+
/// Note that missing indexes will be added to the field name when calculating array items. For example, if the field is "myArray[].myField", the calculation will be applied to all items in the array.
14+
/// </summary>
15+
[JsonPropertyName("field")]
16+
public required string Field { get; init; }
17+
18+
/// <summary>
19+
/// The expression to be used for the calculation. Note that this will be run in the context of the field, so you can use relative paths in the expression.
20+
/// </summary>
21+
[JsonPropertyName("expression")]
22+
public required Expression Expression { get; init; }
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Altinn.App.Core.Models.Calculation;
4+
5+
/// <summary>
6+
/// Represents the schema for the calculation configuration
7+
/// </summary>
8+
public class CalculationSchema
9+
{
10+
/// <summary>
11+
/// Gets the schema for the calculation configuration.
12+
/// </summary>
13+
[JsonPropertyName("$schema")]
14+
public string Schema =>
15+
"https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/calculation/calculation.schema.v1.json";
16+
17+
/// <summary>
18+
/// Gets or sets the list of calculation items in the calculation configuration.
19+
/// </summary>
20+
public required List<CalculationItem> Calculations { get; init; }
21+
}

‎src/Altinn.App.Core/Models/RawDataModelFieldCalculation.cs‎

Lines changed: 0 additions & 25 deletions
This file was deleted.

‎test/Altinn.App.Core.Tests/Features/DataProcessing/DataModelFieldCalculatorTests.cs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Altinn.App.Core.Internal.Expressions;
88
using Altinn.App.Core.Internal.Texts;
99
using Altinn.App.Core.Models;
10+
using Altinn.App.Core.Models.Calculation;
1011
using Altinn.App.Core.Models.Layout;
1112
using Altinn.App.Core.Tests.LayoutExpressions.CommonTests;
1213
using Altinn.App.Core.Tests.LayoutExpressions.TestUtilities;
@@ -78,11 +79,12 @@ public async Task ShouldLogErrorAndThrowWhenExpressionEvaluatorThrowsException()
7879
],
7980
"calculationConfig": {
8081
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/calculation/calculation.schema.v1.json",
81-
"calculations": {
82-
"form.formDataWrapperThrows": {
82+
"calculations": [
83+
{
84+
"field": "form.formDataWrapperThrows",
8385
"expression": ["noneExistingExpression"]
8486
}
85-
}
87+
]
8688
},
8789
"formData": {
8890
"form": {
@@ -178,7 +180,7 @@ testCase.TextResources is null
178180
);
179181
_appResources
180182
.Setup(ar => ar.GetCalculationConfiguration(It.IsAny<string>()))
181-
.Returns(JsonSerializer.Serialize(testCase.CalculationConfig, _jsonSerializerOptions));
183+
.Returns(testCase.CalculationConfig);
182184

183185
if (testCase.DataModels is not null)
184186
{
@@ -237,7 +239,7 @@ private record DataModelFieldCalculatorTestModel
237239
public required Expected[] Expects { get; set; }
238240

239241
[JsonPropertyName("calculationConfig")]
240-
public required JsonElement CalculationConfig { get; set; }
242+
public required CalculationSchema CalculationConfig { get; set; }
241243

242244
// A single data element. Either this or <see cref="DataModels"/> must be set.
243245
[JsonPropertyName("formData")]

‎test/Altinn.App.Core.Tests/Features/DataProcessing/data-field-value-calculator-tests/assert-logger/parse-none-existing-definition.json‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎test/Altinn.App.Core.Tests/Features/DataProcessing/data-field-value-calculator-tests/assert-logger/parse-none-existing-expression.json‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎test/Altinn.App.Core.Tests/Features/DataProcessing/data-field-value-calculator-tests/assert-logger/unsupported-data-type.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
],
88
"calculationConfig": {
99
"$schema": "https://altinncdn.no/toolkits/altinn-app-frontend/4/schemas/json/calculation/calculation.schema.v1.json",
10-
"calculations": {
11-
"form.unsupportedDataType": {
10+
"calculations": [
11+
{
12+
"field": "form.unsupportedDataType",
1213
"expression": ["language"]
1314
}
14-
}
15+
]
1516
},
1617
"formData": {
1718
"form": {

0 commit comments

Comments
 (0)