1use std::collections::HashMap;
7
8use super::cards::{StaticCard, StaticCardsXNodesXWidgets};
9use super::nodes::StaticNodegroup;
10use super::translatable::StaticTranslatableString;
11use super::StaticNode;
12
13#[derive(Debug, Clone)]
15pub struct CardWidgetRef {
16 pub node_id: String,
17 pub node_alias: String,
18 pub widget_id: String,
19 pub widget_name: String,
21 pub label: StaticTranslatableString,
22 pub config: serde_json::Value,
23 pub sortorder: i32,
24 pub visible: bool,
25}
26
27#[derive(Debug, Clone)]
32pub struct CardIndex {
33 pub card_children: HashMap<String, Vec<String>>,
35 pub root_card_ids: Vec<String>,
37 pub card_by_nodegroup: HashMap<String, String>,
39 pub widgets_by_card: HashMap<String, Vec<CardWidgetRef>>,
41 pub alias_by_node_id: HashMap<String, String>,
43 pub cards_by_id: HashMap<String, CardRef>,
45}
46
47#[derive(Debug, Clone)]
49pub struct CardRef {
50 pub cardid: String,
51 pub name: StaticTranslatableString,
52 pub component_id: String,
53 pub nodegroup_id: String,
54 pub sortorder: Option<i32>,
55 pub visible: bool,
56 pub active: bool,
57}
58
59impl From<&StaticCard> for CardRef {
60 fn from(card: &StaticCard) -> Self {
61 CardRef {
62 cardid: card.cardid.clone(),
63 name: card.name.clone(),
64 component_id: card.component_id.clone(),
65 nodegroup_id: card.nodegroup_id.clone(),
66 sortorder: card.sortorder,
67 visible: card.visible,
68 active: card.active,
69 }
70 }
71}
72
73impl CardIndex {
74 pub fn nodegroup_ids_for_card(&self, card_id: &str, max_depth: Option<usize>) -> Vec<String> {
79 let mut result = Vec::new();
80 self.collect_nodegroups(card_id, max_depth, &mut result);
81 result
82 }
83
84 pub fn nodegroup_ids_for_roots(&self, max_depth: Option<usize>) -> Vec<String> {
86 let mut result = Vec::new();
87 for card_id in &self.root_card_ids {
88 self.collect_nodegroups(card_id, max_depth, &mut result);
89 }
90 result
91 }
92
93 fn collect_nodegroups(&self, card_id: &str, max_depth: Option<usize>, out: &mut Vec<String>) {
94 if let Some(card) = self.cards_by_id.get(card_id) {
95 if !out.contains(&card.nodegroup_id) {
96 out.push(card.nodegroup_id.clone());
97 }
98
99 if max_depth == Some(0) {
100 return;
101 }
102
103 let child_depth = max_depth.map(|d| d.saturating_sub(1));
104 if let Some(children) = self.card_children.get(card_id) {
105 for child_id in children {
106 self.collect_nodegroups(child_id, child_depth, out);
107 }
108 }
109 }
110 }
111}
112
113pub fn build_card_index(
118 cards: &[StaticCard],
119 cxnxws: &[StaticCardsXNodesXWidgets],
120 nodegroups: &[StaticNodegroup],
121 nodes: &[StaticNode],
122 widget_name_resolver: impl Fn(&str) -> Option<String>,
123) -> CardIndex {
124 let alias_by_node_id: HashMap<String, String> = nodes
126 .iter()
127 .filter_map(|n| {
128 n.alias
129 .as_ref()
130 .filter(|a| !a.is_empty())
131 .map(|a| (n.nodeid.clone(), a.clone()))
132 })
133 .collect();
134
135 let mut card_by_nodegroup: HashMap<String, String> = HashMap::new();
137 let mut cards_by_id: HashMap<String, CardRef> = HashMap::new();
138 for card in cards {
139 card_by_nodegroup.insert(card.nodegroup_id.clone(), card.cardid.clone());
140 cards_by_id.insert(card.cardid.clone(), CardRef::from(card));
141 }
142
143 let ng_by_id: HashMap<&str, &StaticNodegroup> = nodegroups
145 .iter()
146 .map(|ng| (ng.nodegroupid.as_str(), ng))
147 .collect();
148
149 let mut card_children: HashMap<String, Vec<String>> = HashMap::new();
151 let mut root_card_ids: Vec<String> = Vec::new();
152
153 for card in cards {
154 let ng = ng_by_id.get(card.nodegroup_id.as_str());
155 let parent_ng_id = ng.and_then(|ng| ng.parentnodegroup_id.as_ref());
156
157 match parent_ng_id {
158 Some(parent_ng) => {
159 if let Some(parent_card_id) = card_by_nodegroup.get(parent_ng) {
160 card_children
161 .entry(parent_card_id.clone())
162 .or_default()
163 .push(card.cardid.clone());
164 } else {
165 root_card_ids.push(card.cardid.clone());
167 }
168 }
169 None => {
170 root_card_ids.push(card.cardid.clone());
171 }
172 }
173 }
174
175 let card_sortorder = |card_id: &str| -> i32 {
177 cards_by_id
178 .get(card_id)
179 .and_then(|c| c.sortorder)
180 .unwrap_or(0)
181 };
182 for children in card_children.values_mut() {
183 children.sort_by_key(|id| card_sortorder(id));
184 }
185 root_card_ids.sort_by_key(|id| card_sortorder(id));
186
187 let mut widgets_by_card: HashMap<String, Vec<CardWidgetRef>> = HashMap::new();
189 for cxnxw in cxnxws {
190 let node_alias = alias_by_node_id
191 .get(&cxnxw.node_id)
192 .cloned()
193 .unwrap_or_default();
194 let widget_name = widget_name_resolver(&cxnxw.widget_id).unwrap_or_default();
195
196 let widget_ref = CardWidgetRef {
197 node_id: cxnxw.node_id.clone(),
198 node_alias,
199 widget_id: cxnxw.widget_id.clone(),
200 widget_name,
201 label: cxnxw.label.clone(),
202 config: cxnxw.config.clone(),
203 sortorder: cxnxw.sortorder.unwrap_or(0),
204 visible: cxnxw.visible,
205 };
206
207 widgets_by_card
208 .entry(cxnxw.card_id.clone())
209 .or_default()
210 .push(widget_ref);
211 }
212
213 for widgets in widgets_by_card.values_mut() {
215 widgets.sort_by_key(|w| w.sortorder);
216 }
217
218 CardIndex {
219 card_children,
220 root_card_ids,
221 card_by_nodegroup,
222 widgets_by_card,
223 alias_by_node_id,
224 cards_by_id,
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::graph::translatable::StaticTranslatableString;
232
233 fn make_node(id: &str, alias: &str, ng_id: &str) -> StaticNode {
234 StaticNode {
235 nodeid: id.to_string(),
236 name: alias.to_string(),
237 alias: Some(alias.to_string()),
238 datatype: "string".to_string(),
239 nodegroup_id: Some(ng_id.to_string()),
240 graph_id: "test-graph".to_string(),
241 is_collector: false,
242 isrequired: false,
243 exportable: false,
244 sortorder: Some(0),
245 config: Default::default(),
246 parentproperty: None,
247 ontologyclass: None,
248 description: None,
249 fieldname: None,
250 hascustomalias: false,
251 issearchable: false,
252 istopnode: false,
253 sourcebranchpublication_id: None,
254 source_identifier_id: None,
255 is_immutable: None,
256 }
257 }
258
259 fn make_card(id: &str, name: &str, ng_id: &str, sortorder: i32) -> StaticCard {
260 StaticCard {
261 cardid: id.to_string(),
262 name: StaticTranslatableString::from_string(name),
263 nodegroup_id: ng_id.to_string(),
264 component_id: "default".to_string(),
265 graph_id: "test-graph".to_string(),
266 active: true,
267 visible: true,
268 sortorder: Some(sortorder),
269 config: None,
270 constraints: vec![],
271 cssclass: None,
272 description: None,
273 helpenabled: false,
274 helptext: StaticTranslatableString::empty(),
275 helptitle: StaticTranslatableString::empty(),
276 instructions: StaticTranslatableString::empty(),
277 is_editable: Some(true),
278 source_identifier_id: None,
279 }
280 }
281
282 fn make_ng(id: &str, parent: Option<&str>) -> StaticNodegroup {
283 StaticNodegroup {
284 nodegroupid: id.to_string(),
285 cardinality: Some("1".to_string()),
286 parentnodegroup_id: parent.map(|s| s.to_string()),
287 legacygroupid: None,
288 grouping_node_id: None,
289 }
290 }
291
292 fn make_cxnxw(
293 card_id: &str,
294 node_id: &str,
295 widget_id: &str,
296 sortorder: i32,
297 ) -> StaticCardsXNodesXWidgets {
298 StaticCardsXNodesXWidgets {
299 card_id: card_id.to_string(),
300 node_id: node_id.to_string(),
301 widget_id: widget_id.to_string(),
302 id: format!("cxnxw-{}-{}", card_id, node_id),
303 label: StaticTranslatableString::from_string("Label"),
304 config: serde_json::Value::Object(serde_json::Map::new()),
305 sortorder: Some(sortorder),
306 visible: true,
307 source_identifier_id: None,
308 }
309 }
310
311 #[test]
312 fn test_build_card_index_basic() {
313 let nodes = vec![
314 make_node("n1", "field_a", "ng1"),
315 make_node("n2", "field_b", "ng1"),
316 make_node("n3", "field_c", "ng2"),
317 ];
318 let nodegroups = vec![make_ng("ng1", None), make_ng("ng2", Some("ng1"))];
319 let cards = vec![
320 make_card("card1", "Parent Card", "ng1", 0),
321 make_card("card2", "Child Card", "ng2", 0),
322 ];
323 let cxnxws = vec![
324 make_cxnxw("card1", "n1", "widget-text", 0),
325 make_cxnxw("card1", "n2", "widget-concept", 1),
326 make_cxnxw("card2", "n3", "widget-date", 0),
327 ];
328
329 let index = build_card_index(&cards, &cxnxws, &nodegroups, &nodes, |wid| {
330 Some(format!("resolved-{}", wid))
331 });
332
333 assert_eq!(index.root_card_ids, vec!["card1"]);
335
336 assert_eq!(
338 index.card_children.get("card1").unwrap(),
339 &vec!["card2".to_string()]
340 );
341 assert!(index.card_children.get("card2").is_none());
342
343 let card1_widgets = index.widgets_by_card.get("card1").unwrap();
345 assert_eq!(card1_widgets.len(), 2);
346 assert_eq!(card1_widgets[0].node_alias, "field_a");
347 assert_eq!(card1_widgets[1].node_alias, "field_b");
348
349 let card2_widgets = index.widgets_by_card.get("card2").unwrap();
350 assert_eq!(card2_widgets.len(), 1);
351 assert_eq!(card2_widgets[0].node_alias, "field_c");
352
353 assert_eq!(index.alias_by_node_id.get("n1").unwrap(), "field_a");
355 }
356
357 #[test]
358 fn test_nodegroup_ids_for_card() {
359 let nodes = vec![
360 make_node("n1", "a", "ng1"),
361 make_node("n2", "b", "ng2"),
362 make_node("n3", "c", "ng3"),
363 ];
364 let nodegroups = vec![
365 make_ng("ng1", None),
366 make_ng("ng2", Some("ng1")),
367 make_ng("ng3", Some("ng2")),
368 ];
369 let cards = vec![
370 make_card("card1", "Root", "ng1", 0),
371 make_card("card2", "Child", "ng2", 0),
372 make_card("card3", "Grandchild", "ng3", 0),
373 ];
374 let index = build_card_index(&cards, &[], &nodegroups, &nodes, |_| None);
375
376 let all = index.nodegroup_ids_for_card("card1", None);
378 assert_eq!(all, vec!["ng1", "ng2", "ng3"]);
379
380 let just_root = index.nodegroup_ids_for_card("card1", Some(0));
382 assert_eq!(just_root, vec!["ng1"]);
383
384 let one_level = index.nodegroup_ids_for_card("card1", Some(1));
386 assert_eq!(one_level, vec!["ng1", "ng2"]);
387
388 let from_child = index.nodegroup_ids_for_card("card2", None);
390 assert_eq!(from_child, vec!["ng2", "ng3"]);
391
392 let roots = index.nodegroup_ids_for_roots(Some(0));
394 assert_eq!(roots, vec!["ng1"]);
395 }
396
397 #[test]
398 fn test_card_index_sortorder() {
399 let nodes = vec![
400 make_node("n1", "a", "ng1"),
401 make_node("n2", "b", "ng2"),
402 make_node("n3", "c", "ng3"),
403 ];
404 let nodegroups = vec![
405 make_ng("ng1", None),
406 make_ng("ng2", None),
407 make_ng("ng3", None),
408 ];
409 let cards = vec![
410 make_card("card1", "Third", "ng1", 2),
411 make_card("card2", "First", "ng2", 0),
412 make_card("card3", "Second", "ng3", 1),
413 ];
414 let cxnxws = vec![];
415
416 let index = build_card_index(&cards, &cxnxws, &nodegroups, &nodes, |_| None);
417
418 assert_eq!(index.root_card_ids, vec!["card2", "card3", "card1"]);
420 }
421}