Skip to content

Commit 5aee8cb

Browse files
aquamatthiasmeln1k
andauthored
[core][fix] Compute descendant count based on ancestors section not g… (#2213)
Co-authored-by: Nikita Melkozerov <nikita@some.engineering>
1 parent a5267a3 commit 5aee8cb

File tree

3 files changed

+33
-36
lines changed

3 files changed

+33
-36
lines changed

fixcore/fixcore/db/graphdb.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,8 +1191,11 @@ def parent_edges(edge_type: EdgeType) -> Tuple[str, Json]:
11911191

11921192
graph_update = parent_change.to_update() + change.to_update()
11931193
log.debug(f"Update prepared: {graph_update}. Going to persist the changes.")
1194-
await self._refresh_marked_update(change_id)
1195-
await self._persist_update(change_id, is_batch, change, update_history)
1194+
if change.change_count():
1195+
await self._refresh_marked_update(change_id)
1196+
await self._persist_update(change_id, is_batch, change, update_history)
1197+
else:
1198+
await self.delete_marked_update(change_id)
11961199
return roots, graph_update
11971200
except Exception as ex:
11981201
await self.delete_marked_update(change_id)

fixcore/fixcore/model/graph_access.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
from collections import namedtuple, defaultdict
88
from functools import reduce
9-
from typing import Optional, Generator, Any, Dict, List, Set, Tuple, Union, Iterator
9+
from typing import Optional, Generator, Any, Dict, List, Set, Tuple, Union, Iterator, DefaultDict
1010

1111
from attrs import define
1212
from networkx import DiGraph, MultiDiGraph, is_directed_acyclic_graph
@@ -476,40 +476,34 @@ def resolve(self) -> None:
476476
log.info("Resolve attributes finished.")
477477

478478
def __resolve_count_descendants(self) -> None:
479-
visited: Set[str] = set()
480479
empty_set: Set[str] = set()
481480

482-
def count_successors_by(node_id: NodeId, edge_type: EdgeType, path: List[str]) -> Dict[str, int]:
483-
result: Dict[str, int] = {}
484-
to_visit = list(self.successors(node_id, edge_type))
485-
while to_visit:
486-
visit_next: List[NodeId] = []
487-
for elem_id in to_visit:
488-
if elem_id not in visited:
489-
visited.add(elem_id)
490-
elem = self.nodes[elem_id]
491-
if "phantom" not in elem.get("kinds_set", empty_set):
492-
extracted = value_in_path(elem, path)
493-
if isinstance(extracted, str):
494-
result[extracted] = result.get(extracted, 0) + 1
495-
# check if there is already a successor summary: stop the traversal and take the result.
496-
existing = value_in_path(elem, NodePath.descendant_summary)
497-
if existing and isinstance(existing, dict):
498-
for summary_item, count in existing.items():
499-
result[summary_item] = result.get(summary_item, 0) + count
500-
else:
501-
visit_next.extend(a for a in self.successors(elem_id, edge_type) if a not in visited)
502-
to_visit = visit_next
481+
def count_descendants_of(identifier: str, ancestor_kind: str, path: List[str]) -> Dict[str, int]:
482+
result: DefaultDict[str, int] = defaultdict(int)
483+
ancestor_path = ["ancestors", ancestor_kind, "reported", "id"]
484+
for _, elem in self.g.nodes(data=True):
485+
if value_in_path(elem, ancestor_path) == identifier:
486+
kinds_set = elem.get("kinds_set", empty_set)
487+
extracted = value_in_path(elem, path)
488+
if "phantom_resource" not in kinds_set and isinstance(extracted, str):
489+
result[extracted] += 1
503490
return result
504491

505492
for on_kind, prop in GraphResolver.count_successors.items():
506-
for node_id, node in self.g.nodes(data=True):
493+
for _, node in self.g.nodes(data=True):
507494
kinds = node.get("kinds_set")
508495
if kinds and on_kind in kinds:
509-
summary = count_successors_by(node_id, EdgeTypes.default, prop.extract_path)
510-
set_value_in_path(summary, prop.to_path, node)
511-
total = reduce(lambda left, right: left + right, summary.values(), 0)
512-
set_value_in_path(total, NodePath.descendant_count, node)
496+
if rid := value_in_path(node, NodePath.reported_id):
497+
# descendant summary
498+
summary = count_descendants_of(rid, on_kind, prop.extract_path)
499+
set_value_in_path(summary, prop.to_path, node)
500+
# descendant count
501+
total = reduce(lambda left, right: left + right, summary.values(), 0)
502+
set_value_in_path(total, NodePath.descendant_count, node)
503+
# update hash
504+
node["hash"] = GraphBuilder.content_hash(
505+
node["reported"], node.get("desired"), node.get("metadata")
506+
)
513507

514508
def __resolve(self, node_id: NodeId, node: Json) -> Json:
515509
def with_ancestor(ancestor: Json, prop: ResolveProp) -> None:

fixcore/tests/fixcore/model/graph_access_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,14 @@ def test_resolve_graph_data() -> None:
360360
assert n1.descendant_summary == AccessNone(None)
361361

362362
r1 = AccessJson(graph.node("region_account_cloud_gcp_1_europe")) # type: ignore
363-
assert r1.metadata.descendant_summary == {"child": 9}
364-
assert r1.metadata.descendant_count == 9
363+
assert r1.metadata.descendant_summary == {"child": 9, "parent": 3, "region": 1}
364+
assert r1.metadata.descendant_count == 13
365365
r2 = AccessJson(graph.node("account_cloud_gcp_1")) # type: ignore
366-
assert r2.metadata.descendant_summary == {"child": 54, "region": 6}
367-
assert r2.metadata.descendant_count == 60
366+
assert r2.metadata.descendant_summary == {"account": 1, "child": 54, "parent": 18, "region": 6}
367+
assert r2.metadata.descendant_count == 79
368368
r3 = AccessJson(graph.node("cloud_gcp")) # type: ignore
369-
assert r3.metadata.descendant_summary == {"child": 162, "region": 18, "account": 3}
370-
assert r3.metadata.descendant_count == 183
369+
assert r3.metadata.descendant_summary == {"account": 3, "child": 162, "cloud": 1, "parent": 54, "region": 18}
370+
assert r3.metadata.descendant_count == 238
371371

372372

373373
def test_model_size(person_model: Model) -> None:

0 commit comments

Comments
 (0)