Skip to content

Commit

Permalink
[resoto][fix] Change node should also reflect resolved properties (#1858
Browse files Browse the repository at this point in the history
)
  • Loading branch information
aquamatthias committed Dec 13, 2023
1 parent d48e715 commit 91e31f7
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 15 deletions.
40 changes: 38 additions & 2 deletions resotocore/resotocore/db/graphdb.py
Expand Up @@ -7,6 +7,7 @@
from enum import Enum
from functools import partial
from numbers import Number
from textwrap import dedent
from typing import (
DefaultDict,
Optional,
Expand Down Expand Up @@ -52,7 +53,7 @@
UsageDatapoint,
synthetic_metadata_kinds,
)
from resotocore.model.resolve_in_graph import NodePath, GraphResolver
from resotocore.model.resolve_in_graph import NodePath, GraphResolver, ResolveProp
from resotocore.model.typed_model import to_js
from resotocore.query.model import Query, FulltextTerm, MergeTerm, P, Predicate
from resotocore.report import ReportSeverity
Expand Down Expand Up @@ -352,7 +353,27 @@ async def update_node_with(
if sec in adjusted:
update[sec] = adjusted[sec]

result = await db.update(self.vertex_name, update, return_new=True, merge=not replace)
async def update_resolved_property(id_prop: ResolveProp, patch: Json, history: bool) -> None:
log.info(f"Update resolved property: {id_prop.to}={patch} for node_id={node_id}")
async with await self.db.aql_cursor(
query=self.update_resolved(id_prop, history), bind_vars={"node_id": node_id, "patch": patch}
) as crs:
async for el in crs:
part = self.node_history if history else self.vertex_name
log.info(f"Updated resolved property in {part}: {el} elements changed.")

# update resolved properties in vertex and history collection
if (ra := GraphResolver.resolve_ancestor_for(update)) and (rid := ra.resolves_id()):
changes: Json = {}
for prop in ra.resolved_props():
if value_in_path(node, prop.extract_path) != (nv := value_in_path(update, prop.extract_path)):
set_value_in_path(nv, prop.to_path, changes)
if changes:
await update_resolved_property(rid, changes, False)
await update_resolved_property(rid, changes, True)

# update in database
result = await self.db.update(self.vertex_name, update, return_new=True, merge=not replace)
trafo = self.document_to_instance_fn(model)
return trafo(result["new"])

Expand Down Expand Up @@ -1464,6 +1485,21 @@ def update_active_change(self) -> str:
UPDATE d WITH {{created: DATE_ISO8601(DATE_NOW())}} in `{self.in_progress}`
""" # noqa: E501

def update_resolved(
self,
prop: ResolveProp,
history: bool = False,
) -> str:
coll = self.node_history if history else self.vertex_name
return dedent(
f"""
FOR d in `{coll}` FILTER d._key!=@node_id and d.{prop.to}==@node_id
UPDATE d WITH @patch in `{coll}`
COLLECT WITH COUNT INTO count
RETURN count
"""
)


class EventGraphDB(GraphDB):
def __init__(self, real: ArangoGraphDB, event_sender: AnalyticsEventSender):
Expand Down
11 changes: 11 additions & 0 deletions resotocore/resotocore/model/resolve_in_graph.py
Expand Up @@ -56,6 +56,9 @@ class ResolveAncestor:
# List of all properties to be resolved.
resolve: List[ResolveProp]

def resolved_props(self) -> List[ResolveProp]:
return [prop for prop in self.resolve if prop.extract_path != NodePath.node_id]

def resolves_id(self) -> Optional[ResolveProp]:
for prop in self.resolve:
if prop.extract_path == NodePath.node_id:
Expand Down Expand Up @@ -126,3 +129,11 @@ def resolved_kind(node: Json) -> Optional[str]:
if kind in GraphResolver.resolved_ancestors:
return kind
return None

@staticmethod
def resolve_ancestor_for(node: Json) -> Optional[ResolveAncestor]:
if kind := GraphResolver.resolved_kind(node):
for elem in GraphResolver.to_resolve:
if elem.kind == kind:
return elem
return None
38 changes: 25 additions & 13 deletions resotocore/tests/resotocore/db/graphdb_test.py
Expand Up @@ -511,19 +511,31 @@ async def test_insert_node(graph_db: ArangoGraphDB, foo_model: Model) -> None:


@mark.asyncio
async def test_update_node(graph_db: ArangoGraphDB, foo_model: Model) -> None:
await graph_db.wipe()
await graph_db.create_node(foo_model, NodeId("some_other"), to_json(Foo("some_other", "foo")), NodeId("root"))
json_patch = await graph_db.update_node(foo_model, NodeId("some_other"), {"name": "bla"}, False, "reported")
assert to_foo(json_patch).name == "bla"
assert to_foo(await graph_db.get_node(foo_model, NodeId("some_other"))).name == "bla"
json_replace = (
await graph_db.update_node(
foo_model, NodeId("some_other"), {"kind": "bla", "identifier": "123"}, True, "reported"
)
)["reported"]
json_replace.pop("ctime") # ctime is added by the system automatically. remove it
assert json_replace == {"kind": "bla", "identifier": "123"}
async def test_update_node(filled_graph_db: ArangoGraphDB, foo_model: Model) -> None:
nid = NodeId("0")
# patch
js = await filled_graph_db.update_node(foo_model, nid, {"name": "bla"}, False, "reported")
assert to_foo(js).name == "bla"
assert to_foo(await filled_graph_db.get_node(foo_model, nid)).name == "bla"
# replace
js = await filled_graph_db.update_node(foo_model, nid, {"kind": "bla", "identifier": "123"}, True, "reported")
reported = js["reported"]
reported.pop("ctime") # ctime is added by the system automatically. remove it
assert reported == {"kind": "bla", "identifier": "123"}
# also make sure that all resolved ancestor props are changed
nid = NodeId("sub_root")
# patch
js = await filled_graph_db.update_node(foo_model, nid, {"name": "bat"}, False, "reported")
assert js["reported"]["name"] == "bat"

async def elements(history: bool) -> List[Json]:
fn = filled_graph_db.search_history if history else filled_graph_db.search_list
model = QueryModel(parse_query("ancestors.account.reported.name==bat"), foo_model)
async with await fn(query=model) as crs: # type: ignore
return [e async for e in crs]

assert len(await elements(False)) == 110
assert len(await elements(True)) == 111 # history includes the node itself


@mark.asyncio
Expand Down

0 comments on commit 91e31f7

Please sign in to comment.