Commit 2ac93cb8 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 23d8718b
# GraphQL API
## How GitLab implements GraphQL
We use the [graphql-ruby gem](https://graphql-ruby.org/) written by [Robert Mosolgo](https://github.com/rmosolgo/).
All GraphQL queries are directed to a single endpoint
([`app/controllers/graphql_controller.rb#execute`](https://gitlab.com/gitlab-org/gitlab/blob/master/app%2Fcontrollers%2Fgraphql_controller.rb)),
which is exposed as an API endpoint at `/api/graphql`.
## Deep Dive
In March 2019, Nick Thomas hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
......@@ -22,8 +30,31 @@ add a `HTTP_PRIVATE_TOKEN` header.
## Types
We use a code-first schema, and we declare what type everything is in Ruby.
For example, `app/graphql/types/issue_type.rb`:
```ruby
graphql_name 'Issue'
field :iid, GraphQL::ID_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
# we also have a method here that we've defined, that extends `field`
markdown_field :title_html, null: true
field :description, GraphQL::STRING_TYPE, null: true
markdown_field :description_html, null: true
```
We give each type a name (in this case `Issue`).
The `iid`, `title` and `description` are _scalar_ GraphQL types.
`iid` is a `GraphQL::ID_TYPE`, a special string type that signifies a unique ID.
`title` and `description` are regular `GraphQL::STRING_TYPE` types.
When exposing a model through the GraphQL API, we do so by creating a
new type in `app/graphql/types`.
new type in `app/graphql/types`. You can also declare custom GraphQL data types
for scalar data types (e.g. `TimeType`).
When exposing properties in a type, make sure to keep the logic inside
the definition as minimal as possible. Instead, consider moving any
......@@ -293,6 +324,8 @@ If the:
- Resource is part of a collection, the collection will be filtered to
exclude the objects that the user's authorization checks failed against.
Also see [authorizing resources in a mutation](#authorizing-resources).
TIP: **Tip:**
Try to load only what the currently authenticated user is allowed to
view with our existing finders first, without relying on authorization
......@@ -391,6 +424,11 @@ end
## Resolvers
We define how the application serves the response using _resolvers_
stored in the `app/graphql/resolvers` directory.
The resolver provides the actual implementation logic for retrieving
the objects in question.
To find objects to display in a field, we can add resolvers to
`app/graphql/resolvers`.
......@@ -618,7 +656,61 @@ it 'returns a successful response' do
end
```
## Notes about Query flow and GraphQL infrastructure
GitLab's GraphQL infrastructure can be found in `lib/gitlab/graphql`.
[Instrumentation](https://graphql-ruby.org/queries/instrumentation.html) is functionality
that wraps around a query being executed. It is implemented as a module that uses the `Instrumentation` class.
Example: `Present`
```ruby
module Present
#... some code above...
def self.use(schema_definition)
schema_definition.instrument(:field, Instrumentation.new)
end
end
```
A [Query Analyzer](https://graphql-ruby.org/queries/analysis.html#analyzer-api) contains a series
of callbacks to validate queries before they are executed. Each field can pass through
the analyzer, and the final value is also available to you.
[Multiplex queries](https://graphql-ruby.org/queries/multiplex.html) enable
multiple queries to be sent in a single request. This reduces the number of requests sent to the server.
(there are custom Multiplex Query Analyzers and Multiplex Instrumentation provided by graphql-ruby).
### Query limits
Queries and mutations are limited by depth, complexity, and recursion
to protect server resources from overly ambitious or malicious queries.
These values can be set as defaults and overridden in specific queries as needed.
The complexity values can be set per object as well, and the final query complexity is
evaluated based on how many objects are being returned. This is useful
for objects that are expensive (e.g. requiring Gitaly calls).
For example, a conditional complexity method in a resolver:
```ruby
def self.resolver_complexity(args, child_complexity:)
complexity = super
complexity += 2 if args[:labelName]
complexity
end
```
More about complexity:
[graphql-ruby docs](https://graphql-ruby.org/queries/complexity_and_depth.html)
## Documentation and Schema
Our schema is located at `app/graphql/gitlab_schema.rb`.
See the [schema reference](../api/graphql/reference/index.md) for details.
This generated GraphQL documentation needs to be updated when the schema changes.
For information on generating GraphQL documentation and schema files, see
[Rake tasks related to GraphQL](rake_tasks.md#update-graphql-documentation-and-schema-definitions).
[updating the schema documentation](rake_tasks.md#update-graphql-documentation-and-schema-definitions).
......@@ -39,6 +39,50 @@ To distinguish queries from mutations and fragments, the following naming conven
- `addUser.mutation.graphql` for mutations;
- `basicUser.fragment.graphql` for fragments.
GraphQL:
- Queries are stored in `(ee/)app/assets/javascripts/` under the feature. For example, `respository/queries`. Frontend components can use these stored queries.
- Mutations are stored in
`(ee/)app/assets/javascripts/<subfolders>/<name of mutation>.mutation.graphql`.
### Fragments
Fragments are a way to make your complex GraphQL queries more readable and re-usable.
They can be stored in a separate file and imported.
For example, a fragment that references another fragment:
```ruby
fragment BaseEpic on Epic {
id
iid
title
webPath
relativePosition
userPermissions {
adminEpic
createEpic
}
}
fragment EpicNode on Epic {
...BaseEpic
state
reference(full: true)
relationPath
createdAt
closedAt
hasChildren
hasIssues
group {
fullPath
}
}
```
More about fragments:
[GraphQL Docs](https://graphql.org/learn/queries/#fragments)
## Usage in Vue
To use Vue Apollo, import the [Vue Apollo][vue-apollo] plugin as well
......
......@@ -26,7 +26,17 @@ If you don't exactly understand what we mean by **not everything needs to happen
At GitLab we respect the [test pyramid](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/development/testing_guide/testing_levels.md), and so, we recommend you check the code coverage of a specific feature before writing end-to-end tests, for both [CE](https://gitlab-org.gitlab.io/gitlab-foss/coverage-ruby/#_AllFiles) and [EE](https://gitlab-org.gitlab.io/gitlab/coverage-ruby/#_AllFiles) projects.
Sometimes you may notice that there is already good coverage in other test levels, and we can stay confident that if we break a feature, we will still have quick feedback about it, even without having end-to-end tests.
Sometimes you may notice that there is already good coverage in lower test levels, and we can stay confident that if we break a feature, we will still have quick feedback about it, even without having end-to-end tests.
> For analyzing the code coverage, you will also need to understand which application files implement specific functionalities.
#### Some other guidelines are as follows
- Take a look at the [How to test at the correct level?](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/development/testing_guide/testing_levels.md#how-to-test-at-the-correct-level) section of the [Testing levels](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/development/testing_guide/testing_levels.md) document
- Look into the frequency in which such a feature is changed (_Stable features that don't change very often might not be worth covering with end-to-end tests if they're already covered in lower levels_)
- Finally, discuss with the developer(s) involved in developing the feature and the tests themselves, to get their feeling
If after this analysis you still think that end-to-end tests are needed, keep reading.
......
# frozen_string_literal: true
FactoryBot.define do
factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
factory :dev_ops_score_metric, class: ConversationalDevelopmentIndex::Metric do
leader_issues { 9.256 }
instance_issues { 1.234 }
percentage_issues { 13.331 }
......
......@@ -48,7 +48,7 @@ describe 'Conversational Development Index' do
context 'when there is data to display' do
it 'shows numbers for each metric' do
stub_application_setting(usage_ping_enabled: true)
create(:conversational_development_index_metric)
create(:dev_ops_score_metric)
visit instance_statistics_conversational_development_index_index_path
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
describe ConversationalDevelopmentIndex::Metric do
let(:conv_dev_index) { create(:conversational_development_index_metric) }
let(:conv_dev_index) { create(:dev_ops_score_metric) }
describe '#percentage_score' do
it 'returns stored percentage score' do
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
describe ConversationalDevelopmentIndex::MetricPresenter do
subject { described_class.new(metric) }
let(:metric) { build(:conversational_development_index_metric) }
let(:metric) { build(:dev_ops_score_metric) }
describe '#cards' do
it 'includes instance score, leader score and percentage score' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment