infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__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.
- infrahub/actions/tasks.py +4 -2
- infrahub/api/schema.py +3 -1
- infrahub/artifacts/tasks.py +1 -0
- infrahub/auth.py +2 -2
- infrahub/cli/db.py +6 -6
- infrahub/computed_attribute/gather.py +3 -4
- infrahub/computed_attribute/tasks.py +23 -6
- infrahub/config.py +8 -0
- infrahub/constants/enums.py +12 -0
- infrahub/core/account.py +5 -8
- infrahub/core/attribute.py +106 -108
- infrahub/core/branch/models.py +44 -71
- infrahub/core/branch/tasks.py +5 -3
- infrahub/core/changelog/diff.py +1 -20
- infrahub/core/changelog/models.py +0 -7
- infrahub/core/constants/__init__.py +17 -0
- infrahub/core/constants/database.py +0 -1
- infrahub/core/constants/schema.py +0 -1
- infrahub/core/convert_object_type/repository_conversion.py +3 -4
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/cardinality_one.py +1 -1
- infrahub/core/diff/merger/merger.py +27 -1
- infrahub/core/diff/merger/serializer.py +3 -10
- infrahub/core/diff/model/diff.py +1 -1
- infrahub/core/diff/query/merge.py +376 -135
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/constraints.py +2 -2
- infrahub/core/graph/schema.py +2 -12
- infrahub/core/manager.py +132 -126
- infrahub/core/metadata/__init__.py +0 -0
- infrahub/core/metadata/interface.py +37 -0
- infrahub/core/metadata/model.py +31 -0
- infrahub/core/metadata/query/__init__.py +0 -0
- infrahub/core/metadata/query/node_metadata.py +301 -0
- infrahub/core/migrations/graph/__init__.py +4 -0
- infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
- infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
- infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
- infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
- infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
- infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
- infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
- infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
- infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
- infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
- infrahub/core/migrations/query/attribute_add.py +17 -6
- infrahub/core/migrations/query/attribute_remove.py +19 -5
- infrahub/core/migrations/query/attribute_rename.py +21 -5
- infrahub/core/migrations/query/node_duplicate.py +19 -4
- infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
- infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
- infrahub/core/migrations/schema/models.py +3 -0
- infrahub/core/migrations/schema/node_attribute_add.py +4 -1
- infrahub/core/migrations/schema/node_remove.py +24 -2
- infrahub/core/migrations/schema/tasks.py +4 -1
- infrahub/core/migrations/shared.py +13 -6
- infrahub/core/models.py +6 -6
- infrahub/core/node/__init__.py +156 -57
- infrahub/core/node/create.py +7 -3
- infrahub/core/node/standard.py +100 -14
- infrahub/core/property.py +0 -1
- infrahub/core/protocols_base.py +6 -2
- infrahub/core/query/__init__.py +6 -7
- infrahub/core/query/attribute.py +161 -46
- infrahub/core/query/branch.py +57 -69
- infrahub/core/query/diff.py +4 -4
- infrahub/core/query/node.py +618 -180
- infrahub/core/query/relationship.py +449 -300
- infrahub/core/query/standard_node.py +25 -5
- infrahub/core/query/utils.py +2 -4
- infrahub/core/relationship/constraints/profiles_removal.py +168 -0
- infrahub/core/relationship/model.py +293 -139
- infrahub/core/schema/attribute_parameters.py +1 -28
- infrahub/core/schema/attribute_schema.py +17 -11
- infrahub/core/schema/manager.py +63 -43
- infrahub/core/schema/relationship_schema.py +6 -2
- infrahub/core/schema/schema_branch.py +48 -76
- infrahub/core/task/task.py +4 -2
- infrahub/core/utils.py +0 -22
- infrahub/core/validators/attribute/kind.py +2 -5
- infrahub/core/validators/determiner.py +3 -3
- infrahub/database/__init__.py +3 -3
- infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
- infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
- infrahub/dependencies/registry.py +2 -0
- infrahub/display_labels/tasks.py +12 -3
- infrahub/git/integrator.py +18 -18
- infrahub/git/tasks.py +1 -1
- infrahub/graphql/app.py +2 -2
- infrahub/graphql/constants.py +3 -0
- infrahub/graphql/context.py +1 -1
- infrahub/graphql/initialization.py +11 -0
- infrahub/graphql/loaders/account.py +134 -0
- infrahub/graphql/loaders/node.py +5 -12
- infrahub/graphql/loaders/peers.py +5 -7
- infrahub/graphql/manager.py +158 -18
- infrahub/graphql/metadata.py +91 -0
- infrahub/graphql/models.py +33 -3
- infrahub/graphql/mutations/account.py +5 -5
- infrahub/graphql/mutations/attribute.py +0 -2
- infrahub/graphql/mutations/branch.py +9 -5
- infrahub/graphql/mutations/computed_attribute.py +1 -1
- infrahub/graphql/mutations/display_label.py +1 -1
- infrahub/graphql/mutations/hfid.py +1 -1
- infrahub/graphql/mutations/ipam.py +4 -6
- infrahub/graphql/mutations/main.py +9 -4
- infrahub/graphql/mutations/profile.py +16 -22
- infrahub/graphql/mutations/proposed_change.py +4 -4
- infrahub/graphql/mutations/relationship.py +40 -10
- infrahub/graphql/mutations/repository.py +14 -12
- infrahub/graphql/mutations/schema.py +2 -2
- infrahub/graphql/queries/branch.py +62 -6
- infrahub/graphql/queries/diff/tree.py +5 -5
- infrahub/graphql/resolvers/account_metadata.py +84 -0
- infrahub/graphql/resolvers/ipam.py +6 -8
- infrahub/graphql/resolvers/many_relationship.py +77 -35
- infrahub/graphql/resolvers/resolver.py +16 -12
- infrahub/graphql/resolvers/single_relationship.py +87 -23
- infrahub/graphql/subscription/graphql_query.py +2 -0
- infrahub/graphql/types/__init__.py +0 -1
- infrahub/graphql/types/attribute.py +10 -5
- infrahub/graphql/types/branch.py +40 -53
- infrahub/graphql/types/enums.py +3 -0
- infrahub/graphql/types/metadata.py +28 -0
- infrahub/graphql/types/node.py +22 -2
- infrahub/graphql/types/relationship.py +10 -2
- infrahub/graphql/types/standard_node.py +4 -3
- infrahub/hfid/tasks.py +12 -3
- infrahub/profiles/gather.py +56 -0
- infrahub/profiles/mandatory_fields_checker.py +116 -0
- infrahub/profiles/models.py +66 -0
- infrahub/profiles/node_applier.py +153 -12
- infrahub/profiles/queries/get_profile_data.py +143 -31
- infrahub/profiles/tasks.py +79 -27
- infrahub/profiles/triggers.py +22 -0
- infrahub/proposed_change/tasks.py +4 -1
- infrahub/tasks/artifact.py +1 -0
- infrahub/transformations/tasks.py +2 -2
- infrahub/trigger/catalogue.py +2 -0
- infrahub/trigger/models.py +1 -0
- infrahub/trigger/setup.py +3 -3
- infrahub/trigger/tasks.py +3 -0
- infrahub/validators/tasks.py +1 -0
- infrahub/webhook/models.py +1 -1
- infrahub/webhook/tasks.py +1 -1
- infrahub/workers/dependencies.py +9 -3
- infrahub/workers/infrahub_async.py +13 -4
- infrahub/workflows/catalogue.py +19 -0
- infrahub_sdk/node/constants.py +1 -0
- infrahub_sdk/node/related_node.py +13 -4
- infrahub_sdk/node/relationship.py +8 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
- infrahub_testcontainers/container.py +3 -3
- infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
- infrahub_testcontainers/docker-compose.test.yml +13 -5
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
- {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -9,18 +9,17 @@ from netaddr import IPSet
|
|
|
9
9
|
from opentelemetry import trace
|
|
10
10
|
|
|
11
11
|
from infrahub.core import registry
|
|
12
|
-
from infrahub.core.constants import InfrahubKind
|
|
12
|
+
from infrahub.core.constants import InfrahubKind, MetadataOptions
|
|
13
13
|
from infrahub.core.ipam.constants import PrefixMemberType
|
|
14
14
|
from infrahub.core.manager import NodeManager
|
|
15
15
|
from infrahub.core.node import Node
|
|
16
16
|
from infrahub.core.protocols import BuiltinIPNamespace, BuiltinIPPrefix
|
|
17
17
|
from infrahub.core.schema.generic_schema import GenericSchema
|
|
18
18
|
from infrahub.exceptions import ValidationError
|
|
19
|
+
from infrahub.graphql.models import OrderModel
|
|
19
20
|
from infrahub.graphql.parser import extract_selection
|
|
20
21
|
from infrahub.graphql.permissions import get_permissions
|
|
21
22
|
|
|
22
|
-
from ..models import OrderModel
|
|
23
|
-
|
|
24
23
|
if TYPE_CHECKING:
|
|
25
24
|
from collections.abc import Sequence
|
|
26
25
|
|
|
@@ -31,7 +30,6 @@ if TYPE_CHECKING:
|
|
|
31
30
|
from infrahub.core.schema import NodeSchema
|
|
32
31
|
from infrahub.database import InfrahubDatabase
|
|
33
32
|
from infrahub.graphql.initialization import GraphqlContext
|
|
34
|
-
from infrahub.graphql.models import OrderModel
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
def _ip_range_display_label(node: Node) -> str:
|
|
@@ -311,7 +309,7 @@ async def ipam_paginated_list_resolver( # noqa: PLR0915
|
|
|
311
309
|
info: GraphQLResolveInfo,
|
|
312
310
|
offset: int | None = None,
|
|
313
311
|
limit: int | None = None,
|
|
314
|
-
order:
|
|
312
|
+
order: dict[str, Any] | None = None,
|
|
315
313
|
partial_match: bool = False,
|
|
316
314
|
**kwargs: dict[str, Any],
|
|
317
315
|
) -> dict[str, Any]:
|
|
@@ -324,6 +322,7 @@ async def ipam_paginated_list_resolver( # noqa: PLR0915
|
|
|
324
322
|
if not isinstance(schema, GenericSchema) or schema.kind not in [InfrahubKind.IPADDRESS, InfrahubKind.IPPREFIX]:
|
|
325
323
|
raise ValidationError(f"{schema.kind} is not {InfrahubKind.IPADDRESS} or {InfrahubKind.IPPREFIX}")
|
|
326
324
|
|
|
325
|
+
order_model = OrderModel.from_input(input_data=order)
|
|
327
326
|
fields = await extract_selection(info=info, schema=schema)
|
|
328
327
|
resolve_available = bool(kwargs.pop("include_available", False))
|
|
329
328
|
kinds_to_filter: list[str] = kwargs.pop("kinds", []) # type: ignore[assignment]
|
|
@@ -400,10 +399,9 @@ async def ipam_paginated_list_resolver( # noqa: PLR0915
|
|
|
400
399
|
limit=query_limit,
|
|
401
400
|
offset=offset,
|
|
402
401
|
account=graphql_context.account_session,
|
|
403
|
-
|
|
404
|
-
include_owner=True,
|
|
402
|
+
include_metadata=MetadataOptions.LINKED_NODES,
|
|
405
403
|
partial_match=partial_match,
|
|
406
|
-
order=
|
|
404
|
+
order=order_model,
|
|
407
405
|
)
|
|
408
406
|
|
|
409
407
|
if fetch_first_node_context and len(objs) > 2:
|
|
@@ -3,15 +3,20 @@ from typing import TYPE_CHECKING, Any
|
|
|
3
3
|
from graphql import GraphQLResolveInfo
|
|
4
4
|
|
|
5
5
|
from infrahub.core.branch.models import Branch
|
|
6
|
-
from infrahub.core.constants import
|
|
6
|
+
from infrahub.core.constants import (
|
|
7
|
+
BranchSupportType,
|
|
8
|
+
RelationshipHierarchyDirection,
|
|
9
|
+
)
|
|
7
10
|
from infrahub.core.manager import NodeManager
|
|
11
|
+
from infrahub.core.metadata.model import MetadataQueryOptions
|
|
8
12
|
from infrahub.core.query.node import NodeGetHierarchyQuery
|
|
13
|
+
from infrahub.core.relationship import Relationship
|
|
9
14
|
from infrahub.core.schema.node_schema import NodeSchema
|
|
10
15
|
from infrahub.core.schema.relationship_schema import RelationshipSchema
|
|
11
16
|
from infrahub.core.timestamp import Timestamp
|
|
12
17
|
from infrahub.database import InfrahubDatabase
|
|
13
18
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
14
|
-
from infrahub.
|
|
19
|
+
from infrahub.graphql.metadata import build_metadata_query_options, get_metadata_options_from_fields
|
|
15
20
|
|
|
16
21
|
from ..loaders.peers import PeerRelationshipsDataLoader, QueryPeerParams
|
|
17
22
|
from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
|
|
@@ -68,6 +73,25 @@ class ManyRelationshipResolver:
|
|
|
68
73
|
branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
|
|
69
74
|
)
|
|
70
75
|
|
|
76
|
+
def _build_relationship_meta_response(
|
|
77
|
+
self, relationship: Relationship, metadata_fields: dict[str, Any]
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
data: dict[str, Any] = {}
|
|
80
|
+
for meta_field in metadata_fields.keys():
|
|
81
|
+
if meta_field == "created_at":
|
|
82
|
+
created_at = relationship._get_created_at()
|
|
83
|
+
data["created_at"] = created_at.to_datetime() if created_at else None
|
|
84
|
+
elif meta_field == "created_by":
|
|
85
|
+
account_id = relationship._get_created_by()
|
|
86
|
+
data["created_by"] = {"id": account_id} if account_id else None
|
|
87
|
+
elif meta_field == "updated_at":
|
|
88
|
+
updated_at = relationship._get_updated_at()
|
|
89
|
+
data["updated_at"] = updated_at.to_datetime() if updated_at else None
|
|
90
|
+
elif meta_field == "updated_by":
|
|
91
|
+
account_id = relationship._get_updated_by()
|
|
92
|
+
data["updated_by"] = {"id": account_id} if account_id else None
|
|
93
|
+
return data
|
|
94
|
+
|
|
71
95
|
async def resolve(
|
|
72
96
|
self,
|
|
73
97
|
parent: dict,
|
|
@@ -93,6 +117,10 @@ class ManyRelationshipResolver:
|
|
|
93
117
|
edges = fields.get("edges", {})
|
|
94
118
|
node_fields = edges.get("node", {})
|
|
95
119
|
property_fields = edges.get("properties", {})
|
|
120
|
+
metadata_fields = {
|
|
121
|
+
"node_metadata": edges.get("node_metadata", {}),
|
|
122
|
+
"relationship_metadata": edges.get("relationship_metadata", {}),
|
|
123
|
+
}
|
|
96
124
|
for key, value in property_fields.items():
|
|
97
125
|
mapped_name = RELATIONS_PROPERTY_MAP[key]
|
|
98
126
|
node_fields[mapped_name] = value
|
|
@@ -138,45 +166,71 @@ class ManyRelationshipResolver:
|
|
|
138
166
|
if not node_fields:
|
|
139
167
|
return response
|
|
140
168
|
|
|
169
|
+
include_metadata = build_metadata_query_options(
|
|
170
|
+
node_metadata_fields=metadata_fields.get("node_metadata"),
|
|
171
|
+
relationship_metadata_fields=metadata_fields.get("relationship_metadata"),
|
|
172
|
+
node_fields=node_fields,
|
|
173
|
+
)
|
|
174
|
+
# Add relationship properties metadata to relationship_level
|
|
175
|
+
include_metadata |= MetadataQueryOptions(relationship_level=get_metadata_options_from_fields(property_fields))
|
|
176
|
+
|
|
141
177
|
if offset or limit:
|
|
142
|
-
|
|
178
|
+
relationships = await self._get_entities_simple(
|
|
143
179
|
db=graphql_context.db,
|
|
144
180
|
branch=graphql_context.branch,
|
|
145
181
|
ids=ids,
|
|
146
182
|
at=graphql_context.at,
|
|
147
|
-
related_node_ids=graphql_context.related_node_ids,
|
|
148
183
|
source_kind=source_kind,
|
|
149
184
|
rel_schema=node_rel,
|
|
150
185
|
filters=filters,
|
|
151
186
|
node_fields=node_fields,
|
|
187
|
+
include_metadata=include_metadata,
|
|
152
188
|
offset=offset,
|
|
153
189
|
limit=limit,
|
|
154
190
|
)
|
|
155
191
|
else:
|
|
156
|
-
|
|
192
|
+
relationships = await self._get_entities_with_data_loader(
|
|
157
193
|
db=graphql_context.db,
|
|
158
194
|
branch=graphql_context.branch,
|
|
159
195
|
ids=ids,
|
|
160
196
|
at=graphql_context.at,
|
|
161
|
-
related_node_ids=graphql_context.related_node_ids,
|
|
162
197
|
source_kind=source_kind,
|
|
163
198
|
rel_schema=node_rel,
|
|
164
199
|
filters=filters,
|
|
165
200
|
node_fields=node_fields,
|
|
201
|
+
include_metadata=include_metadata,
|
|
166
202
|
)
|
|
167
203
|
|
|
168
|
-
if not
|
|
204
|
+
if not relationships:
|
|
169
205
|
return response
|
|
170
206
|
|
|
171
207
|
entries = []
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
async with graphql_context.db.start_session(read_only=True) as db:
|
|
209
|
+
for rel in relationships:
|
|
210
|
+
node = await rel.to_graphql(
|
|
211
|
+
db=db,
|
|
212
|
+
fields=node_fields,
|
|
213
|
+
related_node_ids=graphql_context.related_node_ids,
|
|
214
|
+
)
|
|
215
|
+
entry: dict[str, dict[str, Any]] = {"node": {}, "properties": {}}
|
|
216
|
+
for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
|
|
217
|
+
value = node.pop(key, None)
|
|
218
|
+
if value:
|
|
219
|
+
entry["properties"][mapped] = value
|
|
220
|
+
entry["node"] = node
|
|
221
|
+
|
|
222
|
+
if metadata_fields.get("node_metadata"):
|
|
223
|
+
peer = await rel.get_peer(db=db)
|
|
224
|
+
if peer:
|
|
225
|
+
entry["node_metadata"] = await peer._build_meta_response("node_metadata", edges)
|
|
226
|
+
|
|
227
|
+
if metadata_fields.get("relationship_metadata"):
|
|
228
|
+
entry["relationship_metadata"] = self._build_relationship_meta_response(
|
|
229
|
+
relationship=rel,
|
|
230
|
+
metadata_fields=metadata_fields["relationship_metadata"],
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
entries.append(entry)
|
|
180
234
|
|
|
181
235
|
response["edges"] = entries
|
|
182
236
|
return response
|
|
@@ -187,17 +241,14 @@ class ManyRelationshipResolver:
|
|
|
187
241
|
branch: Branch,
|
|
188
242
|
ids: list[str],
|
|
189
243
|
at: Timestamp | None,
|
|
190
|
-
related_node_ids: set[str] | None,
|
|
191
244
|
source_kind: str,
|
|
192
245
|
rel_schema: RelationshipSchema,
|
|
193
246
|
filters: dict[str, Any],
|
|
194
247
|
node_fields: dict[str, Any],
|
|
248
|
+
include_metadata: MetadataQueryOptions,
|
|
195
249
|
offset: int | None = None,
|
|
196
250
|
limit: int | None = None,
|
|
197
|
-
) -> list[
|
|
198
|
-
include_source = has_any_key(data=node_fields, keys=["_relation__source", "source"])
|
|
199
|
-
include_owner = has_any_key(data=node_fields, keys=["_relation__owner", "owner"])
|
|
200
|
-
|
|
251
|
+
) -> list[Relationship] | None:
|
|
201
252
|
async with db.start_session(read_only=True) as dbs:
|
|
202
253
|
objs = await NodeManager.query_peers(
|
|
203
254
|
db=dbs,
|
|
@@ -212,12 +263,11 @@ class ManyRelationshipResolver:
|
|
|
212
263
|
branch=branch,
|
|
213
264
|
branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
|
|
214
265
|
fetch_peers=True,
|
|
215
|
-
|
|
216
|
-
include_owner=include_owner,
|
|
266
|
+
include_metadata=include_metadata,
|
|
217
267
|
)
|
|
218
268
|
if not objs:
|
|
219
269
|
return None
|
|
220
|
-
return
|
|
270
|
+
return objs
|
|
221
271
|
|
|
222
272
|
async def _get_entities_with_data_loader(
|
|
223
273
|
self,
|
|
@@ -225,18 +275,15 @@ class ManyRelationshipResolver:
|
|
|
225
275
|
branch: Branch,
|
|
226
276
|
ids: list[str],
|
|
227
277
|
at: Timestamp | None,
|
|
228
|
-
related_node_ids: set[str] | None,
|
|
229
278
|
source_kind: str,
|
|
230
279
|
rel_schema: RelationshipSchema,
|
|
231
280
|
filters: dict[str, Any],
|
|
232
281
|
node_fields: dict[str, Any],
|
|
233
|
-
|
|
282
|
+
include_metadata: MetadataQueryOptions,
|
|
283
|
+
) -> list[Relationship] | None:
|
|
234
284
|
if node_fields and "hfid" in node_fields:
|
|
235
285
|
node_fields["human_friendly_id"] = None
|
|
236
286
|
|
|
237
|
-
include_source = has_any_key(data=node_fields, keys=["_relation__source", "source"])
|
|
238
|
-
include_owner = has_any_key(data=node_fields, keys=["_relation__owner", "owner"])
|
|
239
|
-
|
|
240
287
|
query_params = QueryPeerParams(
|
|
241
288
|
branch=branch,
|
|
242
289
|
source_kind=source_kind,
|
|
@@ -245,8 +292,7 @@ class ManyRelationshipResolver:
|
|
|
245
292
|
fields=node_fields,
|
|
246
293
|
at=at,
|
|
247
294
|
branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
|
|
248
|
-
|
|
249
|
-
include_owner=include_owner,
|
|
295
|
+
include_metadata=include_metadata,
|
|
250
296
|
)
|
|
251
297
|
if query_params in self._data_loader_instances:
|
|
252
298
|
loader = self._data_loader_instances[query_params]
|
|
@@ -259,8 +305,4 @@ class ManyRelationshipResolver:
|
|
|
259
305
|
all_peer_rels.extend(node_peer_rels)
|
|
260
306
|
if not all_peer_rels:
|
|
261
307
|
return None
|
|
262
|
-
|
|
263
|
-
return [
|
|
264
|
-
await obj.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
|
|
265
|
-
for obj in all_peer_rels
|
|
266
|
-
]
|
|
308
|
+
return all_peer_rels
|
|
@@ -9,7 +9,7 @@ from infrahub.core.constants import BranchSupportType, InfrahubKind, Relationshi
|
|
|
9
9
|
from infrahub.core.manager import NodeManager
|
|
10
10
|
from infrahub.exceptions import NodeNotFoundError
|
|
11
11
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
12
|
-
from infrahub.
|
|
12
|
+
from infrahub.graphql.metadata import build_metadata_query_options
|
|
13
13
|
|
|
14
14
|
from ..models import OrderModel
|
|
15
15
|
from ..parser import extract_selection
|
|
@@ -18,7 +18,7 @@ from ..permissions import get_permissions
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from graphql import GraphQLResolveInfo
|
|
20
20
|
|
|
21
|
-
from infrahub.core.schema import NodeSchema
|
|
21
|
+
from infrahub.core.schema import MainSchemaTypes, NodeSchema
|
|
22
22
|
from infrahub.graphql.initialization import GraphqlContext
|
|
23
23
|
|
|
24
24
|
|
|
@@ -146,16 +146,18 @@ async def default_paginated_list_resolver(
|
|
|
146
146
|
info: GraphQLResolveInfo,
|
|
147
147
|
offset: int | None = None,
|
|
148
148
|
limit: int | None = None,
|
|
149
|
-
order:
|
|
149
|
+
order: dict | None = None,
|
|
150
150
|
partial_match: bool = False,
|
|
151
151
|
**kwargs: dict[str, Any],
|
|
152
152
|
) -> dict[str, Any]:
|
|
153
|
-
schema:
|
|
153
|
+
schema: MainSchemaTypes = (
|
|
154
154
|
info.return_type.of_type.graphene_type._meta.schema
|
|
155
155
|
if isinstance(info.return_type, GraphQLNonNull)
|
|
156
156
|
else info.return_type.graphene_type._meta.schema
|
|
157
157
|
)
|
|
158
158
|
|
|
159
|
+
order_model = OrderModel.from_input(input_data=order)
|
|
160
|
+
|
|
159
161
|
fields = await extract_selection(info=info, schema=schema)
|
|
160
162
|
|
|
161
163
|
graphql_context: GraphqlContext = info.context
|
|
@@ -165,8 +167,13 @@ async def default_paginated_list_resolver(
|
|
|
165
167
|
key: value for key, value in kwargs.items() if ("__" in key and value is not None) or key in ("ids", "hfid")
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
edges = fields.get("edges", {})
|
|
170
|
+
edges: dict[str, Any] = fields.get("edges", {})
|
|
169
171
|
node_fields = edges.get("node", {})
|
|
172
|
+
node_metadata_fields: dict[str, Any] = edges.get("node_metadata", {})
|
|
173
|
+
include_metadata = build_metadata_query_options(
|
|
174
|
+
node_metadata_fields=node_metadata_fields,
|
|
175
|
+
node_fields=node_fields,
|
|
176
|
+
)
|
|
170
177
|
if "hfid" in node_fields:
|
|
171
178
|
node_fields["human_friendly_id"] = None
|
|
172
179
|
|
|
@@ -186,23 +193,19 @@ async def default_paginated_list_resolver(
|
|
|
186
193
|
|
|
187
194
|
objs = []
|
|
188
195
|
if edges or "hfid" in filters:
|
|
189
|
-
include_source = has_any_key(data=node_fields, keys=["_relation__source", "source"])
|
|
190
|
-
include_owner = has_any_key(data=node_fields, keys=["_relation__owner", "owner"])
|
|
191
|
-
|
|
192
196
|
objs = await NodeManager.query(
|
|
193
197
|
db=db,
|
|
194
198
|
schema=schema,
|
|
195
199
|
filters=filters or None,
|
|
196
200
|
fields=node_fields,
|
|
201
|
+
include_metadata=include_metadata,
|
|
197
202
|
at=graphql_context.at,
|
|
198
203
|
branch=graphql_context.branch,
|
|
199
204
|
limit=limit,
|
|
200
205
|
offset=offset,
|
|
201
206
|
account=graphql_context.account_session,
|
|
202
|
-
include_source=include_source,
|
|
203
|
-
include_owner=include_owner,
|
|
204
207
|
partial_match=partial_match,
|
|
205
|
-
order=
|
|
208
|
+
order=order_model,
|
|
206
209
|
)
|
|
207
210
|
|
|
208
211
|
if "count" in fields:
|
|
@@ -227,7 +230,8 @@ async def default_paginated_list_resolver(
|
|
|
227
230
|
fields=node_fields,
|
|
228
231
|
related_node_ids=graphql_context.related_node_ids,
|
|
229
232
|
permissions=permission_set,
|
|
230
|
-
)
|
|
233
|
+
),
|
|
234
|
+
"node_metadata": await obj._build_meta_response("node_metadata", edges),
|
|
231
235
|
}
|
|
232
236
|
for obj in objs
|
|
233
237
|
]
|
|
@@ -4,12 +4,16 @@ from graphql import GraphQLResolveInfo
|
|
|
4
4
|
from graphql.type.definition import GraphQLNonNull
|
|
5
5
|
|
|
6
6
|
from infrahub.core.branch.models import Branch
|
|
7
|
-
from infrahub.core.constants import BranchSupportType
|
|
7
|
+
from infrahub.core.constants import BranchSupportType, MetadataOptions
|
|
8
8
|
from infrahub.core.manager import NodeManager
|
|
9
|
+
from infrahub.core.metadata.model import MetadataQueryOptions
|
|
10
|
+
from infrahub.core.node import Node
|
|
11
|
+
from infrahub.core.relationship import Relationship
|
|
9
12
|
from infrahub.core.schema.relationship_schema import RelationshipSchema
|
|
10
13
|
from infrahub.core.timestamp import Timestamp
|
|
11
14
|
from infrahub.database import InfrahubDatabase
|
|
12
15
|
from infrahub.graphql.field_extractor import extract_graphql_fields
|
|
16
|
+
from infrahub.graphql.metadata import build_metadata_query_options, get_metadata_options_from_fields
|
|
13
17
|
|
|
14
18
|
from ..loaders.node import GetManyParams, NodeDataLoader
|
|
15
19
|
from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
|
|
@@ -24,6 +28,25 @@ class SingleRelationshipResolver:
|
|
|
24
28
|
def __init__(self) -> None:
|
|
25
29
|
self._data_loader_instances: dict[GetManyParams, NodeDataLoader] = {}
|
|
26
30
|
|
|
31
|
+
def _build_relationship_meta_response(
|
|
32
|
+
self, relationship: Relationship, metadata_fields: dict[str, Any]
|
|
33
|
+
) -> dict[str, Any]:
|
|
34
|
+
data: dict[str, Any] = {}
|
|
35
|
+
for meta_field in metadata_fields.keys():
|
|
36
|
+
if meta_field == "created_at":
|
|
37
|
+
created_at = relationship._get_created_at()
|
|
38
|
+
data["created_at"] = created_at.to_datetime() if created_at else None
|
|
39
|
+
elif meta_field == "created_by":
|
|
40
|
+
account_id = relationship._get_created_by()
|
|
41
|
+
data["created_by"] = {"id": account_id} if account_id else None
|
|
42
|
+
elif meta_field == "updated_at":
|
|
43
|
+
updated_at = relationship._get_updated_at()
|
|
44
|
+
data["updated_at"] = updated_at.to_datetime() if updated_at else None
|
|
45
|
+
elif meta_field == "updated_by":
|
|
46
|
+
account_id = relationship._get_updated_by()
|
|
47
|
+
data["updated_by"] = {"id": account_id} if account_id else None
|
|
48
|
+
return data
|
|
49
|
+
|
|
27
50
|
async def resolve(self, parent: dict, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
|
|
28
51
|
"""Resolver for relationships of cardinality=one for Edged responses
|
|
29
52
|
|
|
@@ -45,50 +68,92 @@ class SingleRelationshipResolver:
|
|
|
45
68
|
fields = extract_graphql_fields(info=info)
|
|
46
69
|
node_fields = fields.get("node", {})
|
|
47
70
|
property_fields = fields.get("properties", {})
|
|
71
|
+
metadata_fields = {
|
|
72
|
+
"node_metadata": fields.get("node_metadata", {}),
|
|
73
|
+
"relationship_metadata": fields.get("relationship_metadata", {}),
|
|
74
|
+
}
|
|
48
75
|
for key, value in property_fields.items():
|
|
49
76
|
mapped_name = RELATIONS_PROPERTY_MAP[key]
|
|
50
77
|
node_fields[mapped_name] = value
|
|
51
78
|
|
|
52
79
|
metadata_field_names = {prop_name for prop_name in RELATIONS_PROPERTY_MAP if prop_name != "__typename"}
|
|
53
|
-
|
|
80
|
+
requires_relationship_properties = bool(set(property_fields.keys()) & metadata_field_names)
|
|
81
|
+
requires_relationship_metadata = bool(metadata_fields["relationship_metadata"])
|
|
54
82
|
|
|
55
83
|
# Extract the schema of the node on the other end of the relationship from the GQL Schema
|
|
56
84
|
node_rel = node_schema.get_relationship(info.field_name)
|
|
57
85
|
|
|
58
86
|
response: dict[str, Any] = {"node": None, "properties": {}}
|
|
59
87
|
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
relationship: Relationship | None = None
|
|
89
|
+
peer_node: Node | None = None
|
|
90
|
+
|
|
91
|
+
if requires_relationship_properties or requires_relationship_metadata:
|
|
92
|
+
include_metadata = build_metadata_query_options(
|
|
93
|
+
node_metadata_fields=metadata_fields.get("node_metadata"),
|
|
94
|
+
relationship_metadata_fields=metadata_fields.get("relationship_metadata"),
|
|
95
|
+
node_fields=node_fields,
|
|
96
|
+
)
|
|
97
|
+
# Add relationship properties metadata to relationship_level
|
|
98
|
+
include_metadata |= MetadataQueryOptions(
|
|
99
|
+
relationship_level=get_metadata_options_from_fields(property_fields)
|
|
100
|
+
)
|
|
101
|
+
relationship = await self._get_entities_simple(
|
|
62
102
|
db=graphql_context.db,
|
|
63
103
|
branch=graphql_context.branch,
|
|
64
104
|
at=graphql_context.at,
|
|
65
|
-
related_node_ids=graphql_context.related_node_ids,
|
|
66
105
|
field_name=info.field_name,
|
|
67
106
|
parent_id=parent["id"],
|
|
68
107
|
source_kind=node_schema.kind,
|
|
69
108
|
rel_schema=node_rel,
|
|
70
109
|
node_fields=node_fields,
|
|
110
|
+
include_metadata=include_metadata,
|
|
71
111
|
**kwargs,
|
|
72
112
|
)
|
|
73
113
|
else:
|
|
74
|
-
|
|
114
|
+
include_metadata = build_metadata_query_options(
|
|
115
|
+
node_metadata_fields=metadata_fields.get("node_metadata"),
|
|
116
|
+
node_fields=node_fields,
|
|
117
|
+
)
|
|
118
|
+
peer_node = await self._get_entities_with_data_loader(
|
|
75
119
|
db=graphql_context.db,
|
|
76
120
|
branch=graphql_context.branch,
|
|
77
121
|
at=graphql_context.at,
|
|
78
|
-
related_node_ids=graphql_context.related_node_ids,
|
|
79
122
|
rel_schema=node_rel,
|
|
80
123
|
parent=parent,
|
|
81
124
|
node_fields=node_fields,
|
|
125
|
+
include_metadata=include_metadata,
|
|
82
126
|
)
|
|
83
127
|
|
|
84
|
-
if not
|
|
128
|
+
if not relationship and not peer_node:
|
|
85
129
|
return response
|
|
86
|
-
response["node"] = node_graph
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
131
|
+
async with graphql_context.db.start_session(read_only=True) as db:
|
|
132
|
+
if relationship:
|
|
133
|
+
node_graph = await relationship.to_graphql(
|
|
134
|
+
db=db, fields=node_fields, related_node_ids=graphql_context.related_node_ids
|
|
135
|
+
)
|
|
136
|
+
peer_node = await relationship.get_peer(db=db)
|
|
137
|
+
elif peer_node:
|
|
138
|
+
node_graph = await peer_node.to_graphql(
|
|
139
|
+
db=db, fields=node_fields, related_node_ids=graphql_context.related_node_ids
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
response["node"] = node_graph
|
|
143
|
+
|
|
144
|
+
for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
|
|
145
|
+
value = node_graph.pop(key, None)
|
|
146
|
+
if value:
|
|
147
|
+
response["properties"][mapped] = value
|
|
148
|
+
|
|
149
|
+
if metadata_fields.get("node_metadata") and peer_node:
|
|
150
|
+
response["node_metadata"] = await peer_node._build_meta_response("node_metadata", fields)
|
|
151
|
+
|
|
152
|
+
if metadata_fields.get("relationship_metadata") and relationship:
|
|
153
|
+
response["relationship_metadata"] = self._build_relationship_meta_response(
|
|
154
|
+
relationship=relationship, metadata_fields=metadata_fields["relationship_metadata"]
|
|
155
|
+
)
|
|
156
|
+
|
|
92
157
|
return response
|
|
93
158
|
|
|
94
159
|
async def _get_entities_simple(
|
|
@@ -96,14 +161,14 @@ class SingleRelationshipResolver:
|
|
|
96
161
|
db: InfrahubDatabase,
|
|
97
162
|
branch: Branch,
|
|
98
163
|
at: Timestamp | None,
|
|
99
|
-
related_node_ids: set[str] | None,
|
|
100
164
|
field_name: str,
|
|
101
165
|
parent_id: str,
|
|
102
166
|
source_kind: str,
|
|
103
167
|
rel_schema: RelationshipSchema,
|
|
104
168
|
node_fields: dict[str, Any],
|
|
169
|
+
include_metadata: MetadataQueryOptions,
|
|
105
170
|
**kwargs: Any,
|
|
106
|
-
) ->
|
|
171
|
+
) -> Relationship | None:
|
|
107
172
|
filters = {
|
|
108
173
|
f"{field_name}__{key}": value
|
|
109
174
|
for key, value in kwargs.items()
|
|
@@ -121,21 +186,22 @@ class SingleRelationshipResolver:
|
|
|
121
186
|
branch=branch,
|
|
122
187
|
branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
|
|
123
188
|
fetch_peers=True,
|
|
189
|
+
include_metadata=include_metadata,
|
|
124
190
|
)
|
|
125
191
|
if not objs:
|
|
126
192
|
return None
|
|
127
|
-
return
|
|
193
|
+
return objs[0]
|
|
128
194
|
|
|
129
195
|
async def _get_entities_with_data_loader(
|
|
130
196
|
self,
|
|
131
197
|
db: InfrahubDatabase,
|
|
132
198
|
branch: Branch,
|
|
133
199
|
at: Timestamp | None,
|
|
134
|
-
related_node_ids: set[str] | None,
|
|
135
200
|
rel_schema: RelationshipSchema,
|
|
136
201
|
parent: dict[str, Any],
|
|
137
202
|
node_fields: dict[str, Any],
|
|
138
|
-
|
|
203
|
+
include_metadata: MetadataQueryOptions,
|
|
204
|
+
) -> Node | None:
|
|
139
205
|
try:
|
|
140
206
|
peer_id: str = parent[rel_schema.name][0]["node"]["id"]
|
|
141
207
|
except (KeyError, IndexError):
|
|
@@ -144,14 +210,13 @@ class SingleRelationshipResolver:
|
|
|
144
210
|
if node_fields and "hfid" in node_fields:
|
|
145
211
|
node_fields["human_friendly_id"] = None
|
|
146
212
|
|
|
213
|
+
include_metadata |= MetadataQueryOptions(attribute_level=MetadataOptions.LINKED_NODES)
|
|
147
214
|
query_params = GetManyParams(
|
|
148
215
|
fields=node_fields,
|
|
149
216
|
at=at,
|
|
150
217
|
branch=branch,
|
|
151
|
-
|
|
152
|
-
include_owner=True,
|
|
218
|
+
include_metadata=include_metadata,
|
|
153
219
|
prefetch_relationships=False,
|
|
154
|
-
account=None,
|
|
155
220
|
branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
|
|
156
221
|
)
|
|
157
222
|
if query_params in self._data_loader_instances:
|
|
@@ -162,5 +227,4 @@ class SingleRelationshipResolver:
|
|
|
162
227
|
node = await loader.load(key=peer_id)
|
|
163
228
|
if not node:
|
|
164
229
|
return None
|
|
165
|
-
|
|
166
|
-
return await node.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
|
|
230
|
+
return node
|
|
@@ -8,6 +8,7 @@ from infrahub.core.constants import InfrahubKind
|
|
|
8
8
|
from infrahub.core.manager import NodeManager
|
|
9
9
|
from infrahub.core.protocols import CoreGraphQLQuery
|
|
10
10
|
from infrahub.core.timestamp import Timestamp
|
|
11
|
+
from infrahub.graphql.resolvers.account_metadata import AccountMetadataResolver
|
|
11
12
|
from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
|
|
12
13
|
from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
|
|
13
14
|
from infrahub.log import get_logger
|
|
@@ -50,6 +51,7 @@ async def resolver_graphql_query(
|
|
|
50
51
|
types=graphql_context.types,
|
|
51
52
|
single_relationship_resolver=SingleRelationshipResolver(),
|
|
52
53
|
many_relationship_resolver=ManyRelationshipResolver(),
|
|
54
|
+
account_metadata_resolver=AccountMetadataResolver(),
|
|
53
55
|
),
|
|
54
56
|
root_value=None,
|
|
55
57
|
variable_values=params or {},
|
|
@@ -22,7 +22,6 @@ class RelatedNodeInput(InputObjectType):
|
|
|
22
22
|
hfid = Field(List(of_type=String), required=False)
|
|
23
23
|
kind = String(required=False) # Only used to resolve hfid of a related node on a generic relationship, see #4649
|
|
24
24
|
from_pool = Field(GenericPoolInput, required=False)
|
|
25
|
-
_relation__is_visible = Boolean(required=False)
|
|
26
25
|
_relation__is_protected = Boolean(required=False)
|
|
27
26
|
_relation__owner = String(required=False)
|
|
28
27
|
_relation__source = String(required=False)
|
|
@@ -41,7 +40,6 @@ class IPPrefixPoolInput(GenericPoolInput):
|
|
|
41
40
|
class RelatedIPAddressNodeInput(InputObjectType):
|
|
42
41
|
id = String(required=False)
|
|
43
42
|
from_pool = Field(IPAddressPoolInput, required=False)
|
|
44
|
-
_relation__is_visible = Boolean(required=False)
|
|
45
43
|
_relation__is_protected = Boolean(required=False)
|
|
46
44
|
_relation__owner = String(required=False)
|
|
47
45
|
_relation__source = String(required=False)
|
|
@@ -51,7 +49,6 @@ class RelatedIPPrefixNodeInput(InputObjectType):
|
|
|
51
49
|
id = String(required=False)
|
|
52
50
|
hfid = Field(List(of_type=String), required=False)
|
|
53
51
|
from_pool = Field(IPPrefixPoolInput, required=False)
|
|
54
|
-
_relation__is_visible = Boolean(required=False)
|
|
55
52
|
_relation__is_protected = Boolean(required=False)
|
|
56
53
|
_relation__owner = String(required=False)
|
|
57
54
|
_relation__source = String(required=False)
|
|
@@ -65,7 +62,6 @@ class AttributeInterface(InfrahubInterface):
|
|
|
65
62
|
is_default = Field(Boolean)
|
|
66
63
|
is_inherited = Field(Boolean)
|
|
67
64
|
is_protected = Field(Boolean)
|
|
68
|
-
is_visible = Field(Boolean)
|
|
69
65
|
updated_at = Field(DateTime)
|
|
70
66
|
# Since source and owner are using a Type that is generated dynamically
|
|
71
67
|
# these 2 fields will be dynamically inserted when we generate the GraphQL Schema
|
|
@@ -73,7 +69,16 @@ class AttributeInterface(InfrahubInterface):
|
|
|
73
69
|
# owner = Field("DataOwner")
|
|
74
70
|
|
|
75
71
|
|
|
76
|
-
class
|
|
72
|
+
class InfrahubAttributeMetaObject(ObjectType):
|
|
73
|
+
# updated_by is dynamically added in GraphQLSchemaManager.generate_object_types()
|
|
74
|
+
# to use the account_type (CoreGenericAccount) instead of a plain String
|
|
75
|
+
updated_at = DateTime(
|
|
76
|
+
required=False,
|
|
77
|
+
description="Date/Time when the attribute was last modified by a user or a system task",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class BaseAttribute(InfrahubAttributeMetaObject):
|
|
77
82
|
id = Field(String)
|
|
78
83
|
is_from_profile = Field(Boolean)
|
|
79
84
|
permissions = Field(PermissionType, required=False)
|