summaryrefslogtreecommitdiff
path: root/doc/development
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development')
-rw-r--r--doc/development/README.md9
-rw-r--r--doc/development/automatic_ce_ee_merge.md93
-rw-r--r--doc/development/ee_features.md8
-rw-r--r--doc/development/fe_guide/dropdowns.md12
-rw-r--r--doc/development/fe_guide/style_guide_js.md122
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/i18n/externalization.md18
-rw-r--r--doc/development/i18n/index.md1
-rw-r--r--doc/development/limit_ee_conflicts.md347
-rw-r--r--doc/development/module_with_instance_variables.md242
-rw-r--r--doc/development/performance.md2
-rw-r--r--doc/development/sidekiq_style_guide.md59
-rw-r--r--doc/development/testing_guide/best_practices.md14
-rw-r--r--doc/development/testing_guide/index.md2
-rw-r--r--doc/development/ux_guide/components.md18
-rw-r--r--doc/development/ux_guide/copy.md12
-rw-r--r--doc/development/writing_documentation.md169
17 files changed, 589 insertions, 543 deletions
diff --git a/doc/development/README.md b/doc/development/README.md
index 6892838be7f..b624aa37c70 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -16,7 +16,8 @@ comments: false
- [GitLab core team & GitLab Inc. contribution process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md)
- [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
-- [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md)
+- [Automatic CE->EE merge](automatic_ce_ee_merge.md)
+- [Guidelines for implementing Enterprise Edition features](ee_features.md)
## UX and frontend guides
@@ -36,6 +37,7 @@ comments: false
- [`Gemfile` guidelines](gemfile.md)
- [Sidekiq debugging](sidekiq_debugging.md)
- [Gotchas](gotchas.md) to avoid
+- [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [Issue and merge requests state models](object_state_models.md)
- [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md)
@@ -80,10 +82,9 @@ comments: false
## Documentation guides
-- [Documentation styleguide](doc_styleguide.md): Use this styleguide if you are
- contributing to the documentation.
- [Writing documentation](writing_documentation.md)
- - [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles)
+- [Documentation styleguide](doc_styleguide.md)
+- [Markdown](../user/markdown.md)
## Internationalization (i18n) guides
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
new file mode 100644
index 00000000000..4b9791c95bc
--- /dev/null
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -0,0 +1,93 @@
+# Automatic CE->EE merge
+
+GitLab Community Edition is merged automatically every 3 hours into the
+Enterprise Edition (look for the [`CE Upstream` merge requests]).
+
+This merge is done automatically in a
+[scheduled pipeline](https://gitlab.com/gitlab-org/release-tools/-/jobs/43201679).
+If a merge is already in progress, the job [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687).
+
+**If you are pinged in a `CE Upstream` merge request to resolve a conflict,
+please resolve the conflict as soon as possible or ask someone else to do it!**
+
+>**Note:**
+It's ok to resolve more conflicts than the one that you are asked to resolve. In
+that case, it's a good habit to ask for a double-check on your resolution by
+someone who is familiar with the code you touched.
+
+[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
+
+## Always merge EE merge requests before their CE counterparts
+
+**In order to avoid conflicts in the CE->EE merge, you should always merge the
+EE version of your CE merge request first, if present.**
+
+The rationale for this is that as CE->EE merges are done automatically every few
+hours, it can happen that:
+
+1. A CE merge request that needs EE-specific changes is merged
+1. The automatic CE->EE merge happens
+1. Conflicts due to the CE merge request occur since its EE merge request isn't
+ merged yet
+1. The automatic merge bot will ping someone to resolve the conflict **that are
+ already resolved in the EE merge request that isn't merged yet**
+
+That's a waste of time, and that's why you should merge EE merge request before
+their CE counterpart.
+
+## Avoiding CE->EE merge conflicts beforehand
+
+To avoid the conflicts beforehand, check out the
+[Guidelines for implementing Enterprise Edition features](ee_features.md).
+
+In any case, the CI `ee_compat_check` job will tell you if you need to open an
+EE version of your CE merge request.
+
+### Conflicts detection in CE merge requests
+
+For each commit (except on `master`), the `ee_compat_check` CI job tries to
+detect if the current branch's changes will conflict during the CE->EE merge.
+
+The job reports what files are conflicting and how to setup a merge request
+against EE.
+
+#### How the job works
+
+1. Generates the diff between your branch and current CE `master`
+1. Tries to apply it to current EE `master`
+1. If it applies cleanly, the job succeeds, otherwise...
+1. Detects a branch with the `ee-` prefix or `-ee` suffix in EE
+1. If it exists, generate the diff between this branch and current EE `master`
+1. Tries to apply it to current EE `master`
+1. If it applies cleanly, the job succeeds
+
+In the case where the job fails, it means you should create a `ee-<ce_branch>`
+or `<ce_branch>-ee` branch, push it to EE and open a merge request against EE
+`master`.
+At this point if you retry the failing job in your CE merge request, it should
+now pass.
+
+Notes:
+
+- This task is not a silver-bullet, its current goal is to bring awareness to
+ developers that their work needs to be ported to EE.
+- Community contributors shouldn't be required to submit merge requests against
+ EE, but reviewers should take actions by either creating such EE merge request
+ or asking a GitLab developer to do it **before the merge request is merged**.
+- If you branch is too far behind `master`, the job will fail. In that case you
+ should rebase your branch upon latest `master`.
+- Code reviews for merge requests often consist of multiple iterations of
+ feedback and fixes. There is no need to update your EE MR after each
+ iteration. Instead, create an EE MR as soon as you see the
+ `ee_compat_check` job failing. After you receive the final approval
+ from a Maintainer (but **before the CE MR is merged**) update the EE MR.
+ This helps to identify significant conflicts sooner, but also reduces the
+ number of times you have to resolve conflicts.
+- Please remember to
+ [always have your EE merge request merged before the CE version](#always-merge-ee-merge-requests-before-their-ce-counterparts).
+- You can use [`git rerere`](https://git-scm.com/blog/2010/03/08/rerere.html)
+ to avoid resolving the same conflicts multiple times.
+
+---
+
+[Return to Development documentation](README.md)
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 932a44f65e4..1af839a27e1 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -1,4 +1,4 @@
-# Guidelines for implementing Enterprise Edition feature
+# Guidelines for implementing Enterprise Edition features
- **Write the code and the tests.**: As with any code, EE features should have
good test coverage to prevent regressions.
@@ -380,3 +380,9 @@ to avoid conflicts during CE to EE merge.
}
}
```
+
+## gitlab-svgs
+
+Conflicts in `app/assets/images/icons.json` or `app/assets/images/icons.svg` can
+be resolved simply by regenerating those assets with
+[`yarn run svg`](https://gitlab.com/gitlab-org/gitlab-svgs).
diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md
index e1660ac5caa..6314f8f38d2 100644
--- a/doc/development/fe_guide/dropdowns.md
+++ b/doc/development/fe_guide/dropdowns.md
@@ -4,15 +4,15 @@
## How to style a bootstrap dropdown
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
-
-
+
+
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
%span.dropdown-toggle-text
Toggle Dropdown
= icon('chevron-down')
-
+
%ul.dropdown-menu
%li
%a
@@ -29,10 +29,4 @@
item!
```
-1. Include the mixin in CSS
-
- ```SCSS
- @include new-style-dropdown('.my-dropdown ');
- ```
-
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 10f4c5a0902..1cd66f27492 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -86,34 +86,34 @@ followed by any global declarations, then a blank newline prior to any imports o
#### Modules, Imports, and Exports
1. Use ES module syntax to import modules
- ```javascript
- // bad
- const SomeClass = require('some_class');
+ ```javascript
+ // bad
+ const SomeClass = require('some_class');
- // good
- import SomeClass from 'some_class';
+ // good
+ import SomeClass from 'some_class';
- // bad
- module.exports = SomeClass;
+ // bad
+ module.exports = SomeClass;
- // good
- export default SomeClass;
- ```
-
- Import statements are following usual naming guidelines, for example object literals use camel case:
-
- ```javascript
- // some_object file
- export default {
- key: 'value',
- };
-
- // bad
- import ObjectLiteral from 'some_object';
+ // good
+ export default SomeClass;
+ ```
+
+ Import statements are following usual naming guidelines, for example object literals use camel case:
- // good
- import objectLiteral from 'some_object';
- ```
+ ```javascript
+ // some_object file
+ export default {
+ key: 'value',
+ };
+
+ // bad
+ import ObjectLiteral from 'some_object';
+
+ // good
+ import objectLiteral from 'some_object';
+ ```
1. Relative paths: when importing a module in the same directory, a child
directory, or an immediate parent directory prefer relative paths. When
@@ -334,33 +334,33 @@ A forEach will cause side effects, it will be mutating the array being iterated.
#### Alignment
1. Follow these alignment styles for the template method:
1. With more than one attribute, all attributes should be on a new line:
- ```javascript
- // bad
- <component v-if="bar"
- param="baz" />
+ ```javascript
+ // bad
+ <component v-if="bar"
+ param="baz" />
- <button class="btn">Click me</button>
+ <button class="btn">Click me</button>
- // good
- <component
- v-if="bar"
- param="baz"
- />
+ // good
+ <component
+ v-if="bar"
+ param="baz"
+ />
- <button class="btn">
- Click me
- </button>
- ```
+ <button class="btn">
+ Click me
+ </button>
+ ```
1. The tag can be inline if there is only one attribute:
- ```javascript
- // good
- <component bar="bar" />
+ ```javascript
+ // good
+ <component bar="bar" />
- // good
- <component
- bar="bar"
- />
- ```
+ // good
+ <component
+ bar="bar"
+ />
+ ```
#### Quotes
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
@@ -414,7 +414,6 @@ A forEach will cause side effects, it will be mutating the array being iterated.
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.
On those a default key should not be provided.
-
```javascript
// good
props: {
@@ -494,21 +493,20 @@ On those a default key should not be provided.
#### Ordering
1. Tag order in `.vue` file
-
- ```
- <script>
- // ...
- </script>
-
- <template>
- // ...
- </template>
-
- // We don't use scoped styles but there are few instances of this
- <style>
- // ...
- </style>
- ```
+ ```
+ <script>
+ // ...
+ </script>
+
+ <template>
+ // ...
+ </template>
+
+ // We don't use scoped styles but there are few instances of this
+ <style>
+ // ...
+ </style>
+ ```
1. Properties in a Vue Component:
1. `name`
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index c2ca8966a3f..5786287d00c 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -8,7 +8,7 @@ might encounter or should avoid during development of GitLab CE and EE.
Consider the following factory:
```ruby
-FactoryGirl.define do
+FactoryBot.define do
factory :label do
sequence(:title) { |n| "label#{n}" }
end
@@ -53,7 +53,7 @@ When run, this spec doesn't do what we might expect:
(compared using ==)
```
-That's because FactoryGirl sequences are not reseted for each example.
+That's because FactoryBot sequences are not reseted for each example.
Please remember that sequence-generated values exist only to avoid having to
explicitly set attributes that have a uniqueness constraint when using a factory.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 4b65a0f4a35..f493ad4ae66 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -215,6 +215,9 @@ There is also and alternative method to [translate messages from validation erro
sprintf(__('Hello %{username}'), { username: 'Joe' }) => 'Hello Joe'
```
+The placeholders should match the code style of the respective source file.
+For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+
### Plurals
- In Ruby/HAML:
@@ -259,6 +262,21 @@ Sometimes you need to add some context to the text that you want to translate
s__('OpenedNDaysAgo|Opened')
```
+### Dates / times
+
+- In JavaScript:
+
+```js
+import { createDateTimeFormat } from '.../locale';
+
+const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
+console.log(dateFormat.format(new Date('2063-04-05'))) // April 5, 2063
+```
+
+This makes use of [`Intl.DateTimeFormat`].
+
+[`Intl.DateTimeFormat`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
+
## Adding a new language
Let's suppose you want to add translations for a new language, let's say French.
diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md
index 4cb2624c098..8aa0462d213 100644
--- a/doc/development/i18n/index.md
+++ b/doc/development/i18n/index.md
@@ -59,6 +59,7 @@ Requests to become a proof reader will be considered on the merits of previous t
- French
- German
- Italian
+ - [Paolo Falomo](https://crowdin.com/profile/paolo.falomo)
- Japanese
- Korean
- [Huang Tao](https://crowdin.com/profile/htve)
diff --git a/doc/development/limit_ee_conflicts.md b/doc/development/limit_ee_conflicts.md
deleted file mode 100644
index ba82babb38a..00000000000
--- a/doc/development/limit_ee_conflicts.md
+++ /dev/null
@@ -1,347 +0,0 @@
-# Limit conflicts with EE when developing on CE
-
-This guide contains best-practices for avoiding conflicts between CE and EE.
-
-## Daily CE Upstream merge
-
-GitLab Community Edition is merged daily into the Enterprise Edition (look for
-the [`CE Upstream` merge requests]). The daily merge is currently done manually
-by four individuals.
-
-**If a developer pings you in a `CE Upstream` merge request for help with
-resolving conflicts, please help them because it means that you didn't do your
-job to reduce the conflicts nor to ease their resolution in the first place!**
-
-To avoid the conflicts beforehand when working on CE, there are a few tools and
-techniques that can help you:
-
-- know what are the usual types of conflicts and how to prevent them
-- the CI `rake ee_compat_check` job tells you if you need to open an EE-version
- of your CE merge request
-
-[`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream
-
-## Check the status of the CI `rake ee_compat_check` job
-
-For each commit (except on `master`), the `rake ee_compat_check` CI job tries to
-detect if the current branch's changes will conflict during the CE->EE merge.
-
-The job reports what files are conflicting and how to setup a merge request
-against EE. Here is roughly how it works:
-
-1. Generates the diff between your branch and current CE `master`
-1. Tries to apply it to current EE `master`
-1. If it applies cleanly, the job succeeds, otherwise...
-1. Detects a branch with the `-ee` suffix in EE
-1. If it exists, generate the diff between this branch and current EE `master`
-1. Tries to apply it to current EE `master`
-1. If it applies cleanly, the job succeeds
-
-In the case where the job fails, it means you should create a `<ce_branch>-ee`
-branch, push it to EE and open a merge request against EE `master`. At this
-point if you retry the failing job in your CE merge request, it should now pass.
-
-Notes:
-
-- This task is not a silver-bullet, its current goal is to bring awareness to
- developers that their work needs to be ported to EE.
-- Community contributors shouldn't submit merge requests against EE, but
- reviewers should take actions by either creating such EE merge request or
- asking a GitLab developer to do it once the merge request is merged.
-- If you branch is more than 500 commits behind `master`, the job will fail and
- you should rebase your branch upon latest `master`.
-- Code reviews for merge requests often consist of multiple iterations of
- feedback and fixes. There is no need to update your EE MR after each
- iteration. Instead, create an EE MR as soon as you see the
- `rake ee_compat_check` job failing. After you receive the final acceptance
- from a Maintainer (but before the CE MR is merged) update the EE MR.
- This helps to identify significant conflicts sooner, but also reduces the
- number of times you have to resolve conflicts.
-- You can use [`git rerere`](https://git-scm.com/blog/2010/03/08/rerere.html)
- to avoid resolving the same conflicts multiple times.
-
-## Possible type of conflicts
-
-### Controllers
-
-#### List or arrays are augmented in EE
-
-In controllers, the most common type of conflict is with `before_action` that
-has a list of actions in CE but EE adds some actions to that list.
-
-The same problem often occurs for `params.require` / `params.permit` calls.
-
-##### Mitigations
-
-Separate CE and EE actions/keywords. For instance for `params.require` in
-`ProjectsController`:
-
-```ruby
-def project_params
- params.require(:project).permit(project_params_ce)
- # On EE, this is always:
- # params.require(:project).permit(project_params_ce << project_params_ee)
-end
-
-# Always returns an array of symbols, created however best fits the use case.
-# It _should_ be sorted alphabetically.
-def project_params_ce
- %i[
- description
- name
- path
- ]
-end
-
-# (On EE)
-def project_params_ee
- %i[
- approvals_before_merge
- approver_group_ids
- approver_ids
- ...
- ]
-end
-```
-
-#### Additional condition(s) in EE
-
-For instance for LDAP:
-
-```diff
- def destroy
- @key = current_user.keys.find(params[:id])
- - @key.destroy
- + @key.destroy unless @key.is_a? LDAPKey
-
- respond_to do |format|
-```
-
-Or for Geo:
-
-```diff
-def after_sign_out_path_for(resource)
-- current_application_settings.after_sign_out_path.presence || new_user_session_path
-+ if Gitlab::Geo.secondary?
-+ Gitlab::Geo.primary_node.oauth_logout_url(@geo_logout_state)
-+ else
-+ current_application_settings.after_sign_out_path.presence || new_user_session_path
-+ end
-end
-```
-
-Or even for audit log:
-
-```diff
-def approve_access_request
-- Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
-+ member = Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute
-+
-+ log_audit_event(member, action: :create)
-
- redirect_to polymorphic_url([membershipable, :members])
-end
-```
-
-### Views
-
-#### Additional view code in EE
-
-A block of code added in CE conflicts because there is already another block
-at the same place in EE
-
-##### Mitigations
-
-Blocks of code that are EE-specific should be moved to partials as much as
-possible to avoid conflicts with big chunks of HAML code that that are not fun
-to resolve when you add the indentation to the equation.
-
-For instance this kind of thing:
-
-```haml
-.form-group.detail-page-description
- = form.label :description, 'Description', class: 'control-label'
- .col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: form, attr: :description,
- classes: 'note-textarea',
- placeholder: "Write a comment or drag your files here...",
- supports_quick_actions: !issuable.persisted?
- = render 'projects/notes/hints', supports_quick_actions: !issuable.persisted?
- .clearfix
- .error-alert
-- if issuable.is_a?(Issue)
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = form.label :confidential do
- = form.check_box :confidential
- This issue is confidential and should only be visible to team members with at least Reporter access.
-- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
- - has_due_date = issuable.has_attribute?(:due_date)
- %hr
- .row
- %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
- .form-group.issue-assignee
- = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- - if issuable.assignee_id
- = form.hidden_field :assignee_id
- = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
- .form-group.issue-milestone
- = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
- .form-group
- - has_labels = @labels && @labels.any?
- = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
- = form.hidden_field :label_ids, multiple: true, value: ''
- .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
- .issuable-form-select-holder
- = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
- - if issuable.respond_to?(:weight)
- - weight_options = Issue.weight_options
- - weight_options.delete(Issue::WEIGHT_ALL)
- - weight_options.delete(Issue::WEIGHT_ANY)
- .form-group
- = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
- Weight
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- - if issuable.weight
- = form.hidden_field :weight
- = dropdown_tag(issuable.weight || "Weight", options: { title: "Select weight", toggle_class: 'js-weight-select js-issuable-form-weight', dropdown_class: "dropdown-menu-selectable dropdown-menu-weight",
- placeholder: "Search weight", data: { field_name: "#{issuable.class.model_name.param_key}[weight]" , default_label: "Weight" } }) do
- %ul
- - weight_options.each do |weight|
- %li
- %a{href: "#", data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight)}
- = weight
- - if has_due_date
- .col-lg-6
- .form-group
- = form.label :due_date, "Due date", class: "control-label"
- .col-sm-10
- .issuable-form-select-holder
- = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
-```
-
-could be simplified by using partials:
-
-```haml
-= render 'shared/issuable/form/description', issuable: issuable, form: form
-
-- if issuable.respond_to?(:confidential)
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = form.label :confidential do
- = form.check_box :confidential
- This issue is confidential and should only be visible to team members with at least Reporter access.
-
-= render 'shared/issuable/form/metadata', issuable: issuable, form: form
-```
-
-and then the `app/views/shared/issuable/form/_metadata.html.haml` could be as follows:
-
-```haml
-- issuable = local_assigns.fetch(:issuable)
-
-- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
-
-- has_due_date = issuable.has_attribute?(:due_date)
-- has_labels = @labels && @labels.any?
-- form = local_assigns.fetch(:form)
-
-%hr
-.row
- %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
- .form-group.issue-assignee
- = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- - if issuable.assignee_id
- = form.hidden_field :assignee_id
- = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
- .form-group.issue-milestone
- = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
- .form-group
- - has_labels = @labels && @labels.any?
- = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
- = form.hidden_field :label_ids, multiple: true, value: ''
- .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
- .issuable-form-select-holder
- = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
-
- = render "shared/issuable/form/weight", issuable: issuable, form: form
-
- - if has_due_date
- .col-lg-6
- .form-group
- = form.label :due_date, "Due date", class: "control-label"
- .col-sm-10
- .issuable-form-select-holder
- = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
-```
-
-and then the `app/views/shared/issuable/form/_weight.html.haml` could be as follows:
-
-```haml
-- issuable = local_assigns.fetch(:issuable)
-
-- return unless issuable.respond_to?(:weight)
-
-- has_due_date = issuable.has_attribute?(:due_date)
-- form = local_assigns.fetch(:form)
-
-.form-group
- = form.label :label_ids, class: "control-label #{"col-lg-4" if has_due_date}" do
- Weight
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- - if issuable.weight
- = form.hidden_field :weight
-
- = weight_dropdown_tag(issuable, toggle_class: 'js-issuable-form-weight') do
- %ul
- - Issue.weight_options.each do |weight|
- %li
- %a{ href: '#', data: { id: weight, none: weight === Issue::WEIGHT_NONE }, class: ("is-active" if issuable.weight == weight) }
- = weight
-```
-
-Note:
-
-- The safeguards at the top allow to get rid of an unneccessary indentation level
-- Here we only moved the 'Weight' code to a partial since this is the only
- EE-specific code in that view, so it's the most likely to conflict, but you
- are encouraged to use partials even for code that's in CE to logically split
- big views into several smaller files.
-
-#### Indentation issue
-
-Sometimes a code block is indented more or less in EE because there's an
-additional condition.
-
-##### Mitigations
-
-Blocks of code that are EE-specific should be moved to partials as much as
-possible to avoid conflicts with big chunks of HAML code that that are not fun
-to resolve when you add the indentation in the equation.
-
-### Assets
-
-#### gitlab-svgs
-
-Conflicts in `app/assets/images/icons.json` or `app/assets/images/icons.svg` can be resolved simply by regenerating those assets with [`yarn run svg`](https://gitlab.com/gitlab-org/gitlab-svgs).
-
----
-
-[Return to Development documentation](README.md)
diff --git a/doc/development/module_with_instance_variables.md b/doc/development/module_with_instance_variables.md
new file mode 100644
index 00000000000..48a1b7f847e
--- /dev/null
+++ b/doc/development/module_with_instance_variables.md
@@ -0,0 +1,242 @@
+## Modules with instance variables could be considered harmful
+
+### Background
+
+Rails somehow encourages people using modules and instance variables
+everywhere. For example, using instance variables in the controllers,
+helpers, and views. They're also encouraging the use of
+`ActiveSupport::Concern`, which further strengthens the idea of
+saving everything in a giant, single object, and people could access
+everything in that one giant object.
+
+### The problems
+
+Of course this is convenient to develop, because we just have everything
+within reach. However this has a number of downsides when that chosen object
+is growing, it would later become out of control for the same reason.
+
+There are just too many things in the same context, and we don't know if
+those things are tightly coupled or not, depending on each others or not.
+It's very hard to tell when the complexity grows to a point, and it makes
+tracking the code also extremely hard. For example, a class could be using
+3 different instance variables, and all of them could be initialized and
+manipulated from 3 different modules. It's hard to track when those variables
+start giving us troubles. We don't know which module would suddenly change
+one of the variables. Everything could touch anything.
+
+### Similar concerns
+
+People are saying multiple inheritance is bad. Mixing multiple modules with
+multiple instance variables scattering everywhere suffer from the same issue.
+The same applies to `ActiveSupport::Concern`. See:
+[Consider replacing concerns with dedicated classes & composition](
+https://gitlab.com/gitlab-org/gitlab-ce/issues/23786)
+
+There's also a similar idea:
+[Use decorators and interface segregation to solve overgrowing models problem](
+https://gitlab.com/gitlab-org/gitlab-ce/issues/13484)
+
+Note that `included` doesn't solve the whole issue. They define the
+dependencies, but they still allow each modules to talk implicitly via the
+instance variables in the final giant object, and that's where the problem is.
+
+### Solutions
+
+We should split the giant object into multiple objects, and they communicate
+with each other with the API, i.e. public methods. In short, composition over
+inheritance. This way, each smaller objects would have their own respective
+limited states, i.e. instance variables. If one instance variable goes wrong,
+we would be very clear that it's from that single small object, because
+no one else could be touching it.
+
+With clearly defined API, this would make things less coupled and much easier
+to debug and track, and much more extensible for other objects to use, because
+they communicate in a clear way, rather than implicit dependencies.
+
+### Acceptable use
+
+However, it's not always bad to use instance variables in a module,
+as long as it's contained in the same module; that is, no other modules or
+objects are touching them, then it would be an acceptable use.
+
+We especially allow the case where a single instance variable is used with
+`||=` to setup the value. This would look like:
+
+``` ruby
+module M
+ def f
+ @f ||= true
+ end
+end
+```
+
+Unfortunately it's not easy to code more complex rules into the cop, so
+we rely on people's best judgement. If we could find another good pattern
+we could easily add to the cop, we should do it.
+
+### How to rewrite and avoid disabling this cop
+
+Even if we could just disable the cop, we should avoid doing so. Some code
+could be easily rewritten in simple form. Consider this acceptable method:
+
+``` ruby
+module Gitlab
+ module Emoji
+ def emoji_unicode_version(name)
+ @emoji_unicode_versions_by_name ||=
+ JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json')))
+ @emoji_unicode_versions_by_name[name]
+ end
+ end
+end
+```
+
+This method is totally fine because it's already self-contained. No other
+methods should be using `@emoji_unicode_versions_by_name` and we're good.
+However it's still offending the cop because it's not just `||=`, and the
+cop is not smart enough to judge that this is fine.
+
+On the other hand, we could split this method into two:
+
+``` ruby
+module Gitlab
+ module Emoji
+ def emoji_unicode_version(name)
+ emoji_unicode_versions_by_name[name]
+ end
+
+ private
+
+ def emoji_unicode_versions_by_name
+ @emoji_unicode_versions_by_name ||=
+ JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json')))
+ end
+ end
+end
+```
+
+Now the cop won't complain. Here's a bad example which we could rewrite:
+
+``` ruby
+module SpamCheckService
+ def filter_spam_check_params
+ @request = params.delete(:request)
+ @api = params.delete(:api)
+ @recaptcha_verified = params.delete(:recaptcha_verified)
+ @spam_log_id = params.delete(:spam_log_id)
+ end
+
+ def spam_check(spammable, user)
+ spam_service = SpamService.new(spammable, @request)
+
+ spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
+ user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
+ end
+ end
+end
+```
+
+There are several implicit dependencies here. First, `params` should be
+defined before use. Second, `filter_spam_check_params` should be called
+before `spam_check`. These are all implicit and the includer could be using
+those instance variables without awareness.
+
+This should be rewritten like:
+
+``` ruby
+class SpamCheckService
+ def initialize(request:, api:, recaptcha_verified:, spam_log_id:)
+ @request = request
+ @api = api
+ @recaptcha_verified = recaptcha_verified
+ @spam_log_id = spam_log_id
+ end
+
+ def spam_check(spammable, user)
+ spam_service = SpamService.new(spammable, @request)
+
+ spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
+ user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
+ end
+ end
+end
+```
+
+And use it like:
+
+``` ruby
+class UpdateSnippetService < BaseService
+ def execute
+ # ...
+ spam = SpamCheckService.new(params.slice!(:request, :api, :recaptcha_verified, :spam_log_id))
+
+ spam.check(snippet, current_user)
+ # ...
+ end
+end
+```
+
+This way, all those instance variables are isolated in `SpamCheckService`
+rather than whatever includes the module, and those modules which were also
+included, making it much easier to track down any issues,
+and reducing the chance of having name conflicts.
+
+### How to disable this cop
+
+Put the disabling comment right after your code in the same line:
+
+``` ruby
+module M
+ def violating_method
+ @f + @g # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+end
+```
+
+If there are multiple lines, you could also enable and disable for a section:
+
+``` ruby
+module M
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def violating_method
+ @f = 0
+ @g = 1
+ @h = 2
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+end
+```
+
+Note that you need to enable it at some point, otherwise everything below
+won't be checked.
+
+### Things we might need to ignore right now
+
+Because of the way Rails helpers and mailers work, we might not be able to
+avoid the use of instance variables there. For those cases, we could ignore
+them at the moment. At least we're not going to share those modules with
+other random objects, so they're still somewhat isolated.
+
+### Instance variables in views
+
+They're bad because we can't easily tell who's using the instance variables
+(from controller's point of view) and where we set them up (from partials'
+point of view), making it extremely hard to track data dependency.
+
+We're trying to use something like this instead:
+
+``` haml
+= render 'projects/commits/commit', commit: commit, ref: ref, project: project
+```
+
+And in the partial:
+
+``` haml
+- ref = local_assigns.fetch(:ref)
+- commit = local_assigns.fetch(:commit)
+- project = local_assigns.fetch(:project)
+```
+
+This way it's clearer where those values were coming from, and we gain the
+benefit to have typo check over using instance variables. In the future,
+we should also forbid the use of instance variables in partials.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 04419650b12..e7c5a6ca07a 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -37,7 +37,7 @@ graphs/dashboards.
GitLab provides built-in tools to aid the process of improving performance:
* [Sherlock](profiling.md#sherlock)
-* [GitLab Performance Monitoring](../administration/monitoring/performance/introduction.md)
+* [GitLab Performance Monitoring](../administration/monitoring/performance/index.md)
* [Request Profiling](../administration/monitoring/performance/request_profiling.md)
* [QueryRecoder](query_recorder.md) for preventing `N+1` regressions
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index 085fb8f902c..59ebf41e09f 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -9,25 +9,54 @@ All workers should include `ApplicationWorker` instead of `Sidekiq::Worker`,
which adds some convenience methods and automatically sets the queue based on
the worker's name.
-## Default Queue
+## Dedicated Queues
-Use of the "default" queue is not allowed. Every worker should use a queue that
-matches the worker's purpose the closest. For example, workers that are to be
-executed periodically should use the "cronjob" queue.
+All workers should use their own queue, which is automatically set based on the
+worker class name. For a worker named `ProcessSomethingWorker`, the queue name
+would be `process_something`. If you're not sure what queue a worker uses,
+you can find it using `SomeWorker.queue`. There is almost never a reason to
+manually override the queue name using `sidekiq_options queue: :some_queue`.
-A list of all available queues can be found in `config/sidekiq_queues.yml`.
+## Queue Namespaces
-## Dedicated Queues
+While different workers cannot share a queue, they can share a queue namespace.
-Most workers should use their own queue, which is automatically set based on the
-worker class name. For a worker named `ProcessSomethingWorker`, the queue name
-would be `process_something`. If you're not sure what a worker's queue name is,
-you can find it using `SomeWorker.queue`.
+Defining a queue namespace for a worker makes it possible to start a Sidekiq
+process that automatically handles jobs for all workers in that namespace,
+without needing to explicitly list all their queue names. If, for example, all
+workers that are managed by sidekiq-cron use the `cronjob` queue namespace, we
+can spin up a Sidekiq process specifically for these kinds of scheduled jobs.
+If a new worker using the `cronjob` namespace is added later on, the Sidekiq
+process will automatically pick up jobs for that worker too (after having been
+restarted), without the need to change any configuration.
+
+A queue namespace can be set using the `queue_namespace` DSL class method:
+
+```ruby
+class SomeScheduledTaskWorker
+ include ApplicationWorker
+
+ queue_namespace :cronjob
+
+ # ...
+end
+```
+
+Behind the scenes, this will set `SomeScheduledTaskWorker.queue` to
+`cronjob:some_scheduled_task`. Commonly used namespaces will have their own
+concern module that can easily be included into the worker class, and that may
+set other Sidekiq options besides the queue namespace. `CronjobQueue`, for
+example, sets the namespace, but also disables retries.
+
+`bundle exec sidekiq` is namespace-aware, and will automatically listen on all
+queues in a namespace (technically: all queues prefixed with the namespace name)
+when a namespace is provided instead of a simple queue name in the `--queue`
+(`-q`) option, or in the `:queues:` section in `config/sidekiq_queues.yml`.
-In some cases multiple workers do use the same queue. For example, the various
-workers for updating CI pipelines all use the `pipeline` queue. Adding workers
-to existing queues should be done with care, as adding more workers can lead to
-slow jobs blocking work (even for different jobs) on the shared queue.
+Note that adding a worker to an existing namespace should be done with care, as
+the extra jobs will take resources away from jobs from workers that were already
+there, if the resources available to the Sidekiq process handling the namespace
+are not adjusted appropriately.
## Tests
@@ -36,7 +65,7 @@ tests should be placed in `spec/workers`.
## Removing or renaming queues
-Try to avoid renaming or removing queues in minor and patch releases.
+Try to avoid renaming or removing workers and their queues in minor and patch releases.
During online update instance can have pending jobs and removing the queue can
lead to those jobs being stuck forever. If you can't write migration for those
Sidekiq jobs, please consider doing rename or remove queue in major release only.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 8b7b015427f..edb8f372ea3 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -8,8 +8,8 @@ and effective _as well as_ fast.
Here are some things to keep in mind regarding test performance:
-- `double` and `spy` are faster than `FactoryGirl.build(...)`
-- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`.
+- `double` and `spy` are faster than `FactoryBot.build(...)`
+- `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
`spy`, or `double` will do. Database persistence is slow!
- Don't mark a feature as requiring JavaScript (through `@javascript` in
@@ -254,13 +254,13 @@ end
### Factories
-GitLab uses [factory_girl] as a test fixture replacement.
+GitLab uses [factory_bot] as a test fixture replacement.
- Factory definitions live in `spec/factories/`, named using the pluralization
of their corresponding model (`User` factories are defined in `users.rb`).
- There should be only one top-level factory definition per file.
-- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
- should) call `create(...)` instead of `FactoryGirl.create(...)`.
+- FactoryBot methods are mixed in to all RSpec groups. This means you can (and
+ should) call `create(...)` instead of `FactoryBot.create(...)`.
- Make use of [traits] to clean up definitions and usages.
- When defining a factory, don't define attributes that are not required for the
resulting record to pass validation.
@@ -269,8 +269,8 @@ GitLab uses [factory_girl] as a test fixture replacement.
- Factories don't have to be limited to `ActiveRecord` objects.
[See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
-[factory_girl]: https://github.com/thoughtbot/factory_girl
-[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
+[factory_bot]: https://github.com/thoughtbot/factory_bot
+[traits]: http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Traits
### Fixtures
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 8045bbad7ba..65386f231a0 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -33,7 +33,7 @@ changes should be tested.
## [Testing best practices](best_practices.md)
-Everything you should know about how to write good tests: RSpec, FactoryGirl,
+Everything you should know about how to write good tests: RSpec, FactoryBot,
system tests, parameterized tests etc.
---
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index 16a811dbc74..d396964e7c1 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -10,7 +10,7 @@
* [Tables](#tables)
* [Blocks](#blocks)
* [Panels](#panels)
-* [Dialog modals](#dialog-modals)
+* [Modals](#modals)
* [Alerts](#alerts)
* [Forms](#forms)
* [Search box](#search-box)
@@ -255,18 +255,18 @@ Skeleton loading can replace any existing UI elements for the period in which th
---
-## Dialog modals
+## Modals
-Dialog modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal.
+Modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal.
### Usage
-* When the action is irreversible, dialog modals provide the details and confirm with the user before they take an advanced action.
-* When the action will affect privacy or authorization, dialog modals provide advanced information and confirm with the user.
+* When the action is irreversible, modals provide the details and confirm with the user before they take an advanced action.
+* When the action will affect privacy or authorization, modals provide advanced information and confirm with the user.
### Style
-* Dialog modals contain the header, body, and actions.
+* Modals contain the header, body, and actions.
* **Header(1):** The header title is a question instead of a descriptive phrase.
* **Body(2):** The content in body should never be ambiguous and unclear. It provides specific information.
* **Actions(3):** Contains a affirmative action, a dismissive action, and an extra action. The order of actions from left to right: Dismissive action → Extra action → Affirmative action
@@ -277,13 +277,13 @@ Dialog modals are only used for having a conversation and confirmation with the
### Placement
-* Dialog modals should always be the center of the screen horizontally and be positioned **72px** from the top.
+* Modals should always be the center of the screen horizontally and be positioned **72px** from the top.
-| Dialog with 2 actions | Dialog with 3 actions | Special confirmation |
+| Modal with 2 actions | Modal with 3 actions | Special confirmation |
| --------------------- | --------------------- | -------------------- |
| ![two-actions](img/modals-general-confimation-dialog.png) | ![three-actions](img/modals-three-buttons.png) | ![spcial-confirmation](img/modals-special-confimation-dialog.png) |
-> TODO: Special case for dialog modal.
+> TODO: Special case for modal.
---
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index 12e8d0a31bb..af842da7f62 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -46,11 +46,11 @@ Avoid using periods in solitary sentences in these elements:
* Labels
* Hover text
* Bulleted lists
-* Dialog body text
+* Modal body text
Periods should be used for:
-* Lists or dialogs with multiple sentences
+* Lists or modals with multiple sentences
* Any sentence followed by a link
| :white_check_mark: **Do** place periods after sentences followed by a link | :no_entry_sign: **Don’t** place periods after a link if it‘s not followed by a sentence |
@@ -80,7 +80,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable
| Punctuation mark | Copy and paste | HTML entity | Unicode | Mac shortcut | Windows shortcut | Description |
|---|---|---|---|---|---|---|
-| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and dialog body text.<br><br>Use in lists or dialogs with multiple sentences, and any sentence followed by a link or inline code.<br><br>Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. |
+| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and modal body text.<br><br>Use in lists or modals with multiple sentences, and any sentence followed by a link or inline code.<br><br>Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. |
| Comma | **,** | | | | | Place inside quotation marks.<br><br>Use a [serial comma][serial comma] in lists of three or more terms. |
| Exclamation point | **!** | | | | | Avoid exclamation points as they tend to come across as shouting. Some exceptions include greetings or congratulatory messages. |
| Colon | **:** | `&#58;` | `\u003A` | | | Omit from labels, for example, in the labels for fields in a form. |
@@ -88,7 +88,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable
| Quotation marks | **“**<br><br>**”**<br><br>**‘**<br><br>**’** | `&ldquo;`<br><br>`&rdquo;`<br><br>`&lsquo;`<br><br>`&rsquo;` | `\u201C`<br><br>`\u201D`<br><br>`\u2018`<br><br>`\u2019` | <kbd>⌥ Option</kbd>+<kbd>[</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>[</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>]</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>]</kbd> | <kbd>Alt</kbd>+<kbd>0 1 4 7</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 8</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 5</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 6</kbd> | Use proper quotation marks (also known as smart quotes, curly quotes, or typographer’s quotes) for quotes. Single quotation marks are used for quotes inside of quotes.<br><br>The right single quotation mark symbol is also used for apostrophes.<br><br>Don’t use primes, straight quotes, or free-standing accents for quotation marks. |
| Primes | **′**<br><br>**″** | `&prime;`<br><br>`&Prime;` | `\u2032`<br><br>`\u2033` | | <kbd>Alt</kbd>+<kbd>8 2 4 2</kbd><br><br><kbd>Alt</kbd>+<kbd>8 2 4 3</kbd> | Use prime (′) only in abbreviations for feet, arcminutes, and minutes: 3° 15′<br><br>Use double-prime (″) only in abbreviations for inches, arcseconds, and seconds: 3° 15′ 35″<br><br>Don’t use quotation marks, straight quotes, or free-standing accents for primes. |
| Straight quotes and accents | **"**<br><br>**'**<br><br>**`**<br><br>**´** | `&quot;`<br><br>`&#39;`<br><br>`&#96;`<br><br>`&acute;` | `\u0022`<br><br>`\u0027`<br><br>`\u0060`<br><br>`\u00B4` | | | Don’t use straight quotes or free-standing accents for primes or quotation marks.<br><br>Proper typography never uses straight quotes. They are left over from the age of typewriters and their only modern use is for code. |
-| Ellipsis | **…** | `&hellip;` | | <kbd>⌥ Option</kbd>+<kbd>;</kbd> | <kbd>Alt</kbd>+<kbd>0 1 3 3</kbd> | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.<br><br>Omit from menu items or buttons that open a dialog or start some other process. |
+| Ellipsis | **…** | `&hellip;` | | <kbd>⌥ Option</kbd>+<kbd>;</kbd> | <kbd>Alt</kbd>+<kbd>0 1 3 3</kbd> | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.<br><br>Omit from menu items or buttons that open a modal or start some other process. |
| Chevrons | **«**<br><br>**»**<br><br>**‹**<br><br>**›**<br><br>**<**<br><br>**>** | `&#171;`<br><br>`&#187;`<br><br>`&#8249;`<br><br>`&#8250;`<br><br>`&lt;`<br><br>`&gt;` | `\u00AB`<br><br>`\u00BB`<br><br>`\u2039`<br><br>`\u203A`<br><br>`\u003C`<br><br>`\u003E`<br><br> | | | Omit from links or buttons that open another page or move to the next or previous step in a process. Also known as angle brackets, angular quote brackets, or guillemets. |
| Em dash | **—** | `&mdash;` | `\u2014` | <kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>-</kbd> | <kbd>Alt</kbd>+<kbd>0 1 5 1</kbd> | Avoid using dashes to separate text. If you must use dashes for this purpose — like this — use an em dash surrounded by spaces. |
| En dash | **–** | `&ndash;` | `\u2013` | <kbd>⌥ Option</kbd>+<kbd>-</kbd> | <kbd>Alt</kbd>+<kbd>0 1 5 0</kbd> | Use an en dash without spaces instead of a hyphen to indicate a range of values, such as numbers, times, and dates: “3–5 kg”, “8:00 AM–12:30 PM”, “10–17 Jan” |
@@ -175,7 +175,7 @@ A **comment** is a written piece of text that users of GitLab can create. Commen
#### Discussion
A **discussion** is a group of 1 or more comments. A discussion can include subdiscussions. Some discussions have the special capability of being able to be **resolved**. Both the comments in the discussion and the discussion itself can be resolved.
-## Confirmation dialogs
+## Modals
- Destruction buttons should be clear and always say what they are destroying.
E.g., `Delete page` instead of just `Delete`.
@@ -184,6 +184,8 @@ A **discussion** is a group of 1 or more comments. A discussion can include subd
- Avoid the word `cancel` or `canceled` in the descriptive copy. It can be
confusing when you then see the `Cancel` button.
+see also: guidelines for [modal components](components.md#modals)
+
---
Portions of this page are modifications based on work created and shared by the [Android Open Source Project][android project] and used according to terms described in the [Creative Commons 2.5 Attribution License][creative commons].
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index b6def7ef541..43a79ffcaa5 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -1,75 +1,14 @@
# Writing documentation
- **General Documentation**: written by the [developers responsible by creating features](#contributing-to-docs). Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers.
- - **Technical Articles**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
+ - **[Technical Articles](#technical-articles)**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
- **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs).
-## Distinction between General Documentation and Technical Articles
-
-### General documentation
-
-General documentation is categorized by _User_, _Admin_, and _Contributor_, and describe what that feature is, what it does, and its available settings.
-
-### Technical Articles
-
-Technical articles replace technical content that once lived in the [GitLab Blog](https://about.gitlab.com/blog/), where they got out-of-date and weren't easily found.
-
-They are topic-related documentation, written with an user-friendly approach and language, aiming to provide the community with guidance on specific processes to achieve certain objectives.
-
-A technical article guides users and/or admins to achieve certain objectives (within guides and tutorials), or provide an overview of that particular topic or feature (within technical overviews). It can also describe the use, implementation, or integration of third-party tools with GitLab.
-
-They live under `doc/articles/article-title/index.md`, and their images should be placed under `doc/articles/article-title/img/`. Find a list of existing [technical articles](../articles/index.md) here.
-
-#### Types of Technical Articles
-
-- **User guides**: technical content to guide regular users from point A to point B
-- **Admin guides**: technical content to guide administrators of GitLab instances from point A to point B
-- **Technical Overviews**: technical content describing features, solutions, and third-party integrations
-- **Tutorials**: technical content provided step-by-step on how to do things, or how to reach very specific objectives
-
-#### Understanding guides, tutorials, and technical overviews
-
-Suppose there's a process to go from point A to point B in 5 steps: `(A) 1 > 2 > 3 > 4 > 5 (B)`.
-
-A **guide** can be understood as a description of certain processes to achieve a particular objective. A guide brings you from A to B describing the characteristics of that process, but not necessarily going over each step. It can mention, for example, steps 2 and 3, but does not necessarily explain how to accomplish them.
-
-- Live example: "GitLab Pages from A to Z - [Part 1](../user/project/pages/getting_started_part_one.md) to [Part 4](../user/project/pages/getting_started_part_four.md)"
-
-A **tutorial** requires a clear **step-by-step** guidance to achieve a singular objective. It brings you from A to B, describing precisely all the necessary steps involved in that process, showing each of the 5 steps to go from A to B.
-It does not only describes steps 2 and 3, but also shows you how to accomplish them.
-
-- Live example (on the blog): [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/)
-
-A **technical overview** is a description of what a certain feature is, and what it does, but does not walk
-through the process of how to use it systematically.
-
-- Live example (on the blog): [GitLab Workflow, an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/)
-
-#### Special format
-
-Every **Technical Article** contains, in the very beginning, a blockquote with the following information:
-
-- A reference to the **type of article** (user guide, admin guide, tech overview, tutorial)
-- A reference to the **knowledge level** expected from the reader to be able to follow through (beginner, intermediate, advanced)
-- A reference to the **author's name** and **GitLab.com handle**
-- A reference of the **publication date**
-
-```md
-> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial ||
-> **Level:** intermediary ||
-> **Author:** [Name Surname](https://gitlab.com/username) ||
-> **Publication date:** AAAA/MM/DD
-```
-
-#### Technical Articles - Writing Method
-
-Use the [writing method](https://about.gitlab.com/handbook/product/technical-writing/#writing-method) defined by the Technical Writing team.
-
## Documentation style guidelines
All the docs follow the same [styleguide](doc_styleguide.md).
-### Contributing to docs
+## Contributing to docs
Whenever a feature is changed, updated, introduced, or deprecated, the merge
request introducing these changes must be accompanied by the documentation
@@ -118,13 +57,31 @@ and for every **major** feature present in Community Edition.
Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future.
-## Testing
+### Previewing locally
-We try to treat documentation as code, thus have implemented some testing.
+To preview your changes to documentation locally, please follow
+this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
+
+### Testing
+
+We treat documentation as code, thus have implemented some testing.
Currently, the following tests are in place:
1. `docs lint`: Check that all internal (relative) links work correctly and
- that all cURL examples in API docs use the full switches.
+ that all cURL examples in API docs use the full switches. It's recommended
+ to [check locally](#previewing-locally) before pushing to GitLab by executing the command
+ `bundle exec nanoc check internal_links` on your local
+ [`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs) directory.
+1. [`ee_compat_check`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#avoiding-ce-gt-ee-merge-conflicts-beforehand) (runs on CE only):
+ When you submit a merge request to GitLab Community Edition (CE),
+ there is this additional job that runs against Enterprise Edition (EE)
+ and checks if your changes can apply cleanly to the EE codebase.
+ If that job fails, read the instructions in the job log for what to do next.
+ As CE is merged into EE once a day, it's important to avoid merge conflicts.
+ Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
+ essential to avoid them.
+
+### Branch naming
If your contribution contains **only** documentation changes, you can speed up
the CI process by following some branch naming conventions. You have three
@@ -139,17 +96,7 @@ choices:
If your branch name matches any of the above, it will run only the docs
tests. If it doesn't, the whole test suite will run (including docs).
----
-
-When you submit a merge request to GitLab Community Edition (CE), there is an
-additional job called `rake ee_compat_check` that runs against Enterprise
-Edition (EE) and checks if your changes can apply cleanly to the EE codebase.
-If that job fails, read the instructions in the job log for what to do next.
-Contributors do not need to submit their changes to EE, GitLab Inc. employees
-on the other hand need to make sure that their changes apply cleanly to both
-CE and EE.
-
-## Previewing the changes live
+### Previewing the changes live
If you want to preview the doc changes of your merge request live, you can use
the manual `review-docs-deploy` job in your merge request. You will need at
@@ -164,7 +111,7 @@ You will need to push a branch to those repositories, it doesn't work for forks.
TIP: **Tip:**
If your branch contains only documentation changes, you can use
-[special branch names](#testing) to avoid long running pipelines.
+[special branch names](#branch-naming) to avoid long running pipelines.
In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
reveal the `review-docs-deploy` job. Hit the play button for the job to start.
@@ -209,12 +156,12 @@ working on. If you don't, the remote docs branch won't be removed either,
and the server where the Review Apps are hosted will eventually be out of
disk space.
-### Behind the scenes
+#### Technical aspects
If you want to know the hot details, here's what's really happening:
1. You manually run the `review-docs-deploy` job in a CE/EE merge request.
-1. The job runs the [`scirpts/trigger-build-docs`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/trigger-build-docs)
+1. The job runs the [`scripts/trigger-build-docs`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/trigger-build-docs)
script with the `deploy` flag, which in turn:
1. Takes your branch name and applies the following:
- The slug of the branch name is used to avoid special characters since
@@ -243,3 +190,65 @@ The following GitLab features are used among others:
- [Review Apps](../ci/review_apps/index.md)
- [Artifacts](../ci/yaml/README.md#artifacts)
- [Specific Runner](../ci/runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
+
+## General Documentation vs Technical Articles
+
+### General documentation
+
+General documentation is categorized by _User_, _Admin_, and _Contributor_, and describe what that feature is, what it does, and its available settings.
+
+### Technical Articles
+
+Technical articles replace technical content that once lived in the [GitLab Blog](https://about.gitlab.com/blog/), where they got out-of-date and weren't easily found.
+
+They are topic-related documentation, written with an user-friendly approach and language, aiming to provide the community with guidance on specific processes to achieve certain objectives.
+
+A technical article guides users and/or admins to achieve certain objectives (within guides and tutorials), or provide an overview of that particular topic or feature (within technical overviews). It can also describe the use, implementation, or integration of third-party tools with GitLab.
+
+They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/article-title/`.
+
+#### Types of Technical Articles
+
+- **User guides**: technical content to guide regular users from point A to point B
+- **Admin guides**: technical content to guide administrators of GitLab instances from point A to point B
+- **Technical Overviews**: technical content describing features, solutions, and third-party integrations
+- **Tutorials**: technical content provided step-by-step on how to do things, or how to reach very specific objectives
+
+#### Understanding guides, tutorials, and technical overviews
+
+Suppose there's a process to go from point A to point B in 5 steps: `(A) 1 > 2 > 3 > 4 > 5 (B)`.
+
+A **guide** can be understood as a description of certain processes to achieve a particular objective. A guide brings you from A to B describing the characteristics of that process, but not necessarily going over each step. It can mention, for example, steps 2 and 3, but does not necessarily explain how to accomplish them.
+
+- Live example: "GitLab Pages from A to Z - [Part 1](../user/project/pages/getting_started_part_one.md) to [Part 4](../user/project/pages/getting_started_part_four.md)"
+
+A **tutorial** requires a clear **step-by-step** guidance to achieve a singular objective. It brings you from A to B, describing precisely all the necessary steps involved in that process, showing each of the 5 steps to go from A to B.
+It does not only describes steps 2 and 3, but also shows you how to accomplish them.
+
+- Live example (on the blog): [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/)
+
+A **technical overview** is a description of what a certain feature is, and what it does, but does not walk
+through the process of how to use it systematically.
+
+- Live example (on the blog): [GitLab Workflow, an overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/)
+
+#### Special format
+
+Every **Technical Article** contains, in the very beginning, a blockquote with the following information:
+
+- A reference to the **type of article** (user guide, admin guide, tech overview, tutorial)
+- A reference to the **knowledge level** expected from the reader to be able to follow through (beginner, intermediate, advanced)
+- A reference to the **author's name** and **GitLab.com handle**
+- A reference of the **publication date**
+
+```md
+> **[Article Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Name Surname](https://gitlab.com/username) ||
+> **Publication date:** AAAA-MM-DD
+```
+
+#### Technical Articles - Writing Method
+
+Use the [writing method](https://about.gitlab.com/handbook/product/technical-writing/#writing-method) defined by the Technical Writing team.
+