Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1102ae7
WIP
solababs Oct 13, 2025
4e16ec7
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 15, 2025
c0b3b31
IFC-1886: Paginated branch graphql query
solababs Oct 16, 2025
9a07e23
update repsonse format
solababs Oct 20, 2025
41b1849
fix mypy
solababs Oct 20, 2025
c8fbca1
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 20, 2025
dd242d9
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 21, 2025
9f1dc4a
refactor branch list and count logic
solababs Oct 21, 2025
3a7ff90
fix mypy
solababs Oct 21, 2025
e659340
remove unused limit and offset on get list count
solababs Oct 21, 2025
2ad4b2d
conditionally resolve fields
solababs Oct 28, 2025
32c72ad
fix mypy, update schema
solababs Oct 29, 2025
a38cec2
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 29, 2025
84fac0a
change response format
solababs Oct 29, 2025
f7d4828
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 29, 2025
491ad04
update status, add schema
solababs Oct 29, 2025
86dbefa
update schema
solababs Oct 29, 2025
9eba0b8
fix mypy
solababs Oct 29, 2025
9fe2763
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 30, 2025
8e4dca9
use uuid for id
solababs Oct 30, 2025
f0aa561
Merge branch 'sb-20251013-infrahub-branch-query-ifc-1886' of https://…
solababs Oct 30, 2025
089b015
remove name and ids filter
solababs Oct 31, 2025
fb7d0a6
Merge branch 'develop' into sb-20251013-infrahub-branch-query-ifc-1886
solababs Oct 31, 2025
1157cc4
update graphql schema
solababs Oct 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions backend/infrahub/core/branch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
)
from infrahub.core.models import SchemaBranchHash # noqa: TC001
from infrahub.core.node.standard import StandardNode
from infrahub.core.query import QueryType
from infrahub.core.query import Query, QueryType
from infrahub.core.query.branch import (
BranchNodeGetListQuery,
DeleteBranchRelationshipsQuery,
GetAllBranchInternalRelationshipQuery,
RebaseBranchDeleteRelationshipQuery,
Expand Down Expand Up @@ -160,10 +161,26 @@ async def get_list(
name: str | None = None,
**kwargs: dict[str, Any],
) -> list[Self]:
branches = await super().get_list(db=db, limit=limit, ids=ids, name=name, **kwargs)
branches = [branch for branch in branches if branch.status != BranchStatus.DELETING]
query: Query = await BranchNodeGetListQuery.init(
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
)
await query.execute(db=db)

return [cls.from_db(result.get("n")) for result in query.get_results()]

return branches
@classmethod
async def get_list_count(
cls,
db: InfrahubDatabase,
limit: int = 1000,
ids: list[str] | None = None,
name: str | None = None,
**kwargs: dict[str, Any],
) -> int:
query: Query = await BranchNodeGetListQuery.init(
db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
)
return await query.count(db=db)

@classmethod
def isinstance(cls, obj: Any) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion backend/infrahub/core/query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ async def init(
at: Timestamp | str | None = None,
limit: int | None = None,
offset: int | None = None,
**kwargs: Any,
**kwargs: dict[str, Any],
) -> Self:
query = cls(branch=branch, at=at, limit=limit, offset=offset, **kwargs)

Expand Down
6 changes: 6 additions & 0 deletions backend/infrahub/core/query/branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from typing import TYPE_CHECKING, Any

from infrahub import config
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.constants import GLOBAL_BRANCH_NAME
from infrahub.core.query import Query, QueryType
from infrahub.core.query.standard_node import StandardNodeGetListQuery

if TYPE_CHECKING:
from infrahub.database import InfrahubDatabase
Expand Down Expand Up @@ -146,3 +148,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
self.add_to_query(query=query)

self.params["ids"] = [db.to_database_id(id) for id in self.ids]


class BranchNodeGetListQuery(StandardNodeGetListQuery):
raw_filter = f"n.status <> '{BranchStatus.DELETING.value}'"
Comment on lines +153 to +154
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add docstring and type annotation.

The class is missing a docstring and type annotation, which are required by the coding guidelines.

Apply this diff to add the missing docstring and type annotation:

 class BranchNodeGetListQuery(StandardNodeGetListQuery):
+    """Query to retrieve a list of branches, excluding those with DELETING status.
+    
+    This query extends StandardNodeGetListQuery with a filter to exclude branches
+    that are currently being deleted from the results.
+    """
+    
-    raw_filter = f"n.status <> '{BranchStatus.DELETING.value}'"
+    raw_filter: str = f"n.status <> '{BranchStatus.DELETING.value}'"

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class BranchNodeGetListQuery(StandardNodeGetListQuery):
raw_filter = f"n.status <> '{BranchStatus.DELETING.value}'"
class BranchNodeGetListQuery(StandardNodeGetListQuery):
"""Query to retrieve a list of branches, excluding those with DELETING status.
This query extends StandardNodeGetListQuery with a filter to exclude branches
that are currently being deleted from the results.
"""
raw_filter: str = f"n.status <> '{BranchStatus.DELETING.value}'"
🤖 Prompt for AI Agents
In backend/infrahub/core/query/branch.py around lines 153-154, the class
BranchNodeGetListQuery is missing a docstring and a type annotation for its
raw_filter attribute; add a concise class docstring that explains this Query
filters out deleting Branch nodes and add an explicit type annotation on
raw_filter (raw_filter: str = f"n.status <> '{BranchStatus.DELETING.value}'") to
satisfy the coding guidelines.

3 changes: 3 additions & 0 deletions backend/infrahub/core/query/standard_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
class StandardNodeGetListQuery(Query):
name = "standard_node_list"
type = QueryType.READ
raw_filter: str | None = None

def __init__(
self, node_class: StandardNode, ids: list[str] | None = None, node_name: str | None = None, **kwargs: Any
Expand All @@ -150,6 +151,8 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
if self.node_name:
filters.append("n.name = $name")
self.params["name"] = self.node_name
if self.raw_filter:
filters.append(self.raw_filter)

where = ""
if filters:
Expand Down
3 changes: 2 additions & 1 deletion backend/infrahub/graphql/queries/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .account import AccountPermissions, AccountToken
from .branch import BranchQueryList
from .branch import BranchQueryList, InfrahubBranchQueryList
from .internal import InfrahubInfo
from .ipam import (
DeprecatedIPAddressGetNextAvailable,
Expand All @@ -20,6 +20,7 @@
"BranchQueryList",
"DeprecatedIPAddressGetNextAvailable",
"DeprecatedIPPrefixGetNextAvailable",
"InfrahubBranchQueryList",
"InfrahubIPAddressGetNextAvailable",
"InfrahubIPPrefixGetNextAvailable",
"InfrahubInfo",
Expand Down
30 changes: 28 additions & 2 deletions backend/infrahub/graphql/queries/branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from typing import TYPE_CHECKING, Any

from graphene import ID, Field, List, NonNull, String
from graphene import ID, Field, Int, List, NonNull, String

from infrahub.graphql.field_extractor import extract_graphql_fields
from infrahub.graphql.types import BranchType
from infrahub.graphql.types import BranchType, InfrahubBranchType

if TYPE_CHECKING:
from graphql import GraphQLResolveInfo
Expand All @@ -28,3 +28,29 @@ async def branch_resolver(
resolver=branch_resolver,
required=True,
)


async def infrahub_branch_resolver(
root: dict, # noqa: ARG001
info: GraphQLResolveInfo,
limit: int | None = None,
offset: int | None = None,
) -> dict[str, Any]:
Comment on lines +33 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add validation for pagination parameters.

The limit and offset parameters lack validation, which could lead to issues with negative values or excessively large limits. Per past review feedback, parameter defaults were removed from the field definition, but the resolver signature still has None defaults and no validation.

Apply this diff to add validation:

 async def infrahub_branch_resolver(
     root: dict,  # noqa: ARG001
     info: GraphQLResolveInfo,
     limit: int | None = None,
     offset: int | None = None,
 ) -> dict[str, Any]:
+    """..."""  # Add docstring as per previous comment
+    
+    # Validate and set defaults for pagination parameters
+    if limit is None:
+        limit = 100
+    if offset is None:
+        offset = 0
+    
+    if limit < 1:
+        raise ValueError("limit must be at least 1")
+    if offset < 0:
+        raise ValueError("offset must be non-negative")
+    
     fields = extract_graphql_fields(info)
🤖 Prompt for AI Agents
In backend/infrahub/graphql/queries/branch.py around lines 33 to 38, the
resolver accepts limit and offset but does not validate them; add input
validation that enforces offset >= 0, limit >= 0 and limit <= MAX_LIMIT (choose
a sensible cap like 100 or use a configured constant), treat None as not
provided (or apply a safe default if your schema requires one), and raise a
GraphQLError with a clear message for invalid values. Ensure the validation runs
at the start of infrahub_branch_resolver and that any error raised is
descriptive (e.g., "limit must be between 0 and 100" or "offset must be >= 0"),
avoiding silent truncation or database queries with bad parameters.

fields = extract_graphql_fields(info)
branches = await BranchType.get_list(
graphql_context=info.context, fields=fields.get("edges", {}).get("node", {}), limit=limit, offset=offset
)
count = await InfrahubBranchType.get_list_count(graphql_context=info.context, limit=limit, offset=offset)
return {"count": count, "edges": {"node": branches}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Aaron meant earlier regarding the separation of count and edges is that users can send in queries that request different information:

In this case we'd expect to return both edges and branches:

query MyQuery {
  InfrahubBranch {
    count
    edges {
      node {
        name
      }
    }
  }
}

If we however have a query like the following one we don't need to send an additional query for the "count" since the request doesn't expect to have this information:

query MyQuery {
  InfrahubBranch {
    edges {
      node {
        name
      }
    }
  }
}

Finally it could be that someone just wants to count the branches, and in this case we don't need to query the database for the details of each branch.

query MyQuery {
  InfrahubBranch {
    count
  }
}

One thing that I noticed is that we have different filters for BranchType.get_list compared to InfrahubBranchType.get_list_count where we filter out the -global- branch with .get_list() but not with get_list_count(). So the count returns an extra number which is never seen.

A comment regarding the structure. We'd expect "edges" to be an array, instead of having "node" be an array. I.e. "edges" should be an array and "node" should be an object.

return {"count": count, "edges": {"node": branches}}

On the topic of structure I think we should probably align the output that we get so that it matches the other nodes. I.e. more of a move away from what we had in the original Branch query. I.e. I think that a query would instead look like this:

```graphql
query MyQuery {
  InfrahubBranch {
    edges {
      node {
        name {
           value   # <-- This is the new part we don't currently have.
        }
      }
    }
  }
}



InfrahubBranchQueryList = Field(
InfrahubBranchType,
ids=List(of_type=NonNull(ID)),
name=String(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parameters doesn't seem to work now.

query MyQuery {
  InfrahubBranch(name: "branch2") {
    count
    edges {
      node {
        id
        name {
          value
        }
      }
    }
  }
}

I.e infrahub_branch_resolver() doesn't accept the parameters ids or name so the above query would fail. I think perhaps we can just remove both of them in this first iteration and then consider which filter options we want and need. We will want to add some of them for sure. But it could potentially be quite verbose if we start to add all of the variations that we dynamically generate for other node types, so I think that we can have a discussion around that after this PR is merge.

I.e. if we were to follow the current approach we'd have:

  • name__value
  • name__values
  • description__value
  • description__values
  • etc for all attributes.

offset=Int(),
limit=Int(),
description="Retrieve paginated information about active branches.",
resolver=infrahub_branch_resolver,
required=True,
)
2 changes: 2 additions & 0 deletions backend/infrahub/graphql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
BranchQueryList,
DeprecatedIPAddressGetNextAvailable,
DeprecatedIPPrefixGetNextAvailable,
InfrahubBranchQueryList,
InfrahubInfo,
InfrahubIPAddressGetNextAvailable,
InfrahubIPPrefixGetNextAvailable,
Expand All @@ -65,6 +66,7 @@ class InfrahubBaseQuery(ObjectType):

Relationship = Relationship

InfrahubBranch = InfrahubBranchQueryList
InfrahubInfo = InfrahubInfo
InfrahubStatus = InfrahubStatus

Expand Down
3 changes: 2 additions & 1 deletion backend/infrahub/graphql/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
StrAttributeType,
TextAttributeType,
)
from .branch import BranchType
from .branch import BranchType, InfrahubBranchType
from .interface import InfrahubInterface
from .node import InfrahubObject
from .permission import PaginatedObjectPermission
Expand All @@ -41,6 +41,7 @@
"DropdownType",
"IPHostType",
"IPNetworkType",
"InfrahubBranchType",
"InfrahubInterface",
"InfrahubObject",
"InfrahubObjectType",
Expand Down
16 changes: 15 additions & 1 deletion backend/infrahub/graphql/types/branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any

from graphene import Boolean, Field, String
from graphene import Boolean, Field, Int, List, NonNull, String

from infrahub.core.branch import Branch
from infrahub.core.constants import GLOBAL_BRANCH_NAME
Expand Down Expand Up @@ -44,3 +44,17 @@ async def get_list(
return []

return [await obj.to_graphql(fields=fields) for obj in objs if obj.name != GLOBAL_BRANCH_NAME]


class InfrahubBranchEdge(InfrahubObjectType):
node = Field(List(of_type=NonNull(BranchType), required=True), required=True)


class InfrahubBranchType(InfrahubObjectType):
count = Field(Int, required=True, description="Total number of items")
edges = Field(InfrahubBranchEdge, required=True)

@classmethod
async def get_list_count(cls, graphql_context: GraphqlContext, **kwargs: Any) -> int:
async with graphql_context.db.start_session(read_only=True) as db:
return await Branch.get_list_count(db=db, **kwargs)
100 changes: 90 additions & 10 deletions backend/tests/unit/graphql/queries/test_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ async def test_branch_query(

gql_params = await prepare_graphql_params(
db=db,
include_subscription=False,
branch=default_branch,
account_session=session_admin,
service=service,
Expand Down Expand Up @@ -61,9 +60,7 @@ async def test_branch_query(
}
}
"""
gql_params = await prepare_graphql_params(
db=db, include_subscription=False, branch=default_branch, service=service
)
gql_params = await prepare_graphql_params(db=db, branch=default_branch, service=service)
all_branches = await graphql(
schema=gql_params.schema,
source=query,
Expand Down Expand Up @@ -108,9 +105,7 @@ async def test_branch_query(
}
}
""" % branch3["name"]
gql_params = await prepare_graphql_params(
db=db, include_subscription=False, branch=default_branch, service=service
)
gql_params = await prepare_graphql_params(db=db, branch=default_branch, service=service)
name_response = await graphql(
schema=gql_params.schema,
source=name_query,
Expand All @@ -134,9 +129,7 @@ async def test_branch_query(
""" % [branch3["id"]]
id_query = id_query.replace("'", '"')

gql_params = await prepare_graphql_params(
db=db, include_subscription=False, branch=default_branch, service=service
)
gql_params = await prepare_graphql_params(db=db, branch=default_branch, service=service)
id_response = await graphql(
schema=gql_params.schema,
source=id_query,
Expand All @@ -148,3 +141,90 @@ async def test_branch_query(
assert id_response.data
assert id_response.data["Branch"][0]["name"] == "branch3"
assert len(id_response.data["Branch"]) == 1

async def test_paginated_branch_query(
self,
db: InfrahubDatabase,
default_branch: Branch,
register_core_models_schema,
session_admin,
client,
service,
):
for i in range(10):
create_branch_query = """
mutation {
BranchCreate(data: { name: "%s", description: "%s" }) {
ok
object {
id
name
}
}
}
""" % (
f"sample-branch-{i}",
f"sample description {i}",
)

gql_params = await prepare_graphql_params(
db=db,
branch=default_branch,
account_session=session_admin,
service=service,
)
branch_result = await graphql(
schema=gql_params.schema,
source=create_branch_query,
context_value=gql_params.context,
root_value=None,
variable_values={},
)
assert branch_result.errors is None
assert branch_result.data

query = """
query {
InfrahubBranch(offset: 2, limit: 5) {
count
edges {
node {
name
description
}
}
}
}
"""
gql_params = await prepare_graphql_params(db=db, branch=default_branch, service=service)
all_branches = await graphql(
schema=gql_params.schema,
source=query,
context_value=gql_params.context,
root_value=None,
variable_values={},
)
assert all_branches.errors is None
assert all_branches.data
assert all_branches.data["InfrahubBranch"]["count"] == 13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This highlights the issue I mentioned above with the count. When we start out with the default branch and then create 10 branches. Probably the -global- branch is part of the count. And also one other branch. I'm not sure if this is due to the testing environment an we'd have some duplicate of the default branch. It would be good to determine where the extra branch that isn't the global branch is.


expected_branches = [
{
"description": "Default Branch",
"name": "main",
},
{
"description": "my description",
"name": "branch3",
},
*[
{
"description": f"sample description {i}",
"name": f"sample-branch-{i}",
}
for i in range(10)
],
]
assert all_branches.data["InfrahubBranch"]["edges"]["node"].sort(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As CodeRabbit is mentioning all_branches.data["InfrahubBranch"]["edges"]["node"] should not be a valid field as all_branches.data["InfrahubBranch"]["edges"] should be an array.

key=operator.itemgetter("name")
) == expected_branches.sort(key=operator.itemgetter("name"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Incorrect edges data structure access.

The test accesses edges["node"] as if edges is a single object, but according to the GraphQL Relay pagination pattern (confirmed in past review comments), edges should be an array of edge objects, each containing a node field.

Apply this diff to fix the assertion:

-        assert all_branches.data["InfrahubBranch"]["edges"]["node"].sort(
-            key=operator.itemgetter("name")
-        ) == expected_branches.sort(key=operator.itemgetter("name"))
+        edges = all_branches.data["InfrahubBranch"]["edges"]
+        actual_branches = [edge["node"] for edge in edges]
+        assert sorted(actual_branches, key=operator.itemgetter("name")) == sorted(
+            expected_branches, key=operator.itemgetter("name")
+        )

Note: This also fixes the comparison issue where .sort() returns None rather than the sorted list.

🤖 Prompt for AI Agents
In backend/tests/unit/graphql/queries/test_branch.py around lines 228 to 230,
the assertion incorrectly treats edges as a single dict and uses .sort() (which
returns None); extract the list of nodes from edges (e.g. map or list
comprehension to get each edge["node"]) for both actual and expected data if
needed, then use sorted(..., key=operator.itemgetter("name")) on each list and
compare the two sorted lists.

12 changes: 12 additions & 0 deletions schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6880,6 +6880,16 @@ input InfrahubAccountUpdateSelfInput {
password: String
}

type InfrahubBranchEdge {
node: [Branch!]!
}

type InfrahubBranchType {
"""Total number of items"""
count: Int!
edges: InfrahubBranchEdge!
}

input InfrahubComputedAttributeRecomputeInput {
"""Name of the computed attribute that must be recomputed"""
attribute: String!
Expand Down Expand Up @@ -10397,6 +10407,8 @@ type Query {
"""Retrieve fields mapping for converting object type"""
FieldsMappingTypeConversion(source_kind: String, target_kind: String): FieldsMapping!
InfrahubAccountToken(limit: Int, offset: Int): AccountTokenEdges!
"""Retrieve paginated information about active branches."""
InfrahubBranch(ids: [ID!], limit: Int, name: String, offset: Int): InfrahubBranchType!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Query field descriptor and resolver alignment.

The new InfrahubBranch query accepts ids, limit, name, and offset parameters for flexible pagination and filtering. Verify that the backend resolver (infrahub_branch_resolver per the summary) correctly handles these parameters, particularly the ids filter which allows querying specific branches by identifier.

Run the following script to verify resolver implementation:


🏁 Script executed:

#!/bin/bash
# Search for the infrahub_branch_resolver implementation
rg -n 'infrahub_branch_resolver' --type=py -A 10

Length of output: 1153


I need to examine the complete resolver implementation and verify parameter handling.


🏁 Script executed:

#!/bin/bash
# Get the full infrahub_branch_resolver implementation
cat backend/infrahub/graphql/queries/branch.py | sed -n '33,57p'

Length of output: 885


🏁 Script executed:

#!/bin/bash
# Search for the GraphQL field descriptor definition for InfrahubBranch
rg -n 'InfrahubBranch' backend/infrahub/graphql/queries/branch.py -A 5 -B 5

Length of output: 1162


🏁 Script executed:

#!/bin/bash
# Search for InfrahubBranch.get_list to see if it handles ids/name
rg -n 'def get_list' backend/infrahub --type=py -A 15 | grep -A 15 'InfrahubBranch\|class InfrahubBranch'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for the GraphQL field definition in schema generation or resolvers
rg -n 'ids.*limit.*name.*offset' backend/infrahub/graphql --type=py -C 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the complete branch.py file structure
cat backend/infrahub/graphql/queries/branch.py

Length of output: 1782


🏁 Script executed:

#!/bin/bash
# Search for where InfrahubBranch query field is defined with parameters
rg -n 'InfrahubBranch.*ids.*limit.*name.*offset' backend/infrahub --type=py

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for graphql schema generation that might define these parameters
rg -n 'ids.*limit.*name' backend/infrahub/graphql --type=py -B 5 -A 5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find where the GraphQL schema fields are defined or generated
fd -e py 'schema' backend/infrahub/graphql | head -20

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Look for the actual schema file or mutation/query definitions
rg -n 'class.*Query\|def.*query' backend/infrahub/graphql --type=py -A 10 | head -60

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a schema generation file or mutation definitions
find backend/infrahub/graphql -name '*.py' -type f | xargs grep -l 'InfrahubBranch' | head -10

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Look at the schema.graphql file directly around the InfrahubBranch definition
rg -n 'InfrahubBranch' schema/schema.graphql -B 5 -A 5

Length of output: 1886


🏁 Script executed:

#!/bin/bash
# Check if the resolver accepts **kwargs to handle additional parameters
rg -n 'infrahub_branch_resolver' backend/infrahub/graphql/queries/branch.py -A 20

Length of output: 893


🏁 Script executed:

#!/bin/bash
# Search for the specific line where the resolver is registered
rg -n 'resolver=infrahub_branch_resolver' backend/infrahub/graphql/queries/branch.py -B 10 -A 5

Length of output: 337


🏁 Script executed:

#!/bin/bash
# Find the field descriptor definition to see all parameters
rg -n 'class InfrahubBranch\|Field.*InfrahubBranch\|query_field' backend/infrahub/graphql --type=py -A 20 | head -80

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for name parameter handling in the resolver or nearby
rg -n 'name' backend/infrahub/graphql/queries/branch.py -C 3

Length of output: 476


🏁 Script executed:

#!/bin/bash
# Check if there are multiple resolver definitions or overloads
rg -n 'def.*branch.*resolver\|@.*resolver' backend/infrahub/graphql/queries/branch.py

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for the field descriptor in the schema generation or graphene code
rg -n 'GraphQLField.*InfrahubBranch\|InfrahubBranchType' backend/infrahub --type=py -B 2 -A 8 | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if parameters are dynamically extracted from the GraphQL query
rg -n 'extract_graphql_fields\|extract.*param' backend/infrahub/graphql --type=py -C 2 | head -60

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the complete resolver with more context
sed -n '30,70p' backend/infrahub/graphql/queries/branch.py

Length of output: 949


🏁 Script executed:

#!/bin/bash
# Find schema field definitions - look for where the query fields are built
rg -n 'define_query_field\|query_field_\|InfrahubBranch.*field' backend/infrahub --type=py -C 3 | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if ids parameter handling is in a wrapper or middleware
rg -n 'ids' backend/infrahub/graphql/queries/branch.py -B 5 -A 5

Length of output: 655


🏁 Script executed:

#!/bin/bash
# Look for where the parameters are defined in the GraphQL schema
rg -n '^\s*ids:.*ID' schema/schema.graphql -B 5 -A 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check around line 10789-10790 in the schema file
sed -n '10785,10795p' schema/schema.graphql

Length of output: 946


🏁 Script executed:

#!/bin/bash
# Search more broadly for the InfrahubBranch field and its parameters
sed -n '10780,10800p' schema/schema.graphql

Length of output: 5632


🏁 Script executed:

#!/bin/bash
# Look at the InfrahubBranchType definition
rg -n 'type InfrahubBranchType' schema/schema.graphql -A 20

Length of output: 598


🏁 Script executed:

#!/bin/bash
# Check if there's a resolver parameter mismatch documented
rg -n 'ids.*name' backend/infrahub/graphql/queries/branch.py

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at what parameters InfrahubBranch.get_list accepts
fd -name '*.py' -path '*/infrahub/*' | xargs grep -n 'class InfrahubBranch' | head -5

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Find and examine the InfrahubBranch class
rg -n 'class InfrahubBranch' backend/infrahub --type=py -A 30 | head -50

Length of output: 4177


🏁 Script executed:

#!/bin/bash
# Look for get_list method in InfrahubBranch
rg -n 'def get_list' backend/infrahub --type=py -B 2 -A 20 | grep -B 2 -A 20 'InfrahubBranch\|class.*Branch'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the actual GraphQL field definition more carefully
python3 << 'EOF'
import re

# Read schema.graphql
with open('schema/schema.graphql', 'r') as f:
    content = f.read()

# Find the line with InfrahubBranch query
lines = content.split('\n')
for i, line in enumerate(lines):
    if 'InfrahubBranch' in line and 'Retrieve paginated' in lines[i-1]:
        # Print context around this line
        start = max(0, i-5)
        end = min(len(lines), i+10)
        for j in range(start, end):
            print(f"{j+1}: {lines[j]}")
        break
EOF

Length of output: 1216


Resolver missing ids and name parameter handling.

The InfrahubBranch query field declares ids and name parameters, but the infrahub_branch_resolver function (backend/infrahub/graphql/queries/branch.py:33-48) only accepts limit and offset. These parameters are not forwarded to InfrahubBranch.get_list().

Add the missing parameters to the resolver signature and pass them through:

async def infrahub_branch_resolver(
    root: dict,
    info: GraphQLResolveInfo,
    ids: list[str] | None = None,
    name: str | None = None,
    limit: int | None = None,
    offset: int | None = None,
) -> dict[str, Any]:
    # ... update get_list call to include ids and name

Alternatively, adopt the **kwargs pattern used in branch_resolver (line 16) to handle all parameters.

🤖 Prompt for AI Agents
In schema/schema.graphql around lines 10789-10790, the InfrahubBranch query
declares ids and name args but the resolver
backend/infrahub/graphql/queries/branch.py (lines ~33-48) only accepts limit and
offset and does not forward ids/name to InfrahubBranch.get_list(); update the
resolver signature to accept ids: list[str] | None and name: str | None (or
switch to **kwargs like branch_resolver at line 16) and pass these parameters
through to InfrahubBranch.get_list(ids=ids, name=name, limit=limit,
offset=offset) so the query arguments are respected.

InfrahubEvent(
"""Filter the query to specific accounts"""
account__ids: [String!]
Expand Down
Loading