1+ using UvA . Workflow . Api . Screens . Dtos ;
2+ using UvA . Workflow . Tools ;
3+
4+ namespace UvA . Workflow . Api . Screens ;
5+
6+ public class ScreenDataService (
7+ ModelService modelService ,
8+ IWorkflowInstanceRepository repository )
9+ {
10+ public async Task < ScreenDataDto > GetScreenData ( string screenName , string entityType , CancellationToken ct )
11+ {
12+ // Get the screen definition
13+ var screen = GetScreen ( screenName , entityType ) ;
14+ if ( screen == null )
15+ throw new ArgumentException ( $ "Screen '{ screenName } ' not found for entity type '{ entityType } '") ;
16+
17+ // Build projection based on screen columns
18+ var projection = BuildProjection ( screen , entityType ) ;
19+ var rawData = await repository . GetAllByType ( entityType , projection , ct ) ;
20+
21+ // Process the data and apply templates/expressions
22+ var columns = screen . Columns . Select ( ScreenColumnDto . Create ) . ToArray ( ) ;
23+ var rows = ProcessRows ( rawData , screen , entityType , columns ) ;
24+
25+ return ScreenDataDto . Create ( screen , columns , rows ) ;
26+ }
27+
28+ private Screen ? GetScreen ( string screenName , string entityType )
29+ {
30+ if ( ! modelService . EntityTypes . TryGetValue ( entityType , out var entity ) )
31+ return null ;
32+
33+ return entity . Screens . GetValueOrDefault ( screenName ) ;
34+ }
35+
36+ private Dictionary < string , string > BuildProjection ( Screen screen , string entityType )
37+ {
38+ if ( ! modelService . EntityTypes . TryGetValue ( entityType , out var entity ) )
39+ throw new ArgumentException ( $ "Entity type '{ entityType } ' not found") ;
40+
41+ var projection = new Dictionary < string , string > ( ) ;
42+
43+ foreach ( var column in screen . Columns )
44+ {
45+ if ( column . CurrentStep )
46+ {
47+ projection [ "CurrentStep" ] = "$CurrentStep" ;
48+ }
49+ else if ( ! string . IsNullOrEmpty ( column . Property ) )
50+ {
51+ // Use EntityType.GetKey to get the correct MongoDB path
52+ var mongoPath = entity . GetKey ( column . Property . Split ( '.' ) [ 0 ] ) ;
53+ var propertyName = column . Property . Split ( '.' ) [ 0 ] ;
54+
55+ projection . TryAdd ( propertyName , mongoPath ) ;
56+ }
57+
58+ // If column has templates, we need to include their properties
59+ if ( column . ValueTemplate != null )
60+ {
61+ foreach ( var prop in column . ValueTemplate . Properties )
62+ {
63+ AddLookupToProjection ( projection , prop , entity ) ;
64+ }
65+ }
66+ }
67+
68+ return projection ;
69+ }
70+
71+ private void AddLookupToProjection ( Dictionary < string , string > projection , Lookup lookup , EntityType entity )
72+ {
73+ switch ( lookup )
74+ {
75+ case PropertyLookup propertyLookup :
76+ var propertyName = propertyLookup . Property . Split ( '.' ) [ 0 ] ;
77+ var mongoPath = entity . GetKey ( propertyName ) ;
78+ projection . TryAdd ( propertyName , mongoPath ) ;
79+ break ;
80+ case ComplexLookup complexLookup :
81+ // For complex lookups, we need to add properties from their arguments
82+ foreach ( var arg in complexLookup . Arguments )
83+ {
84+ foreach ( var prop in arg . Properties )
85+ {
86+ AddLookupToProjection ( projection , prop , entity ) ;
87+ }
88+ }
89+
90+ break ;
91+ }
92+ }
93+
94+ private ScreenRowDto [ ] ProcessRows (
95+ List < Dictionary < string , BsonValue > > rawData ,
96+ Screen screen ,
97+ string entityType ,
98+ ScreenColumnDto [ ] columns
99+ )
100+ {
101+ var rows = new List < ScreenRowDto > ( ) ;
102+
103+ foreach ( var rawRow in rawData )
104+ {
105+ var id = rawRow . GetValueOrDefault ( "_id" ) ? . ToString ( ) ?? "Unknown" ;
106+ var processedValues = new Dictionary < int , object ? > ( ) ;
107+
108+ // Process each column and use its ID as the key
109+ for ( int i = 0 ; i < screen . Columns . Length ; i ++ )
110+ {
111+ var column = screen . Columns [ i ] ;
112+ var columnId = columns [ i ] . Id ;
113+ var value = ProcessColumnValue ( rawRow , column , entityType , id ) ;
114+ processedValues [ columnId ] = value ;
115+ }
116+
117+ rows . Add ( ScreenRowDto . Create ( id , processedValues ) ) ;
118+ }
119+
120+ return rows . ToArray ( ) ;
121+ }
122+
123+ private object ? ProcessColumnValue (
124+ Dictionary < string , BsonValue > rawRow ,
125+ Column column ,
126+ string entityType ,
127+ string instanceId
128+ )
129+ {
130+ if ( column . CurrentStep )
131+ {
132+ // Return current step value or default
133+ return rawRow . GetStringValue ( "CurrentStep" ) ?? column . Default ?? "Draft" ;
134+ }
135+
136+ if ( column . ValueTemplate != null )
137+ {
138+ // Process template - create a context and evaluate the template
139+ var context = CreateContextFromRawRow ( rawRow , entityType , instanceId ) ;
140+ return column . ValueTemplate . Execute ( context ) ;
141+ }
142+
143+ if ( ! string . IsNullOrEmpty ( column . Property ) )
144+ {
145+ // Get property value from raw data
146+ var value = GetNestedPropertyValue ( rawRow , column . Property ) ;
147+ if ( value != null && ! value . IsBsonNull )
148+ {
149+ return BsonConversionTools . ConvertBasicBsonValue ( value ) ;
150+ }
151+ }
152+
153+ return column . Default ;
154+ }
155+
156+ private ObjectContext CreateContextFromRawRow ( Dictionary < string , BsonValue > rawRow , string entityType ,
157+ string instanceId )
158+ {
159+ // Create a minimal WorkflowInstance for context creation
160+ // Convert the projected properties back to the expected Properties format
161+ var properties = new Dictionary < string , BsonValue > ( ) ;
162+
163+ foreach ( var kvp in rawRow )
164+ {
165+ if ( kvp . Key == "_id" || kvp . Key == "CurrentStep" || kvp . Key . EndsWith ( "Event" ) ) // TODO this is a bit iffy?
166+ continue ;
167+
168+ // The key is the property name, value is the BsonValue from $Properties.{key}
169+ properties [ kvp . Key ] = kvp . Value ;
170+ }
171+
172+ var instance = new WorkflowInstance
173+ {
174+ Id = instanceId ,
175+ EntityType = entityType ,
176+ Properties = properties ,
177+ Events = new Dictionary < string , InstanceEvent > ( ) ,
178+ CurrentStep = rawRow . GetStringValue ( "CurrentStep" )
179+ } ;
180+
181+ return modelService . CreateContext ( instance ) ;
182+ }
183+
184+ private BsonValue ? GetNestedPropertyValue ( Dictionary < string , BsonValue > data , string propertyPath )
185+ {
186+ var parts = propertyPath . Split ( '.' ) ;
187+ var rootProperty = parts [ 0 ] ;
188+
189+ if ( ! data . TryGetValue ( rootProperty , out var rootValue ) )
190+ return null ;
191+
192+ // If only one part, return the root value
193+ if ( parts . Length == 1 )
194+ return rootValue ;
195+
196+ // Use shared utility to navigate the remaining path
197+ return BsonConversionTools . NavigateNestedBsonValue ( rootValue , parts . Skip ( 1 ) ) ;
198+ }
199+
200+ }
0 commit comments