Geo is the solution for widely distributed development teams.
Geo is the solution for widely distributed development teams.
## Overview
## Overview
...
@@ -8,14 +14,14 @@ Fetching large repositories can take a long time for teams located far from a si
...
@@ -8,14 +14,14 @@ Fetching large repositories can take a long time for teams located far from a si
Geo provides local, read-only instances of your GitLab instances, reducing the time it takes to clone and fetch large repositories and speeding up development.
Geo provides local, read-only instances of your GitLab instances, reducing the time it takes to clone and fetch large repositories and speeding up development.
> **Notes:**
>
> - Geo is part of [GitLab Premium](https://about.gitlab.com/pricing/#self-managed).
> - Geo is part of [GitLab Premium](https://about.gitlab.com/pricing/#self-managed).
> - Introduced in GitLab Enterprise Edition 8.9.
> - We recommend you use:
> - We recommend you use:
> - At least GitLab Enterprise Edition 10.0 for basic Geo features.
> - At least GitLab Enterprise Edition 10.0 for basic Geo features.
> - The latest version for a better experience.
> - The latest version for a better experience.
> - Make sure that all nodes run the same GitLab version.
> - Make sure that all nodes run the same GitLab version.
> - Geo requires PostgreSQL 9.6 and Git 2.9, in addition to GitLab's usual [minimum requirements](../../../install/requirements.md).
> - Geo requires PostgreSQL 9.6 and Git 2.9, in addition to GitLab's usual [minimum requirements](../../../install/requirements.md).
> - Using Geo in combination with [High Availability](../../high_availability/README.md) is considered **Generally Available** (GA) in GitLab [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
For a video introduction to Geo, see [Introduction to GitLab Geo - GitLab Features](https://www.youtube.com/watch?v=-HDLxSjEh6w).
For a video introduction to Geo, see [Introduction to GitLab Geo - GitLab Features](https://www.youtube.com/watch?v=-HDLxSjEh6w).
@@ -36,8 +36,8 @@ For the Troubleshooting sections, people in GitLab Support can merge additions t
...
@@ -36,8 +36,8 @@ For the Troubleshooting sections, people in GitLab Support can merge additions t
Include any media types/sources if the content is relevant to readers. You can freely include or link presentations, diagrams, videos, etc.; no matter who it was originally composed for, if it is helpful to any of our audiences, we can include it.
Include any media types/sources if the content is relevant to readers. You can freely include or link presentations, diagrams, videos, etc.; no matter who it was originally composed for, if it is helpful to any of our audiences, we can include it.
- If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
- If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
- Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
- Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
### No special types
### No special types
...
@@ -237,14 +237,14 @@ Do not include the same information in multiple places. [Link to a SSOT instead.
...
@@ -237,14 +237,14 @@ Do not include the same information in multiple places. [Link to a SSOT instead.
- Use sentence case for titles, headings, labels, menu items, and buttons.
- Use sentence case for titles, headings, labels, menu items, and buttons.
- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
```md
```md
## Header
## Header
Paragraph.
Paragraph.
- List item 1
- List item 1
- List item 2
- List item 2
```
```
### Tables overlapping the TOC
### Tables overlapping the TOC
...
@@ -303,12 +303,12 @@ Check specific punctuation rules for [list items](#list-items) below.
...
@@ -303,12 +303,12 @@ Check specific punctuation rules for [list items](#list-items) below.
- Be consistent throughout the list: if the majority of the items do not end in a period, do not end any of the items in a period, even if they consist of a complete sentence. The opposite is also valid: if the majority of the items end with a period, end all with a period.
- Be consistent throughout the list: if the majority of the items do not end in a period, do not end any of the items in a period, even if they consist of a complete sentence. The opposite is also valid: if the majority of the items end with a period, end all with a period.
- Separate list items from explanatory text with a colon (`:`). For example:
- Separate list items from explanatory text with a colon (`:`). For example:
```md
```md
The list is as follows:
The list is as follows:
- First item: this explains the first item.
- First item: this explains the first item.
- Second item: this explains the second item.
- Second item: this explains the second item.
```
```
**Examples:**
**Examples:**
...
@@ -520,16 +520,16 @@ To embed a video, follow the instructions below and make sure
...
@@ -520,16 +520,16 @@ To embed a video, follow the instructions below and make sure
you have your MR reviewed and approved by a technical writer.
you have your MR reviewed and approved by a technical writer.
1. Copy the code below and paste it into your markdown file.
1. Copy the code below and paste it into your markdown file.
Leave a blank line above and below it. Do NOT edit the code
Leave a blank line above and below it. Do NOT edit the code
(don't remove or add any spaces, etc).
(don't remove or add any spaces, etc).
1. On YouTube, visit the video URL you want to display. Copy
1. On YouTube, visit the video URL you want to display. Copy
the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
and replace the video title and link in the line under `<div class="video-fallback">`.
and replace the video title and link in the line under `<div class="video-fallback">`.
1. On YouTube, click **Share**, then **Embed**.
1. On YouTube, click **Share**, then **Embed**.
1. Copy the `<iframe>` source (`src`) **URL only**
1. Copy the `<iframe>` source (`src`) **URL only**
(`https://www.youtube.com/embed/VIDEO-ID`),
(`https://www.youtube.com/embed/VIDEO-ID`),
and paste it, replacing the content of the `src` field in the
and paste it, replacing the content of the `src` field in the
`iframe` tag.
`iframe` tag.
```html
```html
leave a blank line here
leave a blank line here
...
@@ -611,7 +611,7 @@ In most cases, content considered for a note should be included:
...
@@ -611,7 +611,7 @@ In most cases, content considered for a note should be included:
#### When to use
#### When to use
Use a note when there is a reason that most or all readers who browse the
Use a note when there is a reason that most or all readers who browse the
section should see the content. That is, if missed, it’s likely to cause
section should see the content. That is, if missed, it’s likely to cause
major trouble for a minority of users or significant trouble for a majority
major trouble for a minority of users or significant trouble for a majority
of users.
of users.
...
@@ -747,24 +747,24 @@ a helpful link back to how the feature was developed.
...
@@ -747,24 +747,24 @@ a helpful link back to how the feature was developed.
- For features that need to declare the GitLab version that the feature was introduced. Text similar
- For features that need to declare the GitLab version that the feature was introduced. Text similar
to the following should be added immediately below the heading as a blockquote:
to the following should be added immediately below the heading as a blockquote:
```md
```md
> Introduced in GitLab 11.3.
> Introduced in GitLab 11.3.
```
```
- Whenever possible, version text should have a link to the issue, merge request, or epic that introduced the feature.
- Whenever possible, version text should have a link to the issue, merge request, or epic that introduced the feature.
An issue is preferred over a merge request, and a merge request is preferred over an epic. For example:
An issue is preferred over a merge request, and a merge request is preferred over an epic. For example:
```md
```md
> [Introduced](<link-to-issue>) in GitLab 11.3.
> [Introduced](<link-to-issue>) in GitLab 11.3.
```
```
- If the feature is only available in GitLab Enterprise Edition, mention
- If the feature is only available in GitLab Enterprise Edition, mention
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the feature is available in:
the feature is available in:
```md
```md
> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
```
```
### Removing version text
### Removing version text
...
@@ -871,14 +871,14 @@ When there is a list of steps to perform, usually that entails editing the
...
@@ -871,14 +871,14 @@ When there is a list of steps to perform, usually that entails editing the
configuration file and reconfiguring/restarting GitLab. In such case, follow
configuration file and reconfiguring/restarting GitLab. In such case, follow
the style below as a guide:
the style below as a guide:
```md
````md
**For Omnibus installations**
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb`:
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
```ruby
external_url "https://gitlab.example.com"
external_url "https://gitlab.example.com"
```
```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
1. Save the file and [reconfigure] GitLab for the changes to take effect.
...
@@ -888,17 +888,16 @@ the style below as a guide:
...
@@ -888,17 +888,16 @@ the style below as a guide:
1. Edit `config/gitlab.yml`:
1. Edit `config/gitlab.yml`:
```yaml
```yaml
gitlab:
gitlab:
host: "gitlab.example.com"
host: "gitlab.example.com"
```
```
1. Save the file and [restart] GitLab for the changes to take effect.
1. Save the file and [restart] GitLab for the changes to take effect.
1. Strive to write many small pure functions, and minimize where mutations occur.
1. Strive to write many small pure functions, and minimize where mutations occur.
```javascript
```javascript
// bad
// bad
const values = {foo: 1};
constvalues={foo:1};
function impureFunction(items) {
functionimpureFunction(items){
const bar = 1;
constbar=1;
items.foo = items.a * bar + 2;
items.foo=items.a*bar+2;
return items.a;
returnitems.a;
}
}
const c = impureFunction(values);
constc=impureFunction(values);
// good
// good
var values = {foo: 1};
varvalues={foo:1};
function pureFunction (foo) {
functionpureFunction(foo){
var bar = 1;
varbar=1;
foo = foo * bar + 2;
foo=foo*bar+2;
return foo;
returnfoo;
}
}
var c = pureFunction(values.foo);
varc=pureFunction(values.foo);
```
```
1. Avoid constructors with side-effects.
1. Avoid constructors with side-effects.
Although we aim for code without side-effects we need some side-effects for our code to run.
Although we aim for code without side-effects we need some side-effects for our code to run.
If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
@@ -399,205 +400,205 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
...
@@ -399,205 +400,205 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. With more than one attribute, all attributes should be on a new line:
1. With more than one attribute, all attributes should be on a new line:
```javascript
```javascript
// bad
// bad
<component v-if="bar"
<component v-if="bar"
param="baz" />
param="baz" />
<button class="btn">Click me</button>
<button class="btn">Click me</button>
// good
// good
<component
<component
v-if="bar"
v-if="bar"
param="baz"
param="baz"
/>
/>
<button class="btn">
<button class="btn">
Click me
Click me
</button>
</button>
```
```
1. The tag can be inline if there is only one attribute:
1. The tag can be inline if there is only one attribute:
```javascript
```javascript
// good
// good
<component bar="bar" />
<component bar="bar" />
// good
// good
<component
<component
bar="bar"
bar="bar"
/>
/>
// bad
// bad
<component
<component
bar="bar" />
bar="bar" />
```
```
#### Quotes
#### Quotes
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
```javascript
```javascript
// bad
// bad
template: `
template: `
<button :class='style'>Button</button>
<button :class='style'>Button</button>
`
`
// good
// good
template: `
template: `
<button :class="style">Button</button>
<button :class="style">Button</button>
`
`
```
```
#### Props
#### Props
1. Props should be declared as an object
1. Props should be declared as an object
```javascript
```javascript
// bad
// bad
props: ['foo']
props: ['foo']
// good
// good
props: {
props: {
foo: {
foo: {
type: String,
type: String,
required: false,
required: false,
default: 'bar'
default: 'bar'
}
}
}
}
```
```
1. Required key should always be provided when declaring a prop
1. Required key should always be provided when declaring a prop
```javascript
```javascript
// bad
// bad
props: {
props: {
foo: {
foo: {
type: String,
type: String,
}
}
}
}
// good
// good
props: {
props: {
foo: {
foo: {
type: String,
type: String,
required: false,
required: false,
default: 'bar'
default: 'bar'
}
}
}
}
```
```
1. Default key should be provided if the prop is not required.
1. Default key should be provided if the prop is not required.
_Note:_ There are some scenarios where we need to check for the existence of the property.
_Note:_ There are some scenarios where we need to check for the existence of the property.
On those a default key should not be provided.
On those a default key should not be provided.
```javascript
```javascript
// good
// good
props: {
props: {
foo: {
foo: {
type: String,
type: String,
required: false,
required: false,
}
}
}
}
// good
// good
props: {
props: {
foo: {
foo: {
type: String,
type: String,
required: false,
required: false,
default: 'bar'
default: 'bar'
}
}
}
}
// good
// good
props: {
props: {
foo: {
foo: {
type: String,
type: String,
required: true
required: true
}
}
}
}
```
```
#### Data
#### Data
1. `data` method should always be a function
1. `data` method should always be a function
```javascript
```javascript
// bad
// bad
data: {
data: {
foo: 'foo'
foo: 'foo'
}
}
// good
// good
data() {
data() {
return {
return {
foo: 'foo'
foo: 'foo'
};
};
}
}
```
```
#### Directives
#### Directives
1. Shorthand `@` is preferable over `v-on`
1. Shorthand `@` is preferable over `v-on`
```javascript
```javascript
// bad
// bad
<component v-on:click="eventHandler"/>
<componentv-on:click="eventHandler"/>
// good
// good
<component @click="eventHandler"/>
<component @click="eventHandler"/>
```
```
1. Shorthand `:` is preferable over `v-bind`
1. Shorthand `:` is preferable over `v-bind`
```javascript
```javascript
// bad
// bad
<component v-bind:class="btn"/>
<componentv-bind:class="btn"/>
// good
// good
<component :class="btn"/>
<component :class="btn"/>
```
```
1. Shorthand `#` is preferable over `v-slot`
1. Shorthand `#` is preferable over `v-slot`
```javascript
```javascript
// bad
// bad
<template v-slot:header></template>
<templatev-slot:header></template>
// good
// good
<template #header></template>
<template #header></template>
```
```
#### Closing tags
#### Closing tags
1. Prefer self closing component tags
1. Prefer self closing component tags
```javascript
```javascript
// bad
// bad
<component></component>
<component></component>
// good
// good
<component />
<component />
```
```
#### Ordering
#### Ordering
1. Tag order in `.vue` file
1. Tag order in `.vue` file
```
```
<script>
<script>
// ...
// ...
</script>
</script>
<template>
<template>
// ...
// ...
</template>
</template>
// We don't use scoped styles but there are few instances of this
// We don't use scoped styles but there are few instances of this
<style>
<style>
// ...
// ...
</style>
</style>
```
```
1. Properties in a Vue Component:
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
Check [order of properties in components rule][vue-order].
...
@@ -608,50 +609,50 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
...
@@ -608,50 +609,50 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
1. If the elements of the array being iterated have an unique `id` it is advised to use it:
1. If the elements of the array being iterated have an unique `id` it is advised to use it:
```html
```html
<div
<div
v-for="item in items"
v-for="item in items"
:key="item.id"
:key="item.id"
>
>
<!-- content -->
<!-- content -->
</div>
</div>
```
```
1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
```html
```html
<div
<div
v-for="(item, index) in items"
v-for="(item, index) in items"
:key="index"
:key="index"
>
>
<!-- content -->
<!-- content -->
</div>
</div>
```
```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
```html
```html
<template v-for="(item, index) in items">
<templatev-for="(item, index) in items">
<span :key="`span-${index}`"></span>
<span:key="`span-${index}`"></span>
<button :key="`button-${index}`"></button>
<button:key="`button-${index}`"></button>
</template>
</template>
```
```
1. When dealing with nested `v-for` use the same guidelines as above.
1. When dealing with nested `v-for` use the same guidelines as above.
```html
```html
<div
<div
v-for="item in items"
v-for="item in items"
:key="item.id"
:key="item.id"
>
>
<span
<span
v-for="element in array"
v-for="element in array"
:key="element.id"
:key="element.id"
>
>
<!-- content -->
<!-- content -->
</span>
</span>
</div>
</div>
```
```
Useful links:
Useful links:
...
@@ -662,35 +663,35 @@ Useful links:
...
@@ -662,35 +663,35 @@ Useful links:
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
```javascript
```javascript
// bad
// bad
<span
<span
class="has-tooltip"
class="has-tooltip"
title="Some tooltip text">
title="Some tooltip text">
Text
Text
</span>
</span>
// good
// good
<span
<span
v-tooltip
v-tooltip
title="Some tooltip text">
title="Some tooltip text">
Text
Text
</span>
</span>
```
```
1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js`
1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js`
To manage the state of an application you should use [Vuex][vuex-docs].
To manage the state of an application you should use [Vuex][vuex-docs].
_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
## Separation of concerns
## Separation of concerns
Vuex is composed of State, Getters, Mutations, Actions and Modules.
Vuex is composed of State, Getters, Mutations, Actions and Modules.
When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state.
When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state.
_Note:_ The action itself will not update the state, only a mutation should update the state.
_Note:_ The action itself will not update the state, only a mutation should update the state.
## File structure
## File structure
When using Vuex at GitLab, separate this concerns into different files to improve readability:
When using Vuex at GitLab, separate this concerns into different files to improve readability:
```
```
...
@@ -21,10 +24,12 @@ When using Vuex at GitLab, separate this concerns into different files to improv
...
@@ -21,10 +24,12 @@ When using Vuex at GitLab, separate this concerns into different files to improv
├── state.js # state
├── state.js # state
└── mutation_types.js # mutation types
└── mutation_types.js # mutation types
```
```
The following example shows an application that lists and adds users to the state.
The following example shows an application that lists and adds users to the state.
(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store))
(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store))
### `index.js`
### `index.js`
This is the entry point for our store. You can use the following as a guide:
This is the entry point for our store. You can use the following as a guide:
```javascript
```javascript
...
@@ -47,6 +52,7 @@ export default createStore();
...
@@ -47,6 +52,7 @@ export default createStore();
```
```
### `state.js`
### `state.js`
The first thing you should do before writing any code is to design the state.
The first thing you should do before writing any code is to design the state.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
...
@@ -66,9 +72,11 @@ Often we need to provide data from haml to our Vue application. Let's store it i
...
@@ -66,9 +72,11 @@ Often we need to provide data from haml to our Vue application. Let's store it i
```
```
#### Access `state` properties
#### Access `state` properties
You can use `mapState` to access state properties in the components.
You can use `mapState` to access state properties in the components.
### `actions.js`
### `actions.js`
An action is a payload of information to send data from our application to our store.
An action is a payload of information to send data from our application to our store.
An action is usually composed by a `type` and a `payload` and they describe what happened.
An action is usually composed by a `type` and a `payload` and they describe what happened.
...
@@ -110,6 +118,7 @@ In this file, we will write the actions that will call the respective mutations:
...
@@ -110,6 +118,7 @@ In this file, we will write the actions that will call the respective mutations:
```
```
#### Actions Pattern: `request` and `receive` namespaces
#### Actions Pattern: `request` and `receive` namespaces
When a request is made we often want to show a loading state to the user.
When a request is made we often want to show a loading state to the user.
Instead of creating an action to toggle the loading state and dispatch it in the component,
Instead of creating an action to toggle the loading state and dispatch it in the component,
...
@@ -136,6 +145,7 @@ By following this pattern we guarantee:
...
@@ -136,6 +145,7 @@ By following this pattern we guarantee:
1. Actions are simple and straightforward
1. Actions are simple and straightforward
#### Dispatching actions
#### Dispatching actions
To dispatch an action from a component, use the `mapActions` helper:
To dispatch an action from a component, use the `mapActions` helper:
```javascript
```javascript
...
@@ -154,6 +164,7 @@ import { mapActions } from 'vuex';
...
@@ -154,6 +164,7 @@ import { mapActions } from 'vuex';
```
```
### `mutations.js`
### `mutations.js`
The mutations specify how the application state changes in response to actions sent to the store.
The mutations specify how the application state changes in response to actions sent to the store.
The only way to change state in a Vuex store should be by committing a mutation.
The only way to change state in a Vuex store should be by committing a mutation.
...
@@ -193,6 +204,7 @@ Remember that actions only describe that something happened, they don't describe
...
@@ -193,6 +204,7 @@ Remember that actions only describe that something happened, they don't describe
```
```
### `getters.js`
### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
This can be done through the `getters`:
This can be done through the `getters`:
...
@@ -219,6 +231,7 @@ import { mapGetters } from 'vuex';
...
@@ -219,6 +231,7 @@ import { mapGetters } from 'vuex';
```
```
### `mutation_types.js`
### `mutation_types.js`
From [vuex mutations docs][vuex-mutations]:
From [vuex mutations docs][vuex-mutations]:
> 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 single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
> 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 single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
The store should be included in the main component of your application:
The store should be included in the main component of your application:
```javascript
```javascript
...
@@ -241,6 +255,7 @@ The store should be included in the main component of your application:
...
@@ -241,6 +255,7 @@ The store should be included in the main component of your application:
```
```
### Communicating with the Store
### Communicating with the Store
```javascript
```javascript
<script>
<script>
import{mapActions,mapState,mapGetters}from'vuex';
import{mapActions,mapState,mapGetters}from'vuex';
...
@@ -298,29 +313,33 @@ export default {
...
@@ -298,29 +313,33 @@ export default {
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
> why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
> why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
```javascript
```javascript
// component.vue
// component.vue
// bad
// bad
created() {
created(){
this.$store.commit('mutation');
this.$store.commit('mutation');
}
}
// good
created(){
this.$store.dispatch('action');
}
```
// good
created() {
this.$store.dispatch('action');
}
```
1. Use mutation types instead of hardcoding strings. It will be less error prone.
1. Use mutation types instead of hardcoding strings. It will be less error prone.
1. The State will be accessible in all components descending from the use where the store is instantiated.
1. The State will be accessible in all components descending from the use where the store is instantiated.
### Testing Vuex
### Testing Vuex
#### Testing Vuex concerns
#### Testing Vuex concerns
Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations.
Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations.
#### Testing components that need a store
#### Testing components that need a store
Smaller components might use `store` properties to access the data.
Smaller components might use `store` properties to access the data.
In order to write unit tests for those components, we need to include the store and provide the correct state:
In order to write unit tests for those components, we need to include the store and provide the correct state:
...
@@ -363,6 +382,7 @@ describe('component', () => {
...
@@ -363,6 +382,7 @@ describe('component', () => {
```
```
#### Testing Vuex actions and getters
#### Testing Vuex actions and getters
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
`[vuex] actions should be function or object with "handler" function`
By default, `sprintf` escapes the placeholder values.
By default, `sprintf` escapes the placeholder values.
If you want to take care of that yourself, you can pass `false` as third argument.
If you want to take care of that yourself, you can pass `false` as third argument.
```js
```js
import { __, sprintf } from '~/locale';
import{__,sprintf}from'~/locale';
sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is <strong>bold</strong>'
sprintf(__('This is %{value}'),{value:'<strong>bold</strong>'});// => 'This is <strong>bold</strong>'
sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
sprintf(__('This is %{value}'),{value:'<strong>bold</strong>'},false);// => 'This is <strong>bold</strong>'
```
```
### Plurals
### Plurals
- In Ruby/HAML:
- In Ruby/HAML:
```ruby
```ruby
n_('Apple', 'Apples', 3)
n_('Apple','Apples',3)
# => 'Apples'
# => 'Apples'
```
```
Using interpolation:
Using interpolation:
```ruby
n_("There is a mouse.", "There are %d mice.", size) % size
# => When size == 1: 'There is a mouse.'
# => When size == 2: 'There are 2 mice.'
```
Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
```ruby
n_("There is a mouse.","There are %d mice.",size)%size
# => When size == 1: 'There is a mouse.'
# => When size == 2: 'There are 2 mice.'
```
Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
- In JavaScript:
```js
```js
n__('Apple', 'Apples', 3)
n__('Apple','Apples',3)
// => 'Apples'
// => 'Apples'
```
```
Using interpolation:
Using interpolation:
```js
```js
n__('Last day', 'Last %d days', x)
n__('Last day','Last %d days',x)
// => When x == 1: 'Last day'
// => When x == 1: 'Last day'
// => When x == 2: 'Last 2 days'
// => When x == 2: 'Last 2 days'
```
```
### Namespaces
### Namespaces
...
@@ -202,17 +203,17 @@ Namespaces should be PascalCase.
...
@@ -202,17 +203,17 @@ Namespaces should be PascalCase.
- In Ruby/HAML:
- In Ruby/HAML:
```ruby
```ruby
s_('OpenedNDaysAgo|Opened')
s_('OpenedNDaysAgo|Opened')
```
```
In case the translation is not found it will return `Opened`.
In case the translation is not found it will return `Opened`.
- In JavaScript:
- In JavaScript:
```js
```js
s__('OpenedNDaysAgo|Opened')
s__('OpenedNDaysAgo|Opened')
```
```
Note: The namespace should be removed from the translation. See the [translation
Note: The namespace should be removed from the translation. See the [translation
guidelines for more details](translation.md#namespaced-strings).
guidelines for more details](translation.md#namespaced-strings).
...
@@ -235,12 +236,12 @@ This makes use of [`Intl.DateTimeFormat`].
...
@@ -235,12 +236,12 @@ This makes use of [`Intl.DateTimeFormat`].
- In Ruby/HAML, we have two ways of adding format to dates and times:
- In Ruby/HAML, we have two ways of adding format to dates and times:
1.**Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
1.**Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
[dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
[dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
If you need to add a new format, because other parts of the code could benefit from it,
If you need to add a new format, because other parts of the code could benefit from it,
you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
1.**Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
1.**Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
## Best practices
## Best practices
...
@@ -268,40 +269,40 @@ should be externalized as follows:
...
@@ -268,40 +269,40 @@ should be externalized as follows:
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
=s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe%{zones_link_start: zones_link_start,zones_link_end: '</a>'.html_safe}
=s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe%{zones_link_start: zones_link_start,zones_link_end: '</a>'.html_safe}
```
```
- In JavaScript, instead of:
- In JavaScript, instead of:
```js
```js
{{
{{
sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
sprintf(s__("ClusterIntegration|Learn more about %{link}"),{
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
...
@@ -374,29 +375,29 @@ Let's suppose you want to add translations for a new language, let's say French.
...
@@ -374,29 +375,29 @@ Let's suppose you want to add translations for a new language, let's say French.
1. The first step is to register the new language in `lib/gitlab/i18n.rb`:
1. The first step is to register the new language in `lib/gitlab/i18n.rb`:
```ruby
```ruby
...
...
AVAILABLE_LANGUAGES = {
AVAILABLE_LANGUAGES={
...,
...,
'fr' => 'Français'
'fr'=>'Français'
}.freeze
}.freeze
...
...
```
```
1. Next, you need to add the language:
1. Next, you need to add the language:
```sh
```sh
bin/rake gettext:add_language[fr]
bin/rake gettext:add_language[fr]
```
```
If you want to add a new language for a specific region, the command is similar,
If you want to add a new language for a specific region, the command is similar,
you just need to separate the region with an underscore (`_`). For example:
you just need to separate the region with an underscore (`_`). For example:
```sh
```sh
bin/rake gettext:add_language[en_GB]
bin/rake gettext:add_language[en_GB]
```
```
Please note that you need to specify the region part in capitals.
Please note that you need to specify the region part in capitals.
1. Now that the language is added, a new directory has been created under the
1. Now that the language is added, a new directory has been created under the
path: `locale/fr/`. You can now start using your PO editor to edit the PO file
path: `locale/fr/`. You can now start using your PO editor to edit the PO file
...
@@ -406,9 +407,9 @@ Let's suppose you want to add translations for a new language, let's say French.
...
@@ -406,9 +407,9 @@ Let's suppose you want to add translations for a new language, let's say French.
in order to generate the binary MO files and finally update the JSON files
in order to generate the binary MO files and finally update the JSON files
containing the translations:
containing the translations:
```sh
```sh
bin/rake gettext:compile
bin/rake gettext:compile
```
```
1. In order to see the translated content we need to change our preferred language
1. In order to see the translated content we need to change our preferred language
which can be found under the user's **Settings** (`/profile`).
which can be found under the user's **Settings** (`/profile`).
...
@@ -416,7 +417,7 @@ Let's suppose you want to add translations for a new language, let's say French.
...
@@ -416,7 +417,7 @@ Let's suppose you want to add translations for a new language, let's say French.
1. After checking that the changes are ok, you can proceed to commit the new files.
1. After checking that the changes are ok, you can proceed to commit the new files.