are useful for testing for potential accessibility problems in GitLab.
assist with testing for potential accessibility problems in GitLab.
The [axe](https://www.deque.com/axe/) browser extension (available for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) and [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd)) is
The [axe](https://www.deque.com/axe/) browser extension (available for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) and [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd)) provides running audits and feedback on markup, CSS, and even potentially problematic color usages.
also a handy tool for running audits and getting feedback on markup, CSS and even potentially problematic color usages.
Accessibility best-practices and more in-depth information are available on
Accessibility best-practices and more in-depth information are available on
[the Audit Rules page](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) for the Chrome Accessibility Developer Tools. The [Awesome Accessibility](https://github.com/brunopulis/awesome-a11y) list is also a
[the Audit Rules page](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) for the Chrome Accessibility Developer Tools. The [Awesome Accessibility](https://github.com/brunopulis/awesome-a11y) list is a compilation of accessibility-related material.
useful compilation of accessibility-related material.
@@ -6,9 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
...
@@ -6,9 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Architecture
# Architecture
When you are developing a new feature that requires architectural design, or if
When developing a feature that requires architectural design, or changing the fundamental design of an existing feature, discuss it with a Frontend Architecture Expert.
you are changing the fundamental design of an existing feature, make sure it is
discussed with one of the Frontend Architecture Experts.
A Frontend Architect is an expert who makes high-level Frontend design decisions
A Frontend Architect is an expert who makes high-level Frontend design decisions
and decides on technical standards, including coding standards and frameworks.
and decides on technical standards, including coding standards and frameworks.
@@ -12,9 +12,9 @@ You can find more about the organization of the frontend team in the [handbook](
...
@@ -12,9 +12,9 @@ You can find more about the organization of the frontend team in the [handbook](
The idea is to remind us about specific topics during the time we build a new feature or start something. This is a common practice in other industries (like pilots) that also use standardized checklists to reduce problems early on.
The idea is to remind us about specific topics during the time we build a new feature or start something. This is a common practice in other industries (like pilots) that also use standardized checklists to reduce problems early on.
Copy the content over to your issue or merge request and if something doesn't apply simply remove it from your current list.
Copy the content over to your issue or merge request and if something doesn't apply, remove it from your current list.
This checklist is intended to help us during development of bigger features/refactorings, it's not a "use it always and every point always matches" list.
This checklist is intended to help us during development of bigger features/refactorings. It is not a "use it always and every point always matches" list.
Please use your best judgment when to use it and please contribute new points through merge requests if something comes to your mind.
Please use your best judgment when to use it and please contribute new points through merge requests if something comes to your mind.
...
@@ -77,7 +77,7 @@ With the purpose of being [respectful of others' time](https://about.gitlab.com/
...
@@ -77,7 +77,7 @@ With the purpose of being [respectful of others' time](https://about.gitlab.com/
- includes tests
- includes tests
- includes a changelog entry (when necessary)
- includes a changelog entry (when necessary)
- Before assigning to a maintainer, assign to a reviewer.
- Before assigning to a maintainer, assign to a reviewer.
- If you assigned a merge request or pinged someone directly, be patient because we work in different timezones and asynchronously. Unless the merge request is urgent (like fixing a broken master), please don't DM or reassign the merge request before waiting for a 24-hour window.
- If you assigned a merge request or pinged someone directly, be patient because we work in different timezones and asynchronously. Unless the merge request is urgent (like fixing a broken default branch), please don't DM or reassign the merge request before waiting for a 24-hour window.
- If you have a question regarding your merge request/issue, make it on the merge request/issue. When we DM each other, we no longer have a SSOT and [no one else is able to contribute](https://about.gitlab.com/handbook/values/#public-by-default).
- If you have a question regarding your merge request/issue, make it on the merge request/issue. When we DM each other, we no longer have a SSOT and [no one else is able to contribute](https://about.gitlab.com/handbook/values/#public-by-default).
- When you have a big **Draft** merge request with many changes, you're advised to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewer(s)/maintainer(s) would always prioritize reviewing finished MRs before the **Draft** ones.
- When you have a big **Draft** merge request with many changes, you're advised to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewer(s)/maintainer(s) would always prioritize reviewing finished MRs before the **Draft** ones.
- Make sure to remove the `Draft:` title before the last round of review.
- Make sure to remove the `Draft:` title before the last round of review.
@@ -65,7 +65,7 @@ The editor follows the same public API as [provided by Monaco editor](https://mi
...
@@ -65,7 +65,7 @@ The editor follows the same public API as [provided by Monaco editor](https://mi
1. Editor's loading state.
1. Editor's loading state.
Editor Lite comes with the loading state built-in, making spinners and loaders rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` property on the HTML element that is supposed to contain the editor. Editor Lite will show the loader automatically while it's bootstrapping.
Editor Lite comes with the loading state built-in, making spinners and loaders rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` property on the HTML element that is supposed to contain the editor. Editor Lite shows the loader automatically while it's bootstrapping.
Even though Editor Lite itself is extremely slim, it still depends on Monaco editor. Monaco is not an easily tree-shakeable module. Hence, every time you add Editor Lite to a view, the JavaScript bundle's size significantly increases, affecting your view's loading performance. To avoid that, it is recommended to import the editor on demand on those views where it is not 100% certain that the editor will be used. Or if the editor is a secondary element of the view. Loading Editor Lite on demand is no different from loading any other module:
Even though Editor Lite itself is extremely slim, it still depends on Monaco editor. Monaco is not an easily tree-shakeable module. Hence, every time you add Editor Lite to a view, the JavaScript bundle's size significantly increases, affecting your view's loading performance. It is recommended to import the editor on demand on those views where it is not 100% certain that the editor is needed. Or if the editor is a secondary element of the view. Loading Editor Lite on demand is no different from loading any other module:
```javascript
```javascript
someActionFunction(){
someActionFunction(){
...
@@ -109,8 +109,8 @@ which would not depend on any particular group. Even though the Editor Lite's co
...
@@ -109,8 +109,8 @@ which would not depend on any particular group. Even though the Editor Lite's co
[Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/),
[Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/),
the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea
the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea
is that the core of the editor remains very slim and stable. At the same time, whatever new functionality
is that the core of the editor remains very slim and stable. At the same time, whatever new functionality
is needed can be added as an extension to this core, without touching the core itself. It allows any group
is needed can be added as an extension to this core, without touching the core itself. Any group is allowed
to build and own any new editing functionality without being afraid of it being broken or overridden with
to build and own new editing functionality without being afraid of it being broken or overridden with
the Editor Lite changes.
the Editor Lite changes.
Structurally, the complete implementation of Editor Lite could be presented as the following diagram:
Structurally, the complete implementation of Editor Lite could be presented as the following diagram:
...
@@ -145,7 +145,7 @@ Important things to note here:
...
@@ -145,7 +145,7 @@ Important things to note here:
### Using an existing extension
### Using an existing extension
Adding an extension to Editor Lite's instance is simple:
Adding an extension to Editor Lite's instance requires the following steps:
```javascript
```javascript
importEditorLitefrom'~/editor/editor_lite';
importEditorLitefrom'~/editor/editor_lite';
...
@@ -159,7 +159,7 @@ editor.use(MyExtension);
...
@@ -159,7 +159,7 @@ editor.use(MyExtension);
### Creating an extension
### Creating an extension
Let's create our first Editor Lite extension. As aforementioned, extensions are ES6 modules exporting the simple `Object` that is used to extend Editor Lite's functionality. As the most straightforward test, let's create an extension that extends Editor Lite with a new function that, when called, will output editor's content in `alert`.
Let's create our first Editor Lite extension. Extensions are ES6 modules exporting a basic `Object` that is used to extend Editor Lite's functionality. As a test, let's create an extension that extends Editor Lite with a new function that, when called, outputs editor's content in `alert`.
@@ -21,7 +21,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
...
@@ -21,7 +21,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
## FAQ
## FAQ
### 1. How do I find the Rails route for a page?
### 1. How does one find the Rails route for a page?
#### Check the 'page' data attribute
#### Check the 'page' data attribute
...
@@ -36,7 +36,7 @@ Find here the [source code setting the attribute](https://gitlab.com/gitlab-org/
...
@@ -36,7 +36,7 @@ Find here the [source code setting the attribute](https://gitlab.com/gitlab-org/
#### Rails routes
#### Rails routes
The `rake routes` command can be used to list all the routes available in the application, piping the output into `grep`, we can perform a search through the list of available routes.
The `rake routes` command can be used to list all the routes available in the application. Piping the output into `grep`, we can perform a search through the list of available routes.
The output includes the request types available, route parameters and the relevant controller.
The output includes the request types available, route parameters and the relevant controller.
The `clipboard_button` uses the `copy_to_clipboard.js` behavior, which is
The `clipboard_button` uses the `copy_to_clipboard.js` behavior, which is
initialized on page load, so if there are vue-based clipboard buttons that
initialized on page load. Vue clipboard buttons that
don't exist at page load (such as ones in a `GlModal`), they do not have the
don't exist at page load (such as ones in a `GlModal`) do not have
click handlers associated with the clipboard package.
click handlers associated with the clipboard package.
`modal_copy_button`was added that manages an instance of the
`modal_copy_button` manages an instance of the
[`clipboard` plugin](https://www.npmjs.com/package/clipboard) specific to
[`clipboard` plugin](https://www.npmjs.com/package/clipboard) specific to
the instance of that component, which means that clipboard events are
the instance of that component. This means that clipboard events are
bound on mounting and destroyed when the button is, mitigating the above
bound on mounting and destroyed when the button is, mitigating the above
issue. It also has bindings to a particular container or modal ID
issue. It also has bindings to a particular container or modal ID
available, to work with the focus trap created by our GlModal.
available, to work with the focus trap created by our GlModal.
...
@@ -60,7 +60,7 @@ available, to work with the focus trap created by our GlModal.
...
@@ -60,7 +60,7 @@ available, to work with the focus trap created by our GlModal.
### 3. A `gitlab-ui` component not conforming to [Pajamas Design System](https://design.gitlab.com/)
### 3. A `gitlab-ui` component not conforming to [Pajamas Design System](https://design.gitlab.com/)
Some [Pajamas Design System](https://design.gitlab.com/) components implemented in
Some [Pajamas Design System](https://design.gitlab.com/) components implemented in
`gitlab-ui` do not conform with the design system specs because they lack some
`gitlab-ui` do not conform with the design system specs. This is because they lack some
planned features or are not correctly styled yet. In the Pajamas website, a
planned features or are not correctly styled yet. In the Pajamas website, a
banner on top of the component examples indicates that:
banner on top of the component examples indicates that:
...
@@ -77,18 +77,17 @@ It makes codebase unified and more comfortable to maintain/refactor in the futur
...
@@ -77,18 +77,17 @@ It makes codebase unified and more comfortable to maintain/refactor in the futur
Ensure a [Product Designer](https://about.gitlab.com/company/team/?department=ux-department)
Ensure a [Product Designer](https://about.gitlab.com/company/team/?department=ux-department)
reviews the use of the non-conforming component as part of the MR review. Make a
reviews the use of the non-conforming component as part of the MR review. Make a
follow up issue and attach it to the component implementation epic found within the
follow up issue and attach it to the component implementation epic found in the
[Components of Pajamas Design System epic](https://gitlab.com/groups/gitlab-org/-/epics/973).
[Components of Pajamas Design System epic](https://gitlab.com/groups/gitlab-org/-/epics/973).
### 4. My submit form button becomes disabled after submitting
### 4. My submit form button becomes disabled after submitting
If you are using a submit button inside a form and you attach an `onSubmit` event listener on the form element, [this piece of code](https://gitlab.com/gitlab-org/gitlab/blob/794c247a910e2759ce9b401356432a38a4535d49/app/assets/javascripts/main.js#L225) adds a `disabled` class selector to the submit button when the form is submitted.
A Submit button inside of a form attaches an `onSubmit` event listener on the form element. [This code](https://gitlab.com/gitlab-org/gitlab/blob/794c247a910e2759ce9b401356432a38a4535d49/app/assets/javascripts/main.js#L225) adds a `disabled` class selector to the submit button when the form is submitted. To avoid this behavior, add the class `js-no-auto-disable` to the button.
To avoid this behavior, add the class `js-no-auto-disable` to the button.
### 5. Should I use a full URL (i.e. `gon.gitlab_url`) or a full path (i.e. `gon.relative_url_root`) when referencing backend endpoints?
### 5. Should one use a full URL (for example `gon.gitlab_url`) or a full path (for example `gon.relative_url_root`) when referencing backend endpoints?
It's preferred to use a **full path** over a **full URL** because the URL uses the hostname configured with
It's preferred to use a **full path** over a **full URL**. This is because the URL uses the hostname configured with
GitLab which may not match the request. This causes [CORS issues like this Web IDE one](https://gitlab.com/gitlab-org/gitlab/-/issues/36810).
GitLab which may not match the request. This causes [cross-origin resource sharing issues like this Web IDE example](https://gitlab.com/gitlab-org/gitlab/-/issues/36810).
Example:
Example:
...
@@ -117,7 +116,7 @@ Example:
...
@@ -117,7 +116,7 @@ Example:
### 6. How should the Frontend reference Backend paths?
### 6. How should the Frontend reference Backend paths?
We prefer not to add extra coupling by hardcoding paths. If possible,
We prefer not to add extra coupling by hard-coding paths. If possible,
add these paths as data attributes to the DOM element being referenced in the JavaScript.
add these paths as data attributes to the DOM element being referenced in the JavaScript.
@@ -25,7 +25,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
...
@@ -25,7 +25,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
- A real-life example of implementing a frontend feature in GitLab using GraphQL
- A real-life example of implementing a frontend feature in GitLab using GraphQL
-[🎬 History of client-side GraphQL at GitLab](https://www.youtube.com/watch?v=mCKRJxvMnf0)(video) Illya Klymov and Natalia Tepluhina
-[🎬 History of client-side GraphQL at GitLab](https://www.youtube.com/watch?v=mCKRJxvMnf0)(video) Illya Klymov and Natalia Tepluhina
-[🎬 From Vuex to Apollo](https://www.youtube.com/watch?v=9knwu87IfU8)(video) by Natalia Tepluhina
-[🎬 From Vuex to Apollo](https://www.youtube.com/watch?v=9knwu87IfU8)(video) by Natalia Tepluhina
- A useful overview of when Apollo might be a better choice than Vuex, and how one could go about the transition
- An overview of when Apollo might be a better choice than Vuex, and how one could go about the transition
-[🛠 Vuex -> Apollo Migration: a proof-of-concept project](https://gitlab.com/ntepluhina/vuex-to-apollo/blob/master/README.md)
-[🛠 Vuex -> Apollo Migration: a proof-of-concept project](https://gitlab.com/ntepluhina/vuex-to-apollo/blob/master/README.md)
- A collection of examples that show the possible approaches for state management with Vue+GraphQL+(Vuex or Apollo) apps
- A collection of examples that show the possible approaches for state management with Vue+GraphQL+(Vuex or Apollo) apps
...
@@ -34,7 +34,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
...
@@ -34,7 +34,7 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
We use [Apollo](https://www.apollographql.com/)(specifically[Apollo Client](https://www.apollographql.com/docs/react/)) and [Vue Apollo](https://github.com/vuejs/vue-apollo)
We use [Apollo](https://www.apollographql.com/)(specifically[Apollo Client](https://www.apollographql.com/docs/react/)) and [Vue Apollo](https://github.com/vuejs/vue-apollo)
when using GraphQL for frontend development.
when using GraphQL for frontend development.
If you are using GraphQL within a Vue application, the [Usage in Vue](#usage-in-vue) section
If you are using GraphQL in a Vue application, the [Usage in Vue](#usage-in-vue) section
can help you learn how to integrate Vue Apollo.
can help you learn how to integrate Vue Apollo.
For other use cases, check out the [Usage outside of Vue](#usage-outside-of-vue) section.
For other use cases, check out the [Usage outside of Vue](#usage-outside-of-vue) section.
...
@@ -76,7 +76,7 @@ Our GraphQL API can be explored via GraphiQL at your instance's
...
@@ -76,7 +76,7 @@ Our GraphQL API can be explored via GraphiQL at your instance's
where needed.
where needed.
You can check all existing queries and mutations on the right side
You can check all existing queries and mutations on the right side
of GraphiQL in its **Documentation explorer**. It's also possible to
of GraphiQL in its **Documentation explorer**. You can also
write queries and mutations directly on the left tab and check
write queries and mutations directly on the left tab and check
their execution by clicking **Execute query** button on the top left:
their execution by clicking **Execute query** button on the top left:
...
@@ -93,8 +93,8 @@ Default client accepts two parameters: `resolvers` and `config`.
...
@@ -93,8 +93,8 @@ 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
-`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:
-`config` parameter takes an object of configuration settings:
-`cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://www.apollographql.com/docs/react/caching/cache-configuration/#configuring-the-cache)
-`cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://www.apollographql.com/docs/react/caching/cache-configuration/#configuring-the-cache)
-`baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (i.e.`${gon.relative_url_root}/api/graphql`)
-`baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (for example, `${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`.
-`assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, assumes that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache throws a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`.
-`fetchPolicy` determines how you want your component to interact with the Apollo cache. Defaults to "cache-first".
-`fetchPolicy` determines how you want your component to interact with the Apollo cache. Defaults to "cache-first".
## GraphQL Queries
## GraphQL Queries
...
@@ -139,7 +139,7 @@ fragment DesignItem on Design {
...
@@ -139,7 +139,7 @@ fragment DesignItem on Design {
From Apollo version 3.0.0 all the cache updates need to be immutable; it needs to be replaced entirely
From Apollo version 3.0.0 all the cache updates need to be immutable. It needs to be replaced entirely
with a **new and updated** object.
with a **new and updated** object.
To facilitate the process of updating the cache and returning the new object we use the library [Immer](https://immerjs.github.io/immer/docs/introduction).
To facilitate the process of updating the cache and returning the new object we use the library [Immer](https://immerjs.github.io/immer/docs/introduction).
...
@@ -184,10 +184,10 @@ client.writeQuery({
...
@@ -184,10 +184,10 @@ client.writeQuery({
```
```
As shown in the code example by using `produce`, we can perform any kind of direct manipulation of the
As shown in the code example by using `produce`, we can perform any kind of direct manipulation of the
`draftState`. Besides, `immer` guarantees that a new state which includes the changes to `draftState`will be generated.
`draftState`. Besides, `immer` guarantees that a new state which includes the changes to `draftState`is generated.
Finally, to verify whether the immutable cache update is working properly, we need to change
Finally, to verify whether the immutable cache update is working properly, we need to change
`assumeImmutableResults` to `true` in the default client configuration (see [Apollo Client](#apollo-client) for more information).
`assumeImmutableResults` to `true` in the default client configuration. See [Apollo Client](#apollo-client) for more information.
If everything is working properly `assumeImmutableResults` should remain set to `true`.
If everything is working properly `assumeImmutableResults` should remain set to `true`.
...
@@ -259,11 +259,11 @@ query User {
...
@@ -259,11 +259,11 @@ query User {
}
}
```
```
Along with creating local data, we can also extend existing GraphQL types with `@client` fields. This is extremely useful when we need to mock an API responses for fields not yet added to our GraphQL API.
Along with creating local data, we can also extend existing GraphQL types with `@client` fields. This is extremely helpful when we need to mock an API response for fields not yet added to our GraphQL API.
#### Mocking API response with local Apollo cache
#### Mocking API response with local Apollo cache
Using local Apollo Cache is handy when we have a need to mock some GraphQL API responses, queries or mutations locally (e.g. when they're still not added to our actual API).
Using local Apollo Cache is helpful when we have a need to mock some GraphQL API responses, queries, or mutations locally (such as when they're still not added to our actual API).
For example, we have a [fragment](#fragments) on `DesignVersion` used in our queries:
For example, we have a [fragment](#fragments) on `DesignVersion` used in our queries:
...
@@ -274,7 +274,7 @@ fragment VersionListItem on DesignVersion {
...
@@ -274,7 +274,7 @@ fragment VersionListItem on DesignVersion {
}
}
```
```
We need to fetch also version author and the 'created at' property to display them in the versions dropdown but these changes are still not implemented in our API. We can change the existing fragment to get a mocked response for these new fields:
We also need to fetch the version author and the `created at` property to display in the versions dropdown. But, these changes are still not implemented in our API. We can change the existing fragment to get a mocked response for these new fields:
```javascript
```javascript
fragmentVersionListItemonDesignVersion{
fragmentVersionListItemonDesignVersion{
...
@@ -288,7 +288,7 @@ fragment VersionListItem on DesignVersion {
...
@@ -288,7 +288,7 @@ fragment VersionListItem on DesignVersion {
}
}
```
```
Now Apollo will try to find a _resolver_ for every field marked with `@client` directive. Let's create a resolver for `DesignVersion` type (why `DesignVersion`? because our fragment was created on this type).
Now Apollo tries to find a _resolver_ for every field marked with `@client` directive. Let's create a resolver for `DesignVersion` type (why `DesignVersion`? because our fragment was created on this type).
```javascript
```javascript
// resolvers.js
// resolvers.js
...
@@ -319,13 +319,13 @@ import resolvers from './graphql/resolvers';
...
@@ -319,13 +319,13 @@ import resolvers from './graphql/resolvers';
For each attempt to fetch a version, our client will fetch `id` and `sha` from the remote API endpoint and will assign our hardcoded values to the `author` and `createdAt` version properties. With this data, frontend developers are able to work on their UI without being blocked by backend. When the actual response is added to the API, our custom local resolver can be removed and the only change to the query/fragment is to remove the `@client` directive.
For each attempt to fetch a version, our client fetches `id` and `sha` from the remote API endpoint. It then assigns our hardcoded values to the `author` and `createdAt` version properties. With this data, frontend developers are able to work on their UI without being blocked by backend. When the response is added to the API, our custom local resolver can be removed. The only change to the query/fragment is to remove the `@client` directive.
Read more about local state management with Apollo in the [Vue Apollo documentation](https://vue-apollo.netlify.app/guide/local-state.html#local-state).
Read more about local state management with Apollo in the [Vue Apollo documentation](https://vue-apollo.netlify.app/guide/local-state.html#local-state).
### Using with Vuex
### Using with Vuex
When Apollo Client is used within Vuex and fetched data is stored in the Vuex store, there is no need to keep Apollo Client cache enabled. Otherwise we would have data from the API stored in two places - Vuex store and Apollo Client cache. With Apollo's default settings, a subsequent fetch from the GraphQL API could result in fetching data from Apollo cache (in the case where we have the same query and variables). To prevent this behavior, we need to disable Apollo Client cache by passing a valid `fetchPolicy` option to its constructor:
When the Apollo Client is used in Vuex and fetched data is stored in the Vuex store, the Apollo Client cache does not need to be enabled. Otherwise we would have data from the API stored in two places - Vuex store and Apollo Client cache. With Apollo's default settings, a subsequent fetch from the GraphQL API could result in fetching data from Apollo cache (in the case where we have the same query and variables). To prevent this behavior, we need to disable Apollo Client cache by passing a valid `fetchPolicy` option to its constructor:
Then in the Vue (or JavaScript) call to the query we can pass in our feature flag. This feature
Then in the Vue (or JavaScript) call to the query we can pass in our feature flag. This feature
flag will need to be already setup correctly. See the [feature flag documentation](../feature_flags/development.md)
flag needs to be already set up correctly. See the [feature flag documentation](../feature_flags/development.md)
for the correct way to do this.
for the correct way to do this.
```javascript
```javascript
...
@@ -519,7 +519,7 @@ Note that we are using the [`pageInfo.fragment.graphql`](https://gitlab.com/gitl
...
@@ -519,7 +519,7 @@ Note that we are using the [`pageInfo.fragment.graphql`](https://gitlab.com/gitl
#### Using `fetchMore` method in components
#### Using `fetchMore` method in components
This approach makes sense to use with user-handled pagination (e.g. when the scrolls to fetch more data or explicitly clicks a "Next Page"-button).
This approach makes sense to use with user-handled pagination. For example, when the scrolling to fetch more data or explicitly clicking a **Next Page** button.
When we need to fetch all the data initially, it is recommended to use [a (non-smart) query, instead](#using-a-recursive-query-in-components).
When we need to fetch all the data initially, it is recommended to use [a (non-smart) query, instead](#using-a-recursive-query-in-components).
When making an initial fetch, we usually want to start a pagination from the beginning.
When making an initial fetch, we usually want to start a pagination from the beginning.
...
@@ -529,7 +529,7 @@ In this case, we can either:
...
@@ -529,7 +529,7 @@ In this case, we can either:
- Pass `null` explicitly to `after`.
- Pass `null` explicitly to `after`.
After data is fetched, we can use the `update`-hook as an opportunity [to customize
After data is fetched, we can use the `update`-hook as an opportunity [to customize
the data that is set in the Vue component property](https://apollo.vuejs.org/api/smart-query.html#options), getting a hold of the `pageInfo` object among other data.
the data that is set in the Vue component property](https://apollo.vuejs.org/api/smart-query.html#options). This allows us to get a hold of the `pageInfo` object among other data.
In the `result`-hook, we can inspect the `pageInfo` object to see if we need to fetch
In the `result`-hook, we can inspect the `pageInfo` object to see if we need to fetch
the next page. Note that we also keep a `requestCount` to ensure that the application
the next page. Note that we also keep a `requestCount` to ensure that the application
...
@@ -611,8 +611,8 @@ fetchNextPage(endCursor) {
...
@@ -611,8 +611,8 @@ fetchNextPage(endCursor) {
When it is necessary to fetch all paginated data initially an Apollo query can do the trick for us.
When it is necessary to fetch all paginated data initially an Apollo query can do the trick for us.
If we need to fetch the next page based on user interactions, it is recommend to use a [`smartQuery`](https://apollo.vuejs.org/api/smart-query.html) along with the [`fetchMore`-hook](#using-fetchmore-method-in-components).
If we need to fetch the next page based on user interactions, it is recommend to use a [`smartQuery`](https://apollo.vuejs.org/api/smart-query.html) along with the [`fetchMore`-hook](#using-fetchmore-method-in-components).
When the query resolves we can update the component data and inspect the `pageInfo` object
When the query resolves we can update the component data and inspect the `pageInfo` object. This allows us
to see if we need to fetch the next page, i.e. call the method recursively.
to see if we need to fetch the next page, calling the method recursively.
Note that we also keep a `requestCount` to ensure that the application does not keep
Note that we also keep a `requestCount` to ensure that the application does not keep
requesting the next page, indefinitely.
requesting the next page, indefinitely.
...
@@ -684,7 +684,7 @@ or [`.writeQuery()`](https://www.apollographql.com/docs/react/v2/api/apollo-clie
...
@@ -684,7 +684,7 @@ or [`.writeQuery()`](https://www.apollographql.com/docs/react/v2/api/apollo-clie
This can be tedious and counter-intuitive.
This can be tedious and counter-intuitive.
To make it easier to deal with cached paginated queries, Apollo provides the `@connection` directive.
To make it easier to deal with cached paginated queries, Apollo provides the `@connection` directive.
The directive accepts a `key` parameter that will be used as a static key when caching the data.
The directive accepts a `key` parameter that is used as a static key when caching the data.
You'd then be able to retrieve the data without providing any pagination-specific variables.
You'd then be able to retrieve the data without providing any pagination-specific variables.
Here's an example of a query using the `@connection` directive:
Here's an example of a query using the `@connection` directive:
In this example, Apollo will store the data with the stable `dastSiteProfiles` cache key.
In this example, Apollo stores the data with the stable `dastSiteProfiles` cache key.
To retrieve that data from the cache, you'd then only need to provide the `$fullPath` variable,
To retrieve that data from the cache, you'd then only need to provide the `$fullPath` variable,
omitting pagination-specific variables like `after` or `before`:
omitting pagination-specific variables like `after` or `before`:
...
@@ -729,9 +729,9 @@ Read more about the `@connection` directive in [Apollo's documentation](https://
...
@@ -729,9 +729,9 @@ Read more about the `@connection` directive in [Apollo's documentation](https://
### Managing performance
### Managing performance
The Apollo client will batch queries by default. This means that if you have 3 queries defined,
The Apollo client batches queries by default. Given 3 deferred queries,
Apollo will group them into one request, send the single request off to the server and only
Apollo groups them into one request, sends the single request to the server, and
respond once all 3 queries have completed.
responds after all 3 queries have completed.
If you need to have queries sent as individual requests, additional context can be provided
If you need to have queries sent as individual requests, additional context can be provided
to tell Apollo to do this.
to tell Apollo to do this.
...
@@ -753,7 +753,7 @@ export default {
...
@@ -753,7 +753,7 @@ export default {
#### Mocking response as component data
#### Mocking response as component data
With [Vue test utils](https://vue-test-utils.vuejs.org/)it is easy to quickly test components that
With [Vue test utils](https://vue-test-utils.vuejs.org/)one can quickly test components that
fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
the data on the component
the data on the component
...
@@ -769,7 +769,7 @@ it('tests apollo component', () => {
...
@@ -769,7 +769,7 @@ it('tests apollo component', () => {
#### Testing loading state
#### 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:
To test how a component renders when results from the GraphQL API are still loading, mock a loading state into respective Apollo queries/mutations:
```javascript
```javascript
functioncreateComponent({
functioncreateComponent({
...
@@ -867,9 +867,9 @@ it('calls mutation on submitting form ', () => {
...
@@ -867,9 +867,9 @@ it('calls mutation on submitting form ', () => {
To test the logic of Apollo cache updates, we might want to mock an Apollo Client in our unit tests. We use [`mock-apollo-client`](https://www.npmjs.com/package/mock-apollo-client) library to mock Apollo client and [`createMockApollo` helper](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/frontend/__helpers__/mock_apollo_helper.js) we created on top of it.
To test the logic of Apollo cache updates, we might want to mock an Apollo Client in our unit tests. We use [`mock-apollo-client`](https://www.npmjs.com/package/mock-apollo-client) library to mock Apollo client and [`createMockApollo` helper](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/frontend/__helpers__/mock_apollo_helper.js) we created on top of it.
To separate tests with mocked client from 'usual' unit tests, it's recommended to create an additional factory and pass the created `mockApollo` as an option to the `createComponent`-factory. This way we only create Apollo Client instance when it's necessary.
To separate tests with mocked client from 'usual' unit tests, create an additional factory and pass the created `mockApollo` as an option to the `createComponent`-factory. This way we only create Apollo Client instance when it's necessary.
We need to inject `VueApollo` to the Vue local instance and, likewise, it is recommended to call `localVue.use()`within `createMockApolloProvider()` to only load it when it is necessary.
We need to inject `VueApollo` to the Vue local instance and, likewise, it is recommended to call `localVue.use()` in `createMockApolloProvider()` to only load it when it is necessary.
@@ -1301,9 +1301,9 @@ describe('My Index test with `createMockApollo`', () => {
...
@@ -1301,9 +1301,9 @@ describe('My Index test with `createMockApollo`', () => {
## Handling errors
## Handling errors
The GitLab GraphQL mutations currently have two distinct error modes: [Top-level](#top-level-errors) and [errors-as-data](#errors-as-data).
The GitLab GraphQL mutations have two distinct error modes: [Top-level](#top-level-errors) and [errors-as-data](#errors-as-data).
When utilising a GraphQL mutation, we must consider handling **both of these error modes** to ensure that the user receives the appropriate feedback when an error occurs.
When utilising a GraphQL mutation, consider handling **both of these error modes** to ensure that the user receives the appropriate feedback when an error occurs.
### Top-level errors
### Top-level errors
...
@@ -1311,13 +1311,13 @@ These errors are located at the "top level" of a GraphQL response. These are non
...
@@ -1311,13 +1311,13 @@ These errors are located at the "top level" of a GraphQL response. These are non
#### Handling top-level errors
#### Handling top-level errors
Apollo is aware of top-level errors, so we are able to leverage Apollo's various error-handling mechanisms to handle these errors (e.g. handling Promise rejections after invoking the [`mutate`](https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.mutate) method, or handling the `error` event emitted from the [`ApolloMutation`](https://apollo.vuejs.org/api/apollo-mutation.html#events) component).
Apollo is aware of top-level errors, so we are able to leverage Apollo's various error-handling mechanisms to handle these errors. For example, handling Promise rejections after invoking the [`mutate`](https://www.apollographql.com/docs/react/api/core/ApolloClient/#ApolloClient.mutate) method, or handling the `error` event emitted from the [`ApolloMutation`](https://apollo.vuejs.org/api/apollo-mutation.html#events) component.
Because these errors are not intended for users, error messages for top-level errors should be defined client-side.
Because these errors are not intended for users, error messages for top-level errors should be defined client-side.
### Errors-as-data
### Errors-as-data
These errors are nested within the `data` object of a GraphQL response. These are recoverable errors that, ideally, can be presented directly to the user.
These errors are nested in the `data` object of a GraphQL response. These are recoverable errors that, ideally, can be presented directly to the user.
-`container` (optional): wraps the loading icon in a container, which centers the loading icon using the `text-center` CSS property.
-`container` (optional): wraps the loading icon in a container, which centers the loading icon using the `text-center` CSS property.
-`color` (optional): either `orange` (default), `light`, or `dark`.
-`color` (optional): either `orange` (default), `light`, or `dark`.
-`size` (optional): either `sm` (default), `md`, `lg`, or `xl`.
-`size` (optional): either `sm` (default), `md`, `lg`, or `xl`.
-`css_class` (optional): defaults to an empty string, but can be useful for utility classes to fine-tune alignment or spacing.
-`css_class` (optional): defaults to an empty string, but can be used for utility classes to fine-tune alignment or spacing.
**Example 1:**
**Example 1:**
...
@@ -164,8 +164,8 @@ export default {
...
@@ -164,8 +164,8 @@ export default {
## SVG Illustrations
## SVG Illustrations
Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers.
From now on, use `img` tags to display any SVG based illustrations with either `image_tag` or `image_path` helpers.
Please use the class `svg-content` around it to ensure nice rendering.
Using the class `svg-content` around it ensures nice rendering.
@@ -11,7 +11,7 @@ across the GitLab frontend team.
...
@@ -11,7 +11,7 @@ across the GitLab frontend team.
## Overview
## Overview
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org) using [Haml](https://haml.info/) and also a JavaScript based Frontend with [Vue.js](https://vuejs.org).
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org). It uses [Haml](https://haml.info/) and a JavaScript0based frontend with [Vue.js](https://vuejs.org).
Be wary of [the limitations that come with using Hamlit](https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations). We also use [SCSS](https://sass-lang.com) and plain JavaScript with
Be wary of [the limitations that come with using Hamlit](https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations). We also use [SCSS](https://sass-lang.com) and plain JavaScript with
modern ECMAScript standards supported through [Babel](https://babeljs.io/) and ES module support through [webpack](https://webpack.js.org/).
modern ECMAScript standards supported through [Babel](https://babeljs.io/) and ES module support through [webpack](https://webpack.js.org/).
...
@@ -21,7 +21,7 @@ Working with our frontend assets requires Node (v10.13.0 or greater) and Yarn
...
@@ -21,7 +21,7 @@ Working with our frontend assets requires Node (v10.13.0 or greater) and Yarn
### Browser Support
### Browser Support
For our currently-supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
For supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers.
Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers.
Sign in to BrowserStack with the credentials saved in the **Engineering** vault of the GitLab
Sign in to BrowserStack with the credentials saved in the **Engineering** vault of the GitLab
If you _do_ need to change layout (for example, a sidebar that pushes main content over), prefer [FLIP](https://aerotwist.com/blog/flip-your-animations/) to change expensive
If you _do_ need to change layout (for example, a sidebar that pushes main content over), prefer [FLIP](https://aerotwist.com/blog/flip-your-animations/). FLIP allows you to change expensive
properties once, and handle the actual animation with transforms.
properties once, and handle the actual animation with transforms.
## Reducing Asset Footprint
## Reducing Asset Footprint
...
@@ -251,7 +251,7 @@ properties once, and handle the actual animation with transforms.
...
@@ -251,7 +251,7 @@ properties once, and handle the actual animation with transforms.
### Universal code
### Universal code
Code that is contained in `main.js` and `commons/index.js` is loaded and
Code that is contained in `main.js` and `commons/index.js` is loaded and
run on _all_ pages. **DO NOT ADD** anything to these files unless it is truly
run on _all_ pages. **Do not add** anything to these files unless it is truly
needed _everywhere_. These bundles include ubiquitous libraries like `vue`,
needed _everywhere_. These bundles include ubiquitous libraries like `vue`,
`axios`, and `jQuery`, as well as code for the main navigation and sidebar.
`axios`, and `jQuery`, as well as code for the main navigation and sidebar.
Where possible we should aim to remove modules from these bundles to reduce our
Where possible we should aim to remove modules from these bundles to reduce our
...
@@ -277,9 +277,9 @@ manually generated webpack bundles. However under this new system you should
...
@@ -277,9 +277,9 @@ manually generated webpack bundles. However under this new system you should
not ever need to manually add an entry point to the `webpack.config.js` file.
not ever need to manually add an entry point to the `webpack.config.js` file.
NOTE:
NOTE:
If you are unsure what controller and action corresponds to a given page, you
When unsure what controller and action corresponds to a page,
can find this out by inspecting`document.body.dataset.page` in your
inspect`document.body.dataset.page` in your
browser's developer console while on any page in GitLab.
browser's developer console from any page in GitLab.
#### Important Considerations
#### Important Considerations
...
@@ -294,7 +294,7 @@ browser's developer console while on any page in GitLab.
...
@@ -294,7 +294,7 @@ browser's developer console while on any page in GitLab.
All GitLab JavaScript files are added with the `defer` attribute.
All GitLab JavaScript files are added with the `defer` attribute.
According to the [Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer),
According to the [Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer),
this implies that "the script is meant to be executed after the document has
this implies that "the script is meant to be executed after the document has
been parsed, but before firing `DOMContentLoaded`". Since the document is already
been parsed, but before firing `DOMContentLoaded`". Because the document is already
parsed, `DOMContentLoaded` is not needed to bootstrap applications because all
parsed, `DOMContentLoaded` is not needed to bootstrap applications because all
the DOM nodes are already at our disposal.
the DOM nodes are already at our disposal.
...
@@ -366,9 +366,9 @@ browser's developer console while on any page in GitLab.
...
@@ -366,9 +366,9 @@ browser's developer console while on any page in GitLab.
### Code Splitting
### Code Splitting
For any code that does not need to be run immediately upon page load, (for example,
Code that does not need to be run immediately upon page load (for example,
modals, dropdowns, and other behaviors that can be lazy-loaded), you can split
modals, dropdowns, and other behaviors that can be lazy-loaded) should be split
your module into asynchronous chunks with dynamic import statements. These
into asynchronous chunks with dynamic import statements. These
imports return a Promise which is resolved after the script has loaded:
imports return a Promise which is resolved after the script has loaded:
@@ -111,7 +111,7 @@ preferred editor (all major editors are supported) accordingly. We suggest
...
@@ -111,7 +111,7 @@ preferred editor (all major editors are supported) accordingly. We suggest
setting up Prettier to run when each file is saved. For instructions about using
setting up Prettier to run when each file is saved. For instructions about using
Prettier in your preferred editor, see the [Prettier documentation](https://prettier.io/docs/en/editors.html).
Prettier in your preferred editor, see the [Prettier documentation](https://prettier.io/docs/en/editors.html).
Please take care that you only let Prettier format the same file types as the global Yarn script does (`.js`, `.vue`, `.graphql`, and `.scss`). In VSCode by example you can easily exclude file formats in your settings file:
Please take care that you only let Prettier format the same file types as the global Yarn script does (`.js`, `.vue`, `.graphql`, and `.scss`). For example, you can exclude file formats in your Visual Studio Code settings file:
```json
```json
"prettier.disableLanguages":[
"prettier.disableLanguages":[
...
@@ -128,13 +128,13 @@ The following yarn scripts are available to do global formatting:
...
@@ -128,13 +128,13 @@ The following yarn scripts are available to do global formatting:
yarn prettier-staged-save
yarn prettier-staged-save
```
```
Updates all currently staged files (based on `git diff`) with Prettier and saves the needed changes.
Updates all staged files (based on `git diff`) with Prettier and saves the needed changes.
```shell
```shell
yarn prettier-staged
yarn prettier-staged
```
```
Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
Checks all staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
Vue docs recommend using [mitt](https://github.com/developit/mitt) library. It's relatively small (200 bytes gzipped) and has a simple API:
Vue documentation recommends using the [mitt](https://github.com/developit/mitt) library. It's relatively small (200 bytes gzipped) and has a clear API:
@@ -88,7 +88,7 @@ It is not recommended to replace stateful components with functional components
...
@@ -88,7 +88,7 @@ It is not recommended to replace stateful components with functional components
**Why?**
**Why?**
In Vue 2.6 `slot` attribute was already deprecated in favor of `v-slot` directive but its usage is still allowed and sometimes we prefer using them because it simplifies unit tests (with old syntax, slots are rendered on `shallowMount`). However, in Vue 3 we can't use old syntax anymore.
In Vue 2.6 `slot` attribute was already deprecated in favor of `v-slot` directive. The `slot` attribute usage is still allowed and sometimes we prefer using it because it simplifies unit tests (with old syntax, slots are rendered on `shallowMount`). However, in Vue 3 we can't use old syntax anymore.
@@ -146,7 +146,7 @@ The only way to change state in a Vuex store is by committing a mutation.
...
@@ -146,7 +146,7 @@ The only way to change state in a Vuex store is by committing a mutation.
Most mutations are committed from an action using `commit`. If you don't have any
Most mutations are committed from an action using `commit`. If you don't have any
asynchronous operations, you can call mutations from a component using the `mapMutations` helper.
asynchronous operations, you can call mutations from a component using the `mapMutations` helper.
See the Vuex docs for examples of [committing mutations from components](https://vuex.vuejs.org/guide/mutations.html#committing-mutations-in-components).
See the Vuex documentation for examples of [committing mutations from components](https://vuex.vuejs.org/guide/mutations.html#committing-mutations-in-components).
#### Naming Pattern: `REQUEST` and `RECEIVE` namespaces
#### Naming Pattern: `REQUEST` and `RECEIVE` namespaces
...
@@ -271,7 +271,7 @@ import { mapGetters } from 'vuex';
...
@@ -271,7 +271,7 @@ import { mapGetters } from 'vuex';
### `mutation_types.js`
### `mutation_types.js`
From [vuex mutations docs](https://vuex.vuejs.org/guide/mutations.html):
From [Vuex mutations documentation](https://vuex.vuejs.org/guide/mutations.html):
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations.
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations.
> This allows the code to take advantage of tooling like linters, and putting all constants in a
> This allows the code to take advantage of tooling like linters, and putting all constants in a
> single file allows your collaborators to get an at-a-glance view of what mutations are possible
> single file allows your collaborators to get an at-a-glance view of what mutations are possible
...
@@ -429,7 +429,7 @@ export default {
...
@@ -429,7 +429,7 @@ export default {
#### Testing Vuex concerns
#### Testing Vuex concerns
Refer to [Vuex docs](https://vuex.vuejs.org/guide/testing.html) regarding testing Actions, Getters and Mutations.
Refer to [Vuex documentation](https://vuex.vuejs.org/guide/testing.html) regarding testing Actions, Getters and Mutations.