Skip to content

Commit 3a08fd2

Browse files
authored
Merge pull request #5920 from opsmill/pog-add-missing-relationship-types-IFC-1319
Add missing handling of secondary changelog relationships
2 parents 8611b14 + 38bd792 commit 3a08fd2

File tree

4 files changed

+279
-38
lines changed

4 files changed

+279
-38
lines changed

backend/infrahub/core/changelog/models.py

Lines changed: 189 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from infrahub.core.manager import RelationshipSchema
1414
from infrahub.core.query.relationship import RelationshipPeerData
1515
from infrahub.core.relationship.model import Relationship
16+
from infrahub.core.schema import MainSchemaTypes
17+
from infrahub.core.schema.schema_branch import SchemaBranch
1618
from infrahub.database import InfrahubDatabase
1719

1820

@@ -463,37 +465,198 @@ async def get_changelogs(self, primary_changelog: NodeChangelog) -> list[NodeCha
463465
These will typically include updates to relationships on other nodes.
464466
"""
465467
schema_branch = self._db.schema.get_schema_branch(name=self._branch.name)
466-
node_schema = schema_branch.get(name=primary_changelog.node_kind)
468+
node_schema = schema_branch.get(name=primary_changelog.node_kind, duplicate=False)
467469
secondaries: list[NodeChangelog] = []
468470

469471
for relationship in primary_changelog.relationships.values():
470-
rel_schema = node_schema.get_relationship(name=relationship.name)
471472
if isinstance(relationship, RelationshipCardinalityOneChangelog):
472-
# For now this code only looks at the scenario when a cardinality=one relationship
473-
# is added to a node and it has a cardinality=many relationship coming back from
474-
# another node, it will be expanded to include all variations.
475-
if relationship.peer_status == DiffAction.ADDED:
476-
peer_schema = schema_branch.get(name=str(relationship.peer_kind))
477-
peer_relation = peer_schema.get_relationship_by_identifier(
478-
id=str(rel_schema.identifier), raise_on_error=False
473+
secondaries.extend(
474+
self._parse_cardinality_one_relationship(
475+
relationship=relationship,
476+
node_schema=node_schema,
477+
primary_changelog=primary_changelog,
478+
schema_branch=schema_branch,
479479
)
480-
if peer_relation:
481-
node_changelog = NodeChangelog(
482-
node_id=str(relationship.peer_id),
483-
node_kind=str(relationship.peer_kind),
484-
display_label="n/a",
480+
)
481+
elif isinstance(relationship, RelationshipCardinalityManyChangelog):
482+
secondaries.extend(
483+
self._parse_cardinality_many_relationship(
484+
relationship=relationship,
485+
node_schema=node_schema,
486+
primary_changelog=primary_changelog,
487+
schema_branch=schema_branch,
488+
)
489+
)
490+
491+
return secondaries
492+
493+
def _parse_cardinality_one_relationship(
494+
self,
495+
relationship: RelationshipCardinalityOneChangelog,
496+
node_schema: MainSchemaTypes,
497+
primary_changelog: NodeChangelog,
498+
schema_branch: SchemaBranch,
499+
) -> list[NodeChangelog]:
500+
secondaries: list[NodeChangelog] = []
501+
rel_schema = node_schema.get_relationship(name=relationship.name)
502+
503+
if relationship.peer_status == DiffAction.ADDED:
504+
peer_schema = schema_branch.get(name=str(relationship.peer_kind), duplicate=False)
505+
secondaries.extend(
506+
self._process_added_peers(
507+
peer_id=str(relationship.peer_id),
508+
peer_kind=str(relationship.peer_kind),
509+
peer_schema=peer_schema,
510+
rel_schema=rel_schema,
511+
primary_changelog=primary_changelog,
512+
)
513+
)
514+
515+
elif relationship.peer_status == DiffAction.UPDATED:
516+
peer_schema = schema_branch.get(name=str(relationship.peer_kind), duplicate=False)
517+
secondaries.extend(
518+
self._process_added_peers(
519+
peer_id=str(relationship.peer_id),
520+
peer_kind=str(relationship.peer_kind),
521+
peer_schema=peer_schema,
522+
rel_schema=rel_schema,
523+
primary_changelog=primary_changelog,
524+
)
525+
)
526+
secondaries.extend(
527+
self._process_removed_peers(
528+
peer_schema=peer_schema,
529+
peer_id=str(relationship.peer_id_previous),
530+
peer_kind=str(relationship.peer_kind_previous),
531+
rel_schema=rel_schema,
532+
primary_changelog=primary_changelog,
533+
)
534+
)
535+
536+
elif relationship.peer_status == DiffAction.REMOVED:
537+
peer_schema = schema_branch.get(name=str(relationship.peer_kind_previous), duplicate=False)
538+
539+
secondaries.extend(
540+
self._process_removed_peers(
541+
peer_id=str(relationship.peer_id_previous),
542+
peer_kind=str(relationship.peer_kind_previous),
543+
peer_schema=peer_schema,
544+
rel_schema=rel_schema,
545+
primary_changelog=primary_changelog,
546+
)
547+
)
548+
549+
return secondaries
550+
551+
def _parse_cardinality_many_relationship(
552+
self,
553+
relationship: RelationshipCardinalityManyChangelog,
554+
node_schema: MainSchemaTypes,
555+
primary_changelog: NodeChangelog,
556+
schema_branch: SchemaBranch,
557+
) -> list[NodeChangelog]:
558+
secondaries: list[NodeChangelog] = []
559+
rel_schema = node_schema.get_relationship(name=relationship.name)
560+
561+
for peer in relationship.peers:
562+
if peer.peer_status == DiffAction.ADDED:
563+
peer_schema = schema_branch.get(name=peer.peer_kind)
564+
secondaries.extend(
565+
self._process_added_peers(
566+
peer_id=peer.peer_id,
567+
peer_kind=peer.peer_kind,
568+
peer_schema=peer_schema,
569+
rel_schema=rel_schema,
570+
primary_changelog=primary_changelog,
571+
)
572+
)
573+
574+
elif peer.peer_status == DiffAction.REMOVED:
575+
peer_schema = schema_branch.get(name=peer.peer_kind)
576+
secondaries.extend(
577+
self._process_removed_peers(
578+
peer_id=peer.peer_id,
579+
peer_kind=peer.peer_kind,
580+
peer_schema=peer_schema,
581+
rel_schema=rel_schema,
582+
primary_changelog=primary_changelog,
583+
)
584+
)
585+
586+
return secondaries
587+
588+
def _process_added_peers(
589+
self,
590+
peer_id: str,
591+
peer_kind: str,
592+
peer_schema: MainSchemaTypes,
593+
rel_schema: RelationshipSchema,
594+
primary_changelog: NodeChangelog,
595+
) -> list[NodeChangelog]:
596+
secondaries: list[NodeChangelog] = []
597+
peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
598+
if peer_relation:
599+
node_changelog = NodeChangelog(
600+
node_id=peer_id,
601+
node_kind=peer_kind,
602+
display_label="n/a",
603+
)
604+
if peer_relation.cardinality == RelationshipCardinality.ONE:
605+
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
606+
name=peer_relation.name,
607+
peer_id=primary_changelog.node_id,
608+
peer_kind=primary_changelog.node_kind,
609+
)
610+
secondaries.append(node_changelog)
611+
elif peer_relation.cardinality == RelationshipCardinality.MANY:
612+
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityManyChangelog(
613+
name=peer_relation.name,
614+
peers=[
615+
RelationshipPeerChangelog(
616+
peer_id=primary_changelog.node_id,
617+
peer_kind=primary_changelog.node_kind,
618+
peer_status=DiffAction.ADDED,
619+
)
620+
],
621+
)
622+
secondaries.append(node_changelog)
623+
624+
return secondaries
625+
626+
def _process_removed_peers(
627+
self,
628+
peer_id: str,
629+
peer_kind: str,
630+
peer_schema: MainSchemaTypes,
631+
rel_schema: RelationshipSchema,
632+
primary_changelog: NodeChangelog,
633+
) -> list[NodeChangelog]:
634+
secondaries: list[NodeChangelog] = []
635+
peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
636+
if peer_relation:
637+
node_changelog = NodeChangelog(
638+
node_id=peer_id,
639+
node_kind=peer_kind,
640+
display_label="n/a",
641+
)
642+
if peer_relation.cardinality == RelationshipCardinality.ONE:
643+
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
644+
name=peer_relation.name,
645+
peer_id_previous=primary_changelog.node_id,
646+
peer_kind_previous=primary_changelog.node_kind,
647+
)
648+
secondaries.append(node_changelog)
649+
elif peer_relation.cardinality == RelationshipCardinality.MANY:
650+
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityManyChangelog(
651+
name=peer_relation.name,
652+
peers=[
653+
RelationshipPeerChangelog(
654+
peer_id=primary_changelog.node_id,
655+
peer_kind=primary_changelog.node_kind,
656+
peer_status=DiffAction.REMOVED,
485657
)
486-
if peer_relation.cardinality == RelationshipCardinality.MANY:
487-
node_changelog.relationships[peer_relation.name] = RelationshipCardinalityManyChangelog(
488-
name=peer_relation.name,
489-
peers=[
490-
RelationshipPeerChangelog(
491-
peer_id=primary_changelog.node_id,
492-
peer_kind=primary_changelog.node_kind,
493-
peer_status=DiffAction.ADDED,
494-
)
495-
],
496-
)
497-
secondaries.append(node_changelog)
658+
],
659+
)
660+
secondaries.append(node_changelog)
498661

499662
return secondaries

backend/infrahub/graphql/mutations/relationship.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from infrahub import config
1010
from infrahub.core.account import GlobalPermission, ObjectPermission
11-
from infrahub.core.changelog.models import NodeChangelog
11+
from infrahub.core.changelog.models import NodeChangelog, RelationshipChangelogGetter
1212
from infrahub.core.constants import (
1313
InfrahubKind,
1414
PermissionAction,
@@ -148,14 +148,33 @@ async def mutate(
148148
graphql_context.background.add_task(graphql_context.active_service.event.send, group_add_event)
149149

150150
else:
151-
event = NodeUpdatedEvent(
151+
main_event = NodeUpdatedEvent(
152152
kind=source.get_schema().kind,
153153
node_id=source.id,
154154
changelog=node_changelog,
155155
fields=[relationship_name],
156156
meta=EventMeta(branch=graphql_context.branch, context=graphql_context.get_context()),
157157
)
158-
graphql_context.background.add_task(graphql_context.active_service.event.send, event)
158+
relationship_changelogs = RelationshipChangelogGetter(
159+
db=graphql_context.db, branch=graphql_context.branch
160+
)
161+
node_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=node_changelog)
162+
163+
events = [main_event]
164+
165+
for node_changelog in node_changelogs:
166+
meta = EventMeta.from_parent(parent=main_event)
167+
event = NodeUpdatedEvent(
168+
kind=node_changelog.node_kind,
169+
node_id=node_changelog.node_id,
170+
changelog=node_changelog,
171+
fields=node_changelog.updated_fields,
172+
meta=meta,
173+
)
174+
events.append(event)
175+
176+
for event in events:
177+
graphql_context.background.add_task(graphql_context.active_service.event.send, event)
159178

160179
return cls(ok=True)
161180

@@ -243,14 +262,34 @@ async def mutate(
243262
graphql_context.active_service.event.send, group_remove_event
244263
)
245264
else:
246-
event = NodeUpdatedEvent(
265+
main_event = NodeUpdatedEvent(
247266
kind=source.get_schema().kind,
248267
node_id=source.id,
249268
changelog=node_changelog,
250269
fields=[relationship_name],
251270
meta=EventMeta(branch=graphql_context.branch, context=graphql_context.get_context()),
252271
)
253-
graphql_context.background.add_task(graphql_context.active_service.event.send, event)
272+
273+
relationship_changelogs = RelationshipChangelogGetter(
274+
db=graphql_context.db, branch=graphql_context.branch
275+
)
276+
node_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=node_changelog)
277+
278+
events = [main_event]
279+
280+
for node_changelog in node_changelogs:
281+
meta = EventMeta.from_parent(parent=main_event)
282+
event = NodeUpdatedEvent(
283+
kind=node_changelog.node_kind,
284+
node_id=node_changelog.node_id,
285+
changelog=node_changelog,
286+
fields=node_changelog.updated_fields,
287+
meta=meta,
288+
)
289+
events.append(event)
290+
291+
for event in events:
292+
graphql_context.background.add_task(graphql_context.active_service.event.send, event)
254293

255294
return cls(ok=True)
256295

backend/tests/unit/core/changelog/test_models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,20 @@ async def test_node_changelog_update_with_cardinality_one_relationship(
206206
)
207207
assert not dog1_update.node_changelog.parent
208208

209+
relationship_changelogs = RelationshipChangelogGetter(db=db, branch=default_branch)
210+
secondary_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=dog1_update.node_changelog)
211+
assert len(secondary_changelogs) == 2
212+
213+
person1_secondary = [changelog for changelog in secondary_changelogs if changelog.node_id == person1.id][0]
214+
person2_secondary = [changelog for changelog in secondary_changelogs if changelog.node_id == person2.id][0]
215+
216+
assert isinstance(person1_secondary.relationships["animals"], RelationshipCardinalityManyChangelog)
217+
assert len(person1_secondary.relationships["animals"].peers) == 1
218+
assert person1_secondary.relationships["animals"].peers[0].peer_status == DiffAction.REMOVED
219+
assert isinstance(person2_secondary.relationships["animals"], RelationshipCardinalityManyChangelog)
220+
assert len(person2_secondary.relationships["animals"].peers) == 1
221+
assert person2_secondary.relationships["animals"].peers[0].peer_status == DiffAction.ADDED
222+
209223

210224
async def test_node_changelog_update_with_cardinality_many_relationship(
211225
db: InfrahubDatabase, default_branch, animal_person_schema, standard_group_schema
@@ -302,6 +316,15 @@ async def test_node_changelog_delete_with_cardinality_many_relationship(
302316
assert RelationshipPeerChangelog(peer_id=dog1.id, peer_kind="TestDog", peer_status=DiffAction.REMOVED) in animals
303317
assert RelationshipPeerChangelog(peer_id=dog2.id, peer_kind="TestDog", peer_status=DiffAction.REMOVED) in animals
304318

319+
relationship_changelogs = RelationshipChangelogGetter(db=db, branch=default_branch)
320+
secondary_changelogs = await relationship_changelogs.get_changelogs(primary_changelog=person1_update.node_changelog)
321+
322+
assert len(secondary_changelogs) == 2
323+
for changelog in secondary_changelogs:
324+
assert isinstance(changelog.relationships["owner"], RelationshipCardinalityOneChangelog)
325+
assert changelog.relationships["owner"].peer_kind_previous == "TestPerson"
326+
assert changelog.relationships["owner"].peer_status == DiffAction.REMOVED
327+
305328

306329
async def test_node_changelog_parent(db: InfrahubDatabase, default_branch, car_person_schema: None) -> None:
307330
"""Validate that parents are registrered in the node_changelog for parent/component relationships."""

backend/tests/unit/graphql/test_mutation_update.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from infrahub.auth import AccountSession
55
from infrahub.core import registry
66
from infrahub.core.branch import Branch
7-
from infrahub.core.changelog.models import RelationshipCardinalityOneChangelog
7+
from infrahub.core.changelog.models import RelationshipCardinalityManyChangelog, RelationshipCardinalityOneChangelog
8+
from infrahub.core.constants import DiffAction
89
from infrahub.core.manager import NodeManager
910
from infrahub.core.node import Node
1011
from infrahub.database import InfrahubDatabase
@@ -462,12 +463,27 @@ async def test_update_single_relationship(
462463
assert car_peer.id == person_jim_main.id
463464
assert gql_params.context.background
464465
await gql_params.context.background()
465-
assert len(memory_event.events) == 1
466-
event = memory_event.events[0]
467-
assert isinstance(event, NodeMutatedEvent)
468-
assert isinstance(event.changelog.relationships["owner"], RelationshipCardinalityOneChangelog)
469-
assert event.changelog.relationships["owner"].peer_id == person_jim_main.id
470-
assert event.changelog.relationships["owner"].peer_kind == "TestPerson"
466+
assert len(memory_event.events) == 3
467+
main_event = memory_event.events[0]
468+
related_event_01 = memory_event.events[1]
469+
related_event_02 = memory_event.events[2]
470+
assert isinstance(main_event, NodeMutatedEvent)
471+
assert isinstance(related_event_01, NodeMutatedEvent)
472+
assert isinstance(related_event_02, NodeMutatedEvent)
473+
assert isinstance(main_event.changelog.relationships["owner"], RelationshipCardinalityOneChangelog)
474+
assert main_event.changelog.relationships["owner"].peer_id == person_jim_main.id
475+
assert main_event.changelog.relationships["owner"].peer_kind == "TestPerson"
476+
assert main_event.changelog.relationships["owner"].peer_status == DiffAction.UPDATED
477+
johns_event = [event for event in [related_event_01, related_event_02] if event.node_id == person_john_main.id][0]
478+
jims_event = [event for event in [related_event_01, related_event_02] if event.node_id == person_jim_main.id][0]
479+
assert isinstance(johns_event.changelog.relationships["cars"], RelationshipCardinalityManyChangelog)
480+
assert len(johns_event.changelog.relationships["cars"].peers) == 1
481+
assert johns_event.changelog.relationships["cars"].peers[0].peer_id == car_accord_main.id
482+
assert johns_event.changelog.relationships["cars"].peers[0].peer_status == DiffAction.REMOVED
483+
assert isinstance(jims_event.changelog.relationships["cars"], RelationshipCardinalityManyChangelog)
484+
assert len(jims_event.changelog.relationships["cars"].peers) == 1
485+
assert jims_event.changelog.relationships["cars"].peers[0].peer_id == car_accord_main.id
486+
assert jims_event.changelog.relationships["cars"].peers[0].peer_status == DiffAction.ADDED
471487

472488

473489
async def test_update_default_value(

0 commit comments

Comments
 (0)