Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/fe_guide')
-rw-r--r--doc/development/fe_guide/accessibility.md29
-rw-r--r--doc/development/fe_guide/dark_mode.md3
-rw-r--r--doc/development/fe_guide/frontend_goals.md31
-rw-r--r--doc/development/fe_guide/graphql.md6
-rw-r--r--doc/development/fe_guide/index.md19
-rw-r--r--doc/development/fe_guide/merge_request_widget_extensions.md5
-rw-r--r--doc/development/fe_guide/migrating_from_vuex.md318
-rw-r--r--doc/development/fe_guide/tooling.md13
-rw-r--r--doc/development/fe_guide/vuex.md6
9 files changed, 413 insertions, 17 deletions
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index b00131b12f3..65b50bedb0c 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -518,9 +518,26 @@ Proper research and testing should be done to ensure compliance with [WCAG](http
## Automated accessibility testing with axe
-You can use [axe-core](https://github.com/dequelabs/axe-core) [gems](https://github.com/dequelabs/axe-core-gems)
+We use [axe-core](https://github.com/dequelabs/axe-core) [gems](https://github.com/dequelabs/axe-core-gems)
to run automated accessibility tests in feature tests.
+[We aim to conform to level AA of the World Wide Web Consortium (W3C) Web Content Accessibility Guidelines 2.1](https://design.gitlab.com/accessibility/a11y).
+
+### When to add accessibility tests
+
+When adding a new view to the application, make sure to include the accessibility check in your feature test.
+We aim to have full coverage for all the views.
+
+One of the advantages of testing in feature tests is that we can check different states, not only
+single components in isolation.
+
+Make sure to add assertions, when the view you are working on:
+
+- Has an empty state,
+- Has significant changes in page structure, for example an alert is shown, or a new section is rendered.
+
+### How to add accessibility tests
+
Axe provides the custom matcher `be_axe_clean`, which can be used like the following:
```ruby
@@ -530,10 +547,12 @@ it 'passes axe automated accessibility testing', :js do
wait_for_requests # ensures page is fully loaded
- expect(page).to be_axe_clean
+ expect(page).to be_axe_clean.according_to :wcag21aa
end
```
+Make sure to specify the accessibility standard as `:wcag21aa`.
+
If needed, you can scope testing to a specific area of the page by using `within`.
Axe also provides specific [clauses](https://github.com/dequelabs/axe-core-gems/blob/develop/packages/axe-core-rspec/README.md#clauses),
@@ -543,20 +562,22 @@ for example:
expect(page).to be_axe_clean.within '[data-testid="element"]'
# run only WCAG 2.0 Level AA rules
-expect(page).to be_axe_clean.according_to :wcag2aa
+expect(page).to be_axe_clean.according_to :wcag21aa
# specifies which rule to skip
expect(page).to be_axe_clean.skipping :'link-in-text-block'
# clauses can be chained
expect(page).to be_axe_clean.within('[data-testid="element"]')
- .according_to(:wcag2aa)
+ .according_to(:wcag21aa)
```
Axe does not test hidden regions, such as inactive menus or modal windows. To test
hidden regions for accessibility, write tests that activate or render the regions visible
and run the matcher again.
+You can run accessibility tests locally in the same way as you [run any feature tests](../testing_guide/frontend_testing.md#how-to-run-a-feature-test).
+
### Known accessibility violations
This section documents violations where a recommendation differs with the [design system](https://design.gitlab.com/):
diff --git a/doc/development/fe_guide/dark_mode.md b/doc/development/fe_guide/dark_mode.md
index 55181edd64c..5e8f844172a 100644
--- a/doc/development/fe_guide/dark_mode.md
+++ b/doc/development/fe_guide/dark_mode.md
@@ -5,8 +5,7 @@ group: Development
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-This page is about developing dark mode for GitLab. We also have documentation on how
-[to enable dark mode](../../user/profile/preferences.md#dark-mode).
+This page is about developing dark mode for GitLab. For more information on how to enable dark mode, see [Change the syntax highlighting theme]](../../user/profile/preferences.md#change-the-syntax-highlighting-theme).
# How dark mode works
diff --git a/doc/development/fe_guide/frontend_goals.md b/doc/development/fe_guide/frontend_goals.md
new file mode 100644
index 00000000000..05e9b0df389
--- /dev/null
+++ b/doc/development/fe_guide/frontend_goals.md
@@ -0,0 +1,31 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Frontend Goals
+
+This section defined the _desired state_ of the GitLab frontend how we see it over the next few years.
+
+## Cluster SPAs
+
+Currently, GitLab mostly follows Rails architecture and Rails routing which means every single time we're changing route, we have page reload. This results in long loading times because we are:
+
+- rendering HAML page;
+- mounting Vue applications if we have any;
+- fetching data for these applications
+
+Ideally, we should reduce the number of times user needs to go through this long process. This would be possible with converting GitLab into a single-page application but this would require significant refactoring and is not an achieavable short/mid-term goal.
+
+The realistic goal is to move to _multiple SPAs_ experience where we define the _clusters_ of pages that form the user flow, and move this cluster from Rails routing to a single-page application with client-side routing. This way, we can load all the relevant context from HAML only once, and fetch all the additional data from the API depending on the route. An example of a cluster could be the following pages:
+
+- issues list
+- issue boards
+- issue details page
+- new issue
+- editing an issue
+
+All of them have the same context (project path, current user etc.), we could easily fetch more data with issue-specific parameter (issue `iid`) and store the results on the client (so that opening the same issue won't require more API calls). This leads to a smooth user experience for navigating through issues.
+
+For navigation between clusters, we can still rely on Rails routing. These cases should be relatively more scarce than navigation within clusters.
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 00c7bb5d6c8..62183c7b349 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -416,7 +416,11 @@ Read more about local state management with Apollo in the [Vue Apollo documentat
### Using with Vuex
-We do not recommend creating new applications with Vuex and Apollo Client combined
+We do not recommend creating new applications with Vuex and Apollo Client combined. There are a few reasons:
+
+- VueX and Apollo are both **global stores**, which means sharing responsibilities and having two sources of truth.
+- Keeping VueX and Apollo in sync can be high maintenance.
+- Bugs that would come from the communication between Apollo and VueX would be subtle and hard to debug.
### Working on GraphQL-based features when frontend and backend are not in sync
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 8675866fce6..405e830406e 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -26,6 +26,13 @@ modern ECMAScript standards supported through [Babel](https://babeljs.io/) and E
When making API calls, we use [GraphQL](graphql.md) as [the first choice](../../api/graphql/index.md#vision). There are still instances where GitLab REST API is used such as when creating new simple HAML pages or in legacy part of the codebase, but we should always default to GraphQL when possible.
+We use [Apollo](https://www.apollographql.com/) as our global state manager and [GraphQL client](graphql.md).
+[VueX](vuex.md) is still in use across the codebase, but it is no longer the recommended global state manager.
+You should **not** [use VueX and Apollo together](graphql.md#using-with-vuex),
+and should [avoid adding new VueX stores](migrating_from_vuex.md) whenever possible.
+
+For copy strings and translations, we have frontend utilities available. Please see the JavaScript section of [Preparing a page for translation](../i18n/externalization.md#javascript-files) for more information.
+
Working with our frontend assets requires Node (v12.22.1 or greater) and Yarn
(v1.10.0 or greater). You can find information on how to install these on our
[installation guide](../../install/installation.md#5-node).
@@ -56,7 +63,7 @@ GitLab is now a large, enterprise-grade software and it often requires complex c
- Building tools that solve commonly-faced problems and making them easily discoverable.
- Writing better documentation on how we solve our problems.
-- Writing loosly coupled components that can be easily added or removed from our codebase.
+- Writing loosely coupled components that can be easily added or removed from our codebase.
- Remove older technologies or pattern that we deem are no longer acceptable.
By focusing on these aspects, we aim to allow engineers to contain complexity in well defined boundaries and quickly share them with their peers.
@@ -67,9 +74,9 @@ Now that our values have been defined, we can base our goals on these values and
- Lowest possible FID, LCP and cross-page navigation times
- Minimal page reloads when interacting with the UI
-- Have as little Vue applications per page as possible
-- Leverage Ruby ViewComponents for simple pages and avoid Vue overhead when possible
-- Migrate existing VueX stores to Apollo, but more urgently **stop using both together**
+- [Have as little Vue applications per page as possible](vue.md#avoid-multiple-vue-applications-on-the-page)
+- Leverage [Ruby ViewComponents](view_component.md) for simple pages and avoid Vue overhead when possible
+- [Migrate away from VueX](migrating_from_vuex.md), but more urgently **stop using Apollo and VueX together**
- Remove jQuery from our codebase
- Add a visual testing framework
- Reduce CSS bundle size to a minimum
@@ -77,9 +84,11 @@ Now that our values have been defined, we can base our goals on these values and
- Improve our pipelines speed
- Build a better set of shared components with documentation
+We have detailed description on how we see GitLab frontend in the future in [Frontend Goals](frontend_goals.md) section
+
### Frontend onboarding course
-The [Frontend onboarding course](onboarding_course/index.md) provides a 6-week structured curriculum to learn how to contribute to the GitLab frontend.
+The [Frontend onboarding course](onboarding_course/index.md) provides a 6-week structured curriculum to learn how to contribute to the GitLab frontend.
### Browser Support
diff --git a/doc/development/fe_guide/merge_request_widget_extensions.md b/doc/development/fe_guide/merge_request_widget_extensions.md
index f5bf9049db1..99206d99590 100644
--- a/doc/development/fe_guide/merge_request_widget_extensions.md
+++ b/doc/development/fe_guide/merge_request_widget_extensions.md
@@ -351,10 +351,7 @@ To generate these known events for a single widget:
```
1. Repeat step 6, but change the `data_source` to `redis_hll`.
-1. Add each of the HLL metrics to `lib/gitlab/usage_data_counters/known_events/code_review_events.yml`:
- 1. `name` = (the event)
- 1. `category` = `code_review`
- 1. `aggregation` = `weekly`
+
1. Add each event (those listed in the command in step 7, replacing `test_reports`
with the appropriate name slug) to the aggregate files:
1. `config/metrics/counts_7d/{timestamp}_code_review_category_monthly_active_users.yml`
diff --git a/doc/development/fe_guide/migrating_from_vuex.md b/doc/development/fe_guide/migrating_from_vuex.md
new file mode 100644
index 00000000000..8dca744e192
--- /dev/null
+++ b/doc/development/fe_guide/migrating_from_vuex.md
@@ -0,0 +1,318 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Migrating from Vuex
+
+## Why?
+
+We have defined [GraphQL as our primary API](../../api/graphql/index.md#vision) for all user-facing features,
+so we can safely assume that whenever GraphQL is present, so will the Apollo Client.
+We [do not want to use Vuex with Apollo](graphql.md#using-with-vuex), so the VueX stores count
+will naturally decline over time as we move from the REST API to GraphQL.
+
+This section gives guidelines and methods to translate an existing VueX store to
+pure Vue and Apollo, or how to rely less on VueX.
+
+## How?
+
+### Overview
+
+As a whole, we want to understand how complex our change will be. Sometimes, we only have a few properties that are truly worth being stored in a global state and sometimes they can safely all be extracted to pure `Vue`. `VueX` properties generally fall into one of these categories:
+
+- Static properties
+- Reactive mutable properties
+- Getters
+- API data
+
+Therefore, the first step is to read the current VueX state and determine the category of each property.
+
+At a high level, we could map each category with an equivalent non-VueX code pattern:
+
+- Static properties: Provide/Inject from Vue API.
+- Reactive mutable properties: Vue events and props, Apollo Client.
+- Getters: Utils functions, Apollo `update` hook, computed properties.
+- API data: Apollo Client.
+
+Let's go through an example. In each section we refer to this state and slowly go through migrating it fully:
+
+```javascript
+// state.js AKA our store
+export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) => ({
+ blobPath,
+ summaryEndpoint,
+ suiteEndpoint,
+ testReports: {},
+ selectedSuiteIndex: null,
+ isLoading: false,
+ errorMessage: null,
+ limit : 10,
+ pageInfo: {
+ page: 1,
+ perPage: 20,
+ },
+});
+```
+
+### How to migrate static values
+
+The easiest type of values to migrate are static values, either:
+
+- Client-side constants: If the static value is a client-side constant, it may have been implemented
+ in the store for easy access by other state properties or methods. However, it is generally
+ a better practice to add such values to a `constants.js` file and import it when needed.
+- Rails-injected dataset: These are values that we may need to provide to our Vue apps.
+ They are static, so adding them to the VueX store is not necessary and it could instead
+ be done easily through the `provide/inject` Vue API, which would be equivalent but without the VueX overhead. This should **only** be injected inside the top-most JS file that mounts our component.
+
+If we take a look at our example above, we can already see that two properties contain `Endpoint` in their name, which probably means that these come from our Rails dataset. To confirm this, we would search the codebase for these properties and see where they are defined, which is the case in our example. Additionally, `blobPath` is also a static property, and a little less obvious here is that `pageInfo` is actually a constant! It is never modified and is only used as a default value that we use inside our getter:
+
+```javascript
+// state.js AKA our store
+export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) => ({
+ limit
+ blobPath, // Static - Dataset
+ summaryEndpoint, // Static - Dataset
+ suiteEndpoint, // Static - Dataset
+ testReports: {},
+ selectedSuiteIndex: null,
+ isLoading: false,
+ errorMessage: null,
+ pageInfo: { // Static - Constant
+ page: 1, // Static - Constant
+ perPage: 20, // Static - Constant
+ },
+});
+```
+
+### How to migrate reactive mutable values
+
+These values are especially useful when used by a lot of different components, so we can first evaluate how many reads and writes each property gets, and how far apart these are from each other. The fewer reads there are and the closer together they live, the easier it will be to remove these properties in favor of native Vue props and events.
+
+#### Simple read/write values
+
+If we go back to our example, `selectedSuiteIndex` is only used by **one component** and also **once inside a getter**. Additionally, this getter is only used once itself! It would be quite easy to translate this logic to Vue because this could become a `data` property on the component instance. For the getter, we can use a computed property instead, or a method on the component that returns the right item because we will have access to the index there as well. This is a perfect example of how the VueX store here complicates the application by adding a lot of abstractions when really everything could live inside the same component.
+
+Luckily, in our example all properties could live inside the same component. However, there are cases where it will not be possible. When this happens, we can use Vue events and props to communicate between sibling components. Store the data in question inside a parent component that should know about the state, and when a child component wants to write to the component, it can `$emit` an event with the new value and let the parent update. Then, by cascading props down to all of its children, all instances of the sibling components will share the same data.
+
+Sometimes, it can feel that events and props are cumbersome, especially in very deep component trees. However, it is quite important to be aware that this is mostly an inconvenience issue and not an architectural flaw or problem to fix. Passing down props, even deeply nested, is a very acceptable pattern for cross-components communication.
+
+#### Shared read/write values
+
+Let's assume that we have a property in the store that is used by multiple components for read and writes that are either so numerous or far apart that Vue props and events seem like a bad solution. Instead, we use Apollo client-side resolvers. This section requires knowledge of [Apollo Client](graphql.md), so feel free to check the apollo details as needed.
+
+First we need to set up our Vue app to use `VueApollo`. Then when creating our store, we pass the `resolvers` and `typedefs` (defined later) to the Apollo Client:
+
+```javascript
+import { resolvers } from "./graphql/settings.js"
+import typeDefs from './graphql/typedefs.graphql';
+
+...
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient({
+ resolvers, // To be written soon
+ { typeDefs }, // We are going to create this in a sec
+ }),
+});
+```
+
+For our example, let's call our field `app.status`, and we need is to define queries and mutations that use the `@client` directives. Let's create them right now:
+
+```javascript
+// get_app_status.query.graphql
+query getAppStatus {
+ app @client {
+ status
+ }
+}
+```
+
+```javascript
+// update_app_status.mutation.graphql
+mutation updateAppStatus($appStatus: String) {
+ updateAppStatus(appStatus: $appStatus) @client
+}
+```
+
+For fields that **do not exist in our schema**, we need to set up `typeDefs`. For example:
+
+```javascript
+// typedefs.graphql
+
+type TestReportApp {
+ status: String!
+}
+
+extend type Query {
+ app: TestReportApp
+}
+```
+
+Now we can write our resolvers so that we can update the field with our mutation:
+
+```javascript
+// settings.js
+export const resolvers = {
+ Mutation: {
+ // appStatus is the argument to our mutation
+ updateAppStatus: (_, { appStatus }, { cache }) => {
+ cache.writeQuery({
+ query: getAppStatus,
+ data: {
+ app: {
+ __typename: 'TestReportApp',
+ status: appStatus,
+ },
+ },
+ });
+ },
+ }
+}
+```
+
+For querying, this works without any additional instructions because it behaves like any `Object`, because querying for `app { status }` is equivalent to `app.status`. However, we need to write either a "default" `writeQuery` (to define the very first value our field will have) or we can set up the [`typePolicies` for our `cacheConfig`](graphql.md#local-state-with-apollo) to provide this default value.
+
+So now when we want to read from this value, we can use our local query. When we need to update it, we can call the mutation and pass the new value as an argument.
+
+#### Network-related values
+
+There are values like `isLoading` and `errorMessage` which are tied to the network request state. These are read/write properties, but will easily be replaced later with Apollo Client's own capabilities without us doing any extra work:
+
+```javascript
+// state.js AKA our store
+export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) => ({
+ blobPath, // Static - Dataset
+ summaryEndpoint, // Static - Dataset
+ suiteEndpoint, // Static - Dataset
+ testReports: {},
+ selectedSuiteIndex: null, // Mutable -> data property
+ isLoading: false, // Mutable -> tied to network
+ errorMessage: null, // Mutable -> tied to network
+ pageInfo: { // Static - Constant
+ page: 1, // Static - Constant
+ perPage: 20, // Static - Constant
+ },
+});
+```
+
+### How to migrate getters
+
+Getters have to be reviewed case-by-case, but a general guideline is that it is highly possible to write a pure JavaScript util function that takes as an argument the state values we used to use inside the getter, and then return whatever value we want. Consider the following getter:
+
+```javascript
+// getters.js
+export const getSelectedSuite = (state) =>
+ state.testReports?.test_suites?.[state.selectedSuiteIndex] || {};
+```
+
+All that we do here is reference two state values, which can both become arguments to a function:
+
+```javascript
+//new_utils.js
+export const getSelectedSuite = (testReports, selectedSuiteIndex) =>
+ testReports?.test_suites?.[selectedSuiteIndex] || {};
+```
+
+This new util can then be imported and used as it previously was, but directly inside the component. Also, most of the specs for the getters can be ported to the utils quite easily because the logic is preserved.
+
+### How to migrate API data
+
+Our last property is called `testReports` and it is fetched via an `axios` call to the API. We assume that we are in a pure REST application and that GraphQL data is not yet available:
+
+```javascript
+// actions.js
+export const fetchSummary = ({ state, commit, dispatch }) => {
+ dispatch('toggleLoading');
+
+ return axios
+ .get(state.summaryEndpoint)
+ .then(({ data }) => {
+ commit(types.SET_SUMMARY, data);
+ })
+ .catch(() => {
+ createAlert({
+ message: s__('TestReports|There was an error fetching the summary.'),
+ });
+ })
+ .finally(() => {
+ dispatch('toggleLoading');
+ });
+};
+```
+
+We have two options here. If this action is only used once, there is nothing preventing us from just moving all of this code from the `actions.js` file to the component that does the fetching. Then, it would be easy to remove all the state related code in favor of `data` properties. In that case, `isLoading` and `errorMessages` would both live along with it because it's only used once.
+
+If we are reusing this function multiple time (or plan to), then that Apollo Client can be leveraged to do what it does best: network calls and caching. In this section, we assume Apollo Client knowledge and that you know how to set it up, but feel free to read through [the GraphQL documentation](graphql.md).
+
+We can use a local GraphQL query (with an `@client` directive) to structure how we want to receive the data, and then use a client-side resolver to tell Apollo Client how to resolve that query. We can take a look at our REST call in the browser network tab and determine which structure suits the use case. In our example, we could write our query like:
+
+```graphql
+query getTestReportSummary($fullPath: ID!, $iid: ID!, endpoint: String!) {
+ project(fullPath: $fullPath){
+ id,
+ pipeline(iid: $iid){
+ id,
+ testReportSummary(endpoint: $endpoint) @client {
+ testSuites{
+ nodes{
+ name
+ totalTime,
+ # There are more fields here, but they aren't needed for our example
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+The structure here is arbitrary in the sense that we could write this however we want. It might be tempting to skip the `project.pipeline.testReportSummary` because this is not how the REST call is structured. However, by making the query structure compliant with the `GraphQL` API, we will not need to modify our query if we do decide to transition to `GraphQL` later, and can simply remove the `@client` directive. This also gives us **caching for free** because if we try to fetch the summary again for the same pipeline, Apollo Client knows that we already have the result!
+
+Additionally, we are passing an `endpoint` argument to our field `testReportSummary`. This would not be necessary in pure `GraphQL`, but our resolver is going to need that information to make the `REST` call later.
+
+Now we need to write a client-side resolver. When we mark a field with an `@client` directive, it is **not sent to the server**, and Apollo Client instead expects us to [define our own code to resolve the value](graphql.md#using-client-side-resolvers). We can write a client-side resolver for `testReportSummary` inside the `cacheConfig` object that we pass to Apollo Client. We want this resolver to make the Axios call and return whatever data structure we want. That this is also the perfect place to transfer a getter if it was always used when accessing the API data or massaging the data structure:
+
+```javascript
+// graphql_config.js
+export const resolvers = {
+ Query: {
+ testReportSummary(_, { summaryEndpoint }): {
+ return axios.get(summaryEndpoint).then(({ data }) => {
+ return data // we could format/massage our data here instead of using a getter
+ }
+ }
+}
+```
+
+Any time we make a call to the `testReportSummary @client` field, this resolver is executed and returns the result of the operation, which is essentially doing the same job as the `VueX` action did.
+
+If we assume that our GraphQL call is stored inside a data property called `testReportSummary`, we can replace `isLoading` with `this.$apollo.queries.testReportSummary.lodaing` in any component that fires this query. Errors can be handled inside the `error` hook of the Query.
+
+### Migration strategy
+
+Now that we have gone through each type of data, let's review how to plan for the transition between a VueX-based store and one without. We are trying to avoid VueX and Apollo coexisting, so the less time where both stores are available in the same context the better. To minimize this overlap, we should start our migration by removing from the store all that does not involve adding an Apollo store. Each of the following point could be its own MR:
+
+1. Migrate away from Static values, both `Rails` dataset and client-side constants and use `provide/inject` and `constants.js` files instead.
+1. Replace simple read/write operations with either:
+ - `data` properties and `methods` if in a single component.
+ - `props` and `emits` if shared across a localized group of components.
+1. Replace shared read/write operations with Apollo Client `@client` directives.
+1. Replace network data with Apollo Client, either with actual GraphQL calls when available or by using client-side resolvers to make REST calls.
+
+If it is impossible to quickly replace shared read/write operations or network data (for example in one or two milestones), consider making a different Vue component behind a feature flag that is exclusively functional with Apollo Client, and rename the current component that uses VueX with a `legacy-` prefix. The newer component might not be able to implement all functionality right away, but we can progressively add them as we make MRs. This way, our legacy component is exclusively using VueX as a store and the new one is only Apollo. After the new component has re-implemented all the logic, we can turn the Feature Flag on and ensure that it behaves as expected.
+
+## FAQ
+
+### What if I need a global store without any network call?
+
+This is a rare occurrence and should suggest the following question: "Do I **really** need a global store then?" (the answer is probably no!) If the answer is yes, then you can use the [shared read/write technique with Apollo](#how-to-migrate-reactive-mutable-values) described above. It is perfectly acceptable to use Apollo Client for client-side exclusive stores.
+
+### Are we going to use Pinia?
+
+The short answer is: we don't know, but it is unlikely. It would still mean having two global store libraries, which has the same downsides as VueX and Apollo Client coexisting. Reducing the size of our global stores is positive regardless of whether we end up using Pinia though!
+
+### Apollo client is really verbose for client directives. Can I mix and match with VueX?
+
+Mixing and matching is not recommended. There are a lot of reasons why, but think of how codebases grow organically with what is available. Even if you were really good at separating your network state and your client-side state, other developers might not share the same dedication or simply not understand how to choose what lives in which store. Over time, you will also nearly inevitably need to communicate between your VueX store and Apollo Client, which can only result in problems.
diff --git a/doc/development/fe_guide/tooling.md b/doc/development/fe_guide/tooling.md
index 762ef852d74..c1084ab4453 100644
--- a/doc/development/fe_guide/tooling.md
+++ b/doc/development/fe_guide/tooling.md
@@ -155,6 +155,19 @@ $ grep "eslint-disable.*import/no-deprecated" -r .
./app/assets/javascripts/issuable_form.js: // eslint-disable-next-line import/no-deprecated
```
+### `vue/multi-word-component-names` is disabled in my file
+
+Single name components are discouraged by the
+[Vue style guide](https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names).
+
+They are problematic because they can be confused with other HTML components: We could name a
+component `<table>` and it would stop rendering an HTML `<table>`.
+
+To solve this, you should rename the `.vue` file and its references to use at least two words,
+for example:
+
+- `user/table.vue` could be renamed to `user/users_table.vue` and be imported as `UsersTable` and used with `<users-table />`.
+
### GraphQL schema and operations validation
We use [`@graphql-eslint/eslint-plugin`](https://www.npmjs.com/package/@graphql-eslint/eslint-plugin)
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index e096d25f2d9..06d7070b855 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Vuex
-[Vuex](https://vuex.vuejs.org) should no longer be considered a preferred path to store management and is currently in its legacy phase. This means it is acceptable to add upon existing `Vuex` stores, but we strongly recommend reducing store sizes over time and eventually migrating fully to [Apollo](https://www.apollographql.com/) whenever it's possible. Before adding any new `Vuex` store to an application, first ensure that the `Vue` application you plan to add it into **does not use** `Apollo`. `Vuex` and `Apollo` should not be combined unless absolutely necessary. Please consider reading through [our GraphQL documentation](../fe_guide/graphql.md) for more guidelines on how you can build `Apollo` based applications.
+[Vuex](https://vuex.vuejs.org) should no longer be considered a preferred path to store management and is currently in its legacy phase. This means it is acceptable to add upon existing `Vuex` stores, but we strongly recommend reducing store sizes over time and eventually [migrating away from VueX entirely](migrating_from_vuex.md). Before adding any new `Vuex` store to an application, first ensure that the `Vue` application you plan to add it into **does not use** `Apollo`. `Vuex` and `Apollo` should not be combined unless absolutely necessary. Please consider reading through [our GraphQL documentation](../fe_guide/graphql.md) for more guidelines on how you can build `Apollo` based applications.
The information included in this page is explained in more detail in the
official [Vuex documentation](https://vuex.vuejs.org).
@@ -40,6 +40,7 @@ applications stored in this [repository](https://gitlab.com/gitlab-org/gitlab/-/
This is the entry point for our store. You can use the following as a guide:
```javascript
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
@@ -287,6 +288,7 @@ function when mounting your Vue component:
// in the Vue app's initialization script (for example, mount_show.js)
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { createStore } from './stores';
import AwesomeVueApp from './components/awesome_vue_app.vue'
@@ -372,6 +374,7 @@ when [providing data to a Vue app](vue.md#providing-data-from-haml-to-javascript
```javascript
<script>
+// eslint-disable-next-line no-restricted-imports
import { mapActions, mapState, mapGetters } from 'vuex';
export default {
@@ -433,6 +436,7 @@ components, we need to include the store and provide the correct state:
```javascript
//component_spec.js
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { mount } from '@vue/test-utils';
import { createStore } from './store';