Reduce GraphQL query complexity for security inventory query

What does this MR do and why?

Splits the security inventory's monolithic GraphQL query into two focused queries to reduce complexity below the authenticated limit of 250.

The subgroups_and_projects.query.graphql query had a calculated complexity of ~329 (reported as 340 in the issue), exceeding AUTHENTICATED_MAX_COMPLEXITY (250). This was flagged by the complexity tests added in !213762 (merged). While the query worked in practice because the frontend passes first: 20 (complexity is calculated against the worst-case max_page_size of 100), it was at risk of breaking if fields were added or larger page sizes used.

The original query used @skip(if: $hasSearch) and @include(if: $hasSearch) directives to make the browse path (descendantGroups + projects) and the search path (namespaceSecurityProjects) mutually exclusive. This MR makes that separation explicit by splitting them into two separate .graphql files, each with significantly lower complexity.

Implementation details

  • subgroups_and_projects.query.graphql (browse mode): Stripped down to only fetch descendantGroups and projects under a group. Removed $search, $hasSearch, $namespaceId, filter variables, @skip/@include directives, and the entire namespaceSecurityProjects block.
  • namespace_security_projects.query.graphql (search mode, new file): Fetches security-filtered projects via the namespaceSecurityProjects resolver plus a minimal group { id } fetch. Contains all search/filter variables.
  • inventory_dashboard.vue:
    • Replaced the single subgroupItems Apollo smart query with two: subgroupItems (skipped when searching) and searchResults (skipped when not searching).
    • Split fetchSubgroupsAndProjects() into fetchSubgroupsAndProjects() (browse) and fetchSecurityProjects() (search).
    • Split loadMoreProjects() into loadMoreBrowseProjects() and loadMoreSearchProjects().
    • Added refetchData() that delegates to the active query.
    • Updated isLoading to check both smart queries.
  • Test files (9 files): Extracted shared mock data into reusable mockProjects / mockSubgroups arrays. Added namespaceSecurityProjectsResponse mock and createSearchResponse / createPaginatedSearchHandler helpers. Updated 7 spec files to use the new direct exports. Registered both query handlers in the Apollo mock provider.

How to set up and validate locally

  1. Navigate to a group's Secure > Security inventory page.
  2. Browse mode: Verify subgroups and projects load. Click "Load more" to test pagination for both subgroups and projects.
  3. Search mode: Enter a search term or apply filters (analyzer status, vulnerability counts, security attributes). Verify filtered projects load and paginate correctly.
  4. Transition: Switch between browse and search modes (clear the search bar) and confirm data updates correctly.
  5. Verify sidebar navigation (clicking subgroups) still works.

To verify complexity reduction in a Rails console:

%w[subgroups_and_projects namespace_security_projects].each do |name|
  Gitlab::Graphql::Queries.all
    .select { |q| q.file.include?("#{name}.query.graphql") }
    .each { |q| puts "#{q.file} complexity: #{q.complexity(GitlabSchema)}" }
end
puts "MAX: #{GitlabSchema::AUTHENTICATED_MAX_COMPLEXITY}"
ee/app/assets/javascripts/security_inventory/graphql/subgroups_and_projects.query.graphql complexity: 197
ee/app/assets/javascripts/security_inventory/graphql/namespace_security_projects.query.graphql complexity: 140
MAX COMPLEXITY: 250

MR acceptance checklist

  • Tests added and updated (319 tests across 25 suites pass)
  • No new N+1 queries introduced (query split only, no resolver changes)
  • ESLint, Prettier, and circular dependency checks pass
  • Pipeline passes
  • No breaking changes to user-facing behavior

Closes #584297

References

Changelog: fixed

Edited by Nicolae Rotaru

Merge request reports

Loading