-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose GraphQL search anywhere query (#3688)
The search is now using a dedicated query. The frontend code has also been adapted to use this new query. The search logic will try to look for a specific node if the search parameter looks like a valid UUID. It will default to the previous search behaviour in other cases.
- Loading branch information
Showing
8 changed files
with
203 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Any, Optional | ||
|
||
from graphene import Boolean, Field, Int, List, ObjectType, String | ||
from infrahub_sdk.utils import extract_fields_first_node, is_valid_uuid | ||
|
||
from infrahub.core.constants import InfrahubKind | ||
from infrahub.core.manager import NodeManager | ||
|
||
if TYPE_CHECKING: | ||
from graphql import GraphQLResolveInfo | ||
|
||
from infrahub.core.protocols import CoreNode | ||
from infrahub.graphql import GraphqlContext | ||
|
||
|
||
class Node(ObjectType): | ||
id = Field(String, required=True) | ||
kind = Field(String, required=True, description="The node kind") | ||
|
||
|
||
class NodeEdge(ObjectType): | ||
node = Field(Node, required=True) | ||
|
||
|
||
class NodeEdges(ObjectType): | ||
count = Field(Int, required=True) | ||
edges = Field(List(of_type=NodeEdge, required=True), required=False) | ||
|
||
|
||
async def search_resolver( | ||
root: dict, # pylint: disable=unused-argument | ||
info: GraphQLResolveInfo, | ||
q: str, | ||
limit: int = 10, | ||
partial_match: bool = True, | ||
) -> dict[str, Any]: | ||
context: GraphqlContext = info.context | ||
response: dict[str, Any] = {} | ||
result: list[CoreNode] = [] | ||
|
||
fields = await extract_fields_first_node(info) | ||
|
||
if is_valid_uuid(q): | ||
matching: Optional[CoreNode] = await NodeManager.get_one( | ||
db=context.db, branch=context.branch, at=context.at, id=q | ||
) | ||
if matching: | ||
result.append(matching) | ||
else: | ||
result.extend( | ||
await NodeManager.query( | ||
db=context.db, | ||
branch=context.branch, | ||
schema=InfrahubKind.NODE, | ||
filters={"any__value": q}, | ||
limit=limit, | ||
partial_match=partial_match, | ||
) | ||
) | ||
|
||
if "edges" in fields and result: | ||
response["edges"] = [{"node": {"id": obj.id, "kind": obj.get_kind()}} for obj in result] | ||
|
||
if "count" in fields: | ||
response["count"] = len(result) | ||
|
||
return response | ||
|
||
|
||
InfrahubSearchAnywhere = Field( | ||
NodeEdges, | ||
q=String(required=True), | ||
limit=Int(required=False), | ||
partial_match=Boolean(required=False), | ||
resolver=search_resolver, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
from graphql import graphql | ||
|
||
from infrahub.core.branch import Branch | ||
from infrahub.core.node import Node | ||
from infrahub.database import InfrahubDatabase | ||
from infrahub.graphql import prepare_graphql_params | ||
|
||
SEARCH_QUERY = """ | ||
query ($search: String!) { | ||
InfrahubSearchAnywhere(q: $search) { | ||
count | ||
edges { | ||
node { | ||
id | ||
kind | ||
} | ||
} | ||
} | ||
} | ||
""" | ||
|
||
|
||
async def test_search_anywhere_by_uuid( | ||
db: InfrahubDatabase, | ||
car_accord_main: Node, | ||
car_camry_main: Node, | ||
car_volt_main: Node, | ||
car_prius_main: Node, | ||
car_yaris_main: Node, | ||
branch: Branch, | ||
): | ||
gql_params = prepare_graphql_params(db=db, include_subscription=False, branch=branch) | ||
|
||
result = await graphql( | ||
schema=gql_params.schema, | ||
source=SEARCH_QUERY, | ||
context_value=gql_params.context, | ||
root_value=None, | ||
variable_values={"search": car_accord_main.id}, | ||
) | ||
|
||
assert result.errors is None | ||
assert result.data | ||
assert result.data["InfrahubSearchAnywhere"]["count"] == 1 | ||
assert result.data["InfrahubSearchAnywhere"]["edges"][0]["node"]["id"] == car_accord_main.id | ||
assert result.data["InfrahubSearchAnywhere"]["edges"][0]["node"]["kind"] == car_accord_main.get_kind() | ||
|
||
|
||
async def test_search_anywhere_by_string( | ||
db: InfrahubDatabase, | ||
person_john_main: Node, | ||
person_jane_main: Node, | ||
car_accord_main: Node, | ||
car_camry_main: Node, | ||
car_volt_main: Node, | ||
car_prius_main: Node, | ||
car_yaris_main: Node, | ||
branch: Branch, | ||
): | ||
gql_params = prepare_graphql_params(db=db, include_subscription=False, branch=branch) | ||
|
||
result = await graphql( | ||
schema=gql_params.schema, | ||
source=SEARCH_QUERY, | ||
context_value=gql_params.context, | ||
root_value=None, | ||
variable_values={"search": "prius"}, | ||
) | ||
|
||
assert result.errors is None | ||
assert result.data | ||
assert result.data["InfrahubSearchAnywhere"]["count"] == 1 | ||
assert result.data["InfrahubSearchAnywhere"]["edges"][0]["node"]["id"] == car_prius_main.id | ||
assert result.data["InfrahubSearchAnywhere"]["edges"][0]["node"]["kind"] == car_prius_main.get_kind() | ||
|
||
result = await graphql( | ||
schema=gql_params.schema, | ||
source=SEARCH_QUERY, | ||
context_value=gql_params.context, | ||
root_value=None, | ||
variable_values={"search": "j"}, | ||
) | ||
|
||
assert result.errors is None | ||
assert result.data | ||
assert result.data["InfrahubSearchAnywhere"]["count"] == 2 | ||
|
||
node_ids = [] | ||
node_kinds = [] | ||
for edge in result.data["InfrahubSearchAnywhere"]["edges"]: | ||
node_ids.append(edge["node"]["id"]) | ||
node_kinds.append(edge["node"]["kind"]) | ||
|
||
assert node_ids == [person_john_main.id, person_jane_main.id] | ||
assert node_kinds == [person_john_main.get_kind(), person_jane_main.get_kind()] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters