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

Fix remove hidden data to support list binding and hiddenRow #305

Merged
merged 14 commits into from
Sep 21, 2023
Merged
8 changes: 3 additions & 5 deletions src/Altinn.App.Core/Features/Pdf/DynamicsPdfFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,22 @@
var state = await _layoutStateInit.Init(instance, data, layoutSetId: layoutSet?.Id);
foreach (var pageContext in state.GetComponentContexts())
{
var pageHidden = ExpressionEvaluator.EvaluateBooleanExpression(state, pageContext, "hidden", false);
if (pageHidden)
if (pageContext.IsHidden == true)
{
layoutSettings.Pages.ExcludeFromPdf.Add(pageContext.Component.Id);
}
else
{
//TODO: figure out how pdf reacts to groups one level down.
foreach (var componentContext in pageContext.ChildContexts)
{
var componentHidden = ExpressionEvaluator.EvaluateBooleanExpression(state, componentContext, "hidden", false);
if (componentHidden)
if (componentContext.IsHidden == true)
{
layoutSettings.Components.ExcludeFromPdf.Add(componentContext.Component.Id);
}
}

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(...)'.
}
}
return layoutSettings;
}
}
}
65 changes: 38 additions & 27 deletions src/Altinn.App.Core/Helpers/DataModel/DataModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public DataModel(object serviceModel)
return null;
}

private static readonly Regex KeyPartRegex = new Regex(@"^(\w+)\[(\d+)\]?$");
private static readonly Regex KeyPartRegex = new Regex(@"^([^\s\[\]\.]+)\[(\d+)\]?$");
internal static (string key, int? index) ParseKeyPart(string keypart)
{
if (keypart.Length == 0)
Expand All @@ -130,13 +130,15 @@ private static void AddIndiciesRecursive(List<string> ret, Type currentModelType
var prop = currentModelType.GetProperties().FirstOrDefault(p => IsPropertyWithJsonName(p, key));
if (prop is null)
{
throw new DataModelException($"Unknown model property {key} in {fullKey}");
throw new DataModelException($"Unknown model property {key} in {string.Join(".", ret)}.{key}");
}

var currentIndex = groupIndex ?? (indicies.Length > 0 ? indicies[0] : null);

var childType = prop.PropertyType;
// Strings are enumerable in C#
// Other enumerable types is treated as an collection
if (childType != typeof(string) && childType.IsAssignableTo(typeof(System.Collections.IEnumerable)))
if (childType != typeof(string) && childType.IsAssignableTo(typeof(System.Collections.IEnumerable)) && currentIndex is not null)
{
// Hope the first generic argument is tied to the IEnumerable implementation
var childTypeEnumerableParameter = childType.GetGenericArguments().FirstOrDefault();
Expand All @@ -146,23 +148,13 @@ private static void AddIndiciesRecursive(List<string> ret, Type currentModelType
throw new DataModelException("DataModels must have generic IEnumerable<> implementation for list");
}

if (groupIndex is null)
ret.Add($"{key}[{currentIndex}]");
if (indicies.Length > 0)
{
if (indicies.Length == 0)
{
throw new DataModelException($"Missmatch in indicies in {fullKey} on key {key} and [{string.Join(", ", originalIndicies.ToArray())}]");
}
ret.Add($"{key}[{indicies[0]}]");
indicies = indicies.Slice(1);
}
else
{
ret.Add($"{key}[{groupIndex}]");

// Ignore indexes after a literal index has been set
indicies = new int[] { groupIndex.Value };
}

AddIndiciesRecursive(ret, childTypeEnumerableParameter, keys.Slice(1), fullKey, indicies.Slice(1), originalIndicies);
AddIndiciesRecursive(ret, childTypeEnumerableParameter, keys.Slice(1), fullKey, indicies, originalIndicies);
}
else
{
Expand Down Expand Up @@ -215,25 +207,20 @@ private static bool IsPropertyWithJsonName(PropertyInfo propertyInfo, string key
}

/// <inheritdoc />
public void RemoveField(string key)
public void RemoveField(string key, bool deleteRows = false)
{
var keys_split = key.Split('.');
var keys = keys_split[0..^1];
var (lastKey, lastGroupIndex) = ParseKeyPart(keys_split[^1]);

if (lastGroupIndex is not null)
{
// TODO: Consider implementing. Would be required for rowHidden on groups
throw new NotImplementedException($"Deleting elements in List is not implemented {key}");
}

var containingObject = GetModelDataRecursive(keys, 0, _serviceModel, default);
if (containingObject is null)
{
// Already empty field
return;
}


if (containingObject is System.Collections.IEnumerable)
{
throw new NotImplementedException($"Tried to remove field {key}, ended in an enumerable");
Expand All @@ -246,9 +233,33 @@ public void RemoveField(string key)
return;
}

var nullValue = property.PropertyType.GetTypeInfo().IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
if (lastGroupIndex is not null)
{
// Remove row from list
var propertyValue = property.GetValue(containingObject);
if (propertyValue is not System.Collections.IList listValue)
{
throw new ArgumentException($"Tried to remove row {key}, ended in a non-list ({propertyValue?.GetType()})");
}

if (deleteRows)
{
listValue.RemoveAt(lastGroupIndex.Value);
}
else
{

property.SetValue(containingObject, nullValue);
var genericType = listValue.GetType().GetGenericArguments().FirstOrDefault();
var nullValue = genericType?.IsValueType == true ? Activator.CreateInstance(genericType) : null;
listValue[lastGroupIndex.Value] = nullValue;
}
}
else
{
// Set property to null
var nullValue = property.PropertyType.GetTypeInfo().IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
property.SetValue(containingObject, nullValue);
}
}

/// <inheritdoc />
Expand Down Expand Up @@ -298,4 +309,4 @@ private bool VerifyKeyRecursive(string[] keys, int index, Type currentModel)

return VerifyKeyRecursive(keys, index + 1, childType);
}
}
}
2 changes: 1 addition & 1 deletion src/Altinn.App.Core/Helpers/IDataModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public interface IDataModelAccessor
/// <summary>
/// Remove a value from the wrapped datamodel
/// </summary>
void RemoveField(string key);
void RemoveField(string key, bool deleteRows = false);

/// <summary>
/// Verify that a Key is a valid lookup for the datamodel
Expand Down
14 changes: 8 additions & 6 deletions src/Altinn.App.Core/Implementation/DefaultTaskEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ private async Task RemoveHiddenData(Instance instance, Guid instanceGuid, List<D
// Remove hidden data before validation
var layoutSet = _appResources.GetLayoutSetForTask(dataType.TaskId);
var evaluationState = await _layoutEvaluatorStateInitializer.Init(instance, data, layoutSet?.Id);
LayoutEvaluator.RemoveHiddenData(evaluationState);
LayoutEvaluator.RemoveHiddenData(evaluationState, true);
}

// save the updated data if there are changes
Expand All @@ -282,22 +282,24 @@ private async Task RemoveShadowFields(Instance instance, Guid instanceGuid, List
instanceGuid, modelType, instance.Org, app, instanceOwnerPartyId, dataElementId);

var modifier = new IgnorePropertiesWithPrefix(dataType.AppLogic.ShadowFields.Prefix);
JsonSerializerOptions options = new ()
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { modifier.ModifyPrefixInfo }
},
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

string serializedData = JsonSerializer.Serialize(data, options);
if (dataType.AppLogic.ShadowFields.SaveToDataType != null) {
if (dataType.AppLogic.ShadowFields.SaveToDataType != null)
{
var saveToDataType = dataTypesToLock.Find(dt => dt.Id == dataType.AppLogic.ShadowFields.SaveToDataType);
if (saveToDataType == null) {
if (saveToDataType == null)
{
throw new Exception($"SaveToDataType {dataType.AppLogic.ShadowFields.SaveToDataType} not found");
}

Type saveToModelType = _appModel.GetModelType(saveToDataType.AppLogic.ClassRef);
var updatedData = JsonSerializer.Deserialize(serializedData, saveToModelType);
await _dataClient.InsertFormData(updatedData, instanceGuid, saveToModelType ?? modelType, instance.Org, app, instanceOwnerPartyId, saveToDataType.Id);
Expand Down
37 changes: 20 additions & 17 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
var expr = property switch
{
"hidden" => context.Component.Hidden,
"hiddenRow" => context.Component is RepeatingGroupComponent repeatingGroup ? repeatingGroup.HiddenRow : null,
"required" => context.Component.Required,
_ => throw new ExpressionEvaluatorTypeErrorException($"unknown boolean expression property {property}")
};
Expand All @@ -37,10 +38,10 @@
_ => throw new ExpressionEvaluatorTypeErrorException($"Return was not boolean (value)")
};
}
catch(Exception e)
catch (Exception e)
{
throw new ExpressionEvaluatorTypeErrorException($"Error while evaluating \"{property}\" on \"{context.Component.PageId}.{context.Component.Id}\"", e);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}

/// <summary>
Expand Down Expand Up @@ -108,10 +109,10 @@
{
throw new ArgumentException("component lookup requires the target component to have a simpleBinding");
}
ComponentContext? parent = targetContext;
ComponentContext? parent = targetContext;
while (parent is not null)
{
if(EvaluateBooleanExpression(state, parent, "hidden", false))
if (EvaluateBooleanExpression(state, parent, "hidden", false))
{
// Don't lookup data in hidden components
return null;
Expand All @@ -135,7 +136,7 @@
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
Expand All @@ -152,15 +153,15 @@
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.EndsWith(stringTwo, StringComparison.InvariantCulture);
}

private static bool StartsWith(object?[] args)
{
if (args.Length != 2)
Expand All @@ -169,7 +170,7 @@
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
Expand All @@ -186,12 +187,12 @@
}
string? stringOne = ToStringForEquals(args[0]);
string? stringTwo = ToStringForEquals(args[1]);

if (stringOne is null || stringTwo is null)
{
return false;
}

return stringOne.Split(",").Select(s => s.Trim()).Contains(stringTwo, StringComparer.InvariantCulture);
}

Expand All @@ -207,18 +208,18 @@

private static string Round(object?[] args)
{
if (args.Length < 1 || args.Length> 2)
if (args.Length < 1 || args.Length > 2)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1-2 argument(s), got {args.Length}");
}

var number = PrepareNumericArg(args[0]);
if(number is null)

if (number is null)
{
number = 0;
}

int precision = 0;
if (args.Length == 2 && args[1] is not null)
{
Expand All @@ -237,7 +238,7 @@
string? stringOne = ToStringForEquals(args[0]);
return stringOne?.ToUpperInvariant();
}

private static string? LowerCase(object?[] args)
{
if (args.Length != 1)
Expand All @@ -247,7 +248,7 @@
string? stringOne = ToStringForEquals(args[0]);
return stringOne?.ToLowerInvariant();
}

private static bool PrepareBooleanArg(object? arg)
{
return arg switch
Expand Down Expand Up @@ -303,7 +304,7 @@

private static bool? Not(object?[] args)
{
if(args.Length != 1)
if (args.Length != 1)
{
throw new ExpressionEvaluatorTypeErrorException($"Expected 1 argument(s), got {args.Length}");
}
Expand All @@ -323,13 +324,15 @@

return (a, b);
}

private static double? PrepareNumericArg(object? arg)
{
return arg switch
{
bool ab => throw new ExpressionEvaluatorTypeErrorException($"Expected number, got value {(ab ? "true" : "false")}"),
string s => parseNumber(s),
int i => Convert.ToDouble(i),
decimal d => Convert.ToDouble(d),
object o => o as double?, // assume all relevant numbers are representable as double (as in frontend)
_ => null
};
Expand Down
Loading
Loading