From 8eefd99c0ae9d9fdc94148a090695fb2458bd4b8 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Mon, 25 Jan 2021 14:33:09 +1300 Subject: [PATCH] Expose GraphQL query complexity score Allows the complexity score of a GraphQL query to be queryable by clients: { queryComplexity { limit score } } Changelog: added --- app/graphql/types/query_complexity_type.rb | 30 ++++++++++++++++ app/graphql/types/query_type.rb | 8 +++++ doc/api/graphql/getting_started.md | 18 ++++++++++ doc/api/graphql/index.md | 2 +- doc/api/graphql/reference/index.md | 15 ++++++++ doc/development/api_graphql_styleguide.md | 4 +-- .../types/query_complexity_type_spec.rb | 35 +++++++++++++++++++ 7 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 app/graphql/types/query_complexity_type.rb create mode 100644 spec/graphql/types/query_complexity_type_spec.rb diff --git a/app/graphql/types/query_complexity_type.rb b/app/graphql/types/query_complexity_type.rb new file mode 100644 index 00000000000000..82809fac22fb6b --- /dev/null +++ b/app/graphql/types/query_complexity_type.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class QueryComplexityType < ::Types::BaseObject + ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity } + + graphql_name 'QueryComplexity' + + alias_method :query, :object + + field :limit, GraphQL::INT_TYPE, + null: true, + method: :max_complexity, + see: { + 'GitLab documentation on this limit' => + 'https://docs.gitlab.com/ee/api/graphql/index.html#max-query-complexity' + }, + description: 'GraphQL query complexity limit.' + + field :score, GraphQL::INT_TYPE, + null: true, + description: 'GraphQL query complexity score.' + + def score + ::GraphQL::Analysis.analyze_query(query, [ANALYZER]).first + end + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 8b7b9f0107be6d..d2c67aea95cecd 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -36,6 +36,10 @@ class QueryType < ::Types::BaseObject resolver: Resolvers::MetadataResolver, description: 'Metadata about GitLab.' + field :query_complexity, Types::QueryComplexityType, + null: true, + description: 'Information about the complexity of the GraphQL query.' + field :snippets, Types::SnippetType.connection_type, null: true, @@ -170,6 +174,10 @@ def ci_application_settings def application_settings Gitlab::CurrentSettings.current_application_settings end + + def query_complexity + context.query + end end end diff --git a/doc/api/graphql/getting_started.md b/doc/api/graphql/getting_started.md index ade3ad1f9b66ce..5b482d15c5125e 100644 --- a/doc/api/graphql/getting_started.md +++ b/doc/api/graphql/getting_started.md @@ -298,6 +298,24 @@ query IssueTypes { More about introspection: [GraphQL documentation](https://graphql.org/learn/introspection/) +### Query complexity + +The calculated [complexity score and limit](index.md#max-query-complexity) for a query can be revealed to clients by +querying for `queryComplexity`. + +```graphql +query { + queryComplexity { + score + limit + } + + project(fullPath: "gitlab-org/graphql-sandbox") { + name + } +} +``` + ## Sorting Some of the GitLab GraphQL endpoints allow you to specify how to sort a diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md index e495b916b181b7..b7a82dba7e990e 100644 --- a/doc/api/graphql/index.md +++ b/doc/api/graphql/index.md @@ -166,7 +166,7 @@ The complexity of a single query is limited to a maximum of: - `200` for unauthenticated requests. - `250` for authenticated requests. -There is no way to discover the complexity of a query except by exceeding the limit. +The complexity score of a query and limit for the request [can be queried for](getting_started.md#query-complexity). If a query exceeds the complexity limit an error message response will be returned. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 803e8a3b44b3ff..21f5115747d431 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -286,6 +286,12 @@ four standard [pagination arguments](#connection-pagination-arguments): | `sort` | [`String`](#string) | Sort order of results. | | `topics` | [`[String!]`](#string) | Filters projects by topics. | +### `Query.queryComplexity` + +Information about the complexity of the GraphQL query. + +Returns [`QueryComplexity`](#querycomplexity). + ### `Query.runner` Find a runner. Available only when feature flag `runner_graphql_query` is enabled. @@ -12335,6 +12341,15 @@ Pypi metadata. | `id` | [`PackagesPypiMetadatumID!`](#packagespypimetadatumid) | ID of the metadatum. | | `requiredPython` | [`String`](#string) | Required Python version of the Pypi package. | +### `QueryComplexity` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `limit` | [`Int`](#int) | GraphQL query complexity limit. See [GitLab documentation on this limit](https://docs.gitlab.com/ee/api/graphql/index.html#max-query-complexity). | +| `score` | [`Int`](#int) | GraphQL query complexity score. | + ### `RecentFailures` Recent failure history of a test case. diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 75ab8359d0e312..c12b66a94a778e 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -68,9 +68,7 @@ Complexity is explained [on our client-facing API page](../api/graphql/index.md# Fields default to adding `1` to a query's complexity score, but developers can [specify a custom complexity](#field-complexity) when defining a field. -To estimate the complexity of a query, you can run the -[`gitlab:graphql:analyze`](rake_tasks.md#analyze-graphql-queries) -Rake task. +The complexity score of a query [can itself be queried for](../api/graphql/getting_started.md#query-complexity). ### Request timeout diff --git a/spec/graphql/types/query_complexity_type_spec.rb b/spec/graphql/types/query_complexity_type_spec.rb new file mode 100644 index 00000000000000..6b2330f2b13fef --- /dev/null +++ b/spec/graphql/types/query_complexity_type_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['QueryComplexity'] do + include GraphqlHelpers + + specify do + expect(described_class).to have_graphql_fields(:limit, :score).only + end + + it 'works when executed' do + query = <<-GQL + query { + queryComplexity { + score + limit + } + + currentUser { + name + } + } + GQL + + query_result = run_with_clean_state(query).to_h + + data = graphql_dig_at(query_result, :data, :queryComplexity) + + expect(data).to include( + 'score' => be > 0, + 'limit' => GitlabSchema::DEFAULT_MAX_COMPLEXITY + ) + end +end -- GitLab