Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,18 @@ internal sealed class DataModelFieldCalculator

private readonly ILogger<DataModelFieldCalculator> _logger;
private readonly IAppResources _appResourceService;
private readonly ILayoutEvaluatorStateInitializer _layoutEvaluatorStateInitializer;
private readonly IDataElementAccessChecker _dataElementAccessChecker;
private readonly Telemetry? _telemetry;

public DataModelFieldCalculator(
ILogger<DataModelFieldCalculator> logger,
ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer,
IAppResources appResourceService,
IDataElementAccessChecker dataElementAccessChecker,
Telemetry? telemetry = null
)
{
_logger = logger;
_appResourceService = appResourceService;
_layoutEvaluatorStateInitializer = layoutEvaluatorStateInitializer;
_dataElementAccessChecker = dataElementAccessChecker;
_telemetry = telemetry;
}
Expand All @@ -52,58 +49,44 @@ public async Task Calculate(IInstanceDataAccessor dataAccessor, string taskId)
var calculationConfig = _appResourceService.GetCalculationConfiguration(dataType.Id);
if (!string.IsNullOrEmpty(calculationConfig))
{
await CalculateFormData(dataAccessor, dataElement, taskId, calculationConfig);
await CalculateFormData(dataAccessor, dataElement, calculationConfig);
}
}
}

internal async Task CalculateFormData(
IInstanceDataAccessor dataAccessor,
DataElement dataElement,
string taskId,
string rawCalculationConfig
)
{
var evaluatorState = await _layoutEvaluatorStateInitializer.Init(dataAccessor, taskId);
var hiddenFields = await LayoutEvaluator.GetHiddenFieldsForRemoval(
evaluatorState,
evaluateRemoveWhenHidden: false
);
DataElementIdentifier dataElementIdentifier = dataElement;
var dataModelFieldCalculations = ParseDataModelFieldCalculationConfig(rawCalculationConfig);
var formDataWrapper = await dataAccessor.GetFormDataWrapper(dataElement);

foreach (var (baseField, calculation) in dataModelFieldCalculations)
{
var resolvedFields = await evaluatorState.GetResolvedKeys(
new DataReference() { Field = baseField, DataElementIdentifier = dataElementIdentifier },
true
);
var resolvedFields = formDataWrapper.GetResolvedKeys(baseField);
foreach (var resolvedField in resolvedFields)
{
if (
hiddenFields.Exists(d =>
d.DataElementIdentifier == resolvedField.DataElementIdentifier
&& IsSameOrDescendantField(resolvedField.Field, d.Field)
)
)
var resolvedFieldReference = new DataReference()
{
continue;
}

Field = resolvedField,
DataElementIdentifier = dataElementIdentifier,
};
var context = new ComponentContext(
evaluatorState,
dataAccessor,
component: null,
rowIndices: ExpressionHelper.GetRowIndices(resolvedField.Field),
dataElementIdentifier: resolvedField.DataElementIdentifier
rowIndices: ExpressionHelper.GetRowIndices(resolvedField),
dataElementIdentifier: dataElementIdentifier
);
var positionalArguments = new object[] { resolvedField.Field };
var positionalArguments = new ExpressionValue[] { resolvedFieldReference.Field };

await RunCalculation(
formDataWrapper,
evaluatorState,
resolvedField,
dataAccessor,
context,
formDataWrapper,
resolvedFieldReference,
positionalArguments,
calculation
);
Expand All @@ -112,18 +95,18 @@ await RunCalculation(
}

private async Task RunCalculation(
IInstanceDataAccessor dataAccessor,
ComponentContext context,
IFormDataWrapper formDataWrapper,
LayoutEvaluatorState evaluatorState,
DataReference resolvedField,
ComponentContext context,
object[] positionalArguments,
ExpressionValue[] positionalArguments,
DataModelFieldCalculation calculation
)
{
try
{
var calculationResult = await ExpressionEvaluator.EvaluateExpressionToExpressionValue(
evaluatorState,
dataAccessor,
calculation.Expression,
context,
positionalArguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public class ExpressionValidator : IValidator

private readonly ILogger<ExpressionValidator> _logger;
private readonly IAppResources _appResourceService;
private readonly ILayoutEvaluatorStateInitializer _layoutEvaluatorStateInitializer;
private readonly IAppMetadata _appMetadata;
private readonly IDataElementAccessChecker _dataElementAccessChecker;

Expand All @@ -35,14 +34,12 @@ public class ExpressionValidator : IValidator
public ExpressionValidator(
ILogger<ExpressionValidator> logger,
IAppResources appResourceService,
ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer,
IAppMetadata appMetadata,
IServiceProvider serviceProvider
)
{
_logger = logger;
_appResourceService = appResourceService;
_layoutEvaluatorStateInitializer = layoutEvaluatorStateInitializer;
_appMetadata = appMetadata;
_dataElementAccessChecker = serviceProvider.GetRequiredService<IDataElementAccessChecker>();
}
Expand Down Expand Up @@ -113,14 +110,8 @@ internal async Task<List<ValidationIssue>> ValidateFormData(
string? language
)
{
var evaluatorState = await _layoutEvaluatorStateInitializer.Init(
dataAccessor,
taskId,
gatewayAction: null,
language
);
var hiddenFields = await LayoutEvaluator.GetHiddenFieldsForRemoval(
evaluatorState,
dataAccessor.GetLayoutEvaluatorState(),
evaluateRemoveWhenHidden: false
);

Expand All @@ -130,9 +121,11 @@ internal async Task<List<ValidationIssue>> ValidateFormData(

foreach (var (baseField, validations) in expressionValidations)
{
var resolvedFields = await evaluatorState.GetResolvedKeys(
new DataReference() { Field = baseField, DataElementIdentifier = dataElementIdentifier }
);
var resolvedFields = await dataAccessor
.GetLayoutEvaluatorState()
.GetResolvedKeys(
new DataReference() { Field = baseField, DataElementIdentifier = dataElementIdentifier }
);
foreach (var resolvedField in resolvedFields)
{
if (
Expand All @@ -150,11 +143,11 @@ internal async Task<List<ValidationIssue>> ValidateFormData(
rowIndices: ExpressionHelper.GetRowIndices(resolvedField.Field),
dataElementIdentifier: resolvedField.DataElementIdentifier
);
var positionalArguments = new object[] { resolvedField.Field };
var positionalArguments = new ExpressionValue[] { resolvedField.Field };
foreach (var validation in validations)
{
await RunValidation(
evaluatorState,
dataAccessor,
Comment thread
ivarne marked this conversation as resolved.
validationIssues,
resolvedField,
context,
Expand All @@ -169,27 +162,32 @@ await RunValidation(
}

private async Task RunValidation(
LayoutEvaluatorState evaluatorState,
IInstanceDataAccessor dataAccessor,
List<ValidationIssue> validationIssues,
DataReference resolvedField,
ComponentContext context,
object[] positionalArguments,
ExpressionValue[] positionalArguments,
ExpressionValidation validation
)
{
try
{
var validationResult = await ExpressionEvaluator.EvaluateExpression(
evaluatorState,
var wrapper = await dataAccessor.GetFormDataWrapper(resolvedField.DataElementIdentifier);
if (wrapper.Get(resolvedField.Field) == null)
{
return; // Assume that the required validator will catch empty fields.
}
var validationResult = await ExpressionEvaluator.EvaluateExpressionToExpressionValue(
dataAccessor,
validation.Condition,
context,
positionalArguments
);
switch (validationResult)
switch (validationResult.ValueKind)
{
case true:
var message = await ExpressionEvaluator.EvaluateExpression(
evaluatorState,
case JsonValueKind.True:
var message = await ExpressionEvaluator.EvaluateExpressionToExpressionValue(
dataAccessor,
validation.Message,
context,
positionalArguments
Expand All @@ -200,13 +198,13 @@ ExpressionValidation validation
Field = resolvedField.Field,
DataElementId = resolvedField.DataElementIdentifier.Id,
Severity = validation.Severity ?? ValidationIssueSeverity.Error,
CustomTextKey = message as string ?? "",
Code = message as string ?? "",
Code = message.ToStringForText(),
CustomTextKey = message.ToStringForText(),
};
validationIssues.Add(validationIssue);

break;
case false:
case JsonValueKind.False:
break;
default:
throw new ArgumentException(
Expand Down
44 changes: 9 additions & 35 deletions src/Altinn.App.Core/Helpers/DataModel/DataModelWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,14 @@
/// ]
/// </example>
public string[] GetResolvedKeys(string field)
{
return GetResolvedKeys(field, isCalculating: false);
}

/// <summary>
/// Get all valid indexed keys for the field, depending on the number of rows in repeating groups
/// </summary>
/// <example>
/// GetResolvedKeys("data.bedrifter.styre.medlemmer") =>
/// [
/// "data.bedrifter[0].styre.medlemmer",
/// "data.bedrifter[1].styre.medlemmer"
/// ...
/// ]
/// </example>
public string[] GetResolvedKeys(string field, bool isCalculating)
{
if (_dataModel is null)
{
return [];
}

var fieldParts = field.Split('.');
return GetResolvedKeysRecursive(fieldParts, _dataModel, _dataModel.GetType(), isCalculating: isCalculating);
return GetResolvedKeysRecursive(fieldParts, _dataModel, _dataModel.GetType());
}

private static string JoinFieldKeyParts(string? currentKey, string? key)
Expand All @@ -162,13 +146,12 @@
return currentKey + "." + key;
}

private static string[] GetResolvedKeysRecursive(

Check failure on line 149 in src/Altinn.App.Core/Helpers/DataModel/DataModelWrapper.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 31 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ6pKmoA1A3VsDXBd-uv&open=AZ6pKmoA1A3VsDXBd-uv&pullRequest=1801
string[] keyParts,
object? currentModel,
Type currentType,
int currentIndex = 0,
string currentKey = "",
bool isCalculating = false
string currentKey = ""
)
{
if (currentIndex == keyParts.Length)
Expand Down Expand Up @@ -213,8 +196,7 @@
child,
elementType,
currentIndex + 1,
JoinFieldKeyParts(currentKey, $"{key}[{i}]"),
isCalculating
JoinFieldKeyParts(currentKey, $"{key}[{i}]")
);
resolvedKeys.AddRange(newResolvedKeys);
i++;
Expand All @@ -232,12 +214,11 @@
elementAt,
elementType,
currentIndex + 1,
JoinFieldKeyParts(currentKey, $"{key}[{groupIndex}]"),
isCalculating
JoinFieldKeyParts(currentKey, $"{key}[{groupIndex}]")
);
}

if (isCalculating && currentIndex == keyParts.Length - 1)
if (currentIndex == keyParts.Length - 1)
{
return [JoinFieldKeyParts(currentKey, key)];
}
Expand All @@ -250,24 +231,17 @@
// If this is the last key part
if (currentIndex == keyParts.Length - 1)
{
// Return the key if value exists, or if calculating (to allow null fields during calculation)
return childValue is not null || isCalculating ? [JoinFieldKeyParts(currentKey, key)] : [];
}

// If child is null and we're not calculating, we can't continue
if (childValue is null && !isCalculating)
{
return [];
// Return the key (even if the value is null)
return [JoinFieldKeyParts(currentKey, key)];
}

// Continue recursion using type information (childValue may be null if isCalculating=true)
// Continue recursion using type information
return GetResolvedKeysRecursive(
keyParts,
childValue,
childType,
currentIndex + 1,
JoinFieldKeyParts(currentKey, key),
isCalculating
JoinFieldKeyParts(currentKey, key)
);
}

Expand Down
23 changes: 7 additions & 16 deletions src/Altinn.App.Core/Internal/Data/IFormDataWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Helpers.DataModel;
using Altinn.App.Core.Internal.Expressions;
using Altinn.App.Core.Models.Layout;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.App.Core.Internal.Data;
Expand Down Expand Up @@ -287,30 +286,22 @@ private static int InvokeReturnIntOrError(MethodInfo info, object instance)
}

/// <summary>
/// Get a list of all possible keys for the given data model
/// Get a list of all possible keys for the given data model at the path
/// </summary>
/// <example>
/// intro.fnr
/// group[0].name
/// group[0].age
/// group[1].name
/// group[1].age
/// group.name -> ["group[0].name", "group[1].name"]
/// group.age -> ["group[0].age", "group[1].age"]
/// </example>
public static DataReference[] GetResolvedKeys(
this IFormDataWrapper formDataWrapper,
DataReference reference,
bool isCalculating = false
)
public static string[] GetResolvedKeys(this IFormDataWrapper formDataWrapper, string path)
{
//TODO: write more efficient code that uses the formDataWrapper to resolve keys instead of reflection in DataModelWrapper
// The current implementation also does not throw exceptions when the path ends in an enumerable.
// When resolving "group" it is not clear if the result should be "group[0]", "group[1]", or just "group"."
var data = formDataWrapper.BackingData<object>();
#pragma warning disable CS0618 // Type or member is obsolete
var dataModelWrapper = new DataModelWrapper(data);
#pragma warning restore CS0618 // Type or member is obsolete
return dataModelWrapper
.GetResolvedKeys(reference.Field, isCalculating)
.Select(resolvedField => reference with { Field = resolvedField })
.ToArray();
return dataModelWrapper.GetResolvedKeys(path);
}

private static int GetMaxBufferLength(ReadOnlySpan<char> path, ReadOnlySpan<int> rowIndexes)
Expand Down
Loading
Loading