GraphQL filter functionality missing in v4.3.0-beta1 #11047

Closed
opened 2025-12-29 21:39:36 +01:00 by adam · 3 comments
Owner

Originally created by @tyler-8 on GitHub (Apr 17, 2025).

Originally assigned to: @jeremystretch on GitHub.

Deployment Type

Self-hosted

NetBox Version

v4.3.0-beta1

Python Version

3.12

Steps to Reproduce

  1. Attempt to write a GraphQL query to filter circuit_list by 1 or more sites. Or filter an ip_address_list by the device or VM they belong to.

Expected Behavior

Filters exist to filter circuits by the site one of their terminations belongs to, filters exist to filter IP addresses based on the device or virtual machine they're attached to.

Example query for circuits based on site entries that works pre-4.2:

query siteCircuits($sites: [String!]) {
  circuit_list(filters: {site: $sites}) {
    id
    cid
    provider {
      name
    }
    termination_z {
      termination {
        ... on SiteType {
          id
          name
        }
      }
    }
  }
}

The $sites variable is:

{
  "sites": [
    "dm-albany",
    "dm-rochester"
  ]
}

Observed Behavior

No filters exist to do this on IPAddress or Circuit filter options in GraphQL in 4.3-beta1.

For circuits, the closest I can get is 'reversing' the query and querying circuit terminations instead.

query MyQuery {
  circuit_termination_list(
    filters: {termination_type: {app_label: {exact: "dcim"}, model: {exact: "site"}}, termination_id: "7"}
  ) {
    termination {
      ... on SiteType {
        id
        name
      }
    }
    term_side
    circuit {
      cid
      commit_rate
      install_date
      id
      provider {
        id
        name
      }
      provider_account {
        account
        id
        name
      }
    }
  }
}

However: this requires the NetBox ID of the site to be known (instead of being able to use the slug today), and the only way to filter on multiple related-object IDs is to do a verbose "OR" operator instead of being able to do termination_id: {in_list: [1, 2, 3, 4]} (for circuit terminations, not circuits).

In the case of IP Addresses you can't even do a filter such as assigned_object_id: {in_list: [1, 2, 3, 4]} (for IP Addresses) using multiple interface IDs from a single device.

As has always been the case since GraphQL was introduced, you could write a more "indirect" query starting with the device_list, and adding interfaces for the device, and each interface's IP Addresses nested within, but this is a significant change from previous behavior and also limits the flexibility & functionality of the GraphQL API.

There maybe be other GraphQL objects that are impacted, these are just the two most immediate cases that I've uncovered. Not being able to filter by multiple IDs (even using standard GraphQL syntax of "some_object_id: {in_list: [1, 2, 3, 4]}") is a big hindrance, but being able to filter by indirect relationships is very valuable and is how the NetBox GraphQL API has worked for quite a while now.

Originally created by @tyler-8 on GitHub (Apr 17, 2025). Originally assigned to: @jeremystretch on GitHub. ### Deployment Type Self-hosted ### NetBox Version v4.3.0-beta1 ### Python Version 3.12 ### Steps to Reproduce 1. Attempt to write a GraphQL query to filter `circuit_list` by 1 or more sites. Or filter an `ip_address_list` by the device or VM they belong to. ### Expected Behavior Filters exist to filter circuits by the site one of their terminations belongs to, filters exist to filter IP addresses based on the device or virtual machine they're attached to. Example query for circuits based on site entries that works pre-4.2: ```graphql query siteCircuits($sites: [String!]) { circuit_list(filters: {site: $sites}) { id cid provider { name } termination_z { termination { ... on SiteType { id name } } } } } ``` The `$sites` variable is: ``` { "sites": [ "dm-albany", "dm-rochester" ] } ``` ### Observed Behavior No filters exist to do this on IPAddress or Circuit filter options in GraphQL in 4.3-beta1. For circuits, the closest I can get is 'reversing' the query and querying circuit terminations instead. ```graphql query MyQuery { circuit_termination_list( filters: {termination_type: {app_label: {exact: "dcim"}, model: {exact: "site"}}, termination_id: "7"} ) { termination { ... on SiteType { id name } } term_side circuit { cid commit_rate install_date id provider { id name } provider_account { account id name } } } } ``` However: this requires the NetBox ID of the site to be known (instead of being able to use the slug today), and the only way to filter on multiple related-object IDs is to do a verbose "OR" operator instead of being able to do `termination_id: {in_list: [1, 2, 3, 4]}` (for circuit terminations, not circuits). In the case of IP Addresses you can't even do a filter such as `assigned_object_id: {in_list: [1, 2, 3, 4]}` (for IP Addresses) using multiple interface IDs from a single device. As has always been the case since GraphQL was introduced, you could write a more "indirect" query starting with the device_list, and adding interfaces for the device, and each interface's IP Addresses nested within, but this is a significant change from previous behavior and also limits the flexibility & functionality of the GraphQL API. There maybe be other GraphQL objects that are impacted, these are just the two most immediate cases that I've uncovered. Not being able to filter by multiple IDs (even using standard GraphQL syntax of "`some_object_id: {in_list: [1, 2, 3, 4]}`") is a big hindrance, but being able to filter by indirect relationships is very valuable and is how the NetBox GraphQL API has worked for quite a while now.
adam added the type: bugstatus: acceptedbetatopic: GraphQL labels 2025-12-29 21:39:36 +01:00
adam closed this issue 2025-12-29 21:39:36 +01:00
Author
Owner

@jeremystretch commented on GitHub (Apr 17, 2025):

Prior to NetBox v4.3, the GraphQL filters were automatically generated from the FilterSet classes which exist primarily for the UI and REST API. Due to changes in Strawberry (see #7598 for context), this is no longer the case: Beginning with NetBox v4.3, separate GraphQL filters are now defined for each model.

This divergence is significant because many filters that are defined on the original filter sets out of necessity are not actually needed on their GraphQL counterparts due to the nested nature of GraphQL filtering. For instance, filtering a device by manufacturer requires a dedicated manufacturer filter on DeviceFilterSet to span the relationship from the Device model to DeviceType, and from DeviceType to manufacturer.

GET /api/dcim/devices/?manufacturer=cisco

This filter is not required on a GraphQL filter set because it's possible to nest a manufacturer filter inside a device type filter when querying devices:

device_list (filters: {
  device_type: {
    manufacturer: {
        name: {exact: "Cisco"}
    }
  }
})

Focusing on the example of circuits specifically, I believe it would be an anti-pattern to introduce a site filter directly on the GraphQL circuit filter because there is no direct relationship from a circuit to a site. I think what's missing here is the ability to filter circuits by their terminations, and to filter those terminations by associated objects (e.g. site). Ultimately what we want to achieve is the following pattern:

circuit_list (filters:{
  terminations:{
    site:{
      name:{exact: "DM-Akron"}
    }
  }
})

There maybe be other GraphQL objects that are impacted, these are just the two most immediate cases that I've uncovered.

Further work is needed to discover any additional patterns which need to be implemented.

@jeremystretch commented on GitHub (Apr 17, 2025): Prior to NetBox v4.3, the GraphQL filters were automatically generated from the FilterSet classes which exist primarily for the UI and REST API. Due to changes in Strawberry (see #7598 for context), this is no longer the case: Beginning with NetBox v4.3, separate GraphQL filters are now defined for each model. This divergence is significant because many filters that are defined on the original filter sets out of necessity are not actually needed on their GraphQL counterparts due to the nested nature of GraphQL filtering. For instance, filtering a device by manufacturer requires a dedicated `manufacturer` filter on DeviceFilterSet to span the relationship from the Device model to DeviceType, and from DeviceType to manufacturer. ``` GET /api/dcim/devices/?manufacturer=cisco ``` This filter is not required on a GraphQL filter set because it's possible to nest a manufacturer filter inside a device type filter when querying devices: ``` device_list (filters: { device_type: { manufacturer: { name: {exact: "Cisco"} } } }) ``` Focusing on the example of circuits specifically, I believe it would be an anti-pattern to introduce a `site` filter directly on the GraphQL circuit filter because there is no direct relationship from a circuit to a site. I think what's missing here is the ability to filter circuits by their terminations, and to filter those terminations by associated objects (e.g. site). Ultimately what we want to achieve is the following pattern: ``` circuit_list (filters:{ terminations:{ site:{ name:{exact: "DM-Akron"} } } }) ``` > There maybe be other GraphQL objects that are impacted, these are just the two most immediate cases that I've uncovered. Further work is needed to discover any additional patterns which need to be implemented.
Author
Owner

@jeremystretch commented on GitHub (Apr 17, 2025):

I've started a draft PR adding a terminations filter for circuits as proposed above. Still need to look into IP addresses and other models.

@jeremystretch commented on GitHub (Apr 17, 2025): I've started a [draft PR](https://github.com/netbox-community/netbox/pull/19238) adding a `terminations` filter for circuits as proposed above. Still need to look into IP addresses and other models.
Author
Owner

@tyler-8 commented on GitHub (Apr 18, 2025):

I agree that the overall direction and filtering syntax/structure can be better than the old way.

I'm noticing there's no ability to do an inList filter like:

circuit_list (filters:{
  terminations:{
    site:{
      name:{inList: ["DM-Akron", "DM-Scranton"]}
    }
  }
})

or even something simpler like:

device_list (filters: {
    id: {inList: [1, 2, 3, 4]}
  }
) {
  id
  name
}

strawberry-django seems to support this out of the box in FilterLookup and BaseFilterLookup but it looks like neither of these classes are used in NetBox except one usage of FilterLookup (I could be mistaken or they're referenced/rebuilt in another way).

While you can chain OR filters together, it's far more verbose and much harder to use graphql variables to dynamically search for multiple values on the same field

{
  circuit_list(filters: {provider: {id: 2, OR: {id: 5}}}) {
    cid
    description
  }
}

vs

query SpecialProviderCircuits($providerIDs: [ID!]) {
  circuit_list(filters: {provider: {id: {inList: $providerIDs}}) {
    cid
    description
  }
}
# variables passed in as {"providerIDs": [2,5]}

It probably doesn't make sense to support inList on every single field, but I would think IDs, names/slugs, and any enum-like field would make a lot of sense to support inList filters. This would retain a capability that both the REST API and GraphQL APIs have had up until now, even if the syntax to do it has changed.

@tyler-8 commented on GitHub (Apr 18, 2025): I agree that the overall direction and filtering syntax/structure can be better than the old way. I'm noticing there's no ability to do an `inList` filter like: ```graphql circuit_list (filters:{ terminations:{ site:{ name:{inList: ["DM-Akron", "DM-Scranton"]} } } }) ``` or even something simpler like: ```graphql device_list (filters: { id: {inList: [1, 2, 3, 4]} } ) { id name } ``` strawberry-django seems to support this out of the box in [FilterLookup](https://github.com/strawberry-graphql/strawberry-django/blob/79002235d7aa0a24a13c810f904fcf062a366120/strawberry_django/filters.py#L89) and [BaseFilterLookup](https://github.com/strawberry-graphql/strawberry-django/blob/79002235d7aa0a24a13c810f904fcf062a366120/strawberry_django/fields/filter_types.py#L27) but it looks like neither of these classes are used in NetBox except one usage of [FilterLookup](https://github.com/netbox-community/netbox/blob/248c94bd3536e11b63eac7e05973274911fb5e7b/netbox/netbox/graphql/filter_mixins.py#L29) (I could be mistaken or they're referenced/rebuilt in another way). While you can chain `OR` filters together, it's far more verbose and _much_ harder to use graphql variables to dynamically search for multiple values on the same field ```graphql { circuit_list(filters: {provider: {id: 2, OR: {id: 5}}}) { cid description } } ``` vs ```graphql query SpecialProviderCircuits($providerIDs: [ID!]) { circuit_list(filters: {provider: {id: {inList: $providerIDs}}) { cid description } } # variables passed in as {"providerIDs": [2,5]} ``` It probably doesn't make sense to support `inList` on every single field, but I would think IDs, names/slugs, and any enum-like field would make a lot of sense to support `inList` filters. This would retain a capability that both the REST API and GraphQL APIs have had up until now, even if the syntax to do it has changed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#11047