Skip to content

Commit 1980f77

Browse files
authored
fix hfid/display label migrations for branch-agnostic nodes (#7695)
1 parent 76ac0fb commit 1980f77

File tree

6 files changed

+258
-4
lines changed

6 files changed

+258
-4
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
GRAPH_VERSION = 45
1+
GRAPH_VERSION = 46

backend/infrahub/core/migrations/graph/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from .m043_create_hfid_display_label_in_db import Migration043
4848
from .m044_backfill_hfid_display_label_in_db import Migration044
4949
from .m045_backfill_hfid_display_label_in_db_profile_template import Migration045
50+
from .m046_fill_agnostic_hfid_display_labels import Migration046
5051

5152
if TYPE_CHECKING:
5253
from ..shared import MigrationTypes
@@ -98,6 +99,7 @@
9899
Migration043,
99100
Migration044,
100101
Migration045,
102+
Migration046,
101103
]
102104

103105

backend/infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from infrahub.core import registry
88
from infrahub.core.branch import Branch
9-
from infrahub.core.constants import SchemaPathType
9+
from infrahub.core.constants import BranchSupportType, SchemaPathType
1010
from infrahub.core.initialization import get_root_node
1111
from infrahub.core.migrations.schema.node_attribute_add import NodeAttributeAddMigration
1212
from infrahub.core.migrations.shared import MigrationRequiringRebase, MigrationResult, get_migration_console
@@ -78,6 +78,8 @@ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
7878

7979
for node_schema_kind in main_schema_branch.node_names:
8080
schema = main_schema_branch.get(name=node_schema_kind, duplicate=False)
81+
if schema.branch is not BranchSupportType.AWARE:
82+
continue
8183
migrations.extend(
8284
[
8385
NodeAttributeAddMigration(
@@ -123,6 +125,8 @@ async def execute_against_branch(self, db: InfrahubDatabase, branch: Branch) ->
123125

124126
for node_kind, node_ids in node_ids_by_kind.items():
125127
schema = schema_branch.get(name=node_kind, duplicate=False)
128+
if schema.branch not in (BranchSupportType.AWARE, BranchSupportType.LOCAL):
129+
continue
126130
migrations.extend(
127131
[
128132
NodeAttributeAddMigration(

backend/infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,23 @@ class DefaultBranchNodeCount(Query):
4040
name = "get_branch_node_count"
4141
type = QueryType.READ
4242

43-
def __init__(self, kinds_to_skip: list[str], **kwargs: Any) -> None:
43+
def __init__(
44+
self, kinds_to_skip: list[str] | None = None, kinds_to_include: list[str] | None = None, **kwargs: Any
45+
) -> None:
4446
super().__init__(**kwargs)
45-
self.kinds_to_skip = kinds_to_skip
47+
self.kinds_to_skip = kinds_to_skip or []
48+
self.kinds_to_include = kinds_to_include
4649

4750
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
4851
self.params = {
4952
"branch_names": [registry.default_branch, GLOBAL_BRANCH_NAME],
5053
"kinds_to_skip": self.kinds_to_skip,
54+
"kinds_to_include": self.kinds_to_include,
5155
}
5256
query = """
5357
MATCH (n:Node)-[e:IS_PART_OF]->(:Root)
5458
WHERE NOT n.kind IN $kinds_to_skip
59+
AND ($kinds_to_include IS NULL OR n.kind IN $kinds_to_include)
5560
AND e.branch IN $branch_names
5661
AND e.status = "active"
5762
AND e.to IS NULL
@@ -731,6 +736,10 @@ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
731736
continue
732737

733738
node_schema = main_schema_branch.get_node(name=node_schema_name, duplicate=False)
739+
740+
if node_schema.branch is not BranchSupportType.AWARE:
741+
continue
742+
734743
attribute_schema_map = {}
735744
if node_schema.display_labels:
736745
attribute_schema_map[display_labels_attribute_schema] = display_label_attribute_schema
@@ -826,6 +835,8 @@ async def execute_against_branch(self, db: InfrahubDatabase, branch: Branch) ->
826835
continue
827836

828837
node_schema = schema_branch.get_node(name=node_schema_name, duplicate=False)
838+
if node_schema.branch not in (BranchSupportType.AWARE, BranchSupportType.LOCAL):
839+
continue
829840
try:
830841
default_node_schema = main_schema_branch.get_node(name=node_schema_name, duplicate=False)
831842
except SchemaNotFoundError:
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
from __future__ import annotations
2+
3+
from itertools import chain
4+
from typing import TYPE_CHECKING, Any
5+
6+
import ujson
7+
from rich.progress import Progress, TaskID
8+
9+
from infrahub.core.branch import Branch
10+
from infrahub.core.constants import GLOBAL_BRANCH_NAME, BranchSupportType, SchemaPathType
11+
from infrahub.core.initialization import get_root_node
12+
from infrahub.core.migrations.graph.m044_backfill_hfid_display_label_in_db import (
13+
DefaultBranchNodeCount,
14+
GetPathDetailsDefaultBranch,
15+
GetResultMapQuery,
16+
UpdateAttributeValuesQuery,
17+
)
18+
from infrahub.core.migrations.schema.node_attribute_add import NodeAttributeAddMigration
19+
from infrahub.core.migrations.shared import ArbitraryMigration, MigrationResult, get_migration_console
20+
from infrahub.core.path import SchemaPath
21+
from infrahub.core.query import Query, QueryType
22+
23+
from .load_schema_branch import get_or_load_schema_branch
24+
25+
if TYPE_CHECKING:
26+
from infrahub.core.schema import AttributeSchema, MainSchemaTypes, NodeSchema, SchemaAttributePath
27+
from infrahub.core.schema.schema_branch import SchemaBranch
28+
from infrahub.database import InfrahubDatabase
29+
30+
31+
class DeleteBranchAwareAttrsForBranchAgnosticNodesQuery(Query):
32+
name = "delete_branch_aware_attrs_for_branch_agnostic_nodes_query"
33+
type = QueryType.WRITE
34+
insert_return = False
35+
raise_error_if_empty = False
36+
37+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
38+
query = """
39+
MATCH (n:Node {branch_support: "agnostic"})
40+
MATCH (n)-[:HAS_ATTRIBUTE]->(attr:Attribute)
41+
WHERE attr.name IN ["human_friendly_id", "display_label"]
42+
WITH DISTINCT attr
43+
CALL (attr) {
44+
DETACH DELETE attr
45+
} IN TRANSACTIONS
46+
"""
47+
self.add_to_query(query)
48+
49+
50+
class Migration046(ArbitraryMigration):
51+
"""
52+
Delete any branch-aware human_friendly_id and display_label attributes added to branch-agnostic nodes
53+
Add human_friendly_id and display_label attributes to branch-agnostic nodes
54+
Set human_friendly_id and display_label attributes for branch-agnostic nodes on global branch
55+
56+
Uses and duplicates code from Migration044
57+
"""
58+
59+
name: str = "046_fill_agnostic_hfid_display_labels"
60+
minimum_version: int = 45
61+
update_batch_size: int = 1000
62+
63+
async def _do_one_schema_all(
64+
self,
65+
db: InfrahubDatabase,
66+
branch: Branch,
67+
schema: MainSchemaTypes,
68+
schema_branch: SchemaBranch,
69+
attribute_schema_map: dict[AttributeSchema, AttributeSchema],
70+
progress: Progress | None = None,
71+
update_task: TaskID | None = None,
72+
) -> None:
73+
print(f"Processing {schema.kind}...", end="")
74+
75+
schema_paths_by_name: dict[str, list[SchemaAttributePath]] = {}
76+
for source_attribute_schema in attribute_schema_map.keys():
77+
node_schema_property = getattr(schema, source_attribute_schema.name)
78+
if not node_schema_property:
79+
continue
80+
if isinstance(node_schema_property, list):
81+
schema_paths_by_name[source_attribute_schema.name] = [
82+
schema.parse_schema_path(path=str(path), schema=schema_branch) for path in node_schema_property
83+
]
84+
else:
85+
schema_paths_by_name[source_attribute_schema.name] = [
86+
schema.parse_schema_path(path=str(node_schema_property), schema=schema_branch)
87+
]
88+
all_schema_paths = list(chain(*schema_paths_by_name.values()))
89+
offset = 0
90+
91+
# loop until we get no results from the get_details_query
92+
while True:
93+
get_details_query: GetResultMapQuery = await GetPathDetailsDefaultBranch.init(
94+
db=db,
95+
schema_kind=schema.kind,
96+
schema_paths=all_schema_paths,
97+
offset=offset,
98+
limit=self.update_batch_size,
99+
)
100+
await get_details_query.execute(db=db)
101+
102+
num_updates = 0
103+
for source_attribute_schema, destination_attribute_schema in attribute_schema_map.items():
104+
schema_paths = schema_paths_by_name[source_attribute_schema.name]
105+
schema_path_values_map = get_details_query.get_result_map(schema_paths)
106+
num_updates = max(num_updates, len(schema_path_values_map))
107+
formatted_schema_path_values_map = {}
108+
for k, v in schema_path_values_map.items():
109+
if not v:
110+
continue
111+
if destination_attribute_schema.kind == "List":
112+
formatted_schema_path_values_map[k] = ujson.dumps(v)
113+
else:
114+
formatted_schema_path_values_map[k] = " ".join(item for item in v if item is not None)
115+
116+
if not formatted_schema_path_values_map:
117+
continue
118+
119+
update_display_label_query = await UpdateAttributeValuesQuery.init(
120+
db=db,
121+
branch=branch,
122+
attribute_schema=destination_attribute_schema,
123+
values_by_id_map=formatted_schema_path_values_map,
124+
)
125+
await update_display_label_query.execute(db=db)
126+
127+
if progress is not None and update_task is not None:
128+
progress.update(update_task, advance=num_updates)
129+
130+
if num_updates == 0:
131+
break
132+
133+
offset += self.update_batch_size
134+
135+
print("done")
136+
137+
async def execute(self, db: InfrahubDatabase) -> MigrationResult:
138+
try:
139+
return await self._do_execute(db=db)
140+
except Exception as exc:
141+
return MigrationResult(errors=[str(exc)])
142+
143+
async def _do_execute(self, db: InfrahubDatabase) -> MigrationResult:
144+
console = get_migration_console()
145+
result = MigrationResult()
146+
147+
root_node = await get_root_node(db=db, initialize=False)
148+
default_branch_name = root_node.default_branch
149+
default_branch = await Branch.get_by_name(db=db, name=default_branch_name)
150+
main_schema_branch = await get_or_load_schema_branch(db=db, branch=default_branch)
151+
152+
console.print("Deleting branch-aware attributes for branch-agnostic nodes...", end="")
153+
delete_query = await DeleteBranchAwareAttrsForBranchAgnosticNodesQuery.init(db=db)
154+
await delete_query.execute(db=db)
155+
console.print("done")
156+
157+
branch_agnostic_schemas: list[NodeSchema] = []
158+
migrations = []
159+
for node_schema_kind in main_schema_branch.node_names:
160+
schema = main_schema_branch.get_node(name=node_schema_kind, duplicate=False)
161+
if schema.branch is not BranchSupportType.AGNOSTIC:
162+
continue
163+
branch_agnostic_schemas.append(schema)
164+
migrations.extend(
165+
[
166+
NodeAttributeAddMigration(
167+
new_node_schema=schema,
168+
previous_node_schema=schema,
169+
schema_path=SchemaPath(
170+
schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="human_friendly_id"
171+
),
172+
),
173+
NodeAttributeAddMigration(
174+
new_node_schema=schema,
175+
previous_node_schema=schema,
176+
schema_path=SchemaPath(
177+
schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
178+
),
179+
),
180+
]
181+
)
182+
183+
global_branch = await Branch.get_by_name(db=db, name=GLOBAL_BRANCH_NAME)
184+
with Progress(console=console) as progress:
185+
update_task = progress.add_task(
186+
"Adding HFID and display label attributes to branch-agnostic nodes", total=len(migrations)
187+
)
188+
189+
for migration in migrations:
190+
try:
191+
execution_result = await migration.execute(db=db, branch=global_branch)
192+
result.errors.extend(execution_result.errors)
193+
progress.update(update_task, advance=1)
194+
except Exception as exc:
195+
result.errors.append(str(exc))
196+
return result
197+
198+
total_nodes_query = await DefaultBranchNodeCount.init(
199+
db=db, kinds_to_include=[sch.kind for sch in branch_agnostic_schemas]
200+
)
201+
await total_nodes_query.execute(db=db)
202+
total_nodes_count = total_nodes_query.get_num_nodes()
203+
204+
base_node_schema = main_schema_branch.get("SchemaNode", duplicate=False)
205+
display_label_attribute_schema = base_node_schema.get_attribute("display_label")
206+
display_labels_attribute_schema = base_node_schema.get_attribute("display_labels")
207+
hfid_attribute_schema = base_node_schema.get_attribute("human_friendly_id")
208+
209+
with Progress(console=console) as progress:
210+
update_task = progress.add_task(
211+
f"Set display_label and human_friendly_id for {total_nodes_count} branch-agnostic nodes on global branch",
212+
total=total_nodes_count,
213+
)
214+
for branch_agnostic_schema in branch_agnostic_schemas:
215+
attribute_schema_map = {}
216+
if branch_agnostic_schema.display_labels:
217+
attribute_schema_map[display_labels_attribute_schema] = display_label_attribute_schema
218+
if branch_agnostic_schema.human_friendly_id:
219+
attribute_schema_map[hfid_attribute_schema] = hfid_attribute_schema
220+
if not attribute_schema_map:
221+
continue
222+
223+
await self._do_one_schema_all(
224+
db=db,
225+
branch=global_branch,
226+
schema=branch_agnostic_schema,
227+
schema_branch=main_schema_branch,
228+
attribute_schema_map=attribute_schema_map,
229+
progress=progress,
230+
update_task=update_task,
231+
)
232+
233+
return result
234+
235+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
236+
return MigrationResult()

changelog/7694.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Delete branch-aware human friendly ID and display label attributes from branch-agnostic nodes if they were erroneously added. Add branch-agnostic human friendly ID and display label attributes to branch-agnostic nodes and set their values.

0 commit comments

Comments
 (0)