Skip to content

Commit 7cbb9d3

Browse files
committed
Make component expressions subform aware (#1698)
Previously you could not use "component" expressions in subforms, because the context lookup did error because multiple contexts would match.
1 parent 31d19a3 commit 7cbb9d3

5 files changed

Lines changed: 69 additions & 7 deletions

File tree

src/Altinn.App.Core/Internal/Expressions/ExpressionEvaluator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ LayoutEvaluatorState state
260260
throw new ArgumentException("The component expression requires a component context");
261261
}
262262

263-
var targetContext = await state.GetComponentContext(context.Component?.PageId, componentId, context.RowIndices);
263+
var targetContext = await state.GetComponentContext(componentId, relativeContext: context);
264264

265265
if (targetContext is null)
266266
{

src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,50 @@ public void GetComponent(string pageName, string componentId)
113113
throw new NotSupportedException("GetComponent is not supported, use GetComponentContext instead.");
114114
}
115115

116+
/// <summary>
117+
/// Get a specific component context from the state relative to another contexts subform data element and row indexes.
118+
/// </summary>
119+
public async Task<ComponentContext?> GetComponentContext(string componentId, ComponentContext relativeContext)
120+
{
121+
if (_componentModel is null)
122+
{
123+
throw new InvalidOperationException("Component model not loaded");
124+
}
125+
126+
var contexts = (await GetComponentContexts()).SelectMany(c => c.Descendants);
127+
128+
if (relativeContext.DataElementIdentifier is not null)
129+
{
130+
// Filter out contexts that does not have the same data element identifier as the relative context, this ensures that we only get contexts from the same subform repeat group when there are multiple in the same layout
131+
contexts = contexts.Where(c => c.DataElementIdentifier == relativeContext.DataElementIdentifier);
132+
}
133+
// Filter out all contexts that have the wrong Id
134+
// Filter out contexts that does not have a prefix matching
135+
var filteredContexts = contexts
136+
.Where(c => c.Component?.Id == componentId)
137+
.Where(c => RowIndexMatch(relativeContext.RowIndices, c.RowIndices))
138+
.ToArray();
139+
if (filteredContexts.Length == 0)
140+
{
141+
return null; // No context found
142+
}
143+
144+
if (filteredContexts.Length == 1)
145+
{
146+
return filteredContexts[0];
147+
}
148+
149+
throw new InvalidOperationException(
150+
$"Multiple contexts found for {componentId} with [{(relativeContext.RowIndices is null ? "" : string.Join(", ", relativeContext.RowIndices))}]"
151+
);
152+
}
153+
116154
/// <summary>
117155
/// Get a specific component context from the state
118156
/// </summary>
157+
[Obsolete(
158+
"A context is not uniquely identified by componentId and rowIndexes, use GetComponentContext(string, ComponentContext) instead so that we get subform data element as well."
159+
)]
119160
public async Task<ComponentContext?> GetComponentContext(
120161
string? pageName,
121162
string componentId,

test/Altinn.App.Core.Tests/LayoutExpressions/CommonTests/ExpressionTestCaseRoot.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Altinn.App.Core.Configuration;
44
using Altinn.App.Core.Internal.Expressions;
55
using Altinn.App.Core.Models.Expressions;
6+
using Altinn.App.Core.Models.Layout;
67
using Altinn.App.Core.Models.Layout.Components;
78
using Altinn.Platform.Storage.Interface.Models;
89

@@ -106,11 +107,7 @@ public async Task<ComponentContext> GetContextOrNull(LayoutEvaluatorState state)
106107
if (Context is not null)
107108
{
108109
//! Some tests do not need context, but it is not nullable in expression evaluator
109-
context = await state.GetComponentContext(
110-
Context.CurrentPageName,
111-
Context.ComponentId,
112-
Context.RowIndices
113-
)!;
110+
context = await state.GetComponentContext(Context.ComponentId, Context.ToContext())!;
114111
}
115112

116113
//! Some tests do not need context, but it is not nullable in expression evaluator
@@ -167,4 +164,24 @@ public static ComponentContextForTestSpec FromContext(ComponentContext context)
167164
RowIndices = context.RowIndices,
168165
};
169166
}
167+
168+
public ComponentContext ToContext() =>
169+
new ComponentContext(
170+
null!,
171+
new UnknownComponent
172+
{
173+
Id = ComponentId,
174+
PageId = CurrentPageName,
175+
LayoutId = "layout",
176+
Type = "unknown",
177+
Hidden = default,
178+
RemoveWhenHidden = default,
179+
Required = default,
180+
ReadOnly = default,
181+
DataModelBindings = new Dictionary<string, ModelBinding>(),
182+
TextResourceBindings = new Dictionary<string, Expression>(),
183+
},
184+
RowIndices ?? Array.Empty<int>(),
185+
null
186+
);
170187
}

test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/SubForm/SubFormTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private record SubFormModel(
178178
"dataModelBindings": {
179179
"simpleBinding": "Address"
180180
},
181-
"required": true
181+
"required": ["notEquals", ["component", "Name"], "Always required, by test component lookup"]
182182
},
183183
{
184184
"id": "Phone",

test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3254,6 +3254,10 @@ namespace Altinn.App.Core.Internal.Expressions
32543254
public int CountDataElements(string dataTypeId) { }
32553255
[System.Obsolete("You need to get a context, not a component", true)]
32563256
public void GetComponent(string pageName, string componentId) { }
3257+
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Expressions.ComponentContext?> GetComponentContext(string componentId, Altinn.App.Core.Models.Expressions.ComponentContext relativeContext) { }
3258+
[System.Obsolete("A context is not uniquely identified by componentId and rowIndexes, use GetCompon" +
3259+
"entContext(string, ComponentContext) instead so that we get subform data element" +
3260+
" as well.")]
32573261
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Expressions.ComponentContext?> GetComponentContext(string? pageName, string componentId, int[]? rowIndexes = null) { }
32583262
public System.Threading.Tasks.Task<System.Collections.Generic.List<Altinn.App.Core.Models.Expressions.ComponentContext>> GetComponentContexts() { }
32593263
public Altinn.Platform.Storage.Interface.Models.DataType? GetDefaultDataType() { }

0 commit comments

Comments
 (0)