gitlab-org--gitlab-foss/doc/development/fe_guide/graphql.md

279 lines
7.4 KiB
Markdown
Raw Normal View History

2018-11-28 10:00:03 +00:00
# GraphQL
Our GraphQL API can be explored via GraphiQL at your instance's
`/-/graphql-explorer` or at [GitLab.com](https://gitlab.com/-/graphql-explorer).
You can check all existing queries and mutations on the right side
of GraphiQL in its **Documentation explorer**. It's also possible to
write queries and mutations directly on the left tab and check
their execution by clicking **Execute query** button on the top left:
![GraphiQL interface](img/graphiql_explorer_v12_4.png)
2018-11-28 10:00:03 +00:00
We use [Apollo] and [Vue Apollo][vue-apollo] for working with GraphQL
on the frontend.
## Apollo Client
To save duplicated clients getting created in different apps, we have a
[default client][default-client] that should be used. This setups the
2018-11-28 10:00:03 +00:00
Apollo client with the correct URL and also sets the CSRF headers.
Default client accepts two parameters: `resolvers` and `config`.
- `resolvers` parameter is created to accept an object of resolvers for [local state management](#local-state-with-apollo) queries and mutations
- `config` parameter takes an object of configuration settings:
- `cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://github.com/apollographql/apollo-client/tree/master/packages/apollo-cache-inmemory#configuration)
- `baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (i.e.`${gon.relative_url_root}/api/graphql`)
- `assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, will assume that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache will throw a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`.
2018-11-28 10:00:03 +00:00
## GraphQL Queries
To save query compilation at runtime, webpack can directly import `.graphql`
files. This allows webpack to preprocess the query at compile time instead
of the client doing compilation of queries.
To distinguish queries from mutations and fragments, the following naming convention is recommended:
- `allUsers.query.graphql` for queries;
- `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)
2018-11-28 10:00:03 +00:00
## Usage in Vue
To use Vue Apollo, import the [Vue Apollo][vue-apollo] plugin as well
as the default client. This should be created at the same point
the Vue application is mounted.
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
2019-02-22 12:54:06 +00:00
import createDefaultClient from '~/lib/graphql';
2018-11-28 10:00:03 +00:00
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
2019-02-22 12:54:06 +00:00
defaultClient: createDefaultClient(),
2018-11-28 10:00:03 +00:00
});
new Vue({
...,
apolloProvider,
...
});
```
Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/).
2018-11-28 10:00:03 +00:00
### Local state with Apollo
2019-02-22 12:54:06 +00:00
It is possible to manage an application state with Apollo by passing
in a resolvers object when creating the default client. The default state can be set by writing
to the cache after setting up the default client.
2019-02-22 12:54:06 +00:00
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const defaultClient = createDefaultClient({
Query: {
...
},
Mutations: {
...
},
});
defaultClient.cache.writeData({
data: {
isLoading: true,
},
});
2019-02-22 12:54:06 +00:00
const apolloProvider = new VueApollo({
defaultClient,
2019-02-22 12:54:06 +00:00
});
```
Read more about local state management with Apollo in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/local-state.html#local-state).
2018-11-28 10:00:03 +00:00
### Testing
#### Mocking response as component data
2018-11-28 10:00:03 +00:00
With [Vue test utils][vue-test-utils] it is easy to quickly test components that
fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
the data on the component
```javascript
it('tests apollo component', () => {
const vm = shallowMount(App);
vm.setData({
...mock data
});
});
```
#### Testing loading state
If we need to test how our component renders when results from the GraphQL API are still loading, we can mock a loading state into respective Apollo queries/mutations:
```javascript
function createComponent({
loading = false,
} = {}) {
const $apollo = {
queries: {
designs: {
loading,
},
};
wrapper = shallowMount(Index, {
sync: false,
mocks: { $apollo }
});
}
it('renders loading icon', () => {
createComponent({ loading: true });
expect(wrapper.element).toMatchSnapshot();
})
```
#### Testing Apollo components
If we use `ApolloQuery` or `ApolloMutation` in our components, in order to test their functionality we need to add a stub first:
```javascript
import { ApolloMutation } from 'vue-apollo';
function createComponent(props = {}) {
wrapper = shallowMount(MyComponent, {
sync: false,
propsData: {
...props,
},
stubs: {
ApolloMutation,
},
});
}
```
`ApolloMutation` component exposes `mutate` method via scoped slot. If we want to test this method, we need to add it to mocks:
```javascript
const mutate = jest.fn(() => Promise.resolve());
const $apollo = {
mutate,
};
function createComponent(props = {}) {
wrapper = shallowMount(MyComponent, {
sync: false,
propsData: {
...props,
},
stubs: {
ApolloMutation,
},
mocks: {
$apollo:
}
});
}
```
Then we can check if `mutate` is called with correct variables:
```javascript
const mutationVariables = {
mutation: createNoteMutation,
update: expect.anything(),
variables: {
input: {
noteableId: 'noteable-id',
body: 'test',
discussionId: '0',
},
},
};
it('calls mutation on submitting form ', () => {
createComponent()
findReplyForm().vm.$emit('submitForm');
expect(mutate).toHaveBeenCalledWith(mutationVariables);
});
```
2018-11-28 10:00:03 +00:00
## Usage outside of Vue
It is also possible to use GraphQL outside of Vue by directly importing
and using the default client with queries.
```javascript
import defaultClient from '~/lib/graphql';
import query from './query.graphql';
defaultClient.query(query)
.then(result => console.log(result));
```
Read more about the [Apollo] client in the [Apollo documentation](https://www.apollographql.com/docs/tutorial/client/).
2018-11-28 10:00:03 +00:00
[Apollo]: https://www.apollographql.com/
[vue-apollo]: https://github.com/Akryum/vue-apollo/
[feature-flags]: ../feature_flags.md
[default-client]: https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/lib/graphql.js
2018-11-28 10:00:03 +00:00
[vue-test-utils]: https://vue-test-utils.vuejs.org/
2019-02-22 12:54:06 +00:00
[apollo-link-state]: https://www.apollographql.com/docs/link/links/state.html