infrahub-server 1.2.0b1__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. infrahub/api/dependencies.py +6 -6
  2. infrahub/api/diff/validation_models.py +7 -7
  3. infrahub/api/schema.py +1 -1
  4. infrahub/artifacts/models.py +1 -3
  5. infrahub/artifacts/tasks.py +1 -3
  6. infrahub/cli/__init__.py +13 -9
  7. infrahub/cli/constants.py +3 -0
  8. infrahub/cli/db.py +165 -183
  9. infrahub/cli/upgrade.py +146 -0
  10. infrahub/computed_attribute/gather.py +185 -0
  11. infrahub/computed_attribute/models.py +239 -11
  12. infrahub/computed_attribute/tasks.py +77 -442
  13. infrahub/computed_attribute/triggers.py +11 -45
  14. infrahub/config.py +43 -32
  15. infrahub/context.py +14 -0
  16. infrahub/core/account.py +4 -4
  17. infrahub/core/attribute.py +57 -57
  18. infrahub/core/branch/tasks.py +12 -9
  19. infrahub/core/changelog/diff.py +16 -8
  20. infrahub/core/changelog/models.py +189 -26
  21. infrahub/core/constants/__init__.py +5 -1
  22. infrahub/core/constants/infrahubkind.py +2 -0
  23. infrahub/core/constraint/node/runner.py +9 -8
  24. infrahub/core/diff/branch_differ.py +10 -10
  25. infrahub/core/diff/ipam_diff_parser.py +4 -5
  26. infrahub/core/diff/model/diff.py +27 -27
  27. infrahub/core/diff/model/path.py +3 -3
  28. infrahub/core/diff/query/merge.py +20 -17
  29. infrahub/core/diff/query_parser.py +4 -4
  30. infrahub/core/graph/__init__.py +1 -1
  31. infrahub/core/initialization.py +1 -10
  32. infrahub/core/ipam/constants.py +3 -4
  33. infrahub/core/ipam/reconciler.py +12 -12
  34. infrahub/core/ipam/utilization.py +10 -13
  35. infrahub/core/manager.py +34 -34
  36. infrahub/core/merge.py +7 -7
  37. infrahub/core/migrations/__init__.py +2 -3
  38. infrahub/core/migrations/graph/__init__.py +9 -4
  39. infrahub/core/migrations/graph/m017_add_core_profile.py +1 -5
  40. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +4 -4
  41. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  42. infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py +51 -0
  43. infrahub/core/migrations/graph/{m020_add_generate_template_attr.py → m022_add_generate_template_attr.py} +3 -3
  44. infrahub/core/migrations/graph/m023_deduplicate_cardinality_one_relationships.py +96 -0
  45. infrahub/core/migrations/query/attribute_add.py +2 -2
  46. infrahub/core/migrations/query/node_duplicate.py +18 -21
  47. infrahub/core/migrations/query/schema_attribute_update.py +2 -2
  48. infrahub/core/migrations/schema/models.py +19 -4
  49. infrahub/core/migrations/schema/tasks.py +2 -2
  50. infrahub/core/migrations/shared.py +16 -16
  51. infrahub/core/models.py +15 -6
  52. infrahub/core/node/__init__.py +29 -28
  53. infrahub/core/node/base.py +2 -4
  54. infrahub/core/node/constraints/attribute_uniqueness.py +2 -2
  55. infrahub/core/node/constraints/grouped_uniqueness.py +99 -47
  56. infrahub/core/node/constraints/interface.py +1 -2
  57. infrahub/core/node/delete_validator.py +3 -5
  58. infrahub/core/node/ipam.py +4 -4
  59. infrahub/core/node/permissions.py +7 -7
  60. infrahub/core/node/resource_manager/ip_address_pool.py +6 -6
  61. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -6
  62. infrahub/core/node/resource_manager/number_pool.py +3 -3
  63. infrahub/core/path.py +12 -12
  64. infrahub/core/property.py +11 -11
  65. infrahub/core/protocols.py +5 -0
  66. infrahub/core/protocols_base.py +21 -21
  67. infrahub/core/query/__init__.py +33 -33
  68. infrahub/core/query/attribute.py +6 -4
  69. infrahub/core/query/diff.py +3 -3
  70. infrahub/core/query/node.py +82 -32
  71. infrahub/core/query/relationship.py +24 -24
  72. infrahub/core/query/resource_manager.py +2 -0
  73. infrahub/core/query/standard_node.py +3 -3
  74. infrahub/core/query/subquery.py +9 -9
  75. infrahub/core/registry.py +13 -15
  76. infrahub/core/relationship/constraints/count.py +3 -4
  77. infrahub/core/relationship/constraints/peer_kind.py +3 -4
  78. infrahub/core/relationship/constraints/profiles_kind.py +2 -2
  79. infrahub/core/relationship/model.py +40 -46
  80. infrahub/core/schema/attribute_schema.py +9 -9
  81. infrahub/core/schema/basenode_schema.py +93 -44
  82. infrahub/core/schema/computed_attribute.py +3 -3
  83. infrahub/core/schema/definitions/core/__init__.py +13 -19
  84. infrahub/core/schema/definitions/core/account.py +151 -148
  85. infrahub/core/schema/definitions/core/artifact.py +122 -113
  86. infrahub/core/schema/definitions/core/builtin.py +19 -16
  87. infrahub/core/schema/definitions/core/check.py +61 -53
  88. infrahub/core/schema/definitions/core/core.py +17 -0
  89. infrahub/core/schema/definitions/core/generator.py +89 -85
  90. infrahub/core/schema/definitions/core/graphql_query.py +72 -70
  91. infrahub/core/schema/definitions/core/group.py +96 -93
  92. infrahub/core/schema/definitions/core/ipam.py +176 -235
  93. infrahub/core/schema/definitions/core/lineage.py +18 -16
  94. infrahub/core/schema/definitions/core/menu.py +42 -40
  95. infrahub/core/schema/definitions/core/permission.py +144 -142
  96. infrahub/core/schema/definitions/core/profile.py +16 -27
  97. infrahub/core/schema/definitions/core/propose_change.py +88 -79
  98. infrahub/core/schema/definitions/core/propose_change_comment.py +170 -165
  99. infrahub/core/schema/definitions/core/propose_change_validator.py +290 -288
  100. infrahub/core/schema/definitions/core/repository.py +231 -225
  101. infrahub/core/schema/definitions/core/resource_pool.py +156 -166
  102. infrahub/core/schema/definitions/core/template.py +27 -12
  103. infrahub/core/schema/definitions/core/transform.py +85 -76
  104. infrahub/core/schema/definitions/core/webhook.py +127 -101
  105. infrahub/core/schema/definitions/internal.py +16 -16
  106. infrahub/core/schema/dropdown.py +3 -4
  107. infrahub/core/schema/generated/attribute_schema.py +15 -18
  108. infrahub/core/schema/generated/base_node_schema.py +12 -14
  109. infrahub/core/schema/generated/node_schema.py +3 -5
  110. infrahub/core/schema/generated/relationship_schema.py +9 -11
  111. infrahub/core/schema/generic_schema.py +2 -2
  112. infrahub/core/schema/manager.py +20 -9
  113. infrahub/core/schema/node_schema.py +4 -2
  114. infrahub/core/schema/relationship_schema.py +7 -7
  115. infrahub/core/schema/schema_branch.py +276 -138
  116. infrahub/core/schema/schema_branch_computed.py +41 -4
  117. infrahub/core/task/task.py +3 -3
  118. infrahub/core/task/user_task.py +15 -15
  119. infrahub/core/utils.py +20 -18
  120. infrahub/core/validators/__init__.py +1 -3
  121. infrahub/core/validators/aggregated_checker.py +2 -2
  122. infrahub/core/validators/attribute/choices.py +2 -2
  123. infrahub/core/validators/attribute/enum.py +2 -2
  124. infrahub/core/validators/attribute/kind.py +2 -2
  125. infrahub/core/validators/attribute/length.py +2 -2
  126. infrahub/core/validators/attribute/optional.py +2 -2
  127. infrahub/core/validators/attribute/regex.py +2 -2
  128. infrahub/core/validators/attribute/unique.py +2 -2
  129. infrahub/core/validators/checks_runner.py +25 -2
  130. infrahub/core/validators/determiner.py +1 -3
  131. infrahub/core/validators/interface.py +6 -2
  132. infrahub/core/validators/model.py +22 -3
  133. infrahub/core/validators/models/validate_migration.py +17 -4
  134. infrahub/core/validators/node/attribute.py +2 -2
  135. infrahub/core/validators/node/generate_profile.py +2 -2
  136. infrahub/core/validators/node/hierarchy.py +3 -5
  137. infrahub/core/validators/node/inherit_from.py +27 -5
  138. infrahub/core/validators/node/relationship.py +2 -2
  139. infrahub/core/validators/relationship/count.py +4 -4
  140. infrahub/core/validators/relationship/optional.py +2 -2
  141. infrahub/core/validators/relationship/peer.py +2 -2
  142. infrahub/core/validators/shared.py +2 -2
  143. infrahub/core/validators/tasks.py +8 -0
  144. infrahub/core/validators/uniqueness/checker.py +22 -21
  145. infrahub/core/validators/uniqueness/index.py +2 -2
  146. infrahub/core/validators/uniqueness/model.py +11 -11
  147. infrahub/database/__init__.py +26 -22
  148. infrahub/database/metrics.py +7 -1
  149. infrahub/dependencies/builder/constraint/grouped/node_runner.py +1 -3
  150. infrahub/dependencies/component/registry.py +2 -2
  151. infrahub/events/__init__.py +25 -2
  152. infrahub/events/artifact_action.py +13 -25
  153. infrahub/events/branch_action.py +26 -18
  154. infrahub/events/generator.py +71 -0
  155. infrahub/events/group_action.py +10 -24
  156. infrahub/events/models.py +10 -16
  157. infrahub/events/node_action.py +87 -32
  158. infrahub/events/repository_action.py +5 -18
  159. infrahub/events/schema_action.py +4 -9
  160. infrahub/events/utils.py +16 -0
  161. infrahub/events/validator_action.py +55 -0
  162. infrahub/exceptions.py +23 -24
  163. infrahub/generators/models.py +1 -3
  164. infrahub/git/base.py +7 -7
  165. infrahub/git/integrator.py +26 -25
  166. infrahub/git/models.py +22 -9
  167. infrahub/git/repository.py +3 -3
  168. infrahub/git/tasks.py +67 -49
  169. infrahub/git/utils.py +48 -0
  170. infrahub/git/worktree.py +1 -2
  171. infrahub/git_credential/askpass.py +1 -2
  172. infrahub/graphql/analyzer.py +12 -0
  173. infrahub/graphql/app.py +13 -15
  174. infrahub/graphql/context.py +6 -0
  175. infrahub/graphql/initialization.py +3 -0
  176. infrahub/graphql/loaders/node.py +2 -12
  177. infrahub/graphql/loaders/peers.py +77 -0
  178. infrahub/graphql/loaders/shared.py +13 -0
  179. infrahub/graphql/manager.py +13 -10
  180. infrahub/graphql/mutations/artifact_definition.py +5 -5
  181. infrahub/graphql/mutations/computed_attribute.py +4 -5
  182. infrahub/graphql/mutations/graphql_query.py +5 -5
  183. infrahub/graphql/mutations/ipam.py +50 -70
  184. infrahub/graphql/mutations/main.py +164 -141
  185. infrahub/graphql/mutations/menu.py +5 -5
  186. infrahub/graphql/mutations/models.py +2 -4
  187. infrahub/graphql/mutations/node_getter/by_default_filter.py +10 -10
  188. infrahub/graphql/mutations/node_getter/by_hfid.py +1 -3
  189. infrahub/graphql/mutations/node_getter/by_id.py +1 -3
  190. infrahub/graphql/mutations/node_getter/interface.py +1 -2
  191. infrahub/graphql/mutations/proposed_change.py +7 -7
  192. infrahub/graphql/mutations/relationship.py +67 -35
  193. infrahub/graphql/mutations/repository.py +8 -8
  194. infrahub/graphql/mutations/resource_manager.py +3 -3
  195. infrahub/graphql/mutations/schema.py +4 -4
  196. infrahub/graphql/mutations/webhook.py +137 -0
  197. infrahub/graphql/parser.py +4 -4
  198. infrahub/graphql/queries/diff/tree.py +4 -4
  199. infrahub/graphql/queries/ipam.py +2 -2
  200. infrahub/graphql/queries/relationship.py +2 -2
  201. infrahub/graphql/queries/search.py +2 -2
  202. infrahub/graphql/resolvers/many_relationship.py +264 -0
  203. infrahub/graphql/resolvers/resolver.py +13 -110
  204. infrahub/graphql/subscription/graphql_query.py +2 -0
  205. infrahub/graphql/types/event.py +20 -11
  206. infrahub/graphql/types/node.py +2 -2
  207. infrahub/graphql/utils.py +2 -2
  208. infrahub/groups/ancestors.py +29 -0
  209. infrahub/groups/parsers.py +107 -0
  210. infrahub/menu/generator.py +7 -7
  211. infrahub/menu/menu.py +0 -10
  212. infrahub/menu/models.py +117 -16
  213. infrahub/menu/repository.py +111 -0
  214. infrahub/menu/utils.py +5 -8
  215. infrahub/message_bus/messages/__init__.py +1 -11
  216. infrahub/message_bus/messages/check_generator_run.py +2 -0
  217. infrahub/message_bus/messages/finalize_validator_execution.py +3 -0
  218. infrahub/message_bus/messages/request_generatordefinition_check.py +2 -0
  219. infrahub/message_bus/operations/__init__.py +0 -2
  220. infrahub/message_bus/operations/check/generator.py +1 -0
  221. infrahub/message_bus/operations/event/__init__.py +2 -2
  222. infrahub/message_bus/operations/finalize/validator.py +51 -1
  223. infrahub/message_bus/operations/requests/generator_definition.py +19 -19
  224. infrahub/message_bus/operations/requests/proposed_change.py +3 -1
  225. infrahub/pools/number.py +2 -4
  226. infrahub/proposed_change/tasks.py +37 -28
  227. infrahub/pytest_plugin.py +13 -10
  228. infrahub/server.py +1 -2
  229. infrahub/services/adapters/event/__init__.py +1 -1
  230. infrahub/task_manager/event.py +23 -9
  231. infrahub/tasks/artifact.py +2 -4
  232. infrahub/telemetry/__init__.py +0 -0
  233. infrahub/telemetry/constants.py +9 -0
  234. infrahub/telemetry/database.py +86 -0
  235. infrahub/telemetry/models.py +65 -0
  236. infrahub/telemetry/task_manager.py +77 -0
  237. infrahub/{tasks/telemetry.py → telemetry/tasks.py} +49 -56
  238. infrahub/telemetry/utils.py +11 -0
  239. infrahub/trace.py +4 -4
  240. infrahub/transformations/tasks.py +2 -2
  241. infrahub/trigger/catalogue.py +2 -5
  242. infrahub/trigger/constants.py +0 -8
  243. infrahub/trigger/models.py +14 -1
  244. infrahub/trigger/setup.py +90 -0
  245. infrahub/trigger/tasks.py +35 -90
  246. infrahub/utils.py +11 -1
  247. infrahub/validators/__init__.py +0 -0
  248. infrahub/validators/events.py +42 -0
  249. infrahub/validators/tasks.py +41 -0
  250. infrahub/webhook/gather.py +17 -0
  251. infrahub/webhook/models.py +22 -5
  252. infrahub/webhook/tasks.py +44 -19
  253. infrahub/webhook/triggers.py +22 -5
  254. infrahub/workers/infrahub_async.py +2 -2
  255. infrahub/workers/utils.py +2 -2
  256. infrahub/workflows/catalogue.py +28 -20
  257. infrahub/workflows/initialization.py +1 -3
  258. infrahub/workflows/models.py +1 -1
  259. infrahub/workflows/utils.py +10 -1
  260. infrahub_sdk/client.py +27 -8
  261. infrahub_sdk/config.py +3 -0
  262. infrahub_sdk/context.py +13 -0
  263. infrahub_sdk/exceptions.py +6 -0
  264. infrahub_sdk/generator.py +4 -1
  265. infrahub_sdk/graphql.py +45 -13
  266. infrahub_sdk/node.py +69 -20
  267. infrahub_sdk/protocols_base.py +32 -11
  268. infrahub_sdk/query_groups.py +6 -35
  269. infrahub_sdk/schema/__init__.py +55 -26
  270. infrahub_sdk/schema/main.py +8 -0
  271. infrahub_sdk/task/__init__.py +10 -0
  272. infrahub_sdk/task/manager.py +12 -6
  273. infrahub_sdk/testing/schemas/animal.py +9 -0
  274. infrahub_sdk/timestamp.py +12 -4
  275. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/METADATA +3 -2
  276. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/RECORD +289 -260
  277. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/entry_points.txt +1 -0
  278. infrahub_testcontainers/constants.py +2 -0
  279. infrahub_testcontainers/container.py +157 -12
  280. infrahub_testcontainers/docker-compose.test.yml +31 -6
  281. infrahub_testcontainers/helpers.py +18 -73
  282. infrahub_testcontainers/host.py +41 -0
  283. infrahub_testcontainers/measurements.py +93 -0
  284. infrahub_testcontainers/models.py +38 -0
  285. infrahub_testcontainers/performance_test.py +166 -0
  286. infrahub_testcontainers/plugin.py +136 -0
  287. infrahub_testcontainers/prometheus.yml +30 -0
  288. infrahub/message_bus/messages/event_branch_create.py +0 -11
  289. infrahub/message_bus/messages/event_branch_delete.py +0 -11
  290. infrahub/message_bus/messages/event_branch_rebased.py +0 -9
  291. infrahub/message_bus/messages/event_node_mutated.py +0 -15
  292. infrahub/message_bus/messages/event_schema_update.py +0 -9
  293. infrahub/message_bus/operations/event/node.py +0 -20
  294. infrahub/message_bus/operations/event/schema.py +0 -17
  295. infrahub/webhook/constants.py +0 -1
  296. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/LICENSE.txt +0 -0
  297. {infrahub_server-1.2.0b1.dist-info → infrahub_server-1.2.1.dist-info}/WHEEL +0 -0
@@ -13,6 +13,8 @@ if TYPE_CHECKING:
13
13
  from infrahub.core.manager import RelationshipSchema
14
14
  from infrahub.core.query.relationship import RelationshipPeerData
15
15
  from infrahub.core.relationship.model import Relationship
16
+ from infrahub.core.schema import MainSchemaTypes
17
+ from infrahub.core.schema.schema_branch import SchemaBranch
16
18
  from infrahub.database import InfrahubDatabase
17
19
 
18
20
 
@@ -463,37 +465,198 @@ class RelationshipChangelogGetter:
463
465
  These will typically include updates to relationships on other nodes.
464
466
  """
465
467
  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)
467
469
  secondaries: list[NodeChangelog] = []
468
470
 
469
471
  for relationship in primary_changelog.relationships.values():
470
- rel_schema = node_schema.get_relationship(name=relationship.name)
471
472
  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,
479
479
  )
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,
485
657
  )
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)
498
661
 
499
662
  return secondaries
@@ -52,7 +52,7 @@ class EventType(InfrahubStringEnum):
52
52
  BRANCH_MERGED = f"{EVENT_NAMESPACE}.branch.merged"
53
53
  BRANCH_REBASED = f"{EVENT_NAMESPACE}.branch.rebased"
54
54
 
55
- SCHEMA_UPDATED = f"{EVENT_NAMESPACE}.schema.update"
55
+ SCHEMA_UPDATED = f"{EVENT_NAMESPACE}.schema.updated"
56
56
 
57
57
  NODE_CREATED = f"{EVENT_NAMESPACE}.node.created"
58
58
  NODE_UPDATED = f"{EVENT_NAMESPACE}.node.updated"
@@ -66,6 +66,10 @@ class EventType(InfrahubStringEnum):
66
66
  ARTIFACT_CREATED = f"{EVENT_NAMESPACE}.artifact.created"
67
67
  ARTIFACT_UPDATED = f"{EVENT_NAMESPACE}.artifact.updated"
68
68
 
69
+ VALIDATOR_STARTED = f"{EVENT_NAMESPACE}.validator.started"
70
+ VALIDATOR_PASSED = f"{EVENT_NAMESPACE}.validator.passed"
71
+ VALIDATOR_FAILED = f"{EVENT_NAMESPACE}.validator.failed"
72
+
69
73
 
70
74
  class PermissionLevel(enum.Flag):
71
75
  READ = 1
@@ -11,6 +11,7 @@ ARTIFACTVALIDATOR = "CoreArtifactValidator"
11
11
  BASEPERMISSION = "CoreBasePermission"
12
12
  CHANGECOMMENT = "CoreChangeComment"
13
13
  CHANGETHREAD = "CoreChangeThread"
14
+ CHECK = "CoreCheck"
14
15
  CHECKDEFINITION = "CoreCheckDefinition"
15
16
  COMMENT = "CoreComment"
16
17
  CUSTOMWEBHOOK = "CoreCustomWebhook"
@@ -41,6 +42,7 @@ NODE = "CoreNode"
41
42
  NUMBERPOOL = "CoreNumberPool"
42
43
  LINEAGEOWNER = "LineageOwner"
43
44
  LINEAGESOURCE = "LineageSource"
45
+ OBJECTCOMPONENTTEMPLATE = "CoreObjectComponentTemplate"
44
46
  OBJECTPERMISSION = "CoreObjectPermission"
45
47
  OBJECTTEMPLATE = "CoreObjectTemplate"
46
48
  OBJECTTHREAD = "CoreObjectThread"
@@ -1,8 +1,8 @@
1
- from typing import TYPE_CHECKING, Optional
1
+ from typing import TYPE_CHECKING
2
2
 
3
3
  from infrahub.core.branch import Branch
4
4
  from infrahub.core.node import Node
5
- from infrahub.core.node.constraints.interface import NodeConstraintInterface
5
+ from infrahub.core.node.constraints.grouped_uniqueness import NodeGroupedUniquenessConstraint
6
6
  from infrahub.core.relationship.constraints.interface import RelationshipManagerConstraintInterface
7
7
  from infrahub.database import InfrahubDatabase
8
8
 
@@ -15,21 +15,18 @@ class NodeConstraintRunner:
15
15
  self,
16
16
  db: InfrahubDatabase,
17
17
  branch: Branch,
18
- node_constraints: list[NodeConstraintInterface],
18
+ uniqueness_constraint: NodeGroupedUniquenessConstraint,
19
19
  relationship_manager_constraints: list[RelationshipManagerConstraintInterface],
20
20
  ) -> None:
21
21
  self.db = db
22
22
  self.branch = branch
23
- self.node_constraints = node_constraints
23
+ self.uniqueness_constraint = uniqueness_constraint
24
24
  self.relationship_manager_constraints = relationship_manager_constraints
25
25
 
26
- async def check(self, node: Node, field_filters: Optional[list[str]] = None) -> None:
26
+ async def check(self, node: Node, field_filters: list[str] | None = None) -> None:
27
27
  async with self.db.start_session() as db:
28
28
  await node.resolve_relationships(db=db)
29
29
 
30
- for node_constraint in self.node_constraints:
31
- await node_constraint.check(node, filters=field_filters)
32
-
33
30
  for relationship_name in node.get_schema().relationship_names:
34
31
  if field_filters and relationship_name not in field_filters:
35
32
  continue
@@ -37,3 +34,7 @@ class NodeConstraintRunner:
37
34
  await relationship_manager.fetch_relationship_ids(db=db, force_refresh=True)
38
35
  for relationship_constraint in self.relationship_manager_constraints:
39
36
  await relationship_constraint.check(relm=relationship_manager, node_schema=node.get_schema())
37
+
38
+ # If HFID constraint is the only constraint violated, all other constraints need to have ran before,
39
+ # as it means there is an existing node that we might want to update in the case of an upsert
40
+ await self.uniqueness_constraint.check(node, filters=field_filters)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  from collections import defaultdict
5
- from typing import TYPE_CHECKING, Optional, Union
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from typing_extensions import Self
8
8
 
@@ -35,12 +35,12 @@ class BranchDiffer:
35
35
  def __init__(
36
36
  self,
37
37
  branch: Branch,
38
- origin_branch: Optional[Branch] = None,
38
+ origin_branch: Branch | None = None,
39
39
  branch_only: bool = False,
40
- diff_from: Optional[Union[str, Timestamp]] = None,
41
- diff_to: Optional[Union[str, Timestamp]] = None,
42
- db: Optional[InfrahubDatabase] = None,
43
- service: Optional[InfrahubServices] = None,
40
+ diff_from: str | Timestamp | None = None,
41
+ diff_to: str | Timestamp | None = None,
42
+ db: InfrahubDatabase | None = None,
43
+ service: InfrahubServices | None = None,
44
44
  ):
45
45
  """_summary_
46
46
 
@@ -81,7 +81,7 @@ class BranchDiffer:
81
81
 
82
82
  # Results organized by Branch
83
83
  self._results: dict[str, dict] = defaultdict(lambda: {"nodes": {}, "rels": defaultdict(dict), "files": {}})
84
- self._calculated_diff_files_at: Optional[Timestamp] = None
84
+ self._calculated_diff_files_at: Timestamp | None = None
85
85
 
86
86
  @property
87
87
  def service(self) -> InfrahubServices:
@@ -101,9 +101,9 @@ class BranchDiffer:
101
101
  db: InfrahubDatabase,
102
102
  branch: Branch,
103
103
  branch_only: bool = False,
104
- diff_from: Optional[Union[str, Timestamp]] = None,
105
- diff_to: Optional[Union[str, Timestamp]] = None,
106
- service: Optional[InfrahubServices] = None,
104
+ diff_from: str | Timestamp | None = None,
105
+ diff_to: str | Timestamp | None = None,
106
+ service: InfrahubServices | None = None,
107
107
  ) -> Self:
108
108
  origin_branch = branch.get_origin_branch()
109
109
 
@@ -1,5 +1,4 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
3
2
 
4
3
  from infrahub.core.constants import DiffAction
5
4
  from infrahub.core.constants.database import DatabaseEdgeType
@@ -18,8 +17,8 @@ class ChangedIpamNodeDetails:
18
17
  node_uuid: str
19
18
  is_address: bool
20
19
  is_delete: bool
21
- namespace_id: Optional[str]
22
- ip_value: Optional[str]
20
+ namespace_id: str | None
21
+ ip_value: str | None
23
22
 
24
23
 
25
24
  class IpamDiffParser:
@@ -142,7 +141,7 @@ class IpamDiffParser:
142
141
  uuids_missing_data=uuids_missing_data,
143
142
  )
144
143
 
145
- def _get_ip_value(self, node_diff: EnrichedDiffNode) -> Optional[str]:
144
+ def _get_ip_value(self, node_diff: EnrichedDiffNode) -> str | None:
146
145
  ip_attr_diff = None
147
146
  for diff_attr in node_diff.attributes:
148
147
  if diff_attr.name in {"prefix", "address"}:
@@ -155,7 +154,7 @@ class IpamDiffParser:
155
154
  return diff_property.new_value or diff_property.previous_value
156
155
  return None
157
156
 
158
- def _get_namespace_id(self, node_diff: EnrichedDiffNode) -> Optional[str]:
157
+ def _get_namespace_id(self, node_diff: EnrichedDiffNode) -> str | None:
159
158
  namespace_rel = None
160
159
  for diff_rel in node_diff.relationships:
161
160
  if diff_rel.name == "ip_namespace":
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import Enum
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
 
6
6
  from pydantic import BaseModel, ConfigDict, Field
7
7
 
@@ -46,8 +46,8 @@ class BaseDiffElement(BaseModel):
46
46
 
47
47
 
48
48
  class ValueElement(BaseDiffElement):
49
- previous: Optional[Any] = None
50
- new: Optional[Any] = None
49
+ previous: Any | None = None
50
+ new: Any | None = None
51
51
 
52
52
  def __hash__(self) -> int:
53
53
  return hash(type(self))
@@ -57,12 +57,12 @@ class PropertyDiffElement(BaseDiffElement):
57
57
  branch: str
58
58
  type: str
59
59
  action: DiffAction
60
- path: Optional[str] = None
60
+ path: str | None = None
61
61
  db_id: str = Field(exclude=True)
62
62
  rel_id: str = Field(exclude=True)
63
- origin_rel_id: Optional[str] = Field(None, exclude=True)
64
- value: Optional[ValueElement] = None
65
- changed_at: Optional[Timestamp] = None
63
+ origin_rel_id: str | None = Field(None, exclude=True)
64
+ value: ValueElement | None = None
65
+ changed_at: Timestamp | None = None
66
66
 
67
67
 
68
68
  class NodeAttributeDiffElement(BaseDiffElement):
@@ -72,28 +72,28 @@ class NodeAttributeDiffElement(BaseDiffElement):
72
72
  action: DiffAction
73
73
  db_id: str = Field(exclude=True)
74
74
  rel_id: str = Field(exclude=True)
75
- origin_rel_id: Optional[str] = Field(None, exclude=True)
76
- changed_at: Optional[Timestamp] = None
75
+ origin_rel_id: str | None = Field(None, exclude=True)
76
+ changed_at: Timestamp | None = None
77
77
  properties: dict[str, PropertyDiffElement]
78
78
 
79
79
 
80
80
  class NodeDiffElement(BaseDiffElement):
81
- branch: Optional[str] = None
81
+ branch: str | None = None
82
82
  labels: list[str]
83
83
  kind: str
84
84
  id: str
85
85
  path: str
86
86
  action: DiffAction
87
87
  db_id: str = Field(exclude=True)
88
- rel_id: Optional[str] = Field(None, exclude=True)
89
- changed_at: Optional[Timestamp] = None
88
+ rel_id: str | None = Field(None, exclude=True)
89
+ changed_at: Timestamp | None = None
90
90
  attributes: dict[str, NodeAttributeDiffElement] = Field(default_factory=dict)
91
91
 
92
92
 
93
93
  class RelationshipEdgeNodeDiffElement(BaseDiffElement):
94
94
  id: str
95
- db_id: Optional[str] = Field(None, exclude=True)
96
- rel_id: Optional[str] = Field(None, exclude=True)
95
+ db_id: str | None = Field(None, exclude=True)
96
+ rel_id: str | None = Field(None, exclude=True)
97
97
  labels: list[str]
98
98
  kind: str
99
99
 
@@ -106,11 +106,11 @@ class RelationshipDiffElement(BaseDiffElement):
106
106
  action: DiffAction
107
107
  nodes: dict[str, RelationshipEdgeNodeDiffElement]
108
108
  properties: dict[str, PropertyDiffElement]
109
- changed_at: Optional[Timestamp] = None
109
+ changed_at: Timestamp | None = None
110
110
  paths: list[str]
111
111
  conflict_paths: list[str]
112
112
 
113
- def get_node_id_by_kind(self, kind: str) -> Optional[str]:
113
+ def get_node_id_by_kind(self, kind: str) -> str | None:
114
114
  ids = [rel.id for rel in self.nodes.values() if rel.kind == kind]
115
115
  if ids:
116
116
  return ids[0]
@@ -149,11 +149,11 @@ class ModifiedPath(BaseModel):
149
149
  node_id: str
150
150
  path_type: PathType
151
151
  kind: str
152
- element_name: Optional[str] = None
153
- property_name: Optional[str] = None
154
- peer_id: Optional[str] = None
152
+ element_name: str | None = None
153
+ property_name: str | None = None
154
+ peer_id: str | None = None
155
155
  action: DiffAction
156
- change: Optional[ValueElement] = None
156
+ change: ValueElement | None = None
157
157
 
158
158
  def __eq__(self, other: object) -> bool:
159
159
  if not isinstance(other, ModifiedPath):
@@ -248,7 +248,7 @@ class DataConflict(ObjectConflict):
248
248
  conflict_path: str
249
249
  path: str
250
250
  path_type: PathType
251
- property_name: Optional[str] = None
251
+ property_name: str | None = None
252
252
  change_type: str
253
253
  changes: list[BranchChanges] = Field(default_factory=list)
254
254
 
@@ -311,7 +311,7 @@ class BranchDiffFile(BaseModel):
311
311
  class BranchDiffRepository(BaseModel):
312
312
  branch: str
313
313
  id: str
314
- display_name: Optional[str] = None
314
+ display_name: str | None = None
315
315
  commit_from: str
316
316
  commit_to: str
317
317
  files: list[BranchDiffFile] = Field(default_factory=list)
@@ -325,14 +325,14 @@ class BranchDiffArtifactStorage(BaseModel):
325
325
  class ArtifactTarget(BaseModel):
326
326
  id: str
327
327
  kind: str
328
- display_label: Optional[str] = None
328
+ display_label: str | None = None
329
329
 
330
330
 
331
331
  class BranchDiffArtifact(BaseModel):
332
332
  branch: str
333
333
  id: str
334
- display_label: Optional[str] = None
334
+ display_label: str | None = None
335
335
  action: DiffAction
336
- target: Optional[ArtifactTarget] = None
337
- item_new: Optional[BranchDiffArtifactStorage] = None
338
- item_previous: Optional[BranchDiffArtifactStorage] = None
336
+ target: ArtifactTarget | None = None
337
+ item_new: BranchDiffArtifactStorage | None = None
338
+ item_previous: BranchDiffArtifactStorage | None = None
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict, dataclass, field
4
4
  from enum import Enum
5
- from typing import TYPE_CHECKING, Any, Optional
5
+ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from infrahub.core.constants import (
8
8
  BranchSupportType,
@@ -848,13 +848,13 @@ class DatabasePath:
848
848
  return "Node" in self.property_node.labels
849
849
 
850
850
  @property
851
- def peer_id(self) -> Optional[str]:
851
+ def peer_id(self) -> str | None:
852
852
  if not self.property_is_peer:
853
853
  return None
854
854
  return str(self.property_node.get("uuid"))
855
855
 
856
856
  @property
857
- def peer_kind(self) -> Optional[str]:
857
+ def peer_kind(self) -> str | None:
858
858
  if not self.property_is_peer:
859
859
  return None
860
860
  return str(self.property_node.get("kind"))
@@ -36,6 +36,7 @@ class DiffMergeQuery(Query):
36
36
  "target_branch": self.target_branch.name,
37
37
  "source_branch": self.source_branch_name,
38
38
  }
39
+ # ruff: noqa: E501
39
40
  query = """
40
41
  UNWIND $node_diff_dicts AS node_diff_map
41
42
  CALL {
@@ -242,9 +243,11 @@ CALL {
242
243
  CASE
243
244
  WHEN startNode(source_r_rel_2).uuid = r.uuid THEN "r"
244
245
  ELSE "l"
245
- END AS r2_dir
246
+ END AS r2_dir,
247
+ source_r_rel_1.hierarchy AS r1_hierarchy,
248
+ source_r_rel_2.hierarchy AS r2_hierarchy
246
249
  }
247
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
250
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
248
251
  CALL {
249
252
  WITH n, rel_name, rel_peer_id, related_rel_status
250
253
  OPTIONAL MATCH (n)
@@ -258,12 +261,12 @@ CALL {
258
261
  SET target_r_rel_1.to = $at
259
262
  SET target_r_rel_2.to = $at
260
263
  }
261
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
264
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
262
265
  // ------------------------------
263
266
  // conditionally create new IS_RELATED relationships on target_branch, if necessary
264
267
  // ------------------------------
265
268
  CALL {
266
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
269
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
267
270
  MATCH (p:Node {uuid: rel_peer_id})
268
271
  OPTIONAL MATCH (n)
269
272
  -[r_rel_1:IS_RELATED {branch: $target_branch, status: related_rel_status}]
@@ -274,42 +277,42 @@ CALL {
274
277
  AND (r_rel_1.to >= $at OR r_rel_1.to IS NULL)
275
278
  AND r_rel_2.from <= $at
276
279
  AND (r_rel_2.to >= $at OR r_rel_2.to IS NULL)
277
- WITH n, r, r1_dir, r2_dir, p, related_rel_status, r_rel_1, r_rel_2
280
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, p, related_rel_status, r_rel_1, r_rel_2
278
281
  WHERE r_rel_1 IS NULL
279
282
  AND r_rel_2 IS NULL
280
283
  // ------------------------------
281
284
  // create IS_RELATED relationships with directions maintained from source
282
285
  // ------------------------------
283
286
  CALL {
284
- WITH n, r, r1_dir, related_rel_status
285
- WITH n, r, r1_dir, related_rel_status
287
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
288
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
286
289
  WHERE r1_dir = "r"
287
290
  CREATE (n)
288
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
291
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
289
292
  ->(r)
290
293
  }
291
294
  CALL {
292
- WITH n, r, r1_dir, related_rel_status
293
- WITH n, r, r1_dir, related_rel_status
295
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
296
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
294
297
  WHERE r1_dir = "l"
295
298
  CREATE (n)
296
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
299
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
297
300
  -(r)
298
301
  }
299
302
  CALL {
300
- WITH r, p, r2_dir, related_rel_status
301
- WITH r, p, r2_dir, related_rel_status
303
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
304
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
302
305
  WHERE r2_dir = "r"
303
306
  CREATE (r)
304
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
307
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
305
308
  ->(p)
306
309
  }
307
310
  CALL {
308
- WITH r, p, r2_dir, related_rel_status
309
- WITH r, p, r2_dir, related_rel_status
311
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
312
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
310
313
  WHERE r2_dir = "l"
311
314
  CREATE (r)
312
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
315
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
313
316
  -(p)
314
317
  }
315
318
  }