summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue9
-rw-r--r--app/models/concerns/diff_positionable_note.rb8
-rw-r--r--app/models/diff_note.rb7
-rw-r--r--app/views/explore/projects/_projects.html.haml3
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml3
-rwxr-xr-xbin/web_puma2
-rw-r--r--changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml5
-rw-r--r--changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml5
-rw-r--r--changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml5
-rw-r--r--changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml5
-rw-r--r--config/initializers/peek.rb7
-rw-r--r--db/migrate/20190703043358_add_commit_id_to_draft_notes.rb11
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/auth/ldap.md17
-rw-r--r--doc/api/README.md7
-rw-r--r--doc/development/testing_guide/end_to_end/index.md24
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md48
-rw-r--r--doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.pngbin64862 -> 0 bytes
-rw-r--r--doc/development/testing_guide/img/review_apps_cicd_architecture.pngbin136431 -> 0 bytes
-rw-r--r--doc/development/testing_guide/review_apps.md23
-rw-r--r--doc/gitlab-basics/create-project.md4
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/security/rack_attack.md77
-rw-r--r--doc/security/rate_limits.md32
-rw-r--r--doc/user/admin_area/settings/img/user_and_ip_rate_limits.pngbin0 -> 232777 bytes
-rw-r--r--doc/user/admin_area/settings/index.md1
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md32
-rw-r--r--doc/user/application_security/index.md32
-rw-r--r--doc/user/gitlab_com/index.md73
-rw-r--r--doc/user/markdown.md43
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md4
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md10
-rw-r--r--doc/workflow/shortcuts.md10
-rw-r--r--lib/gitlab/ee_compat_check.rb2
-rw-r--r--lib/gitlab/metrics/samplers/influx_sampler.rb22
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb23
-rw-r--r--package.json1
-rw-r--r--rubocop/cop/rspec/top_level_describe_path.rb35
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/db/schema_spec.rb2
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb2
-rw-r--r--spec/features/groups/labels/user_sees_links_to_issuables_spec.rb (renamed from spec/features/groups/labels/user_sees_links_to_issuables.rb)6
-rw-r--r--spec/features/instance_statistics/instance_statistics_spec.rb (renamed from spec/features/instance_statistics/instance_statistics.rb)0
-rw-r--r--spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb (renamed from spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb)3
-rw-r--r--spec/features/projects/labels/user_sees_links_to_issuables_spec.rb (renamed from spec/features/projects/labels/user_sees_links_to_issuables.rb)36
-rw-r--r--spec/features/snippets/user_sees_breadcrumb_links.rb19
-rw-r--r--spec/features/user_opens_link_to_comment_spec.rb (renamed from spec/features/user_opens_link_to_comment.rb)0
-rw-r--r--spec/features/users/add_email_to_existing_account_spec.rb (renamed from spec/features/users/add_email_to_existing_account.rb)0
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js2
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js682
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb20
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb20
-rw-r--r--spec/models/ci/build_spec.rb5
-rw-r--r--spec/models/diff_note_spec.rb4
-rw-r--r--spec/rubocop/cop/rspec/top_level_describe_path_spec.rb67
-rw-r--r--spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb52
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb27
-rw-r--r--spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb (renamed from spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb)0
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml_spec.rb (renamed from spec/views/dashboard/projects/_nav.html.haml.rb)4
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb (renamed from spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb)0
-rw-r--r--spec/views/shared/_label_row.html.haml_spec.rb (renamed from spec/views/shared/_label_row.html.haml.rb)13
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml_spec.rb (renamed from spec/views/shared/milestones/_issuable.html.haml.rb)0
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml_spec.rb (renamed from spec/views/shared/milestones/_issuables.html.haml.rb)2
-rw-r--r--spec/views/shared/milestones/_top.html.haml_spec.rb (renamed from spec/views/shared/milestones/_top.html.haml.rb)1
-rw-r--r--yarn.lock16
74 files changed, 1004 insertions, 600 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index de1110f39fa..5a3940bdac2 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -33,8 +33,8 @@ review-docs-deploy:
- gem install gitlab --no-document
- ./$SCRIPT_NAME deploy
only:
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ce
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ee
+ - /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ce
+ - /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ee
except:
- /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index fe369ffec13..2bdde9fedaf 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -73,7 +73,7 @@ gitlab:assets:compile pull-cache:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.compile-assets-metadata:
extends: .dedicated-runner
@@ -111,7 +111,7 @@ compile-assets pull-cache:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
karma:
extends: .dedicated-no-docs-pull-cache-job
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 4da7f404767..78ef346d417 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -31,12 +31,12 @@
.no-docs:
except:
refs:
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.no-docs-and-no-qa:
except:
refs:
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
.dedicated-no-docs-pull-cache-job:
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 1392768127b..24b4eb3a4c1 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -207,7 +207,7 @@ downtime_check:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
dependencies:
- setup-test-env
@@ -223,6 +223,7 @@ ee_compat_check:
- /^security-/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
+ - /(^docs[\/-].+|.+-docs$)/
retry: 0
artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 29ed41ce0c6..076858e6c8a 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -7,7 +7,7 @@
except:
refs:
- master
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.review-schedules-only: &review-schedules-only
only:
@@ -20,7 +20,7 @@
except:
refs:
- tags
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.review-base: &review-base
extends: .dedicated-runner
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 3a5735a2be9..4c97a4feb18 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -71,7 +71,7 @@ flaky-examples-check:
except:
refs:
- master
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
artifacts:
expire_in: 30d
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index e9704584c9f..06477477aad 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -73,15 +73,14 @@ export default {
},
renderBlock() {
return (
- this.job.merge_request ||
this.job.duration ||
- this.job.finished_data ||
+ this.job.finished_at ||
this.job.erased_at ||
this.job.queued ||
+ this.hasTimeout ||
this.job.runner ||
this.job.coverage ||
- this.job.tags.length ||
- this.job.cancel_path
+ this.job.tags.length
);
},
hasArtifact() {
@@ -160,7 +159,7 @@ export default {
</gl-link>
</div>
- <div :class="{ block: renderBlock }">
+ <div v-if="renderBlock" class="block">
<detail-row
v-if="job.duration"
:value="duration"
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index 2d09eff0111..195d9e107c5 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -10,6 +10,8 @@ module DiffPositionableNote
serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+
+ validate :diff_refs_match_commit, if: :for_commit?
end
%i(original_position position change_position).each do |meth|
@@ -71,4 +73,10 @@ module DiffPositionableNote
self.position = result[:position]
end
end
+
+ def diff_refs_match_commit
+ return if self.original_position.diff_refs == commit&.diff_refs
+
+ errors.add(:commit_id, 'does not match the diff refs')
+ end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index f75c32633b1..861185dc222 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -20,7 +20,6 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: -> (_note) { noteable_types } }
validate :positions_complete
validate :verify_supported
- validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
@@ -154,12 +153,6 @@ class DiffNote < Note
errors.add(:position, "is invalid")
end
- def diff_refs_match_commit
- return if self.original_position.diff_refs == self.commit.diff_refs
-
- errors.add(:commit_id, 'does not match the diff refs')
- end
-
def keep_around_commits
shas = [
self.original_position.base_sha,
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 67f2f897137..35b32662b8a 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1 +1,2 @@
-= render 'shared/projects/list', projects: projects, user: current_user
+- is_explore_page = defined?(explore_page) && explore_page
+= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index ed508fa2506..153c90e534e 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -10,4 +10,4 @@
= render 'explore/head'
= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
-= render 'projects', projects: @projects
+= render 'projects', projects: @projects, explore_page: true
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 43503e1d08a..fd3317341f6 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -53,7 +53,7 @@
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
-= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
+= render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project
- if is_dynamic_milestone
.table-holder
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 576ec3e1782..67cb1aa549c 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -21,6 +21,7 @@
- own_projects_current_user_empty_message_header = s_('UserProfile|You haven\'t created any personal projects.')
- own_projects_current_user_empty_message_description = s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.')
- own_projects_visitor_empty_message = s_('UserProfile|This user doesn\'t have any personal projects')
+- explore_page_empty_message = s_('UserProfile|Explore public groups to find projects to contribute to.')
- primary_button_label = _('New project')
- primary_button_link = new_project_path
- secondary_button_label = _('Explore groups')
@@ -58,4 +59,4 @@
current_user_empty_message_description: own_projects_current_user_empty_message_description,
primary_button_label: primary_button_label,
primary_button_link: primary_button_link,
- visitor_empty_message: own_projects_visitor_empty_message }
+ visitor_empty_message: defined?(explore_page) && explore_page ? explore_page_empty_message : own_projects_visitor_empty_message }
diff --git a/bin/web_puma b/bin/web_puma
index 178fe84800d..29d72cd4a41 100755
--- a/bin/web_puma
+++ b/bin/web_puma
@@ -10,7 +10,7 @@ puma_config="$app_root/config/puma.rb"
spawn_puma()
{
- exec bundle exec puma --config "${puma_config}" "$@"
+ exec bundle exec puma --config "${puma_config}" --environment "$RAILS_ENV" "$@"
}
get_puma_pid()
diff --git a/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml b/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml
new file mode 100644
index 00000000000..f719338b9cb
--- /dev/null
+++ b/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Incorrect empty state message on Explore projects
+merge_request: 25578
+author:
+type: fixed
diff --git a/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml b/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml
new file mode 100644
index 00000000000..4c93a108f2b
--- /dev/null
+++ b/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml
@@ -0,0 +1,5 @@
+---
+title: Remove blank block from job sidebar
+merge_request: 30754
+author:
+type: fixed
diff --git a/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml b/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml
new file mode 100644
index 00000000000..7943d9573f3
--- /dev/null
+++ b/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml
@@ -0,0 +1,5 @@
+---
+title: Fix GC::Profiler metrics fetching
+merge_request: 31331
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml b/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml
new file mode 100644
index 00000000000..a0564369b02
--- /dev/null
+++ b/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml
@@ -0,0 +1,5 @@
+---
+title: Make `bin/web_puma` consider RAILS_ENV
+merge_request: 31378
+author:
+type: fixed
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index 4a2b7931f30..b6c7f1ff4fc 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -9,5 +9,10 @@ Peek.into Peek::Views::ActiveRecord
Peek.into Peek::Views::Gitaly
Peek.into Peek::Views::RedisDetailed
Peek.into Peek::Views::Rugged
-Peek.into Peek::Views::GC
+
+# `Peek::Views::GC` is currently disabled in production, as it runs with every request
+# even if PerformanceBar is inactive and clears `GC::Profiler` reports we need for metrics.
+# Check https://gitlab.com/gitlab-org/gitlab-ce/issues/65455
+Peek.into Peek::Views::GC if Rails.env.development?
+
Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
diff --git a/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb b/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb
new file mode 100644
index 00000000000..022400ce585
--- /dev/null
+++ b/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddCommitIdToDraftNotes < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :draft_notes, :commit_id, :binary
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3d229c2353b..709f9ce2541 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1136,6 +1136,7 @@ ActiveRecord::Schema.define(version: 2019_07_31_084415) do
t.text "position"
t.text "original_position"
t.text "change_position"
+ t.binary "commit_id"
t.index ["author_id"], name: "index_draft_notes_on_author_id"
t.index ["discussion_id"], name: "index_draft_notes_on_discussion_id"
t.index ["merge_request_id"], name: "index_draft_notes_on_merge_request_id"
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index beacaa99d60..186bf4c4825 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -33,15 +33,18 @@ information services over an Internet Protocol (IP) network.
## Security
-GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email'
-or 'userPrincipalName' attribute. An LDAP user who is allowed to change their
-email on the LDAP server can potentially
-[take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
-on your GitLab server.
+GitLab assumes that LDAP users:
+
+- Are not able to change their LDAP `mail`, `email`, or `userPrincipalName` attribute.
+ An LDAP user who is allowed to change their email on the LDAP server can potentially
+ [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
+ on your GitLab server.
+- Have unique email addresses, otherwise it is possible for LDAP users with the same
+ email address to share the same GitLab account.
We recommend against using LDAP integration if your LDAP users are
-allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
-the LDAP server.
+allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
+the LDAP server or share email addresses.
### User deletion
diff --git a/doc/api/README.md b/doc/api/README.md
index 70540420544..6cd89e34921 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -695,6 +695,13 @@ The correct encoding for the query parameter would be:
There are many unofficial GitLab API Clients for most of the popular
programming languages. Visit the [GitLab website] for a complete list.
+## Rate limits
+
+For administrator documentation on rate limit settings, check out
+[Rate limits](../security/rate_limits.md). To find the settings that are
+specifically used by GitLab.com, see
+[GitLab.com-specific rate limits](../user/gitlab_com/index.md).
+
[GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API"
[lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
index 2dc06ba10a5..882e2230636 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -65,28 +65,24 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
-![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png)
-
-<details>
-<summary>Show mermaid source</summary>
-<pre>
+```mermaid
graph LR
A1 -.->|1. Triggers an omnibus-gitlab pipeline and wait for it to be done| A2
- B2[<b>`Trigger-qa` stage</b><br />`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
+ B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
-subgraph gitlab-ce/ee pipeline
- A1[<b>`test` stage</b><br />`package-and-qa` job]
+subgraph "gitlab-ce/ee pipeline"
+ A1[`test` stage<br>`package-and-qa` job]
end
-subgraph omnibus-gitlab pipeline
- A2[<b>`Trigger-docker` stage</b><br />`Trigger:gitlab-docker` job] -->|once done| B2
+subgraph "omnibus-gitlab pipeline"
+ A2[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2
end
-subgraph gitlab-qa pipeline
- A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br />and post the result on the original commit tested| A1
+subgraph "gitlab-qa pipeline"
+ A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br>and post the result on the original commit tested| A1
end
-</pre>
-</details>
+```
+
1. Developer triggers a manual action, that can be found in CE / EE merge
requests. This starts a chain of pipelines in multiple projects.
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
index 6a888142575..97560e616a1 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -45,7 +45,7 @@ Notice that in the above example, before clicking the `:operations_environments_
> We can create these methods as helpers to abstract multi-step navigation.
-### Element naming convention
+## Element naming convention
When adding new elements to a page, it's important that we have a uniform element naming convention.
@@ -67,7 +67,7 @@ We follow a simple formula roughly based on hungarian notation.
*Note: This list is a work in progress. This list will eventually be the end-all enumeration of all available types.
I.e., any element that does not end with something in this list is bad form.*
-#### Examples
+### Examples
**Good**
@@ -98,3 +98,47 @@ view '...' do
element :ssh_clone_url
end
```
+
+## Block argument naming
+
+To have a standard on how we call pages when using the `.perform` method, we use the name of page object being called, all lowercased, and separated by underscore, if needed (see good and bad examples below.) This also applies to resources. We chose not to simply use `page` because that would shadow the Capybara DSL, potentially leading to confusion and bugs.
+
+### Examples
+
+**Good**
+
+```ruby
+# qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+
+Page::Project::Settings::Members.perform do |members|
+ members.do_something
+end
+```
+
+```ruby
+# qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb
+
+Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.do_something_else
+end
+```
+
+**Bad**
+
+```ruby
+# qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+
+Page::Project::Settings::Members.perform do |project_settings_members_page|
+ project_settings_members_page.do_something
+end
+```
+
+```ruby
+# qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb
+
+Resource::MergeRequest.fabricate! do |merge_request_page|
+ merge_request_page.do_something_else
+end
+```
+
+> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code. \ No newline at end of file
diff --git a/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
deleted file mode 100644
index 5b93a05db96..00000000000
--- a/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/testing_guide/img/review_apps_cicd_architecture.png b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
deleted file mode 100644
index 1ee28d3db91..00000000000
--- a/doc/development/testing_guide/img/review_apps_cicd_architecture.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index d07a7045390..7843fc4c874 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -8,38 +8,33 @@ Review Apps are automatically deployed by each pipeline, both in
### CI/CD architecture diagram
-![Review Apps CI/CD architecture](img/review_apps_cicd_architecture.png)
-
-<details>
-<summary>Show mermaid source</summary>
-<pre>
+```mermaid
graph TD
build-qa-image -.->|once the `prepare` stage is done| gitlab:assets:compile
review-build-cng -->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror
review-build-cng -.->|once the `test` stage is done| review-deploy
review-deploy -.->|once the `review` stage is done| review-qa-smoke
-subgraph 1. gitlab-ce/ee `prepare` stage
+subgraph "1. gitlab-ce/ee `prepare` stage"
build-qa-image
end
-subgraph 2. gitlab-ce/ee `test` stage
+subgraph "2. gitlab-ce/ee `test` stage"
gitlab:assets:compile -->|plays dependent job once done| review-build-cng
end
-subgraph 3. gitlab-ce/ee `review` stage
- review-deploy["review-deploy<br /><br />Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br /><br />Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br />Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
+subgraph "3. gitlab-ce/ee `review` stage"
+ review-deploy["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
end
-subgraph 4. gitlab-ce/ee `qa` stage
- review-qa-smoke[review-qa-smoke<br /><br />gitlab-qa runs the smoke suite against the Review App.]
+subgraph "4. gitlab-ce/ee `qa` stage"
+ review-qa-smoke[review-qa-smoke<br><br>gitlab-qa runs the smoke suite against the Review App.]
end
-subgraph CNG-mirror pipeline
+subgraph "CNG-mirror pipeline"
CNG-mirror>Cloud Native images are built];
end
-</pre>
-</details>
+```
### Detailed explanation
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 1833b27a292..8bbaf5d1927 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -53,7 +53,7 @@ There are two types of project templates:
- [Built-in templates](#built-in-templates), sourced from the following groups:
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
- [`pages`](https://gitlab.com/pages)
-- [Custom project templates](#custom-project-templates-premium-only), for custom templates
+- [Custom project templates](#custom-project-templates-premium), for custom templates
configured by GitLab administrators and users.
#### Built-in templates
@@ -78,7 +78,7 @@ You can improve the existing built-in templates or contribute new ones in the
[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and
[`pages`](https://gitlab.com/pages) groups.
-#### Custom project templates **(PREMIUM ONLY)**
+#### Custom project templates **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in
[GitLab Premium](https://about.gitlab.com/pricing) 11.2.
diff --git a/doc/security/README.md b/doc/security/README.md
index c48d5bc2065..5d498ac7602 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,7 +7,7 @@ type: index
- [Password length limits](password_length_limits.md)
- [Restrict SSH key technologies and minimum length](ssh_keys_restrictions.md)
-- [Rack attack](rack_attack.md)
+- [Rate limits](rate_limits.md)
- [Webhooks and insecure internal web services](webhooks.md)
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 1e5678ec47c..c772f783f71 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -2,7 +2,9 @@
type: reference, howto
---
-# Rack Attack
+# Rack Attack initializer
+
+## Overview
[Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem
that is meant to protect GitLab with the ability to customize throttling and
@@ -14,19 +16,72 @@ If you find throttling is not enough to protect you against abusive clients,
Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering, and
tracking.
-**Note:** Starting with 11.2, Rack Attack is disabled by default. To continue
-using Rack Attack, please enable it by [configuring `gitlab.rb` as described in Settings](#settings).
+For more information on how to use these options see the [Rack Attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
+
+NOTE: **Note:** See
+[User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md)
+for simpler throttles that are configured in UI.
+
+NOTE: **Note:** Starting with 11.2, Rack Attack is disabled by default. If your
+instance is not exposed to the public internet, it is recommended that you leave
+Rack Attack disabled.
+
+## Behavior
+
+If set up as described in the [Settings](#settings) section below, two behaviors
+will be enabled:
+
+- Protected paths will be throttled
+- Failed authentications for Git and container registry requests will trigger a temporary IP ban
+
+### Protected paths throttle
+
+GitLab responds with HTTP status code 429 to POST requests at protected paths
+over 10 requests per minute per IP address.
+
+By default, protected paths are:
+
+```ruby
+default['gitlab']['gitlab-rails']['rack_attack_protected_paths'] = [
+ '/users/password',
+ '/users/sign_in',
+ '/api/#{API::API.version}/session.json',
+ '/api/#{API::API.version}/session',
+ '/users',
+ '/users/confirmation',
+ '/unsubscribes/',
+ '/import/github/personal_access_token'
+]
+```
+
+This header is included in responses to blocked requests:
+
+```
+Retry-After: 60
+```
+
+For example, the following are limited to a maximum 10 requests per minute:
+
+- user sign-in
+- user sign-up (if enabled)
+- user password reset
+
+After trying for 10 times, the client will
+have to wait a minute before to be able to try again.
+
+### Git and container registry failed authentication ban
+
+GitLab responds with HTTP status code 403 for 1 hour, if 30 failed
+authentication requests were received in a 3-minute period from a single IP address.
-By default, user sign-in, user sign-up (if enabled), and user password reset is
-limited to 6 requests per minute. After trying for 6 times, the client will
-have to wait for the next minute to be able to try again.
+This applies only to Git requests and container registry (`/jwt/auth`) requests
+(combined).
-If you installed or upgraded GitLab by following the [official guides](../install/README.md),
-Rack Attack should be disabled by default. If your instance is not exposed to any incoming
-connections, it is recommended that you leave Rack Attack disabled.
+This limit is reset by requests that authenticate successfully. For example, 29
+failed authentication requests followed by 1 successful request, followed by 29
+more failed authentication requests would not trigger a ban.
-For more information on how to use these options check out
-[rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
+No response headers are provided.
## Settings
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
new file mode 100644
index 00000000000..0e5bdcd9c79
--- /dev/null
+++ b/doc/security/rate_limits.md
@@ -0,0 +1,32 @@
+---
+type: reference, howto
+---
+
+# Rate limits
+
+NOTE: **Note:**
+For GitLab.com, please see
+[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
+
+Rate limiting is a common technique used to improve the security and durability
+of a web application.
+
+For example, a simple script can make thousands of web requests per second.
+Whether malicious, apathetic, or just a bug, your application and infrastructure
+may not be able to cope with the load. For more details, see
+[Denial-of-service attack](https://en.wikipedia.org/wiki/Denial-of-service_attack).
+Most cases can be mitigated by limiting the rate of requests from a single IP address.
+
+Most [brute-force attacks](https://en.wikipedia.org/wiki/Brute-force_attack) are
+similarly mitigated by a rate limit.
+
+## Admin Area settings
+
+See
+[User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
+
+## Rack Attack initializer
+
+This method of rate limiting is cumbersome, but has some advantages. It allows
+throttling of specific paths, and is also integrated into Git and container
+registry requests. See [Rack Attack initializer](rack_attack.md).
diff --git a/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png
new file mode 100644
index 00000000000..2bd85a2fd96
--- /dev/null
+++ b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png
Binary files differ
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 5427d04cd7d..2a12614e325 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -18,6 +18,7 @@ include:
- [Third party offers](third_party_offers.md)
- [Usage statistics](usage_statistics.md)
- [Visibility and access controls](visibility_and_access_controls.md)
+- [User and IP rate limits](user_and_ip_rate_limits.md)
- [Custom templates repository](instance_template_repository.md) **(PREMIUM)**
NOTE: **Note:**
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
new file mode 100644
index 00000000000..e3a495750f2
--- /dev/null
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -0,0 +1,32 @@
+---
+type: reference
+---
+
+# User and IP rate limits
+
+Rate limiting is a common technique used to improve the security and durability
+of a web application. For more details, see
+[Rate limits](../../../security/rate_limits.md).
+
+The following limits can be enforced in **Admin Area > Network > User and
+IP rate limits**:
+
+- Unauthenticated requests
+- Authenticated API requests
+- Authenticated web requests
+
+These limits are disabled by default.
+
+![user-and-ip-rate-limits](img/user_and_ip_rate_limits.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 31f0b5a050c..4dcb416c110 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -148,6 +148,38 @@ Clicking on this button will create a merge request to apply the solution onto t
![Create merge request from vulnerability](img/create_issue_with_list_hover.png)
+## Security approvals in merge requests **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9928) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
+
+Merge Request Approvals can be configured to require approval from a member
+of your security team when a vulnerability would be introduced by a merge request.
+
+This threshold is defined as `high`, `critical`, or `unknown`
+severity. When any vulnerabilities are present within a merge request, an
+approval will be required from the `Vulnerability-Check` approver group.
+
+### Enabling Security Approvals within a project
+
+To enable Security Approvals, a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
+must be created with the case-sensitive name `Vulnerability-Check`. This approval
+group must be set with an "Approvals required" count greater than zero.
+
+Once this group has been added to your project, the approval rule will be enabled
+for all Merge Requests.
+
+Any code changes made will cause the count of approvals required to reset.
+
+An approval will be required when a security report:
+
+- Contains a new vulnerability of `high`, `critical`, or `unknown` severity.
+- Is not generated during pipeline execution.
+
+An approval will be optional when a security report:
+
+- Contains no new vulnerabilities.
+- Contains only new vulnerabilities of `low` or `medium` severity.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 7858c419e04..e6c27c33654 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -298,6 +298,79 @@ Web front-ends:
- `memory_limit_min` = 1024MiB
- `memory_limit_max` = 1280MiB
+## GitLab.com-specific rate limits
+
+NOTE: **Note:**
+See [Rate limits](../../security/rate_limits.md) for administrator
+documentation.
+
+IP blocks usually happen when GitLab.com receives unusual traffic from a single
+IP address that the system views as potentially malicious based on rate limit
+settings. After the unusual traffic ceases, the IP address will be automatically
+released depending on the type of block, as described below.
+
+If you receive a `403 Forbidden` error for all requests to GitLab.com, please
+check for any automated processes that may be triggering a block. For
+assistance, contact [GitLab Support](https://support.gitlab.com)
+with details, such as the affected IP address.
+
+### HAProxy API throttle
+
+GitLab.com responds with HTTP status code 429 to API requests over 10 requests
+per second per IP address.
+
+The following example headers are included for all API requests:
+
+```
+RateLimit-Limit: 600
+RateLimit-Observed: 6
+RateLimit-Remaining: 594
+RateLimit-Reset: 1563325137
+RateLimit-ResetTime: Wed, 17 Jul 2019 00:58:57 GMT
+```
+
+Source:
+
+- Search for `rate_limit_http_rate_per_minute` and `rate_limit_sessions_per_second` in [GitLab.com's current HAProxy settings](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy/blob/master/attributes/default.rb).
+
+### Rack Attack initializer
+
+#### Protected paths throttle
+
+GitLab.com responds with HTTP status code 429 to POST requests at protected
+paths over 10 requests per **minute** per IP address.
+
+See the source below for which paths are protected. This includes user creation,
+user confirmation, user sign in, and password reset.
+
+This header is included in responses to blocked requests:
+
+```
+Retry-After: 60
+```
+
+Source:
+
+- Search for `rate_limit_requests_per_period`, `rate_limit_period`, and `rack_attack_protected_paths` in [GitLab.com's current Rails app settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
+
+#### Git and container registry failed authentication ban
+
+GitLab.com responds with HTTP status code 403 for 1 hour, if 30 failed
+authentication requests were received in a 3-minute period from a single IP address.
+
+This applies only to Git requests and container registry (`/jwt/auth`) requests
+(combined).
+
+This limit is reset by requests that authenticate successfully. For example, 29
+failed authentication requests followed by 1 successful request, followed by 29
+more failed authentication requests would not trigger a ban.
+
+No response headers are provided.
+
+### Admin Area settings
+
+GitLab.com does not currently use these settings.
+
## GitLab.com at scale
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 9380dcf2a32..edb1e904f2b 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -185,6 +185,49 @@ graph TD;
C-->D;
```
+#### Subgraphs
+
+NOTE: **Note:** GitLab 12.1 and up now [requires quotes around subgraph
+titles that contain multiple words](https://github.com/knsv/mermaid/pull/845).
+
+Subgraphs can also be included:
+
+~~~
+```mermaid
+graph TB
+
+ SubGraph1 --> SubGraph1Flow
+ subgraph "SubGraph 1 Flow"
+ SubGraph1Flow(SubNode 1)
+ SubGraph1Flow -- Choice1 --> DoChoice1
+ SubGraph1Flow -- Choice2 --> DoChoice2
+ end
+
+ subgraph "Main Graph"
+ Node1[Node 1] --> Node2[Node 2]
+ Node2 --> SubGraph1[Jump to SubGraph1]
+ SubGraph1 --> FinalThing[Final Thing]
+end
+```
+~~~
+
+```mermaid
+graph TB
+
+ SubGraph1 --> SubGraph1Flow
+ subgraph "SubGraph 1 Flow"
+ SubGraph1Flow(SubNode 1)
+ SubGraph1Flow -- Choice1 --> DoChoice1
+ SubGraph1Flow -- Choice2 --> DoChoice2
+ end
+
+ subgraph "Main Graph"
+ Node1[Node 1] --> Node2[Node 2]
+ Node2 --> SubGraph1[Jump to SubGraph1]
+ SubGraph1 --> FinalThing[Final Thing]
+end
+```
+
### Emoji
> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji).
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 5116cbfe9ac..1efd0ff3944 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -3,8 +3,8 @@
We need to create a user in Jira which will have access to all projects that
need to integrate with GitLab.
-As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
-group.
+As an example, we'll create a user named `gitlab` and add it to a new group
+named `gitlab-developers`.
NOTE: **Note**
It is important that the Jira user created for the integration is given 'write'
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 220795d6f15..656459b3b03 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -331,6 +331,16 @@ the dropdown) `approver` and select the user.
![Filter MRs by an approver](img/filter_approver_merge_requests.png)
+## Security approvals in merge requests **(ULTIMATE)**
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
+
+Merge Request Approvals can be configured to require approval from a member
+of your security team when a vulnerability would be introduced by a merge request.
+
+For more information, see
+[Security approvals in merge requests](../../application_security/index.md#security-approvals-in-merge-requests-ultimate).
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index fd67ea8ce87..d61d7eafd18 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -35,11 +35,11 @@ You can see GitLab's keyboard shortcuts by using <kbd>shift</kbd> + <kbd>?</kbd>
| Keyboard Shortcut | Description |
| ----------------- | ----------- |
-| <kbd>g</kbd> + <kbd>a</kbd> | Go to the activity feed |
-| <kbd>g</kbd> + <kbd>p</kbd> | Go to projects |
-| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
-| <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests |
-| <kbd>g</kbd> + <kbd>t</kbd> | Go to todos |
+| <kbd>Shift</kbd> + <kbd>a</kbd> | Go to the activity feed |
+| <kbd>Shift</kbd> + <kbd>p</kbd> | Go to projects |
+| <kbd>Shift</kbd> + <kbd>i</kbd> | Go to issues |
+| <kbd>Shift</kbd> + <kbd>m</kbd> | Go to merge requests |
+| <kbd>Shift</kbd> + <kbd>t</kbd> | Go to todos |
## Project
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 01fd261404b..86e532766b1 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -7,7 +7,7 @@ module Gitlab
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
- IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md/i.freeze
+ IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md|doc\/.+/i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb
index 5138b37f83e..1eae0a7bf45 100644
--- a/lib/gitlab/metrics/samplers/influx_sampler.rb
+++ b/lib/gitlab/metrics/samplers/influx_sampler.rb
@@ -15,19 +15,14 @@ module Gitlab
@last_step = nil
@metrics = []
-
- @last_minor_gc = Delta.new(GC.stat[:minor_gc_count])
- @last_major_gc = Delta.new(GC.stat[:major_gc_count])
end
def sample
sample_memory_usage
sample_file_descriptors
- sample_gc
flush
ensure
- GC::Profiler.clear
@metrics.clear
end
@@ -43,23 +38,6 @@ module Gitlab
add_metric('file_descriptors', value: System.file_descriptor_count)
end
- def sample_gc
- time = GC::Profiler.total_time * 1000.0
- stats = GC.stat.merge(total_time: time)
-
- # We want the difference of GC runs compared to the last sample, not the
- # total amount since the process started.
- stats[:minor_gc_count] =
- @last_minor_gc.compared_with(stats[:minor_gc_count])
-
- stats[:major_gc_count] =
- @last_major_gc.compared_with(stats[:major_gc_count])
-
- stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
-
- add_metric('gc_statistics', stats)
- end
-
def add_metric(series, values, tags = {})
prefix = sidekiq? ? 'sidekiq_' : 'rails_'
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 1e200db0baf..3bfa3da35e0 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -6,7 +6,11 @@ module Gitlab
module Metrics
module Samplers
class RubySampler < BaseSampler
+ GC_REPORT_BUCKETS = [0.001, 0.002, 0.005, 0.01, 0.05, 0.1, 0.5].freeze
+
def initialize(interval)
+ GC::Profiler.clear
+
metrics[:process_start_time_seconds].set(labels, Time.now.to_i)
super
@@ -37,7 +41,7 @@ module Gitlab
process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels),
process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
- total_time: ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ gc_duration_seconds: ::Gitlab::Metrics.histogram(with_prefix(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
}
GC.stat.keys.each do |key|
@@ -57,20 +61,27 @@ module Gitlab
sample_gc
metrics[:sampler_duration].increment(labels, System.monotonic_time - start_time)
- ensure
- GC::Profiler.clear
end
private
def sample_gc
- # Collect generic GC stats.
+ # Observe all GC samples
+ sample_gc_reports.each do |report|
+ metrics[:gc_duration_seconds].observe(labels, report[:GC_TIME])
+ end
+
+ # Collect generic GC stats
GC.stat.each do |key, value|
metrics[key].set(labels, value)
end
+ end
- # Collect the GC time since last sample in float seconds.
- metrics[:total_time].increment(labels, GC::Profiler.total_time)
+ def sample_gc_reports
+ GC::Profiler.enable
+ GC::Profiler.raw_data
+ ensure
+ GC::Profiler.clear
end
def set_memory_usage_metrics
diff --git a/package.json b/package.json
index 056f7616cde..f0a7f3e47af 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,6 @@
"diff": "^3.4.0",
"document-register-element": "1.13.1",
"dropzone": "^4.2.0",
- "echarts": "^4.2.0-rc.2",
"emoji-regex": "^7.0.3",
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
diff --git a/rubocop/cop/rspec/top_level_describe_path.rb b/rubocop/cop/rspec/top_level_describe_path.rb
new file mode 100644
index 00000000000..61796e23af0
--- /dev/null
+++ b/rubocop/cop/rspec/top_level_describe_path.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rubocop/rspec/top_level_describe'
+
+module RuboCop
+ module Cop
+ module RSpec
+ class TopLevelDescribePath < RuboCop::Cop::Cop
+ include RuboCop::RSpec::TopLevelDescribe
+
+ MESSAGE = 'A file with a top-level `describe` must end in _spec.rb.'
+ SHARED_EXAMPLES = %i[shared_examples shared_examples_for].freeze
+
+ def on_top_level_describe(node, args)
+ return if acceptable_file_path?(processed_source.buffer.name)
+ return if shared_example?(node)
+
+ add_offense(node, message: MESSAGE)
+ end
+
+ private
+
+ def acceptable_file_path?(path)
+ File.fnmatch?('*_spec.rb', path) || File.fnmatch?('*/frontend/fixtures/*', path)
+ end
+
+ def shared_example?(node)
+ node.ancestors.any? do |node|
+ node.respond_to?(:method_name) && SHARED_EXAMPLES.include?(node.method_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index ba61a634d97..58a7ead6f13 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -32,6 +32,7 @@ require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
+require_relative 'cop/rspec/top_level_describe_path'
require_relative 'cop/qa/element_with_pattern'
require_relative 'cop/sidekiq_options_queue'
require_relative 'cop/destroy_all'
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 13fad1b6dc9..232890b1bba 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -27,7 +27,7 @@ describe 'Database schema' do
cluster_providers_gcp: %w[gcp_project_id operation_id],
deploy_keys_projects: %w[deploy_key_id],
deployments: %w[deployable_id environment_id user_id],
- draft_notes: %w[discussion_id],
+ draft_notes: %w[discussion_id commit_id],
emails: %w[user_id],
events: %w[target_id],
epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id],
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 659bec177ab..3a47475da2b 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -51,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'P'])
find('.nothing-here-block')
- expect(page).to have_content('This user doesn\'t have any personal projects')
+ expect(page).to have_content('Explore public groups to find projects to contribute to.')
end
end
diff --git a/spec/features/groups/labels/user_sees_links_to_issuables.rb b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
index e636f625b31..6199b566ebc 100644
--- a/spec/features/groups/labels/user_sees_links_to_issuables.rb
+++ b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
@@ -11,7 +11,9 @@ describe 'Groups > Labels > User sees links to issuables' do
end
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
diff --git a/spec/features/instance_statistics/instance_statistics.rb b/spec/features/instance_statistics/instance_statistics_spec.rb
index 40d0f1db207..40d0f1db207 100644
--- a/spec/features/instance_statistics/instance_statistics.rb
+++ b/spec/features/instance_statistics/instance_statistics_spec.rb
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
index 934de2fde8f..c19e46da913 100644
--- a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
+++ b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
-describe 'Projects > Files > User browses a tree with a folder containing only a folder' do
+# Quarantine: https://gitlab.com/gitlab-org/gitlab-ce/issues/65329
+describe 'Projects > Files > User browses a tree with a folder containing only a folder', :quarantine do
let(:project) { create(:project, :empty_repo) }
let(:user) { project.owner }
diff --git a/spec/features/projects/labels/user_sees_links_to_issuables.rb b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
index fd2151a1f8e..7a9b9e6eac2 100644
--- a/spec/features/projects/labels/user_sees_links_to_issuables.rb
+++ b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
@@ -19,8 +19,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -28,8 +30,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, issues_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs but not to issues' do
- expect(page).to have_link('view merge requests')
- expect(page).not_to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).not_to have_link('Issues')
+ end
end
end
@@ -37,8 +41,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'shows links to issues but not to MRs' do
- expect(page).not_to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).not_to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
end
@@ -51,8 +57,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -60,8 +68,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group, issues_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
@@ -69,8 +79,10 @@ describe 'Projects > Labels > User sees links to issuables' do
let(:project) { create(:project, :public, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
end
end
end
diff --git a/spec/features/snippets/user_sees_breadcrumb_links.rb b/spec/features/snippets/user_sees_breadcrumb_links.rb
deleted file mode 100644
index 5b10984ce1d..00000000000
--- a/spec/features/snippets/user_sees_breadcrumb_links.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe 'New user snippet breadcrumbs' do
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
- visit new_snippet_path
- end
-
- it 'display a link to user snippets and new user snippet pages' do
- page.within '.breadcrumbs' do
- expect(find_link('Snippets')[:href]).to end_with(dashboard_snippets_path)
- expect(find_link('New')[:href]).to end_with(new_snippet_path)
- end
- end
-end
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment_spec.rb
index f1e07e55799..f1e07e55799 100644
--- a/spec/features/user_opens_link_to_comment.rb
+++ b/spec/features/user_opens_link_to_comment_spec.rb
diff --git a/spec/features/users/add_email_to_existing_account.rb b/spec/features/users/add_email_to_existing_account_spec.rb
index 42e352399a8..42e352399a8 100644
--- a/spec/features/users/add_email_to_existing_account.rb
+++ b/spec/features/users/add_email_to_existing_account_spec.rb
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 27428197c1c..531686efff1 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/diffs/mock_data/diff_file.js
+
export default {
submodule: false,
submodule_link: null,
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index c58d59b4b16..b53890f8348 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import jobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
+import * as types from '~/jobs/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { waitForMutation } from 'spec/helpers/vue_test_utils_helper';
import { resetStore } from '../store/helpers';
import job from '../mock_data';
@@ -26,6 +28,16 @@ describe('Job App ', () => {
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
+ const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS);
+ const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => {
+ mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData });
+ mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData);
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ return waitForJobReceived();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
store = createStore();
@@ -39,103 +51,81 @@ describe('Job App ', () => {
describe('while loading', () => {
beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {});
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount();
});
- it('renders loading icon', done => {
+ it('renders loading icon', () => {
expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull();
expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull();
expect(vm.$el.querySelector('.js-job-content')).toBeNull();
-
- setTimeout(() => {
- done();
- }, 0);
});
});
describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {});
- });
-
describe('Header section', () => {
describe('job callout message', () => {
it('should not render the reason when reason is absent', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(false);
-
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should render the reason when reason is present', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
- callout_message: 'There is an unknown failure, please try again',
- }),
- );
-
- vm = mountComponentWithStore(Component, { props, store });
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(true);
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ callout_message: 'There is an unkown failure, please try again',
+ },
+ })
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('triggered job', () => {
- beforeEach(() => {
+ beforeEach(done => {
const aYearAgo = new Date();
aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
- mock
- .onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount({ jobData: { started: aYearAgo.toISOString() } })
+ .then(done)
+ .catch(done.fail);
});
- it('should render provided job information', done => {
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 triggered 1 year ago by Root');
- done();
- }, 0);
+ it('should render provided job information', () => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 triggered 1 year ago by Root');
});
- it('should render new issue link', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
- done();
- }, 0);
+ it('should render new issue link', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
});
});
describe('created job', () => {
it('should render created key', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 created 3 weeks ago by Root');
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 created 3 weeks ago by Root');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -143,9 +133,8 @@ describe('Job App ', () => {
describe('stuck block', () => {
describe('without active runners availabl', () => {
it('renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -159,23 +148,23 @@ describe('Job App ', () => {
online: false,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
+ expect(
+ vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'),
+ ).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when available runners can not run specified tag', () => {
it('renders tags in stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -188,27 +177,21 @@ describe('Job App ', () => {
available: false,
online: false,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when runners are offline and build has tags', () => {
it('renders message about job being stuck because of no runners with the specified tags', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -221,48 +204,35 @@ describe('Job App ', () => {
available: true,
online: true,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
it('does not renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
runners: { available: true },
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('unmet prerequisites block', () => {
it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'failed',
icon: 'status_failed',
@@ -282,104 +252,81 @@ describe('Job App ', () => {
available: true,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('environments block', () => {
it('renders environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
deployment_status: {
environment: {
environment_path: '/path',
name: 'foo',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('erased block', () => {
it('renders erased block when `erased` is true', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
erased_by: {
username: 'root',
web_url: 'gitlab.com/root',
},
erased_at: '2016-11-07T11:11:16.525Z',
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render erased block when `erased` is false', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ erased_at: null,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('empty states block', () => {
it('renders empty state when job does not have trace and is not running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'pending',
@@ -399,25 +346,18 @@ describe('Job App ', () => {
path: '/path',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job does not have trace but it is running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'running',
@@ -426,34 +366,23 @@ describe('Job App ', () => {
text: 'running',
details_path: 'path',
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job has trace but it is not running', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({ jobData: { has_trace: true } })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ done();
});
it('displays remaining time for a delayed job', done => {
@@ -461,120 +390,114 @@ describe('Job App ', () => {
spyOn(Date, 'now').and.callFake(
() => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds,
);
- mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture });
+ setupAndMount({ jobData: delayedJobFixture })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- store.subscribeAction(action => {
- if (action.type !== 'receiveJobSuccess') {
- return;
- }
+ const title = vm.$el.querySelector('.js-job-empty-state-title');
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- const title = vm.$el.querySelector('.js-job-empty-state-title');
+ expect(title).toContainText('01:00:00');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
- expect(title).toContainText('01:00:00');
- done();
- })
- .catch(done.fail);
- });
+ describe('sidebar', () => {
+ it('has no blank blocks', done => {
+ setupAndMount({
+ jobData: {
+ duration: null,
+ finished_at: null,
+ erased_at: null,
+ queued: null,
+ runner: null,
+ coverage: null,
+ tags: [],
+ cancel_path: null,
+ },
+ })
+ .then(() => {
+ vm.$el.querySelectorAll('.blocks-container > *').forEach(block => {
+ expect(block.textContent.trim()).not.toBe('');
+ });
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({ jobData: { archived: true } })
+ .then(done)
+ .catch(done.fail);
});
- it('renders warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
- done();
- }, 0);
+ it('renders warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
});
});
describe('non-archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount()
+ .then(done)
+ .catch(done.fail);
});
- it('does not warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
- done();
- }, 0);
+ it('does not warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
});
});
describe('trace output', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- });
-
describe('with append flag', () => {
it('appends the log content to the existing one', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
-
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>More<span>',
+ status: 'running',
+ state: 'newstate',
+ append: true,
+ complete: true,
+ },
+ })
+ .then(() => {
+ vm.$store.state.trace = 'Update';
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('without append flag', () => {
it('replaces the trace', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
- 'Update',
- );
+ setupAndMount({
+ traceData: {
+ html: '<span>Different<span>',
+ status: 'running',
+ append: false,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
+ 'Update',
+ );
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
- done();
- }, 0);
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain(
+ 'Different',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -590,83 +513,76 @@ describe('Job App ', () => {
complete: true,
});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when size is equal than total', () => {
it('does not show the truncated information', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('trace controls', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('should render scroll buttons', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
- expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
- done();
- }, 0);
+ it('should render scroll buttons', () => {
+ expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
});
- it('should render link to raw ouput', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to raw ouput', () => {
+ expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
});
- it('should render link to erase job', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to erase job', () => {
+ expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
});
});
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 1df5cf9ef68..5f81a168498 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/notes/mock_data.js
+
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: 1501862675,
diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
index 81954fcf8c5..2923048f742 100644
--- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
@@ -17,18 +17,10 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
it 'samples various statistics' do
expect(sampler).to receive(:sample_memory_usage)
expect(sampler).to receive(:sample_file_descriptors)
- expect(sampler).to receive(:sample_gc)
expect(sampler).to receive(:flush)
sampler.sample
end
-
- it 'clears any GC profiles' do
- expect(sampler).to receive(:flush)
- expect(GC::Profiler).to receive(:clear)
-
- sampler.sample
- end
end
describe '#flush' do
@@ -67,18 +59,6 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
end
end
- describe '#sample_gc' do
- it 'adds a metric containing garbage collection statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
-
- expect(sampler).to receive(:add_metric)
- .with(/gc_statistics/, an_instance_of(Hash))
- .and_call_original
-
- sampler.sample_gc
- end
- end
-
describe '#add_metric' do
it 'prefixes the series name for a Rails process' do
expect(sampler).to receive(:sidekiq?).and_return(false)
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 4d93b70e6e3..5005a5d9ebc 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -59,17 +59,29 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
it 'clears any GC profiles' do
- expect(GC::Profiler).to receive(:clear)
+ expect(GC::Profiler).to receive(:clear).at_least(:once)
sampler.sample
end
end
describe '#sample_gc' do
- it 'adds a metric containing garbage collection time statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
+ let!(:sampler) { described_class.new(5) }
- expect(sampler.metrics[:total_time]).to receive(:increment).with({}, 0.24)
+ let(:gc_reports) { [{ GC_TIME: 0.1 }, { GC_TIME: 0.2 }, { GC_TIME: 0.3 }] }
+
+ it 're-enables GC::Profiler if needed' do
+ expect(GC::Profiler).to receive(:enable)
+
+ sampler.sample
+ end
+
+ it 'observes GC cycles time' do
+ expect(sampler).to receive(:sample_gc_reports).and_return(gc_reports)
+
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.1).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.2).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.3).ordered
sampler.sample
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 9369f393b5e..8768e914284 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -185,8 +185,9 @@ describe Ci::Build do
describe '.with_needs' do
let!(:build) { create(:ci_build) }
+ let!(:build_b) { create(:ci_build) }
let!(:build_need_a) { create(:ci_build_need, build: build) }
- let!(:build_need_b) { create(:ci_build_need, build: build) }
+ let!(:build_need_b) { create(:ci_build_need, build: build_b) }
context 'when passing build name' do
subject { described_class.with_needs(build_need_a.name) }
@@ -197,7 +198,7 @@ describe Ci::Build do
context 'when not passing any build name' do
subject { described_class.with_needs }
- it { is_expected.to contain_exactly(build) }
+ it { is_expected.to contain_exactly(build, build_b) }
end
context 'when not matching build name' do
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index d9e1fe4b165..8d7dafc523d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -34,6 +34,10 @@ describe DiffNote do
subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
+ describe 'validations' do
+ it_behaves_like 'a valid diff positionable note', :diff_note_on_commit
+ end
+
describe "#position=" do
context "when provided a string" do
it "sets the position" do
diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
new file mode 100644
index 00000000000..258144d4000
--- /dev/null
+++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/top_level_describe_path'
+
+describe RuboCop::Cop::RSpec::TopLevelDescribePath do
+ include RuboCop::RSpec::ExpectOffense
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when the file ends in _spec.rb' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo_spec.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the file is a frontend fixture' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/frontend/fixtures/foo.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the describe is in a shared example' do
+ context 'with shared_examples' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+
+ context 'with shared_examples_for' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples_for 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+ end
+
+ context 'when the describe is at the top level' do
+ it 'marks the describe as offending' do
+ expect_offense(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ describe 'Foo' do
+ ^^^^^^^^^^^^^^ #{described_class::MESSAGE}
+ end
+ SOURCE
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
new file mode 100644
index 00000000000..8b298c5c974
--- /dev/null
+++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+shared_examples_for 'a valid diff positionable note' do |factory_on_commit|
+ context 'for commit' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:commit_id) { commit.id }
+ let(:diff_refs) { commit.diff_refs }
+
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: diff_refs
+ )
+ end
+
+ subject { build(factory_on_commit, commit_id: commit_id, position: position) }
+
+ context 'position diff refs matches commit diff refs' do
+ it 'is valid' do
+ expect(subject).to be_valid
+ expect(subject.errors).not_to have_key(:commit_id)
+ end
+ end
+
+ context 'position diff refs does not match commit diff refs' do
+ let(:diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: "not_existing_sha",
+ head_sha: "existing_sha"
+ )
+ end
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+
+ context 'commit does not exist' do
+ let(:commit_id) { 'non-existing' }
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
deleted file mode 100644
index 8d1cff7a261..00000000000
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-require 'rake'
-
-describe 'gitlab:mail_google_schema_whitelisting rake task' do
- before :all do
- Rake.application.rake_require "tasks/gitlab/helpers"
- Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
- # empty task as env is already loaded
- Rake::Task.define_task :environment
- end
-
- describe 'call' do
- before do
- # avoid writing task output to spec progress
- allow($stdout).to receive :write
- end
-
- let :run_rake_task do
- Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
- Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
- end
-
- it 'runs the task without errors' do
- expect { run_rake_task }.not_to raise_error
- end
- end
-end
diff --git a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
index 2f58eec86dc..2f58eec86dc 100644
--- a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb
+++ b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
diff --git a/spec/views/dashboard/projects/_nav.html.haml.rb b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
index f6a8ca13040..cbdd3c0acc3 100644
--- a/spec/views/dashboard/projects/_nav.html.haml.rb
+++ b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'dashboard/projects/_nav.html.haml' do
it 'highlights All tab by default' do
render
- expect(rendered).to have_css('li.active a', text: 'All')
+ expect(rendered).to have_css('a.active', text: 'All')
end
it 'highlights Personal tab personal param is present' do
@@ -12,6 +12,6 @@ describe 'dashboard/projects/_nav.html.haml' do
render
- expect(rendered).to have_css('li.active a', text: 'Personal')
+ expect(rendered).to have_css('a.active', text: 'Personal')
end
end
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
index 54ec4f32856..54ec4f32856 100644
--- a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
diff --git a/spec/views/shared/_label_row.html.haml.rb b/spec/views/shared/_label_row.html.haml_spec.rb
index a58d5efc1e3..4cce13aa37c 100644
--- a/spec/views/shared/_label_row.html.haml.rb
+++ b/spec/views/shared/_label_row.html.haml_spec.rb
@@ -7,9 +7,20 @@ describe 'shared/_label_row.html.haml' do
}
label_types.each do |label_type, label_factory|
- let!(:label) { create(label_factory) }
+ let!(:label) do
+ label_record = create(label_factory)
+ label_record.present(issuable_subject: label_record.subject)
+ end
context "for a #{label_type}" do
+ before do
+ if label.project_label?
+ @project = label.project
+ else
+ @group = label.group
+ end
+ end
+
it 'has a non-linked label title' do
render 'shared/label_row', label: label
diff --git a/spec/views/shared/milestones/_issuable.html.haml.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
index 0a3f877cae0..0a3f877cae0 100644
--- a/spec/views/shared/milestones/_issuable.html.haml.rb
+++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
index cbbb984935f..24b55338db3 100644
--- a/spec/views/shared/milestones/_issuables.html.haml.rb
+++ b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
@@ -6,7 +6,7 @@ describe 'shared/milestones/_issuables.html.haml' do
before do
allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil,
show_full_project_name: nil, dom_class: '',
- issuables: double(size: issuables_size).as_null_object)
+ issuables: double(length: issuables_size).as_null_object)
stub_template 'shared/milestones/_issuable.html.haml' => ''
end
diff --git a/spec/views/shared/milestones/_top.html.haml.rb b/spec/views/shared/milestones/_top.html.haml_spec.rb
index 516d81c87ac..f2ee8be5857 100644
--- a/spec/views/shared/milestones/_top.html.haml.rb
+++ b/spec/views/shared/milestones/_top.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'shared/milestones/_top.html.haml' do
before do
allow(milestone).to receive(:milestones) { [] }
+ allow(milestone).to receive(:milestone) { milestone }
end
it 'renders a deprecation message for a legacy milestone' do
diff --git a/yarn.lock b/yarn.lock
index 22145f5a5af..221ffa27f6c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4310,11 +4310,11 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0"
echarts@^4.2.0-rc.2:
- version "4.2.0-rc.2"
- resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.0-rc.2.tgz#6a98397aafa81b65cbf0bc15d9afdbfb244df91e"
- integrity sha512-5Y4Kyi4eNsRM9Cnl7Q8C6PFVjznBJv1VIiMm/VSQ9zyqeo+ce1695GqUd9v4zfVx+Ow1gnwMJX67h0FNvarScw==
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.1.tgz#9a8ea3b03354f86f824d97625c334cf16965ef03"
+ integrity sha512-pw4xScRPsLegD/cqEcoXRKeA2SD4+s+Kyo0Na166NamOWhzNl2yI5RZ2rE97tBlAopNmhyMeBVpAeD5qb+ee1A==
dependencies:
- zrender "4.0.5"
+ zrender "4.0.7"
editions@^1.3.3:
version "1.3.4"
@@ -13217,7 +13217,7 @@ zen-observable@^0.8.0:
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==
-zrender@4.0.5:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.5.tgz#6e8f738971ce2cd624aac82b2156729b1c0e5a82"
- integrity sha512-SintgipGEJPT9Sz2ABRoE4ZD7Yzy7oR7j7KP6H+C9FlbHWnLUfGVK7E8UV27pGwlxAMB0EsnrqhXx5XjAfv/KA==
+zrender@4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.7.tgz#15ae960822f5efed410995d37e5107fe3de10e6d"
+ integrity sha512-TNloHe0ums6zxbHfnaCryM61J4IWDajZwNq6dHk9vfWhhysO/OeFvvR0drBs/nbXha2YxSzfQj2FiCd6RVBe+Q==