summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShinya Maeda <shinya@gitlab.com>2018-10-05 10:13:06 +0900
committerShinya Maeda <shinya@gitlab.com>2018-10-05 10:13:06 +0900
commitaf2c51d7ca73e16cdb9a11fff6d097116c7c5324 (patch)
treecd7f07f36e09a357902a262cbb932f25d254a16c
parentacfe7ec65c75f65f2f87bf3e8598c1725c9150f4 (diff)
parentf71c497f5da791a35876206255e342a9bb5e49c5 (diff)
downloadgitlab-ce-af2c51d7ca73e16cdb9a11fff6d097116c7c5324.tar.gz
Merge branch 'master-ce' into scheduled-manual-jobs
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.rubocop_todo.yml79
-rw-r--r--CHANGELOG.md4
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock3
-rw-r--r--Gemfile.rails5.lock3
-rw-r--r--app/assets/javascripts/api.js10
-rw-r--r--app/assets/javascripts/awards_handler.js32
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue10
-rw-r--r--app/assets/javascripts/diffs/store/utils.js5
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue6
-rw-r--r--app/assets/javascripts/header.js55
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue4
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue15
-rw-r--r--app/assets/javascripts/jobs/store/getters.js13
-rw-r--r--app/assets/javascripts/notes/stores/getters.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js12
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js4
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js26
-rw-r--r--app/assets/javascripts/pages/users/user_overview_block.js42
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js84
-rw-r--r--app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js21
-rw-r--r--app/assets/javascripts/set_status_modal/event_hub.js3
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue33
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue241
-rw-r--r--app/assets/javascripts/users_select.js2
-rw-r--r--app/assets/stylesheets/framework/calendar.scss12
-rw-r--r--app/assets/stylesheets/framework/header.scss36
-rw-r--r--app/assets/stylesheets/framework/mixins.scss66
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss1
-rw-r--r--app/assets/stylesheets/pages/notes.scss54
-rw-r--r--app/assets/stylesheets/pages/profile.scss10
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/assets/stylesheets/performance_bar.scss3
-rw-r--r--app/controllers/admin/application_settings_controller.rb3
-rw-r--r--app/controllers/concerns/labels_as_hash.rb28
-rw-r--r--app/controllers/groups/labels_controller.rb3
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb7
-rw-r--r--app/controllers/projects/labels_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb8
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb7
-rw-r--r--app/controllers/users_controller.rb13
-rw-r--r--app/finders/group_labels_finder.rb16
-rw-r--r--app/finders/labels_finder.rb13
-rw-r--r--app/finders/merge_requests_finder.rb4
-rw-r--r--app/finders/user_recent_events_finder.rb2
-rw-r--r--app/helpers/boards_helper.rb4
-rw-r--r--app/helpers/clusters_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb20
-rw-r--r--app/helpers/repository_languages_helper.rb3
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/commit.rb6
-rw-r--r--app/models/concerns/subscribable.rb8
-rw-r--r--app/models/instance_configuration.rb4
-rw-r--r--app/models/label.rb9
-rw-r--r--app/policies/project_policy.rb6
-rw-r--r--app/serializers/commit_entity.rb21
-rw-r--r--app/serializers/detailed_status_entity.rb8
-rw-r--r--app/serializers/diffs_entity.rb4
-rw-r--r--app/services/projects/autocomplete_service.rb29
-rw-r--r--app/services/quick_actions/interpret_service.rb19
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml183
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml4
-rw-r--r--app/views/ci/runner/_how_to_setup_shared_runner.html.haml3
-rw-r--r--app/views/ci/runner/_how_to_setup_specific_runner.html.haml26
-rw-r--r--app/views/groups/labels/index.html.haml21
-rw-r--r--app/views/groups/runners/_group_runners.html.haml4
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml7
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml6
-rw-r--r--app/views/projects/clusters/gcp/_form.html.haml17
-rw-r--r--app/views/projects/clusters/gcp/_show.html.haml15
-rw-r--r--app/views/projects/clusters/user/_form.html.haml17
-rw-r--r--app/views/projects/clusters/user/_show.html.haml15
-rw-r--r--app/views/projects/environments/show.html.haml9
-rw-r--r--app/views/projects/jobs/_empty_state.html.haml18
-rw-r--r--app/views/projects/jobs/_empty_states.html.haml9
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml23
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml30
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml10
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/labels/_nav.html.haml20
-rw-r--r--app/views/shared/labels/_sort_dropdown.html.haml2
-rw-r--r--app/views/users/_overview.html.haml32
-rw-r--r--app/views/users/show.html.haml38
-rw-r--r--changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml5
-rw-r--r--changelogs/unreleased/22104-fix-environment-name-overlap.yml4
-rw-r--r--changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml5
-rw-r--r--changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml5
-rw-r--r--changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml5
-rw-r--r--changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml5
-rw-r--r--changelogs/unreleased/48222-fix-todos-status-button.yml6
-rw-r--r--changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml5
-rw-r--r--changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml5
-rw-r--r--changelogs/unreleased/50552-unable-to-close-performance-bar.yml5
-rw-r--r--changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml5
-rw-r--r--changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml5
-rw-r--r--changelogs/unreleased/51958-fix-mr-discussion-loading.yml5
-rw-r--r--changelogs/unreleased/52178-markdown-table-borders.yml5
-rw-r--r--changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml5
-rw-r--r--changelogs/unreleased/add_reliable_fetcher.yml5
-rw-r--r--changelogs/unreleased/clone-nurtch-demo-repo.yml5
-rw-r--r--changelogs/unreleased/dz-labels-subscribe-filter.yml5
-rw-r--r--config/initializers/sidekiq.rb10
-rw-r--r--config/routes/admin.rb2
-rw-r--r--config/routes/group.rb4
-rw-r--r--config/routes/project.rb3
-rw-r--r--config/routes/user.rb1
-rw-r--r--db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb2
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb2
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb2
-rw-r--r--doc/administration/operations/moving_repositories.md26
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/api/merge_requests.md682
-rw-r--r--doc/user/profile/account/two_factor_authentication.md16
-rw-r--r--doc/user/profile/index.md7
-rw-r--r--doc/user/project/clusters/index.md31
-rw-r--r--doc/user/project/import/svn.md7
-rw-r--r--doc/user/project/quick_actions.md12
-rw-r--r--lib/api/commits.rb3
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/banzai/filter/epic_reference_filter.rb6
-rw-r--r--lib/declarative_policy.rb2
-rw-r--r--lib/gitlab/diff/position.rb4
-rw-r--r--lib/gitlab/git_access.rb4
-rw-r--r--lib/gitlab/url_sanitizer.rb2
-rw-r--r--locale/ar_SA/gitlab.po2
-rw-r--r--locale/bg/gitlab.po2
-rw-r--r--locale/ca_ES/gitlab.po2
-rw-r--r--locale/cs_CZ/gitlab.po2
-rw-r--r--locale/da_DK/gitlab.po2
-rw-r--r--locale/de/gitlab.po2
-rw-r--r--locale/eo/gitlab.po2
-rw-r--r--locale/es/gitlab.po2
-rw-r--r--locale/et_EE/gitlab.po2
-rw-r--r--locale/fil_PH/gitlab.po2
-rw-r--r--locale/fr/gitlab.po2
-rw-r--r--locale/gitlab.pot99
-rw-r--r--locale/gl_ES/gitlab.po2
-rw-r--r--locale/he_IL/gitlab.po2
-rw-r--r--locale/id_ID/gitlab.po2
-rw-r--r--locale/it/gitlab.po2
-rw-r--r--locale/ja/gitlab.po2
-rw-r--r--locale/ko/gitlab.po2
-rw-r--r--locale/nl_NL/gitlab.po2
-rw-r--r--locale/pl_PL/gitlab.po2
-rw-r--r--locale/pt_BR/gitlab.po2
-rw-r--r--locale/ro_RO/gitlab.po2
-rw-r--r--locale/ru/gitlab.po2
-rw-r--r--locale/sq_AL/gitlab.po2
-rw-r--r--locale/tr_TR/gitlab.po2
-rw-r--r--locale/uk/gitlab.po2
-rw-r--r--locale/zh_CN/gitlab.po2
-rw-r--r--locale/zh_HK/gitlab.po2
-rw-r--r--locale/zh_TW/gitlab.po2
-rw-r--r--qa/qa/factory/resource/kubernetes_cluster.rb1
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb5
-rw-r--r--qa/qa/service/kubernetes_cluster.rb62
-rw-r--r--qa/qa/service/shellout.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb94
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb18
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb14
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb13
-rw-r--r--spec/factories/broadcast_messages.rb12
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/factories/ci/runners.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb2
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb3
-rw-r--r--spec/factories/emails.rb2
-rw-r--r--spec/factories/gpg_keys.rb4
-rw-r--r--spec/factories/group_members.rb2
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/notes.rb2
-rw-r--r--spec/factories/oauth_access_grants.rb2
-rw-r--r--spec/factories/project_members.rb2
-rw-r--r--spec/factories/todos.rb2
-rw-r--r--spec/factories/uploads.rb10
-rw-r--r--spec/features/calendar_spec.rb21
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb2
-rw-r--r--spec/features/groups/labels/subscription_spec.rb50
-rw-r--r--spec/features/groups/settings/ci_cd_spec.rb39
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb254
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb4
-rw-r--r--spec/features/projects/clusters/user_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb12
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb24
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb4
-rw-r--r--spec/features/u2f_spec.rb4
-rw-r--r--spec/features/users/overview_spec.rb123
-rw-r--r--spec/features/users/show_spec.rb2
-rw-r--r--spec/finders/group_labels_finder_spec.rb19
-rw-r--r--spec/finders/labels_finder_spec.rb10
-rw-r--r--spec/finders/merge_requests_finder_spec.rb32
-rw-r--r--spec/fixtures/api/schemas/job/runners.json3
-rw-r--r--spec/fixtures/ssh_host_example_key.pub2
-rw-r--r--spec/javascripts/diffs/components/commit_item_spec.js35
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js16
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js9
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js16
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js16
-rw-r--r--spec/javascripts/jobs/components/commit_block_spec.js2
-rw-r--r--spec/javascripts/jobs/components/empty_state_spec.js2
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js139
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js92
-rw-r--r--spec/javascripts/notes/mock_data.js24
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb88
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb4
-rw-r--r--spec/models/ci/pipeline_spec.rb30
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb5
-rw-r--r--spec/models/instance_configuration_spec.rb6
-rw-r--r--spec/models/label_spec.rb36
-rw-r--r--spec/requests/api/commits_spec.rb14
-rw-r--r--spec/routing/project_routing_spec.rb8
-rw-r--r--spec/serializers/commit_entity_spec.rb28
-rw-r--r--spec/services/notification_recipient_service_spec.rb47
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/support/services/clusters/create_service_shared.rb4
-rw-r--r--vendor/jupyter/values.yaml4
-rw-r--r--yarn.lock79
231 files changed, 3137 insertions, 1224 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2a299ea79ef..c652b6c75e2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -606,7 +606,7 @@ static-analysis:
docs lint:
<<: *dedicated-runner
<<: *except-qa
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
stage: test
cache: {}
dependencies: []
@@ -614,8 +614,8 @@ docs lint:
script:
- scripts/lint-doc.sh
- scripts/lint-changelog-yaml
- - mv doc/ /nanoc/content/
- - cd /nanoc
+ - mv doc/ /tmp/gitlab-docs/content/
+ - cd /tmp/gitlab-docs
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d4f7615c80e..571df7534cb 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -10,24 +10,6 @@
Capybara/CurrentPathExpectation:
Enabled: false
-# Offense count: 23
-FactoryBot/DynamicAttributeDefinedStatically:
- Exclude:
- - 'spec/factories/broadcast_messages.rb'
- - 'spec/factories/ci/builds.rb'
- - 'spec/factories/ci/runners.rb'
- - 'spec/factories/clusters/applications/helm.rb'
- - 'spec/factories/clusters/platforms/kubernetes.rb'
- - 'spec/factories/emails.rb'
- - 'spec/factories/gpg_keys.rb'
- - 'spec/factories/group_members.rb'
- - 'spec/factories/merge_requests.rb'
- - 'spec/factories/notes.rb'
- - 'spec/factories/oauth_access_grants.rb'
- - 'spec/factories/project_members.rb'
- - 'spec/factories/todos.rb'
- - 'spec/factories/uploads.rb'
-
# Offense count: 167
# Cop supports --auto-correct.
Layout/EmptyLinesAroundArguments:
@@ -53,20 +35,6 @@ Layout/IndentArray:
Layout/IndentHash:
Enabled: false
-# Offense count: 11
-# Cop supports --auto-correct.
-# Configuration parameters: AllowForAlignment.
-Layout/SpaceBeforeFirstArg:
- Exclude:
- - 'config/routes/project.rb'
- - 'db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb'
- - 'features/steps/project/source/browse_files.rb'
- - 'features/steps/project/source/markdown_render.rb'
- - 'lib/api/runners.rb'
- - 'spec/features/search/user_uses_search_filters_spec.rb'
- - 'spec/routing/project_routing_spec.rb'
- - 'spec/services/system_note_service_spec.rb'
-
# Offense count: 93
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -74,15 +42,6 @@ Layout/SpaceBeforeFirstArg:
Layout/SpaceInLambdaLiteral:
Enabled: false
-# Offense count: 1
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
-# SupportedStyles: space, no_space, compact
-# SupportedStylesForEmptyBrackets: space, no_space
-Layout/SpaceInsideArrayLiteralBrackets:
- Exclude:
- - 'spec/lib/gitlab/import_export/relation_factory_spec.rb'
-
# Offense count: 327
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
@@ -96,14 +55,6 @@ Layout/SpaceInsideBlockBraces:
Layout/SpaceInsideParens:
Enabled: false
-# Offense count: 14
-# Cop supports --auto-correct.
-Layout/SpaceInsidePercentLiteralDelimiters:
- Exclude:
- - 'lib/gitlab/git_access.rb'
- - 'lib/gitlab/health_checks/fs_shards_check.rb'
- - 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb'
-
# Offense count: 26
Lint/DuplicateMethods:
Exclude:
@@ -135,31 +86,11 @@ Lint/InterpolationCheck:
Lint/MissingCopEnableDirective:
Enabled: false
-# Offense count: 2
-Lint/NestedPercentLiteral:
- Exclude:
- - 'lib/gitlab/git/repository.rb'
- - 'spec/support/shared_examples/email_format_shared_examples.rb'
-
# Offense count: 1
Lint/ReturnInVoidContext:
Exclude:
- 'app/models/project.rb'
-# Offense count: 1
-# Configuration parameters: IgnoreImplicitReferences.
-Lint/ShadowedArgument:
- Exclude:
- - 'lib/gitlab/database/sha_attribute.rb'
-
-# Offense count: 3
-# Cop supports --auto-correct.
-Lint/UnneededRequireStatement:
- Exclude:
- - 'db/post_migrate/20161221153951_rename_reserved_project_names.rb'
- - 'db/post_migrate/20170313133418_rename_more_reserved_project_names.rb'
- - 'lib/declarative_policy.rb'
-
# Offense count: 9
Lint/UriEscapeUnescape:
Exclude:
@@ -199,16 +130,6 @@ Naming/HeredocDelimiterCase:
Naming/HeredocDelimiterNaming:
Enabled: false
-# Offense count: 1
-Performance/UnfreezeString:
- Exclude:
- - 'features/steps/project/commits/commits.rb'
-
-# Offense count: 1
-# Cop supports --auto-correct.
-Performance/UriDefaultParser:
- Exclude:
- - 'lib/gitlab/url_sanitizer.rb'
# Offense count: 3821
# Configuration parameters: Prefixes.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be204a76645..a02d1bc38b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.3.3 (2018-10-04)
+
+- No changes.
+
## 11.3.2 (2018-10-03)
### Fixed (4 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 3ba7bd5ba83..ee476f3ae84 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.123.0
+0.124.0
diff --git a/Gemfile b/Gemfile
index 52de588deb3..ecbfba0827d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -295,6 +295,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
+gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch'
# Metrics
group :metrics do
diff --git a/Gemfile.lock b/Gemfile.lock
index 301f4132476..9837a195d8c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -301,6 +301,8 @@ GEM
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.4)
+ gitlab-sidekiq-fetcher (0.3.0)
+ sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
@@ -1031,6 +1033,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4)
+ gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 1e129bc2b54..9eab07d9659 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -304,6 +304,8 @@ GEM
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.4)
+ gitlab-sidekiq-fetcher (0.3.0)
+ sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
@@ -1040,6 +1042,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4)
+ gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index cd800d75f7a..ecc2440c7e6 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -22,6 +22,7 @@ const Api = {
dockerfilePath: '/api/:version/templates/dockerfiles/:key',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
usersPath: '/api/:version/users.json',
+ userStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
@@ -266,6 +267,15 @@ const Api = {
});
},
+ postUserStatus({ emoji, message }) {
+ const url = Api.buildUrl(this.userStatusPath);
+
+ return axios.put(url, {
+ emoji,
+ message,
+ });
+ },
+
templates(key, params = {}) {
const url = Api.buildUrl(this.templatesPath).replace(':key', key);
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 5b0c4285339..cace8bb9dba 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -42,10 +42,11 @@ export class AwardsHandler {
}
bindEvents() {
+ const $parentEl = this.targetContainerEl ? $(this.targetContainerEl) : $(document);
// If the user shows intent let's pre-build the menu
this.registerEventListener(
'one',
- $(document),
+ $parentEl,
'mouseenter focus',
this.toggleButtonSelector,
'mouseenter focus',
@@ -58,7 +59,7 @@ export class AwardsHandler {
}
},
);
- this.registerEventListener('on', $(document), 'click', this.toggleButtonSelector, e => {
+ this.registerEventListener('on', $parentEl, 'click', this.toggleButtonSelector, e => {
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
@@ -76,7 +77,7 @@ export class AwardsHandler {
});
const emojiButtonSelector = `.js-awards-block .js-emoji-btn, .${this.menuClass} .js-emoji-btn`;
- this.registerEventListener('on', $(document), 'click', emojiButtonSelector, e => {
+ this.registerEventListener('on', $parentEl, 'click', emojiButtonSelector, e => {
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
@@ -168,7 +169,8 @@ export class AwardsHandler {
</div>
`;
- document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup);
+ const targetEl = this.targetContainerEl ? this.targetContainerEl : document.body;
+ targetEl.insertAdjacentHTML('beforeend', emojiMenuMarkup);
this.addRemainingEmojiMenuCategories();
this.setupSearch();
@@ -250,6 +252,12 @@ export class AwardsHandler {
}
positionMenu($menu, $addBtn) {
+ if (this.targetContainerEl) {
+ return $menu.css({
+ top: `${$addBtn.outerHeight()}px`,
+ });
+ }
+
const position = $addBtn.data('position');
// The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body
@@ -424,9 +432,7 @@ export class AwardsHandler {
users = origTitle.trim().split(FROM_SENTENCE_REGEX);
}
users.unshift('You');
- return awardBlock
- .attr('title', this.toSentence(users))
- .tooltip('_fixTitle');
+ return awardBlock.attr('title', this.toSentence(users)).tooltip('_fixTitle');
}
createAwardButtonForVotesBlock(votesBlock, emojiName) {
@@ -609,13 +615,11 @@ export class AwardsHandler {
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
- awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(
- Emoji => {
- const awardsHandler = new AwardsHandler(Emoji);
- awardsHandler.bindEvents();
- return awardsHandler;
- },
- );
+ awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => {
+ const awardsHandler = new AwardsHandler(Emoji);
+ awardsHandler.bindEvents();
+ return awardsHandler;
+ });
}
return awardsHandlerPromise;
}
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 5758588e82e..993206b2e73 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CIIcon from '~/vue_shared/components/ci_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
/**
* CommitItem
@@ -29,6 +30,7 @@ export default {
ClipboardButton,
CIIcon,
TimeAgoTooltip,
+ CommitPipelineStatus,
},
props: {
commit: {
@@ -102,6 +104,14 @@ export default {
></pre>
</div>
<div class="commit-actions flex-row d-none d-sm-flex">
+ <div
+ v-if="commit.signatureHtml"
+ v-html="commit.signatureHtml"
+ ></div>
+ <commit-pipeline-status
+ v-if="commit.pipelineStatusPath"
+ :endpoint="commit.pipelineStatusPath"
+ />
<div class="commit-sha-group">
<div
class="label label-monospace"
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 4ae588042e4..c39403f1021 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -244,6 +244,7 @@ export function getDiffPositionByLineCode(diffFiles) {
oldLine,
newLine,
lineCode,
+ positionType: 'text',
};
}
});
@@ -259,8 +260,8 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
const { lineCode, ...diffPositionCopy } = diffPosition;
if (discussion.original_position && discussion.position) {
- const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter);
- const refs = convertObjectPropsToCamelCase(discussion.position.formatter);
+ const originalRefs = convertObjectPropsToCamelCase(discussion.original_position);
+ const refs = convertObjectPropsToCamelCase(discussion.position);
return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy);
}
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 11e3b781e5a..a1d8e531940 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -466,7 +466,9 @@ export default {
class="gl-responsive-table-row"
role="row">
<div
- class="table-section section-wrap section-15"
+ v-tooltip
+ :title="model.name"
+ class="table-section section-wrap section-15 text-truncate"
role="gridcell"
>
<div
@@ -480,9 +482,7 @@ export default {
v-if="!model.isFolder"
class="environment-name table-mobile-content">
<a
- v-tooltip
:href="environmentPath"
- :title="model.name"
>
{{ model.name }}
</a>
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 4ae3a714bee..175d0b8498b 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,5 +1,9 @@
import $ from 'jquery';
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
import { highCountTrim } from '~/lib/utils/text_utility';
+import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue';
+import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue';
/**
* Updates todo counter when todos are toggled.
@@ -17,3 +21,54 @@ export default function initTodoToggle() {
$todoPendingCount.toggleClass('hidden', parsedCount === 0);
});
}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger');
+ const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
+
+ if (setStatusModalTriggerEl || setStatusModalWrapperEl) {
+ Vue.use(Translate);
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: setStatusModalTriggerEl,
+ data() {
+ const { hasStatus } = this.$options.el.dataset;
+
+ return {
+ hasStatus: hasStatus === 'true',
+ };
+ },
+ render(createElement) {
+ return createElement(SetStatusModalTrigger, {
+ props: {
+ hasStatus: this.hasStatus,
+ },
+ });
+ },
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: setStatusModalWrapperEl,
+ data() {
+ const { currentEmoji, currentMessage } = this.$options.el.dataset;
+
+ return {
+ currentEmoji,
+ currentMessage,
+ };
+ },
+ render(createElement) {
+ const { currentEmoji, currentMessage } = this;
+
+ return createElement(SetStatusModalWrapper, {
+ props: {
+ currentEmoji,
+ currentMessage,
+ },
+ });
+ },
+ });
+ }
+});
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 39a4ff159e2..4b1788a1c16 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -46,7 +46,7 @@
v-if="mergeRequest"
:href="mergeRequest.path"
class="js-link-commit link-commit"
- >{{ mergeRequest.iid }}</a>
+ >!{{ mergeRequest.iid }}</a>
</p>
<p class="build-light-text append-bottom-0">
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index ff45a5b05f8..ff500eddddb 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -27,7 +27,7 @@
value === null ||
(Object.prototype.hasOwnProperty.call(value, 'path') &&
Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'title'))
+ Object.prototype.hasOwnProperty.call(value, 'button_title'))
);
},
},
@@ -67,7 +67,7 @@
:data-method="action.method"
class="js-job-empty-state-action btn btn-primary"
>
- {{ action.title }}
+ {{ action.button_title }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index bac8bd71d64..047e55866ce 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -2,6 +2,7 @@
import { mapGetters, mapState } from 'vuex';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
+ import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
import StuckBlock from './stuck_block.vue';
@@ -11,6 +12,7 @@
components: {
CiHeader,
Callout,
+ EmptyState,
EnvironmentsBlock,
ErasedBlock,
StuckBlock,
@@ -31,6 +33,8 @@
'jobHasStarted',
'hasEnvironment',
'isJobStuck',
+ 'hasTrace',
+ 'emptyStateIllustration',
]),
},
};
@@ -77,12 +81,14 @@
<environments-block
v-if="hasEnvironment"
+ class="js-job-environment"
:deployment-status="job.deployment_status"
:icon-status="job.status"
/>
<erased-block
v-if="job.erased"
+ class="js-job-erased"
:user="job.erased_by"
:erased-at="job.erased_at"
/>
@@ -91,6 +97,15 @@
<!-- EO job log -->
<!--empty state -->
+ <empty-state
+ v-if="!hasTrace"
+ class="js-job-empty-state"
+ :illustration-path="emptyStateIllustration.image"
+ :illustration-size-class="emptyStateIllustration.size"
+ :title="emptyStateIllustration.title"
+ :content="emptyStateIllustration.content"
+ :action="job.status.action"
+ />
<!-- EO empty state -->
<!-- EO Body Section -->
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 62d154ff584..afe5f88b292 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -30,13 +30,24 @@ export const jobHasStarted = state => !(state.job.started === false);
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
/**
+ * Checks if it the job has trace.
+ * Used to check if it should render the job log or the empty state
+ * @returns {Boolean}
+ */
+export const hasTrace = state => state.job.has_trace || state.job.status.group === 'running';
+
+export const emptyStateIllustration = state =>
+ (state.job && state.job.status && state.job.status.illustration) || {};
+
+/**
* When the job is pending and there are no available runners
* we need to render the stuck block;
*
* @returns {Boolean}
*/
export const isJobStuck = state =>
- state.job.status.group === 'pending' && state.job.runners && state.job.runners.available === false;
+ state.job.status.group === 'pending' &&
+ (!_.isEmpty(state.job.runners) && state.job.runners.available === false);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index d4babf1fab2..75832884711 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -126,8 +126,8 @@ export const unresolvedDiscussionsIdsByDiff = (state, getters) =>
const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path);
// Get the line numbers, to compare within the same file
- const aLines = [a.position.formatter.new_line, a.position.formatter.old_line];
- const bLines = [b.position.formatter.new_line, b.position.formatter.old_line];
+ const aLines = [a.position.new_line, a.position.old_line];
+ const bLines = [b.position.new_line, b.position.old_line];
return filenameComparison < 0 ||
(filenameComparison === 0 &&
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 9aa83ce6269..72f3f70b98f 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -79,10 +79,13 @@ export default class Todos {
.then(({ data }) => {
this.updateRowState(target);
this.updateBadges(data);
- }).catch(() => flash(__('Error updating todo status.')));
+ }).catch(() => {
+ this.updateRowState(target, true);
+ return flash(__('Error updating todo status.'));
+ });
}
- updateRowState(target) {
+ updateRowState(target, isInactive = false) {
const row = target.closest('li');
const restoreBtn = row.querySelector('.js-undo-todo');
const doneBtn = row.querySelector('.js-done-todo');
@@ -91,7 +94,10 @@ export default class Todos {
target.removeAttribute('disabled');
target.classList.remove('disabled');
- if (target === doneBtn) {
+ if (isInactive === true) {
+ restoreBtn.classList.add('hidden');
+ doneBtn.classList.remove('hidden');
+ } else if (target === doneBtn) {
row.classList.add('done-reversible');
restoreBtn.classList.remove('hidden');
} else if (target === restoreBtn) {
diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js
index aea7b649c20..c7ce4675573 100644
--- a/app/assets/javascripts/pages/profiles/show/index.js
+++ b/app/assets/javascripts/pages/profiles/show/index.js
@@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
const statusEmojiField = document.getElementById('js-status-emoji-field');
const statusMessageField = document.getElementById('js-status-message-field');
- const toggleNoEmojiPlaceholder = (isVisible) => {
+ const toggleNoEmojiPlaceholder = isVisible => {
const placeholderElement = document.getElementById('js-no-emoji-placeholder');
placeholderElement.classList.toggle('hidden', !isVisible);
};
@@ -69,5 +69,5 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
})
- .catch(() => createFlash('Failed to load emoji list!'));
+ .catch(() => createFlash('Failed to load emoji list.'));
});
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 9892a039941..bf592ba7a3c 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -43,7 +43,15 @@ const initColorKey = () =>
.domain([0, 3]);
export default class ActivityCalendar {
- constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0, firstDayOfWeek = 0) {
+ constructor(
+ container,
+ activitiesContainer,
+ timestamps,
+ calendarActivitiesPath,
+ utcOffset = 0,
+ firstDayOfWeek = 0,
+ monthsAgo = 12,
+ ) {
this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
@@ -66,6 +74,8 @@ export default class ActivityCalendar {
];
this.months = [];
this.firstDayOfWeek = firstDayOfWeek;
+ this.activitiesContainer = activitiesContainer;
+ this.container = container;
// Loop through the timestamps to create a group of objects
// The group of objects will be grouped based on the day of the week they are
@@ -75,13 +85,13 @@ export default class ActivityCalendar {
const today = getSystemDate(utcOffset);
today.setHours(0, 0, 0, 0, 0);
- const oneYearAgo = new Date(today);
- oneYearAgo.setFullYear(today.getFullYear() - 1);
+ const timeAgo = new Date(today);
+ timeAgo.setMonth(today.getMonth() - monthsAgo);
- const days = getDayDifference(oneYearAgo, today);
+ const days = getDayDifference(timeAgo, today);
for (let i = 0; i <= days; i += 1) {
- const date = new Date(oneYearAgo);
+ const date = new Date(timeAgo);
date.setDate(date.getDate() + i);
const day = date.getDay();
@@ -280,7 +290,7 @@ export default class ActivityCalendar {
this.currentSelectedDate.getDate(),
].join('-');
- $('.user-calendar-activities').html(LOADING_HTML);
+ $(this.activitiesContainer).html(LOADING_HTML);
axios
.get(this.calendarActivitiesPath, {
@@ -289,11 +299,11 @@ export default class ActivityCalendar {
},
responseType: 'text',
})
- .then(({ data }) => $('.user-calendar-activities').html(data))
+ .then(({ data }) => $(this.activitiesContainer).html(data))
.catch(() => flash(__('An error occurred while retrieving calendar activity')));
} else {
this.currentSelectedDate = '';
- $('.user-calendar-activities').html('');
+ $(this.activitiesContainer).html('');
}
}
}
diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js
new file mode 100644
index 00000000000..0009419cd0c
--- /dev/null
+++ b/app/assets/javascripts/pages/users/user_overview_block.js
@@ -0,0 +1,42 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default class UserOverviewBlock {
+ constructor(options = {}) {
+ this.container = options.container;
+ this.url = options.url;
+ this.limit = options.limit || 20;
+ this.loadData();
+ }
+
+ loadData() {
+ const loadingEl = document.querySelector(`${this.container} .loading`);
+
+ loadingEl.classList.remove('hide');
+
+ axios
+ .get(this.url, {
+ params: {
+ limit: this.limit,
+ },
+ })
+ .then(({ data }) => this.render(data))
+ .catch(() => loadingEl.classList.add('hide'));
+ }
+
+ render(data) {
+ const { html, count } = data;
+ const contentList = document.querySelector(`${this.container} .overview-content-list`);
+
+ contentList.innerHTML += html;
+
+ const loadingEl = document.querySelector(`${this.container} .loading`);
+
+ if (count && count > 0) {
+ document.querySelector(`${this.container} .js-view-all`).classList.remove('hide');
+ } else {
+ document.querySelector(`${this.container} .nothing-here-block`).classList.add('text-left', 'p-0');
+ }
+
+ loadingEl.classList.add('hide');
+ }
+}
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index a2ca03536f2..23b0348a99f 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -2,9 +2,10 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import Activities from '~/activities';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import flash from '~/flash';
import ActivityCalendar from './activity_calendar';
+import UserOverviewBlock from './user_overview_block';
/**
* UserTabs
@@ -61,19 +62,28 @@ import ActivityCalendar from './activity_calendar';
* </div>
*/
-const CALENDAR_TEMPLATE = `
- <div class="clearfix calendar">
- <div class="js-contrib-calendar"></div>
- <div class="calendar-hint">
- Summary of issues, merge requests, push events, and comments
+const CALENDAR_TEMPLATES = {
+ activity: `
+ <div class="clearfix calendar">
+ <div class="js-contrib-calendar"></div>
+ <div class="calendar-hint bottom-right"></div>
</div>
- </div>
-`;
+ `,
+ overview: `
+ <div class="clearfix calendar">
+ <div class="calendar-hint"></div>
+ <div class="js-contrib-calendar prepend-top-20"></div>
+ </div>
+ `,
+};
+
+const CALENDAR_PERIOD_6_MONTHS = 6;
+const CALENDAR_PERIOD_12_MONTHS = 12;
export default class UserTabs {
constructor({ defaultAction, action, parentEl }) {
this.loaded = {};
- this.defaultAction = defaultAction || 'activity';
+ this.defaultAction = defaultAction || 'overview';
this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document);
this.windowLocation = window.location;
@@ -124,6 +134,8 @@ export default class UserTabs {
}
if (action === 'activity') {
this.loadActivities();
+ } else if (action === 'overview') {
+ this.loadOverviewTab();
}
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
@@ -154,7 +166,40 @@ export default class UserTabs {
if (this.loaded.activity) {
return;
}
- const $calendarWrap = this.$parentEl.find('.user-calendar');
+
+ this.loadActivityCalendar('activity');
+
+ // eslint-disable-next-line no-new
+ new Activities();
+
+ this.loaded.activity = true;
+ }
+
+ loadOverviewTab() {
+ if (this.loaded.overview) {
+ return;
+ }
+
+ this.loadActivityCalendar('overview');
+
+ UserTabs.renderMostRecentBlocks('#js-overview .activities-block', 5);
+ UserTabs.renderMostRecentBlocks('#js-overview .projects-block', 10);
+
+ this.loaded.overview = true;
+ }
+
+ static renderMostRecentBlocks(container, limit) {
+ // eslint-disable-next-line no-new
+ new UserOverviewBlock({
+ container,
+ url: $(`${container} .overview-content-list`).data('href'),
+ limit,
+ });
+ }
+
+ loadActivityCalendar(action) {
+ const monthsAgo = action === 'overview' ? CALENDAR_PERIOD_6_MONTHS : CALENDAR_PERIOD_12_MONTHS;
+ const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar');
const calendarPath = $calendarWrap.data('calendarPath');
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
const utcOffset = $calendarWrap.data('utcOffset');
@@ -166,17 +211,22 @@ export default class UserTabs {
axios
.get(calendarPath)
.then(({ data }) => {
- $calendarWrap.html(CALENDAR_TEMPLATE);
- $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
+ $calendarWrap.html(CALENDAR_TEMPLATES[action]);
+
+ let calendarHint = '';
+
+ if (action === 'activity') {
+ calendarHint = sprintf(__('Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})'), { utcFormatted });
+ } else if (action === 'overview') {
+ calendarHint = __('Issues, merge requests, pushes and comments.');
+ }
+
+ $calendarWrap.find('.calendar-hint').text(calendarHint);
// eslint-disable-next-line no-new
- new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset);
+ new ActivityCalendar('.tab-pane.active .js-contrib-calendar', '.tab-pane.active .user-calendar-activities', data, calendarActivitiesPath, utcOffset, 0, monthsAgo);
})
.catch(() => flash(__('There was an error loading users activity calendar.')));
-
- // eslint-disable-next-line no-new
- new Activities();
- this.loaded.activity = true;
}
toggleLoading(status) {
diff --git a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
new file mode 100644
index 00000000000..14a89ef9293
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
@@ -0,0 +1,21 @@
+import { AwardsHandler } from '~/awards_handler';
+
+class EmojiMenuInModal extends AwardsHandler {
+ constructor(emoji, toggleButtonSelector, menuClass, selectEmojiCallback, targetContainerEl) {
+ super(emoji);
+
+ this.selectEmojiCallback = selectEmojiCallback;
+ this.toggleButtonSelector = toggleButtonSelector;
+ this.menuClass = menuClass;
+ this.targetContainerEl = targetContainerEl;
+
+ this.bindEvents();
+ }
+
+ postEmoji($emojiButton, awardUrl, selectedEmoji, callback) {
+ this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji));
+ callback();
+ }
+}
+
+export default EmojiMenuInModal;
diff --git a/app/assets/javascripts/set_status_modal/event_hub.js b/app/assets/javascripts/set_status_modal/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue
new file mode 100644
index 00000000000..48e5ede80f2
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue
@@ -0,0 +1,33 @@
+<script>
+import { s__ } from '~/locale';
+import eventHub from './event_hub';
+
+export default {
+ props: {
+ hasStatus: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ buttonText() {
+ return this.hasStatus ? s__('SetStatusModal|Edit status') : s__('SetStatusModal|Set status');
+ },
+ },
+ methods: {
+ openModal() {
+ eventHub.$emit('openModal');
+ },
+ },
+};
+</script>
+
+<template>
+ <button
+ type="button"
+ class="btn menu-item"
+ @click="openModal"
+ >
+ {{ buttonText }}
+ </button>
+</template>
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
new file mode 100644
index 00000000000..43f0b6651b9
--- /dev/null
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -0,0 +1,241 @@
+<script>
+import $ from 'jquery';
+import createFlash from '~/flash';
+import Icon from '~/vue_shared/components/icon.vue';
+import GfmAutoComplete from '~/gfm_auto_complete';
+import { __, s__ } from '~/locale';
+import Api from '~/api';
+import eventHub from './event_hub';
+import EmojiMenuInModal from './emoji_menu_in_modal';
+
+const emojiMenuClass = 'js-modal-status-emoji-menu';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ currentEmoji: {
+ type: String,
+ required: true,
+ },
+ currentMessage: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ defaultEmojiTag: '',
+ emoji: this.currentEmoji,
+ emojiMenu: null,
+ emojiTag: '',
+ isEmojiMenuVisible: false,
+ message: this.currentMessage,
+ modalId: 'set-user-status-modal',
+ noEmoji: true,
+ };
+ },
+ computed: {
+ isDirty() {
+ return this.message.length || this.emoji.length;
+ },
+ },
+ mounted() {
+ eventHub.$on('openModal', this.openModal);
+ },
+ beforeDestroy() {
+ this.emojiMenu.destroy();
+ },
+ methods: {
+ openModal() {
+ this.$root.$emit('bv::show::modal', this.modalId);
+ },
+ closeModal() {
+ this.$root.$emit('bv::hide::modal', this.modalId);
+ },
+ setupEmojiListAndAutocomplete() {
+ const toggleEmojiMenuButtonSelector = '#set-user-status-modal .js-toggle-emoji-menu';
+ const emojiAutocomplete = new GfmAutoComplete();
+ emojiAutocomplete.setup($(this.$refs.statusMessageField), { emojis: true });
+
+ import(/* webpackChunkName: 'emoji' */ '~/emoji')
+ .then(Emoji => {
+ if (this.emoji) {
+ this.emojiTag = Emoji.glEmojiTag(this.emoji);
+ }
+ this.noEmoji = this.emoji === '';
+ this.defaultEmojiTag = Emoji.glEmojiTag('speech_balloon');
+
+ this.emojiMenu = new EmojiMenuInModal(
+ Emoji,
+ toggleEmojiMenuButtonSelector,
+ emojiMenuClass,
+ this.setEmoji,
+ this.$refs.userStatusForm,
+ );
+ })
+ .catch(() => createFlash(__('Failed to load emoji list.')));
+ },
+ showEmojiMenu() {
+ this.isEmojiMenuVisible = true;
+ this.emojiMenu.showEmojiMenu($(this.$refs.toggleEmojiMenuButton));
+ },
+ hideEmojiMenu() {
+ if (!this.isEmojiMenuVisible) {
+ return;
+ }
+
+ this.isEmojiMenuVisible = false;
+ this.emojiMenu.hideMenuElement($(`.${emojiMenuClass}`));
+ },
+ setDefaultEmoji() {
+ const { emojiTag } = this;
+ const hasStatusMessage = this.message;
+ if (hasStatusMessage && emojiTag) {
+ return;
+ }
+
+ if (hasStatusMessage) {
+ this.noEmoji = false;
+ this.emojiTag = this.defaultEmojiTag;
+ } else if (emojiTag === this.defaultEmojiTag) {
+ this.noEmoji = true;
+ this.clearEmoji();
+ }
+ },
+ setEmoji(emoji, emojiTag) {
+ this.emoji = emoji;
+ this.noEmoji = false;
+ this.clearEmoji();
+ this.emojiTag = emojiTag;
+ },
+ clearEmoji() {
+ if (this.emojiTag) {
+ this.emojiTag = '';
+ }
+ },
+ clearStatusInputs() {
+ this.emoji = '';
+ this.message = '';
+ this.noEmoji = true;
+ this.clearEmoji();
+ this.hideEmojiMenu();
+ },
+ removeStatus() {
+ this.clearStatusInputs();
+ this.setStatus();
+ },
+ setStatus() {
+ const { emoji, message } = this;
+
+ Api.postUserStatus({
+ emoji,
+ message,
+ })
+ .then(this.onUpdateSuccess)
+ .catch(this.onUpdateFail);
+ },
+ onUpdateSuccess() {
+ this.closeModal();
+ window.location.reload();
+ },
+ onUpdateFail() {
+ createFlash(
+ s__("SetStatusModal|Sorry, we weren't able to set your status. Please try again later."),
+ );
+
+ this.closeModal();
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-ui-modal
+ :title="s__('SetStatusModal|Set a status')"
+ :modal-id="modalId"
+ :ok-title="s__('SetStatusModal|Set status')"
+ :cancel-title="s__('SetStatusModal|Remove status')"
+ ok-variant="success"
+ class="set-user-status-modal"
+ @shown="setupEmojiListAndAutocomplete"
+ @hide="hideEmojiMenu"
+ @ok="setStatus"
+ @cancel="removeStatus"
+ >
+ <div>
+ <input
+ v-model="emoji"
+ class="js-status-emoji-field"
+ type="hidden"
+ name="user[status][emoji]"
+ />
+ <div
+ ref="userStatusForm"
+ class="form-group position-relative m-0"
+ >
+ <div class="input-group">
+ <span class="input-group-btn">
+ <button
+ ref="toggleEmojiMenuButton"
+ v-gl-tooltip.bottom
+ :title="s__('SetStatusModal|Add status emoji')"
+ :aria-label="s__('SetStatusModal|Add status emoji')"
+ name="button"
+ type="button"
+ class="js-toggle-emoji-menu emoji-menu-toggle-button btn"
+ @click="showEmojiMenu"
+ >
+ <span v-html="emojiTag"></span>
+ <span
+ v-show="noEmoji"
+ class="js-no-emoji-placeholder no-emoji-placeholder position-relative"
+ >
+ <icon
+ name="emoji_slightly_smiling_face"
+ css-classes="award-control-icon-neutral"
+ />
+ <icon
+ name="emoji_smiley"
+ css-classes="award-control-icon-positive"
+ />
+ <icon
+ name="emoji_smile"
+ css-classes="award-control-icon-super-positive"
+ />
+ </span>
+ </button>
+ </span>
+ <input
+ ref="statusMessageField"
+ v-model="message"
+ :placeholder="s__('SetStatusModal|What\'s your status?')"
+ type="text"
+ class="form-control form-control input-lg js-status-message-field"
+ name="user[status][message]"
+ @keyup="setDefaultEmoji"
+ @keyup.enter.prevent
+ @click="hideEmojiMenu"
+ />
+ <span
+ v-show="isDirty"
+ class="input-group-btn"
+ >
+ <button
+ v-gl-tooltip.bottom
+ :title="s__('SetStatusModal|Clear status')"
+ :aria-label="s__('SetStatusModal|Clear status')"
+ name="button"
+ type="button"
+ class="js-clear-user-status-button clear-user-status btn"
+ @click="clearStatusInputs()"
+ >
+ <icon name="close" />
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </gl-ui-modal>
+</template>
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index e19bbbacf4d..b9dfa22dd49 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim();
emailUser = {
- name: "Invite \"" + query.term + "\" by email",
+ name: "Invite \"" + trimmed + "\" by email",
username: trimmed,
id: trimmed,
invite: true
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0b9dff64b0b..9638fee6078 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,8 +1,7 @@
-.calender-block {
+.calendar-block {
padding-left: 0;
padding-right: 0;
border-top: 0;
- direction: rtl;
@media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) {
overflow-x: auto;
@@ -42,10 +41,13 @@
}
.calendar-hint {
- margin-top: -23px;
- float: right;
font-size: 12px;
- direction: ltr;
+
+ &.bottom-right {
+ direction: ltr;
+ margin-top: -23px;
+ float: right;
+ }
}
.pika-single.gitlab-theme {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 11a30d83f03..c430009bfe0 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -529,9 +529,10 @@
}
.header-user {
- .dropdown-menu {
+ &.show .dropdown-menu {
width: auto;
min-width: unset;
+ max-height: 323px;
margin-top: 4px;
color: $gl-text-color;
left: auto;
@@ -542,6 +543,18 @@
.user-name {
display: block;
}
+
+ .user-status-emoji {
+ margin-right: 0;
+ display: block;
+ vertical-align: text-top;
+ max-width: 148px;
+ font-size: 12px;
+
+ gl-emoji {
+ font-size: $gl-font-size;
+ }
+ }
}
svg {
@@ -573,3 +586,24 @@
}
}
}
+
+.set-user-status-modal {
+ .modal-body {
+ min-height: unset;
+ }
+
+ .input-lg {
+ max-width: unset;
+ }
+
+ .no-emoji-placeholder,
+ .clear-user-status {
+ svg {
+ fill: $gl-text-color-secondary;
+ }
+ }
+
+ .emoji-menu-toggle-button {
+ @include emoji-menu-toggle-button;
+ }
+}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 7f37dd3de91..be41dbfc61f 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -20,20 +20,24 @@
display: inline-block;
overflow-x: auto;
border: 0;
- border-color: $gray-100;
+ border-color: $gl-gray-100;
@supports (width: fit-content) {
display: block;
width: fit-content;
}
+ tbody {
+ background-color: $white-light;
+ }
+
tr {
th {
- border-bottom: solid 2px $gray-100;
+ border-bottom: solid 2px $gl-gray-100;
}
td {
- border-color: $gray-100;
+ border-color: $gl-gray-100;
}
}
}
@@ -266,3 +270,59 @@
border-radius: 50%;
}
}
+
+@mixin emoji-menu-toggle-button {
+ line-height: 1;
+ padding: 0;
+ min-width: 16px;
+ color: $gray-darkest;
+ fill: $gray-darkest;
+
+ .fa {
+ position: relative;
+ font-size: 16px;
+ }
+
+ svg {
+ @include btn-svg;
+ margin: 0;
+ }
+
+ .award-control-icon-positive,
+ .award-control-icon-super-positive {
+ position: absolute;
+ top: 0;
+ left: 0;
+ opacity: 0;
+ }
+
+ &:hover,
+ &.is-active {
+ .danger-highlight {
+ color: $red-500;
+ }
+
+ .link-highlight {
+ color: $blue-600;
+ fill: $blue-600;
+ }
+
+ .award-control-icon-neutral {
+ opacity: 0;
+ }
+
+ .award-control-icon-positive {
+ opacity: 1;
+ }
+ }
+
+ &.is-active {
+ .award-control-icon-positive {
+ opacity: 0;
+ }
+
+ .award-control-icon-super-positive {
+ opacity: 1;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 837016db67b..b7a95f604b8 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -314,7 +314,8 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
$monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono',
'Courier New', 'andale mono', 'lucida console', monospace;
$regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+ 'Noto Color Emoji';
/*
* Dropdowns
@@ -634,5 +635,4 @@ Modals
*/
$modal-body-height: 134px;
-
$priority-label-empty-state-width: 114px;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 7d90452e1f4..759b4f333ca 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -18,3 +18,4 @@ $success: $green-500;
$info: $blue-500;
$warning: $orange-500;
$danger: $red-500;
+$zindex-modal-backdrop: 1040;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index c9e0899425f..a2070087963 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -519,59 +519,7 @@ ul.notes {
}
.note-action-button {
- line-height: 1;
- padding: 0;
- min-width: 16px;
- color: $gray-darkest;
- fill: $gray-darkest;
-
- .fa {
- position: relative;
- font-size: 16px;
- }
-
- svg {
- @include btn-svg;
- margin: 0;
- }
-
- .award-control-icon-positive,
- .award-control-icon-super-positive {
- position: absolute;
- top: 0;
- left: 0;
- opacity: 0;
- }
-
- &:hover,
- &.is-active {
- .danger-highlight {
- color: $red-500;
- }
-
- .link-highlight {
- color: $blue-600;
- fill: $blue-600;
- }
-
- .award-control-icon-neutral {
- opacity: 0;
- }
-
- .award-control-icon-positive {
- opacity: 1;
- }
- }
-
- &.is-active {
- .award-control-icon-positive {
- opacity: 0;
- }
-
- .award-control-icon-super-positive {
- opacity: 1;
- }
- }
+ @include emoji-menu-toggle-button;
}
.discussion-toggle-button {
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index caa839c32a5..f084adaf5d3 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -81,14 +81,14 @@
// Middle dot divider between each element in a list of items.
.middle-dot-divider {
&::after {
- content: "\00B7"; // Middle Dot
+ content: '\00B7'; // Middle Dot
padding: 0 6px;
font-weight: $gl-font-weight-bold;
}
&:last-child {
&::after {
- content: "";
+ content: '';
padding: 0;
}
}
@@ -191,7 +191,6 @@
@include media-breakpoint-down(xs) {
width: auto;
}
-
}
.profile-crop-image-container {
@@ -215,7 +214,6 @@
}
}
-
.user-profile {
.cover-controls a {
margin-left: 5px;
@@ -418,7 +416,7 @@ table.u2f-registrations {
}
&.unverified {
- @include status-color($gray-dark, color("gray"), $common-gray-dark);
+ @include status-color($gray-dark, color('gray'), $common-gray-dark);
}
}
}
@@ -431,7 +429,7 @@ table.u2f-registrations {
}
.emoji-menu-toggle-button {
- @extend .note-action-button;
+ @include emoji-menu-toggle-button;
.no-emoji-placeholder {
position: relative;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 7c42dcad959..da3d8aa53ad 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -830,6 +830,14 @@
}
}
+.repository-language-bar-tooltip-language {
+ font-weight: $gl-font-weight-bold;
+}
+
+.repository-language-bar-tooltip-share {
+ color: $theme-gray-400;
+}
+
pre.light-well {
border-color: $well-light-border;
}
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 2e2ab8532d2..59fdbf31fe9 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,4 +1,5 @@
@import 'framework/variables';
+@import 'framework/variables_overrides';
@import 'peek/views/rblineprof';
#js-peek {
@@ -6,7 +7,7 @@
left: 0;
top: 0;
width: 100%;
- z-index: 1039;
+ z-index: #{$zindex-modal-backdrop + 1};
height: $performance-bar-height;
background: $black;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b7c758a42ed..8040a14ef56 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -67,8 +67,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
end
- def reset_runners_token
+ def reset_registration_token
@application_setting.reset_runners_registration_token!
+
flash[:notice] = 'New runners registration token has been generated!'
redirect_to admin_runners_path
end
diff --git a/app/controllers/concerns/labels_as_hash.rb b/app/controllers/concerns/labels_as_hash.rb
new file mode 100644
index 00000000000..1171aa9cf44
--- /dev/null
+++ b/app/controllers/concerns/labels_as_hash.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module LabelsAsHash
+ extend ActiveSupport::Concern
+
+ def labels_as_hash(target = nil, params = {})
+ available_labels = LabelsFinder.new(
+ current_user,
+ params
+ ).execute
+
+ label_hashes = available_labels.as_json(only: [:title, :color])
+
+ if target&.respond_to?(:labels)
+ already_set_labels = available_labels & target.labels
+ if already_set_labels.present?
+ titles = already_set_labels.map(&:title)
+ label_hashes.each do |hash|
+ if titles.include?(hash['title'])
+ hash[:set] = true
+ end
+ end
+ end
+ end
+
+ label_hashes
+ end
+end
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index cb9ab35de85..26768c628ca 100644
--- a/app/controllers/groups/labels_controller.rb
+++ b/app/controllers/groups/labels_controller.rb
@@ -12,7 +12,8 @@ class Groups::LabelsController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
- @labels = GroupLabelsFinder.new(@group, params.merge(sort: sort)).execute
+ @labels = GroupLabelsFinder
+ .new(current_user, @group, params.merge(sort: sort)).execute
end
format.json do
render json: LabelSerializer.new.represent_appearance(available_labels)
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 6d9a225b771..93f3eb2be6d 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -10,6 +10,13 @@ module Groups
define_secret_variables
end
+ def reset_registration_token
+ @group.reset_runners_token!
+
+ flash[:notice] = 'New runners registration token has been generated!'
+ redirect_to group_settings_ci_cd_path
+ end
+
private
def define_secret_variables
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index a0ce3b08d9f..640038818f2 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -163,6 +163,7 @@ class Projects::LabelsController < Projects::ApplicationController
project_id: @project.id,
include_ancestor_groups: params[:include_ancestor_groups],
search: params[:search],
+ subscribed: params[:subscribed],
sort: sort).execute
end
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 25d2c11b7db..5307cd0720a 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -25,7 +25,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@diffs.write_cache
- render json: DiffsSerializer.new(current_user: current_user, project: @merge_request.project).represent(@diffs, additional_attributes)
+ request = {
+ current_user: current_user,
+ project: @merge_request.project,
+ render: ->(partial, locals) { view_to_html_string(partial, locals) }
+ }
+
+ render json: DiffsSerializer.new(request).represent(@diffs, additional_attributes)
end
def define_diff_vars
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index a2d1b7866c2..3a1344651df 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -36,6 +36,13 @@ module Projects
end
end
+ def reset_registration_token
+ @project.reset_runners_token!
+
+ flash[:notice] = 'New runners registration token has been generated!'
+ redirect_to namespace_project_settings_ci_cd_path
+ end
+
private
def update_params
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index e509098d778..d16240af404 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -29,11 +29,17 @@ class UsersController < ApplicationController
format.json do
load_events
- pager_json("events/_events", @events.count)
+ pager_json("events/_events", @events.count, events: @events)
end
end
end
+ def activity
+ respond_to do |format|
+ format.html { render 'show' }
+ end
+ end
+
def groups
load_groups
@@ -53,9 +59,7 @@ class UsersController < ApplicationController
respond_to do |format|
format.html { render 'show' }
format.json do
- render json: {
- html: view_to_html_string("shared/projects/_list", projects: @projects)
- }
+ pager_json("shared/projects/_list", @projects.count, projects: @projects)
end
end
end
@@ -125,6 +129,7 @@ class UsersController < ApplicationController
@projects =
PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page])
+ .per(params[:limit])
prepare_projects_for_rendering(@projects)
end
diff --git a/app/finders/group_labels_finder.rb b/app/finders/group_labels_finder.rb
index 903023033ed..a668a0f0fae 100644
--- a/app/finders/group_labels_finder.rb
+++ b/app/finders/group_labels_finder.rb
@@ -1,17 +1,29 @@
# frozen_string_literal: true
class GroupLabelsFinder
- attr_reader :group, :params
+ attr_reader :current_user, :group, :params
- def initialize(group, params = {})
+ def initialize(current_user, group, params = {})
+ @current_user = current_user
@group = group
@params = params
end
def execute
group.labels
+ .optionally_subscribed_by(subscriber_id)
.optionally_search(params[:search])
.order_by(params[:sort])
.page(params[:page])
end
+
+ private
+
+ def subscriber_id
+ current_user&.id if subscribed?
+ end
+
+ def subscribed?
+ params[:subscribed] == 'true'
+ end
end
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 08fc2968e77..82e0b2ed9e1 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -17,6 +17,7 @@ class LabelsFinder < UnionFinder
@skip_authorization = skip_authorization
items = find_union(label_ids, Label) || Label.none
items = with_title(items)
+ items = by_subscription(items)
items = by_search(items)
sort(items)
end
@@ -84,6 +85,18 @@ class LabelsFinder < UnionFinder
labels.search(params[:search])
end
+ def by_subscription(labels)
+ labels.optionally_subscribed_by(subscriber_id)
+ end
+
+ def subscriber_id
+ current_user&.id if subscribed?
+ end
+
+ def subscribed?
+ params[:subscribed] == 'true'
+ end
+
# Gets redacted array of group ids
# which can include the ancestors and descendants of the requested group.
def group_ids_for(group)
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 50c051c3aa1..e190d5d90c9 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -66,10 +66,6 @@ class MergeRequestsFinder < IssuableFinder
items.where(target_branch: target_branch)
end
- def item_project_ids(items)
- items&.reorder(nil)&.select(:target_project_id)
- end
-
def by_wip(items)
if params[:wip] == 'yes'
items.where(wip_match(items.arel_table))
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index eeca5026da1..3f2e813d381 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -31,7 +31,7 @@ class UserRecentEventsFinder
recent_events(params[:offset] || 0)
.joins(:project)
.with_associations
- .limit_recent(LIMIT, params[:offset])
+ .limit_recent(params[:limit].presence || LIMIT, params[:offset])
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index e3b74f443f7..be1e7016a1e 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -59,8 +59,8 @@ module BoardsHelper
{
toggle: "dropdown",
- list_labels_path: labels_filter_path(true, include_ancestor_groups: true),
- labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups),
+ list_labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_ancestor_groups: true),
+ labels: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: include_descendant_groups),
labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path,
project_path: @project&.path,
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index a67c91b21d7..19eb763e1de 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -13,8 +13,4 @@ module ClustersHelper
render 'projects/clusters/gcp_signup_offer_banner'
end
end
-
- def rbac_clusters_feature_enabled?
- Feature.enabled?(:rbac_clusters)
- end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 6c51739ba1a..76ed8efe2c6 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -131,20 +131,26 @@ module LabelsHelper
end
end
- def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false)
- project = @target_project || @project
-
+ def labels_filter_path_with_defaults(only_group_labels: false, include_ancestor_groups: true, include_descendant_groups: false)
options = {}
options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups
options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups
+ options[:only_group_labels] = only_group_labels if only_group_labels && @group
+ options[:format] = :json
+
+ labels_filter_path(options)
+ end
+
+ def labels_filter_path(options = {})
+ project = @target_project || @project
+ format = options.delete(:format) || :html
if project
- project_labels_path(project, :json, options)
+ project_labels_path(project, format, options)
elsif @group
- options[:only_group_labels] = only_group_labels if only_group_labels
- group_labels_path(@group, :json, options)
+ group_labels_path(@group, format, options)
else
- dashboard_labels_path(:json)
+ dashboard_labels_path(format, options)
end
end
diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb
index c1505b52808..cf7eee7fff3 100644
--- a/app/helpers/repository_languages_helper.rb
+++ b/app/helpers/repository_languages_helper.rb
@@ -13,6 +13,7 @@ module RepositoryLanguagesHelper
content_tag :div, nil,
class: "progress-bar has-tooltip",
style: "width: #{lang.share}%; background-color:#{lang.color}",
- title: lang.name
+ data: { html: true },
+ title: "<span class=\"repository-language-bar-tooltip-language\">#{escape_javascript(lang.name)}</span>&nbsp;<span class=\"repository-language-bar-tooltip-share\">#{lang.share.round(1)}%</span>"
end
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index bcd91f619c8..42b533ad772 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -76,7 +76,7 @@ module UsersHelper
tabs = []
if can?(current_user, :read_user_profile, @user)
- tabs += [:activity, :groups, :contributed, :projects, :snippets]
+ tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets]
end
tabs
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 765943104a0..d986e33280d 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -633,6 +633,10 @@ module Ci
end
end
+ def default_branch?
+ ref == project.default_branch
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 49c36ad9d3f..a61ed03cf35 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -319,7 +319,11 @@ class Commit
def status(ref = nil)
return @statuses[ref] if @statuses.key?(ref)
- @statuses[ref] = project.pipelines.latest_status_per_commit(id, ref)[id]
+ @statuses[ref] = status_for_project(ref, project)
+ end
+
+ def status_for_project(ref, pipeline_project)
+ pipeline_project.pipelines.latest_status_per_commit(id, ref)[id]
end
def set_status_for_ref(ref, status)
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index 1d0a61364b0..92a5c1112af 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -31,9 +31,11 @@ module Subscribable
end
def subscribers(project)
- subscriptions_available(project)
- .where(subscribed: true)
- .map(&:user)
+ relation = subscriptions_available(project)
+ .where(subscribed: true)
+ .select(:user_id)
+
+ User.where(id: relation)
end
def toggle_subscription(user, project = nil)
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 7d8ce0bbd05..11289887e00 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -64,10 +64,10 @@ class InstanceConfiguration
end
def ssh_algorithm_md5(ssh_file_content)
- OpenSSL::Digest::MD5.hexdigest(ssh_file_content).scan(/../).join(':')
+ Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint
end
def ssh_algorithm_sha256(ssh_file_content)
- OpenSSL::Digest::SHA256.hexdigest(ssh_file_content)
+ Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint('SHA256')
end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 9ef57a05b3e..43b49445765 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -45,6 +45,7 @@ class Label < ActiveRecord::Base
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
scope :order_name_asc, -> { reorder(title: :asc) }
scope :order_name_desc, -> { reorder(title: :desc) }
+ scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) }
def self.prioritized(project)
joins(:priorities)
@@ -74,6 +75,14 @@ class Label < ActiveRecord::Base
joins(label_priorities)
end
+ def self.optionally_subscribed_by(user_id)
+ if user_id
+ subscribed_by(user_id)
+ else
+ all
+ end
+ end
+
alias_attribute :name, :title
def self.reference_prefix
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index d0e84b1aa38..f2c246cd969 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -390,7 +390,11 @@ class ProjectPolicy < BasePolicy
greedy_load_subject ||= !@user.persisted?
if greedy_load_subject
- project.team.members.include?(user)
+ # We want to load all the members with one query. Calling #include? on
+ # project.team.members will perform a separate query for each user, unless
+ # project.team.members was loaded before somewhere else. Calling #to_a
+ # ensures it's always loaded before checking for membership.
+ project.team.members.to_a.include?(user)
else
# otherwise we just make a specific query for
# this particular user.
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index 396e95a03c8..a94e32478ce 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -25,4 +25,25 @@ class CommitEntity < API::Entities::Commit
expose :title_html, if: { type: :full } do |commit|
markdown_field(commit, :title)
end
+
+ expose :signature_html, if: { type: :full } do |commit|
+ render('projects/commit/_signature', signature: commit.signature) if commit.has_signature?
+ end
+
+ expose :pipeline_status_path, if: { type: :full } do |commit, options|
+ pipeline_ref = options[:pipeline_ref]
+ pipeline_project = options[:pipeline_project] || commit.project
+ next unless pipeline_ref && pipeline_project
+
+ status = commit.status_for_project(pipeline_ref, pipeline_project)
+ next unless status
+
+ pipelines_project_commit_path(pipeline_project, commit.id, ref: pipeline_ref)
+ end
+
+ def render(*args)
+ return unless request.respond_to?(:render) && request.render.respond_to?(:call)
+
+ request.render.call(*args)
+ end
end
diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb
index c772c807f76..da994d78286 100644
--- a/app/serializers/detailed_status_entity.rb
+++ b/app/serializers/detailed_status_entity.rb
@@ -10,7 +10,12 @@ class DetailedStatusEntity < Grape::Entity
expose :illustration do |status|
begin
- status.illustration
+ illustration = {
+ image: ActionController::Base.helpers.image_path(status.illustration[:image])
+ }
+ illustration = status.illustration.merge(illustration)
+
+ illustration
rescue NotImplementedError
# ignored
end
@@ -25,5 +30,6 @@ class DetailedStatusEntity < Grape::Entity
expose :action_title, as: :title
expose :action_path, as: :path
expose :action_method, as: :method
+ expose :action_button_title, as: :button_title
end
end
diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb
index 00dc55fc004..b51e4a7e6d0 100644
--- a/app/serializers/diffs_entity.rb
+++ b/app/serializers/diffs_entity.rb
@@ -18,7 +18,9 @@ class DiffsEntity < Grape::Entity
expose :commit do |diffs, options|
CommitEntity.represent options[:commit], options.merge(
type: :full,
- commit_url_params: { merge_request_iid: merge_request&.iid }
+ commit_url_params: { merge_request_iid: merge_request&.iid },
+ pipeline_ref: merge_request&.source_branch,
+ pipeline_project: merge_request&.source_project
)
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 5286b92ab6b..7b747171d9c 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -2,6 +2,7 @@
module Projects
class AutocompleteService < BaseService
+ include LabelsAsHash
def issues
IssuesFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
end
@@ -22,34 +23,14 @@ module Projects
MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
end
- def labels_as_hash(target = nil)
- available_labels = LabelsFinder.new(
- current_user,
- project_id: project.id,
- include_ancestor_groups: true
- ).execute
-
- label_hashes = available_labels.as_json(only: [:title, :color])
-
- if target&.respond_to?(:labels)
- already_set_labels = available_labels & target.labels
- if already_set_labels.present?
- titles = already_set_labels.map(&:title)
- label_hashes.each do |hash|
- if titles.include?(hash['title'])
- hash[:set] = true
- end
- end
- end
- end
-
- label_hashes
- end
-
def commands(noteable, type)
return [] unless noteable
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end
+
+ def labels_as_hash(target)
+ super(target, project_id: project.id, include_ancestor_groups: true)
+ end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 8933bef29ee..defa579f9a8 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -210,9 +210,14 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
+ if project
+ available_labels = LabelsFinder
+ .new(current_user, project_id: project.id, include_ancestor_groups: true)
+ .execute
+ end
- current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
+ project &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
available_labels.any?
end
command :label do |labels_param|
@@ -286,7 +291,8 @@ module QuickActions
end
params '#issue | !merge_request'
condition do
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ [MergeRequest, Issue].include?(issuable.class) &&
+ current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
@@ -443,7 +449,8 @@ module QuickActions
end
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
+ issuable.is_a?(TimeTrackable) &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
end
parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
@@ -493,7 +500,7 @@ module QuickActions
desc "Lock the discussion"
explanation "Locks the discussion"
condition do
- issuable.is_a?(Issuable) &&
+ [MergeRequest, Issue].include?(issuable.class) &&
issuable.persisted? &&
!issuable.discussion_locked? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
@@ -505,7 +512,7 @@ module QuickActions
desc "Unlock the discussion"
explanation "Unlocks the discussion"
condition do
- issuable.is_a?(Issuable) &&
+ [MergeRequest, Issue].include?(issuable.class) &&
issuable.persisted? &&
issuable.discussion_locked? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index ccba1c461fc..32f6eefc16a 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -1,6 +1,8 @@
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects"
+- @content_class = "admin-projects"
+
%h3.page-title
Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index a5326f4b909..e9e4e0847d3 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -2,106 +2,103 @@
- @no_container = true
%div{ class: container_class }
- .bs-callout
- %p
- = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
- %br
- = _('Runners can be placed on separate users, servers, even on your local machine.')
- %br
+ .row
+ .col-sm-6
+ .bs-callout
+ %p
+ = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
+ %br
+ = _('Runners can be placed on separate users, servers, even on your local machine.')
+ %br
- %div
- %span= _('Each Runner can be in one of the following states:')
- %ul
- %li
- %span.badge.badge-success shared
- \-
- = _('Runner runs jobs from all unassigned projects')
- %li
- %span.badge.badge-success group
- \-
- = _('Runner runs jobs from all unassigned projects in its group')
- %li
- %span.badge.badge-info specific
- \-
- = _('Runner runs jobs from assigned projects')
- %li
- %span.badge.badge-warning locked
- \-
- = _('Runner cannot be assigned to other projects')
- %li
- %span.badge.badge-danger paused
- \-
- = _('Runner will not receive any new jobs')
+ %div
+ %span= _('Each Runner can be in one of the following states:')
+ %ul
+ %li
+ %span.badge.badge-success shared
+ \-
+ = _('Runner runs jobs from all unassigned projects')
+ %li
+ %span.badge.badge-success group
+ \-
+ = _('Runner runs jobs from all unassigned projects in its group')
+ %li
+ %span.badge.badge-info specific
+ \-
+ = _('Runner runs jobs from assigned projects')
+ %li
+ %span.badge.badge-warning locked
+ \-
+ = _('Runner cannot be assigned to other projects')
+ %li
+ %span.badge.badge-danger paused
+ \-
+ = _('Runner will not receive any new jobs')
- .bs-callout.clearfix
- .float-left
- %p
- = _('You can reset runners registration token by pressing a button below.')
- .prepend-top-10
- = button_to _('Reset runners registration token'), reset_runners_token_admin_application_settings_path,
- method: :put, class: 'btn btn-default',
- data: { confirm: _('Are you sure you want to reset registration token?') }
+ .col-sm-6
+ .bs-callout
+ = render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
+ type: 'shared',
+ reset_token_url: reset_registration_token_admin_application_settings_path }
- = render partial: 'ci/runner/how_to_setup_shared_runner',
- locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token }
+ .row
+ .col-sm-9
+ = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
+ .filtered-search-wrapper
+ .filtered-search-box
+ = dropdown_tag(custom_icon('icon_history'),
+ options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
+ toggle_class: 'filtered-search-history-dropdown-toggle-button',
+ dropdown_class: 'filtered-search-history-dropdown',
+ content_class: 'filtered-search-history-dropdown-content',
+ title: _('Recent searches') }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ = button_tag class: %w[btn btn-link] do
+ = sprite_icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ = button_tag class: %w[btn btn-link] do
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
- .bs-callout
- %p
- = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
+ #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_STATUSES.each do |status|
+ %li.filter-dropdown-item{ data: { value: status } }
+ = button_tag class: %w[btn btn-link] do
+ = status.titleize
- .row-content-block.second-block
- = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
- .filtered-search-wrapper
- .filtered-search-box
- = dropdown_tag(custom_icon('icon_history'),
- options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
- toggle_class: 'filtered-search-history-dropdown-toggle-button',
- dropdown_class: 'filtered-search-history-dropdown',
- content_class: 'filtered-search-history-dropdown-content',
- title: _('Recent searches') }) do
- .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { action: 'submit' } }
- = button_tag class: %w[btn btn-link] do
- = sprite_icon('search')
- %span
- = _('Press Enter or click to search')
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- = button_tag class: %w[btn btn-link] do
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{hint}}
- %span.js-filter-tag.dropdown-light-content
- {{tag}}
+ #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
+ %li.filter-dropdown-item{ data: { value: runner_type } }
+ = button_tag class: %w[btn btn-link] do
+ = runner_type.titleize
- #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- - Ci::Runner::AVAILABLE_STATUSES.each do |status|
- %li.filter-dropdown-item{ data: { value: status } }
- = button_tag class: %w[btn btn-link] do
- = status.titleize
+ = button_tag class: %w[clear-search hidden] do
+ = icon('times')
+ .filter-dropdown-container
+ = render 'sort_dropdown'
- #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
- %li.filter-dropdown-item{ data: { value: runner_type } }
- = button_tag class: %w[btn btn-link] do
- = runner_type.titleize
-
- = button_tag class: %w[clear-search hidden] do
- = icon('times')
- .filter-dropdown-container
- = render 'sort_dropdown'
+ .col-sm-3.text-right-lg
+ = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
- if @runners.any?
.runners-content.content-list
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
index b1b142460b0..4307060d748 100644
--- a/app/views/ci/runner/_how_to_setup_runner.html.haml
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -13,5 +13,9 @@
= _("Use the following registration token during setup:")
%code#registration_token= registration_token
= clipboard_button(target: '#registration_token', title: _("Copy token to clipboard"), class: "btn-transparent btn-clipboard")
+ .prepend-top-10.append-bottom-10
+ = button_to _("Reset runners registration token"), reset_token_url,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: _("Are you sure you want to reset registration token?") }
%li
= _("Start the Runner!")
diff --git a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml b/app/views/ci/runner/_how_to_setup_shared_runner.html.haml
deleted file mode 100644
index 2a190cb9250..00000000000
--- a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-.bs-callout.help-callout
- = render partial: 'ci/runner/how_to_setup_runner',
- locals: { registration_token: registration_token, type: 'shared' }
diff --git a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml b/app/views/ci/runner/_how_to_setup_specific_runner.html.haml
deleted file mode 100644
index afe57bdfa01..00000000000
--- a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml
+++ /dev/null
@@ -1,26 +0,0 @@
-.bs-callout.help-callout
- .append-bottom-10
- %h4= _('Set up a specific Runner automatically')
-
- %p
- - link_to_help_page = link_to(_('Learn more about Kubernetes'),
- help_page_path('user/project/clusters/index'),
- target: '_blank',
- rel: 'noopener noreferrer')
-
- = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page }
-
- %ol
- %li
- = _('Click the button below to begin the install process by navigating to the Kubernetes page')
- %li
- = _('Select an existing Kubernetes cluster or create a new one')
- %li
- = _('From the Kubernetes cluster details view, install Runner from the applications list')
-
- = link_to _('Install Runner on Kubernetes'),
- project_clusters_path(@project),
- class: 'btn btn-info'
- %hr
- = render partial: 'ci/runner/how_to_setup_runner',
- locals: { registration_token: registration_token, type: 'specific' }
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 003bd25dd06..5b78ce910b8 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -3,29 +3,23 @@
- can_admin_label = can?(current_user, :admin_label, @group)
- issuables = ['issues', 'merge requests']
- search = params[:search]
+- subscribed = params[:subscribed]
+- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
- if can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_group_label_path(@group), class: "btn btn-success"
-- if @labels.exists? || search.present?
+- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- .top-area.adjust
- .nav-text
- = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence }
- .nav-controls
- = form_tag group_labels_path(@group), method: :get do
- .input-group
- = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false }
- %span.input-group-append
- %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
- = icon("search")
- = render 'shared/labels/sort_dropdown'
+ = render 'shared/labels/nav'
.labels-container.prepend-top-5
- if @labels.any?
+ .text-muted
+ = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence }
.other-labels
%h5= _('Labels')
%ul.content-list.manage-labels-list.js-other-labels
@@ -34,6 +28,9 @@
- elsif search.present?
.nothing-here-block
= _('No labels with such name or description')
+ - elsif subscribed.present?
+ .nothing-here-block
+ = _('You do not have any subscriptions yet')
- else
= render 'shared/empty_states/labels'
diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml
index e6c089c3494..bcfb6d99716 100644
--- a/app/views/groups/runners/_group_runners.html.haml
+++ b/app/views/groups/runners/_group_runners.html.haml
@@ -11,7 +11,9 @@
-# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894
- if can?(current_user, :admin_pipeline, @group)
= render partial: 'ci/runner/how_to_setup_runner',
- locals: { registration_token: @group.runners_token, type: 'group' }
+ locals: { registration_token: @group.runners_token,
+ type: 'group',
+ reset_token_url: reset_registration_token_group_settings_ci_cd_path }
- if @group.runners.empty?
%h4.underlined-title
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 9ed05d6e3d0..261d758622b 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -5,7 +5,14 @@
.user-name.bold
= current_user.name
= current_user.to_reference
+ - if current_user.status
+ .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
+ = emoji_icon current_user.status.emoji
+ = current_user.status.message_html.html_safe
%li.divider
+ - if can?(current_user, :update_user_status, current_user)
+ %li
+ .js-set-status-modal-trigger{ data: { has_status: current_user.status.present? ? 'true' : 'false' } }
- if current_user_menu?(:profile)
%li
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 044b49c12cc..39604611440 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -74,3 +74,6 @@
%span.sr-only= _('Toggle navigation')
= sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right')
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
+
+- if can?(current_user, :update_user_status, current_user)
+ .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } }
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index fb4fff12027..759d39cf5f5 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -1,5 +1,5 @@
%p.slead
- Should you ever lose your phone, each of these recovery codes can be used one
+ Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
time each to regain access to your account. Please save them in a safe place, or you
%b will
lose access to your account.
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index cd10b8758f6..94ec0cc5db8 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -6,13 +6,13 @@
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Two-Factor Authentication App
+ Register Two-Factor Authenticator
%p
- Use an app on your mobile device to enable two-factor authentication (2FA).
+ Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
.col-lg-8
- if current_user.two_factor_otp_enabled?
%p
- You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication.
+ You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
%p
If you lose your recovery codes you can generate new ones, invalidating all previous codes.
%div
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml
index 0222bbf7338..eaf3a93bd15 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/projects/clusters/gcp/_form.html.haml
@@ -61,15 +61,14 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
- - if rbac_clusters_feature_enabled?
- .form-group
- .form-check
- = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
- = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+ .form-group
+ .form-check
+ = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
+ = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
.form-group
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml
index be84f2ae67c..779c9c245c1 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/projects/clusters/gcp/_show.html.haml
@@ -37,14 +37,13 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
- - if rbac_clusters_feature_enabled?
- .form-group
- .form-check
- = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
- = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml
index f497f5b606c..56551ed4d65 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/projects/clusters/user/_form.html.haml
@@ -25,15 +25,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
- - if rbac_clusters_feature_enabled?
- .form-group
- .form-check
- = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac'
- = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml
index 56b597d295a..5b57f7ceb7d 100644
--- a/app/views/projects/clusters/user/_show.html.haml
+++ b/app/views/projects/clusters/user/_show.html.haml
@@ -26,14 +26,13 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
- - if rbac_clusters_feature_enabled?
- .form-group
- .form-check
- = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
- = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
- .form-text.text-muted
- = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
- = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index c7890b37381..8c5b6e089ea 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -49,15 +49,16 @@
.environments-container
- if @deployments.blank?
- .blank-state-row
- .blank-state-center
- %h2.blank-state-title
+ .empty-state
+ .text-content
+ %h4.state-title
You don't have any deployments right now.
%p.blank-state-text
Define environments in the deploy stage(s) in
%code .gitlab-ci.yml
to track deployments here.
- = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
+ .text-center
+ = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
.ci-table.environments{ role: 'grid' }
diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml
deleted file mode 100644
index ea552c73c92..00000000000
--- a/app/views/projects/jobs/_empty_state.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-- illustration = local_assigns.fetch(:illustration)
-- illustration_size = local_assigns.fetch(:illustration_size)
-- title = local_assigns.fetch(:title)
-- content = local_assigns.fetch(:content, nil)
-- action = local_assigns.fetch(:action, nil)
-
-.row.empty-state
- .col-12
- .svg-content{ class: illustration_size }
- = image_tag illustration
- .col-12
- .text-content
- %h4.text-center= title
- - if content
- %p= content
- - if action
- .text-center
- = action
diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml
deleted file mode 100644
index e5198d047df..00000000000
--- a/app/views/projects/jobs/_empty_states.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- detailed_status = @build.detailed_status(current_user)
-- illustration = detailed_status.illustration
-
-= render 'empty_state',
- illustration: illustration[:image],
- illustration_size: illustration[:size],
- title: illustration[:title],
- content: illustration[:content],
- action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index ab7963737ca..a5f814b722d 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -42,8 +42,6 @@
= custom_icon('scroll_down')
= render 'shared/builds/build_output'
- - else
- = render "empty_states"
#js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 683dda4f166..11a05eada30 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -2,32 +2,25 @@
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @project)
- search = params[:search]
+- subscribed = params[:subscribed]
+- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
- if can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-success"
-- if @labels.exists? || @prioritized_labels.exists? || search.present?
+- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- .top-area.adjust
- .nav-text
- = _('Labels can be applied to issues and merge requests.')
-
- .nav-controls
- = form_tag project_labels_path(@project), method: :get do
- .input-group
- = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false }
- %span.input-group-append
- %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
- = icon("search")
- = render 'shared/labels/sort_dropdown'
+ = render 'shared/labels/nav'
.labels-container.prepend-top-10
- if can_admin_label
- if search.blank?
%p.text-muted
+ = _('Labels can be applied to issues and merge requests.')
+ %br
= _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
@@ -59,7 +52,9 @@
- else
.nothing-here-block
= _('No labels with such name or description')
-
+ - elsif subscribed.present?
+ .nothing-here-block
+ = _('You do not have any subscriptions yet')
- else
= render 'shared/empty_states/labels'
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 314af44490e..ec503cd8bef 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -1,8 +1,34 @@
%h3
= _('Specific Runners')
-= render partial: 'ci/runner/how_to_setup_specific_runner',
- locals: { registration_token: @project.runners_token }
+.bs-callout.help-callout
+ .append-bottom-10
+ %h4= _('Set up a specific Runner automatically')
+
+ %p
+ - link_to_help_page = link_to(_('Learn more about Kubernetes'),
+ help_page_path('user/project/clusters/index'),
+ target: '_blank',
+ rel: 'noopener noreferrer')
+
+ = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page }
+
+ %ol
+ %li
+ = _('Click the button below to begin the install process by navigating to the Kubernetes page')
+ %li
+ = _('Select an existing Kubernetes cluster or create a new one')
+ %li
+ = _('From the Kubernetes cluster details view, install Runner from the applications list')
+
+ = link_to _('Install Runner on Kubernetes'),
+ project_clusters_path(@project),
+ class: 'btn btn-info'
+ %hr
+ = render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: @project.runners_token,
+ type: 'specific',
+ reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path }
- if @project_runners.any?
%h4.underlined-title Runners activated for this project
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index ae923d8e6dc..41afaa9ffc0 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -3,16 +3,6 @@
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature
- .form-group.append-bottom-default.js-secret-runner-token
- = f.label :runners_token, _("Runner token"), class: 'label-bold'
- .form-control.js-secret-value-placeholder
- = '*' * 20
- = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89'
- %p.form-text.text-muted= _("The secure token used by the Runner to checkout the project")
- %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } }
- = _('Reveal value')
-
- %hr
.form-group
%h5.prepend-top-0
= _("Git strategy for pipelines")
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 16961784e00..98e2829ba43 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -12,7 +12,7 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _("Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report.")
+ = _("Customize your pipeline configuration, view your pipeline status and coverage report.")
.settings-content
= render 'form'
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 28e6fe1b16d..0d2f6bb77d6 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -33,7 +33,7 @@
- if @project
%board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),
"milestone-path" => milestones_filter_dropdown_path,
- "label-path" => labels_filter_path,
+ "label-path" => labels_filter_path_with_defaults,
"empty-state-svg" => image_path('illustrations/issues.svg'),
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath",
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 532045f3697..6138914206b 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -25,7 +25,7 @@
show_no: "true",
show_any: "true",
project_id: @project&.try(:id),
- labels: labels_filter_path(false),
+ labels: labels_filter_path_with_defaults,
namespace_path: @namespace_path,
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 0b42b33581a..6eb1f8f0853 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -8,7 +8,7 @@
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
-- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
+- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path_with_defaults, default_label: "Labels"}
- dropdown_data.merge!(data_options)
- label_name = local_assigns.fetch(:label_name, "Labels")
- no_default_styles = local_assigns.fetch(:no_default_styles, false)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 32b609eed0d..aa136af1955 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -109,7 +109,7 @@
- selected_labels.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path(false) if @project), display: 'static' } }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml
new file mode 100644
index 00000000000..98572db738b
--- /dev/null
+++ b/app/views/shared/labels/_nav.html.haml
@@ -0,0 +1,20 @@
+- subscribed = params[:subscribed]
+
+.top-area.adjust
+ %ul.nav-links.nav.nav-tabs
+ %li{ class: active_when(subscribed != 'true') }>
+ = link_to labels_filter_path do
+ = _('All')
+ - if current_user
+ %li{ class: active_when(subscribed == 'true') }>
+ = link_to labels_filter_path(subscribed: 'true') do
+ = _('Subscribed')
+ .nav-controls
+ = form_tag labels_filter_path, method: :get do
+ = hidden_field_tag :subscribed, params[:subscribed]
+ .input-group
+ = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false }
+ %span.input-group-append
+ %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
+ = icon("search")
+ = render 'shared/labels/sort_dropdown'
diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml
index ff6e2947ffd..8a7d037e15b 100644
--- a/app/views/shared/labels/_sort_dropdown.html.haml
+++ b/app/views/shared/labels/_sort_dropdown.html.haml
@@ -6,4 +6,4 @@
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort
%li
- label_sort_options_hash.each do |value, title|
- = sortable_item(title, page_filter_path(sort: value, label: true), sort_title)
+ = sortable_item(title, page_filter_path(sort: value, label: true, subscribed: params[:subscribed]), sort_title)
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
new file mode 100644
index 00000000000..f8b3754840d
--- /dev/null
+++ b/app/views/users/_overview.html.haml
@@ -0,0 +1,32 @@
+.row
+ .col-md-12.col-lg-6
+ .calendar-block
+ .content-block.hide-bottom-border
+ %h4
+ = s_('UserProfile|Activity')
+ .user-calendar.d-none.d-sm-block.text-left{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ .user-calendar-activities.d-none.d-sm-block
+
+ - if can?(current_user, :read_cross_project)
+ .activities-block
+ .content-block
+ %h5.prepend-top-10
+ = s_('UserProfile|Recent contributions')
+ .overview-content-list{ data: { href: user_path } }
+ .center.light.loading
+ %i.fa.fa-spinner.fa-spin
+ .prepend-top-10
+ = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
+
+ .col-md-12.col-lg-6
+ .projects-block
+ .content-block
+ %h4
+ = s_('UserProfile|Personal projects')
+ .overview-content-list{ data: { href: user_projects_path } }
+ .center.light.loading
+ %i.fa.fa-spinner.fa-spin
+ .prepend-top-10
+ = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 7a38d290915..d6c8420b744 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -12,22 +12,22 @@
.cover-block.user-cover-block.top-area
.cover-controls
- if @user == current_user
- = link_to profile_path, class: 'btn btn-default has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do
+ = link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
= icon('pencil')
- elsif current_user
- if @user.abuse_report
- %button.btn.btn-danger{ title: 'Already reported for abuse',
+ %button.btn.btn-danger{ title: s_('UserProfile|Already reported for abuse'),
data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } }
= icon('exclamation-circle')
- else
= link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn',
- title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('exclamation-circle')
- if can?(current_user, :read_user_profile, @user)
- = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do
+ = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do
= icon('rss')
- if current_user && current_user.admin?
- = link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area',
+ = link_to [:admin, @user], class: 'btn btn-default', title: s_('UserProfile|View user in admin area'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('users')
@@ -51,7 +51,7 @@
@#{@user.username}
- if can?(current_user, :read_user_profile, @user)
%span.middle-dot-divider
- Member since #{@user.created_at.to_date.to_s(:long)}
+ = s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) }
.cover-desc
- unless @user.public_email.blank?
@@ -91,32 +91,40 @@
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
+ - if profile_tab?(:overview)
+ %li.js-overview-tab
+ = link_to user_path, data: { target: 'div#js-overview', action: 'overview', toggle: 'tab' } do
+ = s_('UserProfile|Overview')
- if profile_tab?(:activity)
%li.js-activity-tab
- = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
- Activity
+ = link_to user_activity_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
+ = s_('UserProfile|Activity')
- if profile_tab?(:groups)
%li.js-groups-tab
= link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do
- Groups
+ = s_('UserProfile|Groups')
- if profile_tab?(:contributed)
%li.js-contributed-tab
= link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do
- Contributed projects
+ = s_('UserProfile|Contributed projects')
- if profile_tab?(:projects)
%li.js-projects-tab
= link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
- Personal projects
+ = s_('UserProfile|Personal projects')
- if profile_tab?(:snippets)
%li.js-snippets-tab
= link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
- Snippets
+ = s_('UserProfile|Snippets')
%div{ class: container_class }
.tab-content
+ - if profile_tab?(:overview)
+ #js-overview.tab-pane
+ = render "users/overview"
+
- if profile_tab?(:activity)
#activity.tab-pane
- .row-content-block.calender-block.white.second-block.d-none.d-sm-block
+ .row-content-block.calendar-block.white.second-block.d-none.d-sm-block
.user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light
%i.fa.fa-spinner.fa-spin
@@ -124,7 +132,7 @@
- if can?(current_user, :read_cross_project)
%h4.prepend-top-20
- Most Recent Activity
+ = s_('UserProfile|Most Recent Activity')
.content_list{ data: { href: user_path } }
= spinner
@@ -155,4 +163,4 @@
.col-12.text-center
.text-content
%h4
- This user has a private profile
+ = s_('UserProfile|This user has a private profile')
diff --git a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml
new file mode 100644
index 00000000000..074cc9d642b
--- /dev/null
+++ b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Show percentage of language detection on the language bar
+merge_request: 22056
+author: Johann Hubert Sonntagbauer
+type: added
diff --git a/changelogs/unreleased/22104-fix-environment-name-overlap.yml b/changelogs/unreleased/22104-fix-environment-name-overlap.yml
new file mode 100644
index 00000000000..aaa1a1709c8
--- /dev/null
+++ b/changelogs/unreleased/22104-fix-environment-name-overlap.yml
@@ -0,0 +1,4 @@
+---
+title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it."
+merge_request: 22104
+type: fixed
diff --git a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml
new file mode 100644
index 00000000000..8131e2ff54f
--- /dev/null
+++ b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml
@@ -0,0 +1,5 @@
+---
+title: Rephrase 2FA and TOTP documentation and view
+merge_request: 21998
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml
new file mode 100644
index 00000000000..1ebad500e9f
--- /dev/null
+++ b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml
@@ -0,0 +1,5 @@
+---
+title: Instance Configuration page now displays correct SSH fingerprints
+merge_request: 22081
+author:
+type: fixed
diff --git a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml
new file mode 100644
index 00000000000..582d7824d27
--- /dev/null
+++ b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml
@@ -0,0 +1,5 @@
+---
+title: Simplify runner registration token resetting
+merge_request: 21658
+author:
+type: changed
diff --git a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml
new file mode 100644
index 00000000000..f70011ac827
--- /dev/null
+++ b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce queries needed to compute notification recipients
+merge_request: 22050
+author:
+type: performance
diff --git a/changelogs/unreleased/48222-fix-todos-status-button.yml b/changelogs/unreleased/48222-fix-todos-status-button.yml
new file mode 100644
index 00000000000..2f7c79a07d0
--- /dev/null
+++ b/changelogs/unreleased/48222-fix-todos-status-button.yml
@@ -0,0 +1,6 @@
+---
+title: Fix the state of the Done button when there is an error in the GitLab Todos
+ section
+merge_request:
+author: marcos8896
+type: fixed
diff --git a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
new file mode 100644
index 00000000000..2c65c92dd8b
--- /dev/null
+++ b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
@@ -0,0 +1,5 @@
+---
+title: Set user status from within user menu
+merge_request: 21643
+author:
+type: added
diff --git a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml
new file mode 100644
index 00000000000..5e2be42c8b7
--- /dev/null
+++ b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml
@@ -0,0 +1,5 @@
+---
+title: Adds new 'Overview' tab on user profile page
+merge_request: 21663
+author:
+type: other
diff --git a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml
new file mode 100644
index 00000000000..e3619149d2a
--- /dev/null
+++ b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Fix performance bar modal position
+merge_request: 21577
+author:
+type: fixed
diff --git a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml
new file mode 100644
index 00000000000..99946b954ce
--- /dev/null
+++ b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove 'rbac_clusters' feature flag
+merge_request: 22096
+author:
+type: changed
diff --git a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml
new file mode 100644
index 00000000000..e67cc27f852
--- /dev/null
+++ b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml
@@ -0,0 +1,5 @@
+---
+title: Includes commit stats in POST project commits API
+merge_request: 21968
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/51958-fix-mr-discussion-loading.yml b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml
new file mode 100644
index 00000000000..f80ee51291d
--- /dev/null
+++ b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml
@@ -0,0 +1,5 @@
+---
+title: Fix loading issue on some merge request discussion
+merge_request: 21982
+author:
+type: fixed
diff --git a/changelogs/unreleased/52178-markdown-table-borders.yml b/changelogs/unreleased/52178-markdown-table-borders.yml
new file mode 100644
index 00000000000..965f21f2a97
--- /dev/null
+++ b/changelogs/unreleased/52178-markdown-table-borders.yml
@@ -0,0 +1,5 @@
+---
+title: Add borders and white background to markdown tables
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml
new file mode 100644
index 00000000000..d96c2bc7acd
--- /dev/null
+++ b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml
@@ -0,0 +1,5 @@
+---
+title: Trim whitespace when inviting a new user by email
+merge_request: 22119
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/add_reliable_fetcher.yml b/changelogs/unreleased/add_reliable_fetcher.yml
new file mode 100644
index 00000000000..c08c755e546
--- /dev/null
+++ b/changelogs/unreleased/add_reliable_fetcher.yml
@@ -0,0 +1,5 @@
+---
+title: Use Reliable Sidekiq fetch
+merge_request: 21715
+author:
+type: fixed
diff --git a/changelogs/unreleased/clone-nurtch-demo-repo.yml b/changelogs/unreleased/clone-nurtch-demo-repo.yml
new file mode 100644
index 00000000000..c77138d27f0
--- /dev/null
+++ b/changelogs/unreleased/clone-nurtch-demo-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Copy nurtch demo notebooks at Jupyter startup
+merge_request: 21698
+author: Amit Rathi
+type: added
diff --git a/changelogs/unreleased/dz-labels-subscribe-filter.yml b/changelogs/unreleased/dz-labels-subscribe-filter.yml
new file mode 100644
index 00000000000..768c20c77c7
--- /dev/null
+++ b/changelogs/unreleased/dz-labels-subscribe-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Add subscribe filter to group and project labels pages
+merge_request: 21965
+author:
+type: added
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 6c1079faad1..bc6b7aed6aa 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -40,6 +40,10 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections!
end
+ if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
+ Sidekiq::ReliableFetcher.setup_reliable_fetch!(config)
+ end
+
# Sidekiq-cron: load recurring jobs from gitlab.yml
# UGLY Hack to get nested hash from settingslogic
cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
@@ -57,10 +61,10 @@ Sidekiq.configure_server do |config|
Gitlab::SidekiqVersioning.install!
- config = Gitlab::Database.config ||
+ db_config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env]
- config['pool'] = Sidekiq.options[:concurrency]
- ActiveRecord::Base.establish_connection(config)
+ db_config['pool'] = Sidekiq.options[:concurrency]
+ ActiveRecord::Base.establish_connection(db_config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 7489b01ded6..7cdaa2daa14 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -107,7 +107,7 @@ namespace :admin do
resource :application_settings, only: [:show, :update] do
resources :services, only: [:index, :edit, :update]
get :usage_data
- put :reset_runners_token
+ put :reset_registration_token
put :reset_health_check_token
put :clear_repository_check_states
get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 893ec8a4e58..602bbe837cf 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -27,7 +27,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
as: :group,
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
namespace :settings do
- resource :ci_cd, only: [:show], controller: 'ci_cd'
+ resource :ci_cd, only: [:show], controller: 'ci_cd' do
+ put :reset_registration_token
+ end
end
resource :variables, only: [:show, :update]
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 04a270c5cc9..356a509da75 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -366,7 +366,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :discussions, format: :json
end
collection do
- post :bulk_update
+ post :bulk_update
end
end
@@ -438,6 +438,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
post :reset_cache
+ put :reset_registration_token
end
resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository do
diff --git a/config/routes/user.rb b/config/routes/user.rb
index bc7df5e7584..e0ae264e2c0 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -45,6 +45,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get :contributed, as: :contributed_projects
get :snippets
get :exists
+ get :activity
get '/', to: redirect('%{username}'), as: nil
end
diff --git a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb
index 55bf40ba24d..cc5cb355579 100644
--- a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb
+++ b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb
@@ -13,7 +13,7 @@ class AddForeignKeyPipelineSchedulesAndPipelines < ActiveRecord::Migration
'SET NULL'
end
- add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules,
+ add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules,
column: :pipeline_schedule_id, on_delete: on_delete
end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 017c58477ac..08d7f499eec 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -1,5 +1,3 @@
-require 'thread'
-
class RenameReservedProjectNames < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index 3e8ccfdb899..43a37667250 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -1,5 +1,3 @@
-require 'thread'
-
class RenameMoreReservedProjectNames < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md
index 54adb99386a..ec11a92db1b 100644
--- a/doc/administration/operations/moving_repositories.md
+++ b/doc/administration/operations/moving_repositories.md
@@ -22,9 +22,8 @@ However, it is not possible to resume an interrupted tar pipe: if
that happens then all data must be copied again.
```
-# As the git user
-tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
- tar -C /mnt/gitlab/repositories -xf -
+sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
+ tar -C /mnt/gitlab/repositories -xf -'
```
If you want to see progress, replace `-xf` with `-xvf`.
@@ -36,9 +35,8 @@ You can also use a tar pipe to copy data to another server. If your
can pipe the data through SSH.
```
-# As the git user
-tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
- ssh git@newserver tar -C /mnt/gitlab/repositories -xf -
+sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
+ ssh git@newserver tar -C /mnt/gitlab/repositories -xf -'
```
If you want to compress the data before it goes over the network
@@ -53,9 +51,8 @@ is either already installed on your system or easily installable
via apt, yum etc.
```
-# As the 'git' user
-rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
- /mnt/gitlab/repositories
+sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
+ /mnt/gitlab/repositories'
```
The `/.` in the command above is very important, without it you can
@@ -68,9 +65,8 @@ If the 'git' user on your source system has SSH access to the target
server you can send the repositories over the network with rsync.
```
-# As the 'git' user
-rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
- git@newserver:/mnt/gitlab/repositories
+sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
+ git@newserver:/mnt/gitlab/repositories'
```
## Thousands of Git repositories: use one rsync per repository
@@ -125,7 +121,7 @@ sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-lo
Now we can start the transfer. The command below is idempotent, and
the number of jobs done by GNU Parallel should converge to zero. If it
-does not some repositories listed in all-repos-1234.txt may have been
+does not, some repositories listed in `all-repos-1234.txt` may have been
deleted/renamed before they could be copied.
```
@@ -155,8 +151,8 @@ cat /home/git/transfer-logs/* | sort | uniq -u |\
Suppose you have already done one sync that started after 2015-10-1 12:00 UTC.
Then you might only want to sync repositories that were changed via GitLab
-_after_ that time. You can use the 'SINCE' variable to tell 'rake
-gitlab:list_repos' to only print repositories with recent activity.
+_after_ that time. You can use the `SINCE` variable to tell `rake
+gitlab:list_repos` to only print repositories with recent activity.
```
# Omnibus
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 5ff1e1f60e0..9b7ca4b6e70 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -79,6 +79,7 @@ POST /projects/:id/repository/commits
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
+| `stats` | boolean | no | Include commit stats. Default is true |
| `actions[]` Attribute | Type | Required | Description |
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index b37e7698ab4..862ee398a84 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -54,35 +54,38 @@ Parameters:
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
"created_at": "2017-04-29T08:46:00Z",
"updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"source_project_id": 2,
"target_project_id": 3,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -93,23 +96,28 @@ Parameters:
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false
}
]
```
@@ -169,35 +177,38 @@ Parameters:
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
"created_at": "2017-04-29T08:46:00Z",
"updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"source_project_id": 2,
"target_project_id": 3,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -208,24 +219,28 @@ Parameters:
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false
}
]
```
@@ -275,35 +290,38 @@ Parameters:
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
"created_at": "2017-04-29T08:46:00Z",
"updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"source_project_id": 2,
"target_project_id": 3,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -314,23 +332,26 @@ Parameters:
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-10-22",
+ "start_date": "2018-09-08",
+ "web_url": "gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false
}
]
```
@@ -359,35 +380,38 @@ Parameters:
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
- "state": "merged",
+ "description": "fixed login page css paddings",
+ "state": "opened",
"created_at": "2017-04-29T08:46:00Z",
"updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
- "state" : "active",
- "web_url" : "https://gitlab.example.com/root",
- "avatar_url" : null,
- "username" : "root",
- "id" : 1,
- "name" : "Administrator"
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
+ "state": "active",
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
- "state" : "active",
- "web_url" : "https://gitlab.example.com/root",
- "avatar_url" : null,
- "username" : "root",
- "id" : 1,
- "name" : "Administrator"
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
+ "state": "active",
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
"source_project_id": 2,
"target_project_id": 3,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -398,50 +422,55 @@ Parameters:
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
- "merge_commit_sha": "9999999999999999999999999999999999999999",
+ "merge_commit_sha": null,
"user_notes_count": 1,
- "changes_count": "1",
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
},
- "closed_at": "2018-01-19T14:36:11.086Z",
- "latest_build_started_at": null,
- "latest_build_finished_at": null,
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
"first_deployed_to_production_at": null,
"pipeline": {
- "id": 8,
- "ref": "master",
- "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0",
- "status": "created"
- },
- "merged_by": null,
- "merged_at": null,
- "closed_by": {
- "state" : "active",
- "web_url" : "https://gitlab.example.com/root",
- "avatar_url" : null,
- "username" : "root",
- "id" : 1,
- "name" : "Administrator"
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
},
"diff_refs": {
- "base_sha": "1111111111111111111111111111111111111111",
- "head_sha": "2222222222222222222222222222222222222222",
- "start_sha": "3333333333333333333333333333333333333333"
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
"diverged_commits_count": 2
}
@@ -663,65 +692,99 @@ POST /projects/:id/merge_requests
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
- "project_id": 4,
+ "project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 3,
- "target_project_id": 4,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
"iid": 1,
- "project_id": 4,
+ "project_id": 3,
"title": "v2.0",
"description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
- "user_notes_count": 0,
- "changes_count": "1",
+ "user_notes_count": 1,
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
"allow_collaboration": false,
"allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
@@ -756,64 +819,99 @@ Must include at least one non-required attribute from above.
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "project_id": 4,
+ "project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 3,
- "target_project_id": 4,
- "labels": [ ],
- "description": "description1",
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
"iid": 1,
- "project_id": 4,
+ "project_id": 3,
"title": "v2.0",
"description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
- "changes_count": "1",
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
"allow_collaboration": false,
"allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
@@ -857,70 +955,106 @@ Parameters:
- `merge_request_iid` (required) - Internal ID of MR
- `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch
-- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
+- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
```json
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
- "state": "merged",
+ "description": "fixed login page css paddings",
+ "state": "opened",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 4,
- "target_project_id": 4,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
"iid": 1,
- "project_id": 4,
+ "project_id": 3,
"title": "v2.0",
"description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
- "merge_commit_sha": "9999999999999999999999999999999999999999",
+ "merge_commit_sha": null,
"user_notes_count": 1,
- "changes_count": "1",
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
@@ -937,69 +1071,105 @@ PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_s
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
-- `merge_request_iid` (required) - Internal ID of MR
+- `merge_request_iid` (required) - Internal ID of MR
```json
{
"id": 1,
"iid": 1,
- "target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
"id": 1,
- "username": "admin",
"name": "Administrator",
+ "username": "admin",
"state": "active",
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 4,
- "target_project_id": 4,
- "labels": [ ],
- "description": "fixed login page css paddings",
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
"id": 5,
"iid": 1,
- "project_id": 4,
+ "project_id": 3,
"title": "v2.0",
"description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
"created_at": "2015-02-02T19:49:26.013Z",
"updated_at": "2015-02-02T19:49:26.013Z",
- "due_date": null
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
- "merge_when_pipeline_succeeds": true,
+ "merge_when_pipeline_succeeds": false,
"merge_status": "can_be_merged",
- "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
- "changes_count": "1",
+ "discussion_locked": null,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
- "squash": false,
- "web_url": "http://example.com/example/example/merge_requests/1",
- "discussion_locked": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
@@ -1067,7 +1237,7 @@ Example response when the GitLab issue tracker is used:
"labels" : [],
"user_notes_count": 1,
"changes_count": "1"
- },
+ }
]
```
@@ -1104,54 +1274,101 @@ Example response:
```json
{
- "id": 17,
+ "id": 1,
"iid": 1,
- "project_id": 5,
- "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
- "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "project_id": 3,
+ "title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
- "created_at": "2016-04-05T21:42:23.233Z",
- "updated_at": "2016-04-05T22:11:52.900Z",
- "target_branch": "ui-dev-kit",
- "source_branch": "version-1-9",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
- "name": "Eileen Skiles",
- "username": "leila",
- "id": 19,
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
- "web_url": "https://gitlab.example.com/leila"
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
- "name": "Celine Wehner",
- "username": "carli",
- "id": 16,
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
- "web_url": "https://gitlab.example.com/carli"
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 5,
- "target_project_id": 5,
- "labels": [],
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
- "id": 7,
+ "id": 5,
"iid": 1,
- "project_id": 5,
+ "project_id": 3,
"title": "v2.0",
- "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
- "created_at": "2016-04-05T21:41:40.905Z",
- "updated_at": "2016-04-05T21:41:40.905Z",
- "due_date": null
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
- "merge_when_pipeline_succeeds": false,
- "merge_status": "cannot_be_merged",
- "subscribed": true,
+ "merge_when_pipeline_succeeds": true,
+ "merge_status": "can_be_merged",
"sha": "8888888888888888888888888888888888888888",
- "merge_commit_sha": null
+ "merge_commit_sha": null,
+ "user_notes_count": 1,
+ "discussion_locked": null,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
@@ -1178,54 +1395,101 @@ Example response:
```json
{
- "id": 17,
+ "id": 1,
"iid": 1,
- "project_id": 5,
- "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
- "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "project_id": 3,
+ "title": "test1",
+ "description": "fixed login page css paddings",
"state": "opened",
- "created_at": "2016-04-05T21:42:23.233Z",
- "updated_at": "2016-04-05T22:11:52.900Z",
- "target_branch": "ui-dev-kit",
- "source_branch": "version-1-9",
+ "created_at": "2017-04-29T08:46:00Z",
+ "updated_at": "2017-04-29T08:46:00Z",
+ "target_branch": "master",
+ "source_branch": "test1",
"upvotes": 0,
"downvotes": 0,
"author": {
- "name": "Eileen Skiles",
- "username": "leila",
- "id": 19,
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
- "web_url": "https://gitlab.example.com/leila"
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
"assignee": {
- "name": "Celine Wehner",
- "username": "carli",
- "id": 16,
+ "id": 1,
+ "name": "Administrator",
+ "username": "admin",
"state": "active",
- "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
- "web_url": "https://gitlab.example.com/carli"
+ "avatar_url": null,
+ "web_url" : "https://gitlab.example.com/admin"
},
- "source_project_id": 5,
- "target_project_id": 5,
- "labels": [],
+ "source_project_id": 2,
+ "target_project_id": 3,
+ "labels": [
+ "Community contribution",
+ "Manage"
+ ],
"work_in_progress": false,
"milestone": {
- "id": 7,
+ "id": 5,
"iid": 1,
- "project_id": 5,
+ "project_id": 3,
"title": "v2.0",
- "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
"state": "closed",
- "created_at": "2016-04-05T21:41:40.905Z",
- "updated_at": "2016-04-05T21:41:40.905Z",
- "due_date": null
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": "2018-09-22",
+ "start_date": "2018-08-08",
+ "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1"
},
- "merge_when_pipeline_succeeds": false,
- "merge_status": "cannot_be_merged",
- "subscribed": false,
+ "merge_when_pipeline_succeeds": true,
+ "merge_status": "can_be_merged",
"sha": "8888888888888888888888888888888888888888",
- "merge_commit_sha": null
+ "merge_commit_sha": null,
+ "user_notes_count": 1,
+ "discussion_locked": null,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false,
+ "allow_collaboration": false,
+ "allow_maintainer_to_push": false,
+ "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1",
+ "time_stats": {
+ "time_estimate": 0,
+ "total_time_spent": 0,
+ "human_time_estimate": null,
+ "human_total_time_spent": null
+ },
+ "squash": false,
+ "subscribed": false,
+ "changes_count": "1",
+ "merged_by": {
+ "id": 87854,
+ "name": "Douwe Maan",
+ "username": "DouweM",
+ "state": "active",
+ "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png",
+ "web_url": "https://gitlab.com/DouweM"
+ },
+ "merged_at": "2018-09-07T11:16:17.520Z",
+ "closed_by": null,
+ "closed_at": null,
+ "latest_build_started_at": "2018-09-07T07:27:38.472Z",
+ "latest_build_finished_at": "2018-09-07T08:07:06.012Z",
+ "first_deployed_to_production_at": null,
+ "pipeline": {
+ "id": 29626725,
+ "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "ref": "patch-28",
+ "status": "success",
+ "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725"
+ },
+ "diff_refs": {
+ "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00",
+ "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
+ "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
+ },
+ "diverged_commits_count": 2
}
```
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index e5411662511..bc6ecdf4f32 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -2,18 +2,18 @@
Two-factor Authentication (2FA) provides an additional level of security to your
GitLab account. Once enabled, in addition to supplying your username and
-password to login, you'll be prompted for a code generated by an application on
-your phone.
+password to login, you'll be prompted for a code generated by your one time password
+authenticator. For example, a password manager on one of your devices.
By enabling 2FA, the only way someone other than you can log into your account
-is to know your username and password *and* have access to your phone.
+is to know your username and password *and* have access to your one time password secret.
## Overview
> **Note:**
When you enable 2FA, don't forget to back up your recovery codes.
-In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as
+In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as
the second factor of authentication. Once enabled, in addition to supplying your username and
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
@@ -24,10 +24,10 @@ from other browsers.
## Enabling 2FA
-There are two ways to enable two-factor authentication: via a mobile application
+There are two ways to enable two-factor authentication: via a one time password authenticator
or a U2F device.
-### Enable 2FA via mobile application
+### Enable 2FA via one time password authenticator
**In GitLab:**
@@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process.
> **Note:**
Recovery codes are not generated for U2F devices.
-Should you ever lose access to your phone, you can use one of the ten provided
+Should you ever lose access to your one time password authenticator, you can use one of the ten provided
backup codes to login to your account. We suggest copying or printing them for
storage in a safe place. **Each code can be used only once** to log in to your
account.
@@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled
### Log in via mobile application
-Enter the pin from your phone's application or a recovery code to log in.
+Enter the pin from your one time password authenticator's application or a recovery code to log in.
![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 8604ea27f99..ab62762f343 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -115,6 +115,13 @@ Please be aware that your status is publicly visible even if your [profile is pr
To set your current status:
+1. Open the user menu in the top-right corner of the navigation bar.
+1. Hit **Set status**, or **Edit status** if you have already set a status.
+1. Set the emoji and/or status message to your liking.
+1. Hit **Set status**. Alternatively, you can also hit **Remove status** to remove your user status entirely.
+
+or
+
1. Navigate to your personal [profile settings](#profile-settings).
1. In the text field below `Your status`, enter your status message.
1. Select an emoji from the dropdown if you like.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 41768998a59..3ec17806490 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -134,36 +134,11 @@ authorization is [experimental](#role-based-access-control-rbac).
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21401) in GitLab 11.4.
CAUTION: **Warning:**
-The RBAC authorization is experimental. To enable it you need access to the
-server where GitLab is installed.
+The RBAC authorization is experimental.
-The support for RBAC-enabled clusters is hidden behind a feature flag. Once
-the feature flag is enabled, GitLab will create the necessary service accounts
+Once RBAC is enabled for a cluster, GitLab will create the necessary service accounts
and privileges in order to install and run [GitLab managed applications](#installing-applications).
-To enable the feature flag:
-
-1. SSH into the server where GitLab is installed.
-1. Enter the Rails console:
-
- **For Omnibus GitLab**
-
- ```sh
- sudo gitlab-rails console
- ```
-
- **For installations from source**
-
- ```sh
- sudo -u git -H bundle exec rails console
- ```
-
-1. Enable the RBAC authorization:
-
- ```ruby
- Feature.enable('rbac_clusters')
- ```
-
If you are creating a [new GKE cluster via
GitLab](#adding-and-creating-a-new-gke-cluster-via-gitlab), you will be
asked if you would like to create an RBAC-enabled cluster. Enabling this
@@ -240,7 +215,7 @@ twice, which can lead to confusion during deployments.
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
+| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
## Getting the external IP address
diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md
index 16bc5121027..a5923986292 100644
--- a/doc/user/project/import/svn.md
+++ b/doc/user/project/import/svn.md
@@ -29,7 +29,7 @@ directly in a filesystem level.
1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can
follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html).
-1. Download SubGit from https://subgit.com/download/.
+1. Download SubGit from <https://subgit.com/download/>.
1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit`
command will be available at `/opt/subgit-VERSION/bin/subgit`.
@@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config
```
For more information regarding the SubGit configuration options, refer to
-[SubGit's documentation](https://subgit.com/documentation.html) website.
+[SubGit's documentation](https://subgit.com/documentation/) website.
### Initial translation
@@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH
### SubGit licensing
Running SubGit in a mirror mode requires a
-[registration](https://subgit.com/pricing.html). Registration is free for open
+[registration](https://subgit.com/pricing/). Registration is free for open
source, academic and startup projects.
We're currently working on deeper GitLab/SubGit integration. You may track our
@@ -179,5 +179,6 @@ git push --tags origin
```
## Contribute to this guide
+
We welcome all contributions that would expand this guide with instructions on
how to migrate from SVN and other version control systems.
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index df045822740..c2f53540089 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -1,9 +1,9 @@
# GitLab quick actions
-Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
+Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
and commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, or
-in comments of issues, epics, merge requests, and commits. Each command should be
+in comments of issues, epics, merge requests, and commits. Each command should be
on a separate line in order to be properly detected and executed. Once executed,
the commands are removed from the text body and not visible to anyone else.
@@ -38,7 +38,9 @@ discussions, and descriptions:
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
-| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code>| Set due date | ✓ |
+| `/lock` | Lock the discussion | ✓ | ✓ |
+| `/unlock` | Unlock the discussion | ✓ | ✓ |
+| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code>| Set due date | ✓ | |
| `/remove_due_date` | Remove due date | ✓ | |
| `/weight 0,1,2, ...` | Set weight **[STARTER]** | ✓ | |
| `/clear_weight` | Clears weight **[STARTER]** | ✓ | |
@@ -68,7 +70,7 @@ The following quick actions are applicable for epics threads and description:
|:---------------------------|:----------------------------------------|
| `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ â”»â”â”»` |
| `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` |
-| `/todo` | Add a todo |
+| `/todo` | Add a todo |
| `/done` | Mark todo as done |
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
@@ -78,4 +80,4 @@ The following quick actions are applicable for epics threads and description:
| `/award :emoji:` | Toggle emoji award |
| `/label ~label1 ~label2` | Add label(s) |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
-| `/relabel ~label1 ~label2` | Replace label | \ No newline at end of file
+| `/relabel ~label1 ~label2` | Replace label |
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index ff927d1aa3c..e59abd3e3d0 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -98,6 +98,7 @@ module API
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
+ optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
end
post ':id/repository/commits' do
authorize_push_to_branch!(params[:branch])
@@ -113,7 +114,7 @@ module API
Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden
- present commit_detail, with: Entities::CommitDetail
+ present commit_detail, with: Entities::CommitDetail, stats: params[:stats]
else
render_api_error!(result[:message], 400)
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 60868821810..ce70460af11 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -113,7 +113,7 @@ module API
optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
use :pagination
end
- get ':id/jobs' do
+ get ':id/jobs' do
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb
index e06e2fb3870..26bcf5c04b4 100644
--- a/lib/banzai/filter/epic_reference_filter.rb
+++ b/lib/banzai/filter/epic_reference_filter.rb
@@ -9,6 +9,12 @@ module Banzai
def self.object_class
Epic
end
+
+ private
+
+ def group
+ context[:group] || context[:project]&.group
+ end
end
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index dda6cd38dcd..10d34b0c6e7 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -10,8 +10,6 @@ require_dependency 'declarative_policy/step'
require_dependency 'declarative_policy/base'
-require 'thread'
-
module DeclarativePolicy
CLASS_CACHE_MUTEX = Mutex.new
CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index fc280f96ec1..f967494199e 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -69,6 +69,10 @@ module Gitlab
JSON.generate(formatter.to_h, opts)
end
+ def as_json(opts = nil)
+ to_h.as_json(opts)
+ end
+
def type
formatter.line_age
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 30cd09a0ca7..240a0d7d1b8 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -24,8 +24,8 @@ module Gitlab
cannot_push_to_read_only: "You can't push code to a read-only GitLab instance."
}.freeze
- DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
- PUSH_COMMANDS = %w{ git-receive-pack }.freeze
+ DOWNLOAD_COMMANDS = %w{git-upload-pack git-upload-archive}.freeze
+ PUSH_COMMANDS = %w{git-receive-pack}.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 308a95d2f09..29672d68cad 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -3,7 +3,7 @@ module Gitlab
ALLOWED_SCHEMES = %w[http https ssh git].freeze
def self.sanitize(content)
- regexp = URI::Parser.new.make_regexp(ALLOWED_SCHEMES)
+ regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
content.gsub(regexp) { |url| new(url).masked_url }
rescue Addressable::URI::InvalidURIError
diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index 1b03fe9ce28..d196fac6c60 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -480,7 +480,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 3a925c27e9b..0d5026c0f4a 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index 007b2a4d393..1a052348522 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index 013152917e6..3a2267c4bf7 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -420,7 +420,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index ed25f935b9a..1a6e564ed36 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 41848faeb30..c27a0dea04d 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "Zugriff auf fehlerhafte Speicher wurde vorübergehend deaktiviert, um die Wiederherstellung zu ermöglichen. Für den zukünftigen Zugriff, behebe bitte das Problem und setze danach die Speicherinformationen zurück."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 8bd42855b44..d0a67a1d089 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 2a8fb756192..6ce5b6a3aff 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index a9637d4098e..8e4edc84c83 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index 0929e3dd7cb..73eeb56bea2 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index cf00055272c..93b30d0ef31 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -360,7 +360,7 @@ msgstr "Accès à « %{classification_label} » non autorisé"
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "L’accès aux stockages défaillants a été temporairement désactivé pour permettre la récupération du montage. Réinitialisez les informations de stockage quand le problème sera résolu pour permettre à nouveau l’accès."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Accédez à votre jeton d’exécuteur, personnalisez la configuration de votre pipeline et affichez l’état de votre pipeline et le rapport de couverture."
msgid "Account"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0d36b9b1170..d16a72b76b8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -317,9 +317,6 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
-msgstr ""
-
msgid "Account"
msgstr ""
@@ -2099,6 +2096,9 @@ msgstr ""
msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
+msgstr ""
+
msgid "Cycle Analytics"
msgstr ""
@@ -2746,6 +2746,9 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to load emoji list."
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3369,6 +3372,9 @@ msgstr ""
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
+msgid "Issues, merge requests, pushes and comments."
+msgstr ""
+
msgid "Jan"
msgstr ""
@@ -3733,6 +3739,9 @@ msgstr ""
msgid "Median"
msgstr ""
+msgid "Member since %{date}"
+msgstr ""
+
msgid "Members"
msgstr ""
@@ -5463,6 +5472,30 @@ msgstr ""
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
+msgid "SetStatusModal|Add status emoji"
+msgstr ""
+
+msgid "SetStatusModal|Clear status"
+msgstr ""
+
+msgid "SetStatusModal|Edit status"
+msgstr ""
+
+msgid "SetStatusModal|Remove status"
+msgstr ""
+
+msgid "SetStatusModal|Set a status"
+msgstr ""
+
+msgid "SetStatusModal|Set status"
+msgstr ""
+
+msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
+msgstr ""
+
+msgid "SetStatusModal|What's your status?"
+msgstr ""
+
msgid "Settings"
msgstr ""
@@ -5777,6 +5810,12 @@ msgstr ""
msgid "Subscribe at project level"
msgstr ""
+msgid "Subscribed"
+msgstr ""
+
+msgid "Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
@@ -5974,9 +6013,6 @@ msgstr ""
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
msgstr ""
-msgid "The secure token used by the Runner to checkout the project"
-msgstr ""
-
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
msgstr ""
@@ -6614,6 +6650,51 @@ msgstr ""
msgid "User map"
msgstr ""
+msgid "UserProfile|Activity"
+msgstr ""
+
+msgid "UserProfile|Already reported for abuse"
+msgstr ""
+
+msgid "UserProfile|Contributed projects"
+msgstr ""
+
+msgid "UserProfile|Edit profile"
+msgstr ""
+
+msgid "UserProfile|Groups"
+msgstr ""
+
+msgid "UserProfile|Most Recent Activity"
+msgstr ""
+
+msgid "UserProfile|Overview"
+msgstr ""
+
+msgid "UserProfile|Personal projects"
+msgstr ""
+
+msgid "UserProfile|Recent contributions"
+msgstr ""
+
+msgid "UserProfile|Report abuse"
+msgstr ""
+
+msgid "UserProfile|Snippets"
+msgstr ""
+
+msgid "UserProfile|Subscribe"
+msgstr ""
+
+msgid "UserProfile|This user has a private profile"
+msgstr ""
+
+msgid "UserProfile|View all"
+msgstr ""
+
+msgid "UserProfile|View user in admin area"
+msgstr ""
+
msgid "Users"
msgstr ""
@@ -6902,9 +6983,6 @@ msgstr ""
msgid "You can only edit files when you are on a branch"
msgstr ""
-msgid "You can reset runners registration token by pressing a button below."
-msgstr ""
-
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -6914,6 +6992,9 @@ msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You do not have any subscriptions yet"
+msgstr ""
+
msgid "You don't have any applications"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index 2b6dcc6595e..c77dc236458 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index f34f862b9b1..ab014982a72 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -420,7 +420,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index ba43d50b726..d5c48520155 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -330,7 +330,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 375311ccf72..87bcd939fb1 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "L'accesso agli storages è stato temporaneamente disabilitato per consentire il mount di ripristino. Resetta le info d'archiviazione dopo che l'issue è stato risolto per consentire nuovamente l'accesso."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index a618fa97381..ee5ea023fb5 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -330,7 +330,7 @@ msgstr "'%{classification_label}'ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã¯è¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "mount ã«ã‚ˆã£ã¦å¾©æ—§ã§ãるよã†ã«ã€å¤±æ•—ãŒç™ºç”Ÿã—ã¦ã„るストレージã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’一時的ã«æŠ‘æ­¢ã—ã¾ã—ãŸã€‚å†åº¦ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ãŸã‚ã«ã¯ã€å•é¡Œã‚’解決ã—ã¦ã‹ã‚‰ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸æƒ…報をリセットã—ã¦ãã ã•ã„。"
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Runner トークンã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã€ãƒ‘イプラインã®è¨­å®šã‚’カスタマイズã€ãã—ã¦ãƒ‘イプラインã®çŠ¶æ…‹ã¨ã‚«ãƒãƒ¬ãƒƒã‚¸ãƒ¬ãƒãƒ¼ãƒˆã‚’閲覧ã—ã¾ã™ã€‚"
msgid "Account"
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index ce0c069712d..3c3bcf9688a 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -330,7 +330,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "오ë™ìž‘ì¤‘ì¸ ì €ìž¥ê³µê°„ì— ëŒ€í•œ ì ‘ê·¼ì´ ë³µêµ¬ ìž‘ì—…ì„ ìœ„í•´ 마운트할 수 있ë„ë¡ ìž„ì‹œë¡œ 허용ë˜ì—ˆìŠµë‹ˆë‹¤. 문제가 í•´ê²°ëœ í›„ 다시 ì ‘ê·¼ì„ í—ˆìš©í•  수 있게 저장공간 정보를 리셋 해주세요."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index d039b51ce40..f354ca50f32 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 3f30108892f..1d6dc4c4399 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -420,7 +420,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index bcc7659e5a2..c76c639e8db 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -360,7 +360,7 @@ msgstr "Acesso a '%{classification_label}' não permitido"
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "Os acessos à storages com defeito foram temporariamente desabilitados para permitir a sua remontagem. Redefina as informações de armazenamento depois que o problema foi resolvido para permitir o acesso de novo."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Acesse seu runner token, personalize sua configuração de pipeline e visualize o status do seu pipeline e o relatório de coverage."
msgid "Account"
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index 3fb198ae037..bae64f360fc 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -390,7 +390,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index dc7a0fc9f51..bc2c26da457 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -420,7 +420,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "ДоÑтуп к вышедшим из ÑÑ‚Ñ€Ð¾Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð°Ð¼ временно отключен Ð´Ð»Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñти Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² целÑÑ… воÑÑтановлениÑ. СброÑьте информацию о хранилищах поÑле уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹, чтобы разрешить доÑтуп."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index 681827065da..42eeed11534 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index cb77032cc50..d1087ffd29e 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -360,7 +360,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 57a131c4ee6..33019a3e5a8 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -420,7 +420,7 @@ msgstr "ДоÑтуп до \"%{classification_label}\" заборонено"
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "ДоÑтуп до Ñховищ, що вийшли з ладу, тимчаÑово прибраний Ð·Ð°Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ. ПіÑÐ»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ обнуліть інформацію Ñховища Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ñтупу."
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "Отримайте доÑтуп до Gitlab Runner токену, налаштуйте конфігурацію конвеєра та переглÑньте його ÑтатуÑ, а також звіт про покриттÑ."
msgid "Account"
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 9629a63e976..861e459bcac 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -330,7 +330,7 @@ msgstr "ä¸å…许访问%{classification_label}"
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "为方便修å¤æŒ‚载问题,访问故障存储已被暂时ç¦ç”¨ã€‚在问题解决åŽè¯·é‡ç½®å­˜å‚¨è¿è¡ŒçŠ¶å†µä¿¡æ¯ï¼Œä»¥å…许å†æ¬¡è®¿é—®ã€‚"
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "访问您的 runner 令牌,自定义æµæ°´çº¿é…置,以åŠæŸ¥çœ‹æµæ°´çº¿çŠ¶æ€å’Œè¦†ç›–率报告。"
msgid "Account"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 632da40cd54..3ecd9fc4cd2 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -330,7 +330,7 @@ msgstr ""
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "å› æ¢å¾©å®‰è£ï¼Œè¨ªå•æ•…障存儲已被暫時ç¦ç”¨ã€‚在å•é¡Œè§£æ±ºå¾Œå°‡é‡ç½®å­˜å„²ä¿¡æ¯ï¼Œä»¥ä¾¿å†æ¬¡è¨ªå•ã€‚"
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
msgid "Account"
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 0d6fe4395ef..bb907d9a583 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -330,7 +330,7 @@ msgstr "ä¸å…許存å–「%{classification_label}ã€"
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr "已暫時åœç”¨å¤±æ•—çš„ Git 儲存空間。當儲存空間æ¢å¾©æ­£å¸¸å¾Œï¼Œè«‹é‡ç½®å„²å­˜ç©ºé–“å¥åº·æŒ‡æ•¸ã€‚"
-msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report."
+msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr "å­˜å–您執行器憑證,自定義您的æµæ°´ç·šé…置,並查看你的æµæ°´ç¾ç‹€æ…‹åŠæ¸¬è©¦æ¶µè“‹çŽ‡å ±å‘Šã€‚"
msgid "Account"
diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb
index 94d7df7128b..ed9d0329081 100644
--- a/qa/qa/factory/resource/kubernetes_cluster.rb
+++ b/qa/qa/factory/resource/kubernetes_cluster.rb
@@ -31,6 +31,7 @@ module QA
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
+ page.check_rbac! if @cluster.rbac
page.add_cluster!
end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index eef82b5f329..38f8527b9b4 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -10,6 +10,7 @@ module QA
element :ca_certificate, 'text_area :ca_cert'
element :token, 'text_field :token'
element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
+ element :rbac_checkbox
end
def set_cluster_name(name)
@@ -31,6 +32,10 @@ module QA
def add_cluster!
click_on 'Add Kubernetes cluster'
end
+
+ def check_rbac!
+ check_element :rbac_checkbox
+ end
end
end
end
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index abd9d53554f..d868515555c 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -1,12 +1,17 @@
require 'securerandom'
require 'mkmf'
+require 'pathname'
module QA
module Service
class KubernetesCluster
include Service::Shellout
- attr_reader :api_url, :ca_certificate, :token
+ attr_reader :api_url, :ca_certificate, :token, :rbac
+
+ def initialize(rbac: false)
+ @rbac = rbac
+ end
def cluster_name
@cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
@@ -19,7 +24,7 @@ module QA
shell <<~CMD.tr("\n", ' ')
gcloud container clusters
create #{cluster_name}
- --enable-legacy-authorization
+ #{auth_options}
--zone #{Runtime::Env.gcloud_zone}
&& gcloud container clusters
get-credentials
@@ -28,8 +33,21 @@ module QA
CMD
@api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
- @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
- @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
+ if rbac
+ create_service_account
+
+ secrets = JSON.parse(`kubectl get secrets -o json`)
+ gitlab_account = secrets['items'].find do |item|
+ item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account'
+ end
+
+ @ca_certificate = Base64.decode64(gitlab_account['data']['ca.crt'])
+ @token = Base64.decode64(gitlab_account['data']['token'])
+ else
+ @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
+ @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
+ end
+
self
end
@@ -44,6 +62,42 @@ module QA
private
+ def create_service_account
+ shell('kubectl create -f -', stdin_data: service_account)
+ shell('kubectl create -f -', stdin_data: service_account_role_binding)
+ end
+
+ def service_account
+ <<~YAML
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: gitlab-account
+ namespace: default
+ YAML
+ end
+
+ def service_account_role_binding
+ <<~YAML
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+ name: gitlab-account-binding
+ subjects:
+ - kind: ServiceAccount
+ name: gitlab-account
+ namespace: default
+ roleRef:
+ kind: ClusterRole
+ name: cluster-admin
+ apiGroup: rbac.authorization.k8s.io
+ YAML
+ end
+
+ def auth_options
+ "--enable-legacy-authorization" unless rbac
+ end
+
def validate_dependencies
find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.")
find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 1ca9504bb33..43dc0851571 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -11,10 +11,12 @@ module QA
# TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94
#
- def shell(command)
+ def shell(command, stdin_data: nil)
puts "Executing `#{command}`"
- Open3.popen2e(*command) do |_in, out, wait|
+ Open3.popen2e(*command) do |stdin, out, wait|
+ stdin.puts(stdin_data) if stdin_data
+ stdin.close if stdin_data
out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero?
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 844cc1236c7..4604936916b 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -9,59 +9,63 @@ module QA
@cluster&.remove!
end
- it 'user creates a new project and runs auto devops' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ [true, false].each do |rbac|
+ context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
+ it 'user creates a new project and runs auto devops' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
- project = Factory::Resource::Project.fabricate! do |p|
- p.name = 'project-with-autodevops'
- p.description = 'Project with Auto Devops'
- end
+ project = Factory::Resource::Project.fabricate! do |p|
+ p.name = 'project-with-autodevops'
+ p.description = 'Project with Auto Devops'
+ end
- # Disable code_quality check in Auto DevOps pipeline as it takes
- # too long and times out the test
- Factory::Resource::SecretVariable.fabricate! do |resource|
- resource.project = project
- resource.key = 'CODE_QUALITY_DISABLED'
- resource.value = '1'
- end
+ # Disable code_quality check in Auto DevOps pipeline as it takes
+ # too long and times out the test
+ Factory::Resource::SecretVariable.fabricate! do |resource|
+ resource.project = project
+ resource.key = 'CODE_QUALITY_DISABLED'
+ resource.value = '1'
+ end
- # Create Auto Devops compatible repo
- Factory::Repository::ProjectPush.fabricate! do |push|
- push.project = project
- push.directory = Pathname
- .new(__dir__)
- .join('../../../../../fixtures/auto_devops_rack')
- push.commit_message = 'Create Auto DevOps compatible rack application'
- end
+ # Create Auto Devops compatible repo
+ Factory::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.directory = Pathname
+ .new(__dir__)
+ .join('../../../../../fixtures/auto_devops_rack')
+ push.commit_message = 'Create Auto DevOps compatible rack application'
+ end
- Page::Project::Show.act { wait_for_push }
+ Page::Project::Show.act { wait_for_push }
- # Create and connect K8s cluster
- @cluster = Service::KubernetesCluster.new.create!
- kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
- cluster.project = project
- cluster.cluster = @cluster
- cluster.install_helm_tiller = true
- cluster.install_ingress = true
- cluster.install_prometheus = true
- cluster.install_runner = true
- end
+ # Create and connect K8s cluster
+ @cluster = Service::KubernetesCluster.new(rbac: rbac).create!
+ kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
+ cluster.project = project
+ cluster.cluster = @cluster
+ cluster.install_helm_tiller = true
+ cluster.install_ingress = true
+ cluster.install_prometheus = true
+ cluster.install_runner = true
+ end
- project.visit!
- Page::Menu::Side.act { click_ci_cd_settings }
- Page::Project::Settings::CICD.perform do |p|
- p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
- end
+ project.visit!
+ Page::Menu::Side.act { click_ci_cd_settings }
+ Page::Project::Settings::CICD.perform do |p|
+ p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
+ end
- project.visit!
- Page::Menu::Side.act { click_ci_cd_pipelines }
- Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
+ project.visit!
+ Page::Menu::Side.act { click_ci_cd_pipelines }
+ Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
- Page::Project::Pipeline::Show.perform do |pipeline|
- expect(pipeline).to have_build('build', status: :success, wait: 600)
- expect(pipeline).to have_build('test', status: :success, wait: 600)
- expect(pipeline).to have_build('production', status: :success, wait: 1200)
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to have_build('build', status: :success, wait: 600)
+ expect(pipeline).to have_build('test', status: :success, wait: 600)
+ expect(pipeline).to have_build('production', status: :success, wait: 1200)
+ end
+ end
end
end
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 10e1bfc30f9..2e0f79cd313 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -86,4 +86,22 @@ describe Admin::ApplicationSettingsController do
expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
end
end
+
+ describe 'PUT #reset_registration_token' do
+ before do
+ sign_in(admin)
+ end
+
+ subject { put :reset_registration_token }
+
+ it 'resets runner registration token' do
+ expect { subject }.to change { ApplicationSetting.current.runners_registration_token }
+ end
+
+ it 'redirects the user to admin runners page' do
+ subject
+
+ expect(response).to redirect_to(admin_runners_path)
+ end
+ end
end
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index ea18122e0c3..06ccace8242 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -17,4 +17,18 @@ describe Groups::Settings::CiCdController do
expect(response).to render_template(:show)
end
end
+
+ describe 'PUT #reset_registration_token' do
+ subject { put :reset_registration_token, group_id: group }
+
+ it 'resets runner registration token' do
+ expect { subject }.to change { group.reload.runners_token }
+ end
+
+ it 'redirects the user to admin runners page' do
+ subject
+
+ expect(response).to redirect_to(group_settings_ci_cd_path)
+ end
+ end
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 1f14a0cc381..4629929f9af 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -74,6 +74,19 @@ describe Projects::Settings::CiCdController do
end
end
+ describe 'PUT #reset_registration_token' do
+ subject { put :reset_registration_token, namespace_id: project.namespace, project_id: project }
+ it 'resets runner registration token' do
+ expect { subject }.to change { project.reload.runners_token }
+ end
+
+ it 'redirects the user to admin runners page' do
+ subject
+
+ expect(response).to redirect_to(namespace_project_settings_ci_cd_path)
+ end
+ end
+
describe 'PATCH update' do
let(:params) { { ci_config_path: '' } }
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 9a65e7f8a3f..1a2be5e9552 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,17 +1,17 @@
FactoryBot.define do
factory :broadcast_message do
message "MyText"
- starts_at 1.day.ago
- ends_at 1.day.from_now
+ starts_at { 1.day.ago }
+ ends_at { 1.day.from_now }
trait :expired do
- starts_at 5.days.ago
- ends_at 3.days.ago
+ starts_at { 5.days.ago }
+ ends_at { 3.days.ago }
end
trait :future do
- starts_at 5.days.from_now
- ends_at 6.days.from_now
+ starts_at { 5.days.from_now }
+ ends_at { 6.days.from_now }
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 7a4b1dfafac..85ba7d4097d 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -180,12 +180,12 @@ FactoryBot.define do
end
trait :erased do
- erased_at Time.now
+ erased_at { Time.now }
erased_by factory: :user
end
trait :queued do
- queued_at Time.now
+ queued_at { Time.now }
runner factory: :ci_runner
end
@@ -215,7 +215,7 @@ FactoryBot.define do
end
trait :expired do
- artifacts_expire_at 1.minute.ago
+ artifacts_expire_at { 1.minute.ago }
end
trait :with_commit do
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 347e4f433e2..f564e7bee47 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -9,7 +9,7 @@ FactoryBot.define do
runner_type :instance_type
trait :online do
- contacted_at Time.now
+ contacted_at { Time.now }
end
trait :instance do
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index 5756486df27..3c9ca22a051 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -42,7 +42,7 @@ FactoryBot.define do
trait :timeouted do
installing
- updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
+ updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
end
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index 36ac2372204..4a0d1b181ea 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -3,11 +3,10 @@ FactoryBot.define do
cluster
namespace nil
api_url 'https://kubernetes.example.com'
- token 'a' * 40
+ token { 'a' * 40 }
trait :configured do
api_url 'https://kubernetes.example.com'
- token 'a' * 40
username 'xxxxxx'
password 'xxxxxx'
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index d23ddf9d79b..feacd5ccf15 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -3,7 +3,7 @@ FactoryBot.define do
user
email { generate(:email_alias) }
- trait(:confirmed) { confirmed_at Time.now }
+ trait(:confirmed) { confirmed_at { Time.now } }
trait(:skip_validate) { to_create {|instance| instance.save(validate: false) } }
end
end
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
index 51b8ddc9934..3c0f43cc1b6 100644
--- a/spec/factories/gpg_keys.rb
+++ b/spec/factories/gpg_keys.rb
@@ -2,11 +2,11 @@ require_relative '../support/helpers/gpg_helpers'
FactoryBot.define do
factory :gpg_key do
- key GpgHelpers::User1.public_key
+ key { GpgHelpers::User1.public_key }
user
factory :gpg_key_with_subkeys do
- key GpgHelpers::User1.public_key_with_extra_signing_key
+ key { GpgHelpers::User1.public_key_with_extra_signing_key }
end
end
end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 47036560b9d..12be63e5d92 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -9,7 +9,7 @@ FactoryBot.define do
trait(:developer) { access_level GroupMember::DEVELOPER }
trait(:maintainer) { access_level GroupMember::MAINTAINER }
trait(:owner) { access_level GroupMember::OWNER }
- trait(:access_request) { requested_at Time.now }
+ trait(:access_request) { requested_at { Time.now } }
trait(:invited) do
user_id nil
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index b8b089b069b..8094c43b065 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -80,7 +80,7 @@ FactoryBot.define do
trait :merge_when_pipeline_succeeds do
merge_when_pipeline_succeeds true
- merge_user author
+ merge_user { author }
end
trait :remove_source_branch do
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 6844ed8aa4a..2d1f48bf249 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -90,7 +90,7 @@ FactoryBot.define do
noteable nil
noteable_type 'Commit'
noteable_id nil
- commit_id RepoHelpers.sample_commit.id
+ commit_id { RepoHelpers.sample_commit.id }
end
trait :legacy_diff_note do
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
index 9e6af24c4eb..02c51cd9899 100644
--- a/spec/factories/oauth_access_grants.rb
+++ b/spec/factories/oauth_access_grants.rb
@@ -3,7 +3,7 @@ FactoryBot.define do
resource_owner_id { create(:user).id }
application
token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
- expires_in 2.hours
+ expires_in { 2.hours }
redirect_uri { application.redirect_uri }
scopes { application.scopes }
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index 22a8085ea45..c72e0487895 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -8,7 +8,7 @@ FactoryBot.define do
trait(:reporter) { access_level ProjectMember::REPORTER }
trait(:developer) { access_level ProjectMember::DEVELOPER }
trait(:maintainer) { access_level ProjectMember::MAINTAINER }
- trait(:access_request) { requested_at Time.now }
+ trait(:access_request) { requested_at { Time.now } }
trait(:invited) do
user_id nil
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 14486c80341..ed3d87eb76b 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -49,7 +49,7 @@ FactoryBot.define do
author
user
action { Todo::ASSIGNED }
- commit_id RepoHelpers.sample_commit.id
+ commit_id { RepoHelpers.sample_commit.id }
target_type "Commit"
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 81c485fba1a..7256f785e1f 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory :upload do
model { build(:project) }
- size 100.kilobytes
+ size { 100.kilobytes }
uploader "AvatarUploader"
mount_point :avatar
secret nil
@@ -19,13 +19,13 @@ FactoryBot.define do
uploader "PersonalFileUploader"
path { File.join(secret, filename) }
model { build(:personal_snippet) }
- secret SecureRandom.hex
+ secret { SecureRandom.hex }
end
trait :issuable_upload do
uploader "FileUploader"
path { File.join(secret, filename) }
- secret SecureRandom.hex
+ secret { SecureRandom.hex }
end
trait :with_file do
@@ -43,14 +43,14 @@ FactoryBot.define do
model { build(:group) }
path { File.join(secret, filename) }
uploader "NamespaceFileUploader"
- secret SecureRandom.hex
+ secret { SecureRandom.hex }
end
trait :favicon_upload do
model { build(:appearance) }
path { File.join(secret, filename) }
uploader "FaviconUploader"
- secret SecureRandom.hex
+ secret { SecureRandom.hex }
end
trait :attachment_upload do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index f08946b0593..aa3ca8923ff 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -64,7 +64,7 @@ describe 'Contributions Calendar', :js do
end
def selected_day_activities(visible: true)
- find('.user-calendar-activities', visible: visible).text
+ find('.tab-pane#activity .user-calendar-activities', visible: visible).text
end
before do
@@ -74,15 +74,16 @@ describe 'Contributions Calendar', :js do
describe 'calendar day selection' do
before do
visit user.username
+ page.find('.js-activity-tab a').click
wait_for_requests
end
it 'displays calendar' do
- expect(page).to have_css('.js-contrib-calendar')
+ expect(find('.tab-pane#activity')).to have_css('.js-contrib-calendar')
end
describe 'select calendar day' do
- let(:cells) { page.all('.user-contrib-cell') }
+ let(:cells) { page.all('.tab-pane#activity .user-contrib-cell') }
before do
cells[0].click
@@ -108,6 +109,7 @@ describe 'Contributions Calendar', :js do
describe 'deselect calendar day' do
before do
cells[0].click
+ page.find('.js-activity-tab a').click
wait_for_requests
end
@@ -122,6 +124,7 @@ describe 'Contributions Calendar', :js do
shared_context 'visit user page' do
before do
visit user.username
+ page.find('.js-activity-tab a').click
wait_for_requests
end
end
@@ -130,12 +133,12 @@ describe 'Contributions Calendar', :js do
include_context 'visit user page'
it 'displays calendar activity square color for 1 contribution' do
- expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1)
+ expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(contribution_count), count: 1)
end
it 'displays calendar activity square on the correct date' do
today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
+ expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
end
end
@@ -150,7 +153,7 @@ describe 'Contributions Calendar', :js do
include_context 'visit user page'
it 'displays calendar activity log' do
- expect(find('.content_list .event-note')).to have_content issue_title
+ expect(find('.tab-pane#activity .content_list .event-note')).to have_content issue_title
end
end
end
@@ -182,17 +185,17 @@ describe 'Contributions Calendar', :js do
include_context 'visit user page'
it 'displays calendar activity squares for both days' do
- expect(page).to have_selector(get_cell_color_selector(1), count: 2)
+ expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(1), count: 2)
end
it 'displays calendar activity square for yesterday' do
yesterday = Date.yesterday.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
end
it 'displays calendar activity square for today' do
today = Date.today.strftime(date_format)
- expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, today), count: 1)
end
end
end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index d7234158fa1..0db8093411b 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -14,7 +14,7 @@ describe 'Tooltips on .timeago dates', :js do
updated_at: created_date, created_at: created_date)
sign_in user
- visit user_path(user)
+ visit user_activity_path(user)
wait_for_requests()
page.find('.js-timeago').hover
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
index d9543bfa97f..22b51b297a6 100644
--- a/spec/features/groups/labels/subscription_spec.rb
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -3,7 +3,8 @@ require 'spec_helper'
describe 'Labels subscription' do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let!(:feature) { create(:group_label, group: group, title: 'feature') }
+ let!(:label1) { create(:group_label, group: group, title: 'foo') }
+ let!(:label2) { create(:group_label, group: group, title: 'bar') }
context 'when signed in' do
before do
@@ -14,9 +15,9 @@ describe 'Labels subscription' do
it 'users can subscribe/unsubscribe to group labels', :js do
visit group_labels_path(group)
- expect(page).to have_content('feature')
+ expect(page).to have_content(label1.title)
- within "#group_label_#{feature.id}" do
+ within "#group_label_#{label1.id}" do
expect(page).not_to have_button 'Unsubscribe'
click_button 'Subscribe'
@@ -30,15 +31,48 @@ describe 'Labels subscription' do
expect(page).not_to have_button 'Unsubscribe'
end
end
+
+ context 'subscription filter' do
+ before do
+ visit group_labels_path(group)
+ end
+
+ it 'shows only subscribed labels' do
+ label1.subscribe(user)
+
+ click_subscribed_tab
+
+ page.within('.labels-container') do
+ expect(page).to have_content label1.title
+ end
+ end
+
+ it 'shows no subscribed labels message' do
+ click_subscribed_tab
+
+ page.within('.labels-container') do
+ expect(page).not_to have_content label1.title
+ expect(page).to have_content('You do not have any subscriptions yet')
+ end
+ end
+ end
end
context 'when not signed in' do
- it 'users can not subscribe/unsubscribe to labels' do
+ before do
visit group_labels_path(group)
+ end
- expect(page).to have_content 'feature'
+ it 'users can not subscribe/unsubscribe to labels' do
+ expect(page).to have_content label1.title
expect(page).not_to have_button('Subscribe')
end
+
+ it 'does not show subscribed tab' do
+ page.within('.nav-tabs') do
+ expect(page).not_to have_link 'Subscribed'
+ end
+ end
end
def click_link_on_dropdown(text)
@@ -48,4 +82,10 @@ describe 'Labels subscription' do
find('a.js-subscribe-button', text: text).click
end
end
+
+ def click_subscribed_tab
+ page.within('.nav-tabs') do
+ click_link 'Subscribed'
+ end
+ end
end
diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb
new file mode 100644
index 00000000000..d422fd18346
--- /dev/null
+++ b/spec/features/groups/settings/ci_cd_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Group CI/CD settings' do
+ include WaitForRequests
+
+ let(:user) {create(:user)}
+ let(:group) {create(:group)}
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ describe 'runners registration token' do
+ let!(:token) { group.runners_token }
+
+ before do
+ visit group_settings_ci_cd_path(group)
+ end
+
+ it 'has a registration token' do
+ expect(page.find('#registration_token')).to have_content(token)
+ end
+
+ describe 'reload registration token' do
+ let(:page_token) { find('#registration_token').text }
+
+ before do
+ click_button 'Reset runners registration token'
+ end
+
+ it 'changes registration token' do
+ expect(page_token).not_to eq token
+ end
+ end
+ end
+end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 206a3a4fe9a..e168bb0fc89 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -61,83 +61,229 @@ describe 'User edit profile' do
end
context 'user status', :js do
- def select_emoji(emoji_name)
+ def select_emoji(emoji_name, is_modal = false)
+ emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu'
toggle_button = find('.js-toggle-emoji-menu')
toggle_button.click
- emoji_button = find(%Q{.js-status-emoji-menu .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]})
+ emoji_button = find(%Q{#{emoji_menu_class} .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]})
emoji_button.click
end
- it 'shows the user status form' do
- visit(profile_path)
+ context 'profile edit form' do
+ it 'shows the user status form' do
+ visit(profile_path)
- expect(page).to have_content('Current status')
- end
+ expect(page).to have_content('Current status')
+ end
- it 'adds emoji to user status' do
- emoji = 'biohazard'
- visit(profile_path)
- select_emoji(emoji)
- submit_settings
+ it 'adds emoji to user status' do
+ emoji = 'biohazard'
+ visit(profile_path)
+ select_emoji(emoji)
+ submit_settings
- visit user_path(user)
- within('.cover-status') do
- expect(page).to have_emoji(emoji)
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ end
end
- end
- it 'adds message to user status' do
- message = 'I have something to say'
- visit(profile_path)
- fill_in 'js-status-message-field', with: message
- submit_settings
+ it 'adds message to user status' do
+ message = 'I have something to say'
+ visit(profile_path)
+ fill_in 'js-status-message-field', with: message
+ submit_settings
- visit user_path(user)
- within('.cover-status') do
- expect(page).to have_emoji('speech_balloon')
- expect(page).to have_content message
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji('speech_balloon')
+ expect(page).to have_content message
+ end
end
- end
- it 'adds message and emoji to user status' do
- emoji = 'tanabata_tree'
- message = 'Playing outside'
- visit(profile_path)
- select_emoji(emoji)
- fill_in 'js-status-message-field', with: message
- submit_settings
+ it 'adds message and emoji to user status' do
+ emoji = 'tanabata_tree'
+ message = 'Playing outside'
+ visit(profile_path)
+ select_emoji(emoji)
+ fill_in 'js-status-message-field', with: message
+ submit_settings
- visit user_path(user)
- within('.cover-status') do
- expect(page).to have_emoji(emoji)
- expect(page).to have_content message
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ expect(page).to have_content message
+ end
end
- end
- it 'clears the user status' do
- user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+ it 'clears the user status' do
+ user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(user_status.emoji)
+ expect(page).to have_content user_status.message
+ end
+
+ visit(profile_path)
+ click_button 'js-clear-user-status-button'
+ submit_settings
- visit user_path(user)
- within('.cover-status') do
- expect(page).to have_emoji(user_status.emoji)
- expect(page).to have_content user_status.message
+ visit user_path(user)
+ expect(page).not_to have_selector '.cover-status'
end
- visit(profile_path)
- click_button 'js-clear-user-status-button'
- submit_settings
+ it 'displays a default emoji if only message is entered' do
+ message = 'a status without emoji'
+ visit(profile_path)
+ fill_in 'js-status-message-field', with: message
- visit user_path(user)
- expect(page).not_to have_selector '.cover-status'
+ within('.js-toggle-emoji-menu') do
+ expect(page).to have_emoji('speech_balloon')
+ end
+ end
end
- it 'displays a default emoji if only message is entered' do
- message = 'a status without emoji'
- visit(profile_path)
- fill_in 'js-status-message-field', with: message
+ context 'user menu' do
+ def open_user_status_modal
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ click_button 'Set status'
+ end
+ end
+
+ def set_user_status_in_modal
+ page.within "#set-user-status-modal" do
+ click_button 'Set status'
+ end
+ end
+
+ before do
+ visit root_path(user)
+ end
+
+ it 'shows the "Set status" menu item in the user menu' do
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ expect(page).to have_content('Set status')
+ end
+ end
+
+ it 'shows the "Edit status" menu item in the user menu' do
+ user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+ visit root_path(user)
+
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ expect(page).to have_emoji(user_status.emoji)
+ expect(page).to have_content user_status.message
+ expect(page).to have_content('Edit status')
+ end
+ end
+
+ it 'shows user status modal' do
+ open_user_status_modal
+
+ expect(page.find('#set-user-status-modal')).to be_visible
+ expect(page).to have_content('Set a status')
+ end
+
+ it 'adds emoji to user status' do
+ emoji = 'biohazard'
+ open_user_status_modal
+ select_emoji(emoji, true)
+ set_user_status_in_modal
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ end
+ end
+
+ it 'adds message to user status' do
+ message = 'I have something to say'
+ open_user_status_modal
+ find('.js-status-message-field').native.send_keys(message)
+ set_user_status_in_modal
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji('speech_balloon')
+ expect(page).to have_content message
+ end
+ end
+
+ it 'adds message and emoji to user status' do
+ emoji = 'tanabata_tree'
+ message = 'Playing outside'
+ open_user_status_modal
+ select_emoji(emoji, true)
+ find('.js-status-message-field').native.send_keys(message)
+ set_user_status_in_modal
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ expect(page).to have_content message
+ end
+ end
+
+ it 'clears the user status with the "X" button' do
+ user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(user_status.emoji)
+ expect(page).to have_content user_status.message
+ end
+
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ click_button 'Edit status'
+ end
+
+ find('.js-clear-user-status-button').click
+ set_user_status_in_modal
+
+ visit user_path(user)
+ expect(page).not_to have_selector '.cover-status'
+ end
+
+ it 'clears the user status with the "Remove status" button' do
+ user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(user_status.emoji)
+ expect(page).to have_content user_status.message
+ end
+
+ find('.header-user-dropdown-toggle').click
+
+ page.within ".header-user" do
+ click_button 'Edit status'
+ end
+
+ page.within "#set-user-status-modal" do
+ click_button 'Remove status'
+ end
+
+ visit user_path(user)
+ expect(page).not_to have_selector '.cover-status'
+ end
+
+ it 'displays a default emoji if only message is entered' do
+ message = 'a status without emoji'
+ open_user_status_modal
+ find('.js-status-message-field').native.send_keys(message)
- within('.js-toggle-emoji-menu') do
- expect(page).to have_emoji('speech_balloon')
+ within('.js-toggle-emoji-menu') do
+ expect(page).to have_emoji('speech_balloon')
+ end
end
end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index edc763ad0ad..8b92b9fc869 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -84,10 +84,8 @@ describe 'Gcp Cluster', :js do
it_behaves_like 'valid cluster gcp form'
- context 'rbac_clusters feature flag is enabled' do
+ context 'RBAC is enabled for the cluster' do
before do
- stub_feature_flags(rbac_clusters: true)
-
check 'cluster_provider_gcp_attributes_legacy_abac'
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 2b4998ed5ac..9ae1dba60b5 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -44,10 +44,8 @@ describe 'User Cluster', :js do
it_behaves_like 'valid cluster user form'
- context 'rbac_clusters feature flag is enabled' do
+ context 'RBAC is enabled for the cluster' do
before do
- stub_feature_flags(rbac_clusters: true)
-
check 'cluster_platform_kubernetes_attributes_authorization_type'
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 6a7bf0860e9..7bebd066b80 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -542,7 +542,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
visit project_job_path(project, job)
end
- it 'shows manual action empty state' do
+ it 'shows manual action empty state', :js do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job requires a manual action')
expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
@@ -591,14 +591,14 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
visit project_job_path(project, job)
end
- it 'shows empty state' do
+ it 'shows empty state', :js do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job has not been triggered yet')
expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered')
end
end
- context 'Pending job' do
+ context 'Pending job', :js do
let(:job) { create(:ci_build, :pending, pipeline: pipeline) }
before do
@@ -625,7 +625,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- context 'without log' do
+ context 'without log', :js do
let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
before do
@@ -640,7 +640,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- context 'Skipped job' do
+ context 'Skipped job', :js do
let(:job) { create(:ci_build, :skipped, pipeline: pipeline) }
before do
@@ -654,7 +654,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- context 'when job is failed but has no trace' do
+ context 'when job is failed but has no trace', :js do
let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
it 'renders empty state' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index 30b0a5578ea..6f8ec0015ad 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -137,5 +137,29 @@ describe "Projects > Settings > Pipelines settings" do
end
end
end
+
+ describe 'runners registration token' do
+ let!(:token) { project.runners_token }
+
+ before do
+ visit project_settings_ci_cd_path(project)
+ end
+
+ it 'has a registration token' do
+ expect(page.find('#registration_token')).to have_content(token)
+ end
+
+ describe 'reload registration token' do
+ let(:page_token) { find('#registration_token').text }
+
+ before do
+ click_button 'Reset runners registration token'
+ end
+
+ it 'changes registration token' do
+ expect(page_token).not_to eq token
+ end
+ end
+ end
end
end
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index 66afe163447..0725ff178ac 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -14,7 +14,7 @@ describe 'User uses search filters', :js do
visit(search_path)
end
- context' when filtering by group' do
+ context 'when filtering by group' do
it 'shows group projects' do
find('.js-search-group-dropdown').click
@@ -36,7 +36,7 @@ describe 'User uses search filters', :js do
end
end
- context' when filtering by project' do
+ context 'when filtering by project' do
it 'shows a project' do
page.within('.project-filter') do
find('.js-search-project-dropdown').click
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index f1192f48b86..ae9b65d1a39 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows registering a new device with a name' do
visit profile_account_path
manage_two_factor_authentication
- expect(page).to have_content("You've already enabled two-factor authentication using mobile")
+ expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
u2f_device = register_u2f_device
@@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it 'allows deleting a device' do
visit profile_account_path
manage_two_factor_authentication
- expect(page).to have_content("You've already enabled two-factor authentication using mobile")
+ expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators")
first_u2f_device = register_u2f_device
second_u2f_device = register_u2f_device(name: 'My other device')
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
new file mode 100644
index 00000000000..11f357cbaa5
--- /dev/null
+++ b/spec/features/users/overview_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe 'Overview tab on a user profile', :js do
+ let(:user) { create(:user) }
+ let(:contributed_project) { create(:project, :public, :repository) }
+
+ def push_code_contribution
+ event = create(:push_event, project: contributed_project, author: user)
+
+ create(:push_event_payload,
+ event: event,
+ commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 3,
+ ref: 'master')
+ end
+
+ before do
+ sign_in user
+ end
+
+ describe 'activities section' do
+ shared_context 'visit overview tab' do
+ before do
+ visit user.username
+ page.find('.js-overview-tab a').click
+ wait_for_requests
+ end
+ end
+
+ describe 'user has no activities' do
+ include_context 'visit overview tab'
+
+ it 'does not show any entries in the list of activities' do
+ page.within('.activities-block') do
+ expect(page).not_to have_selector('.event-item')
+ end
+ end
+
+ it 'does not show a link to the activity list' do
+ expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: false)
+ end
+ end
+
+ describe 'user has 3 activities' do
+ before do
+ 3.times { push_code_contribution }
+ end
+
+ include_context 'visit overview tab'
+
+ it 'display 3 entries in the list of activities' do
+ expect(find('#js-overview')).to have_selector('.event-item', count: 3)
+ end
+ end
+
+ describe 'user has 10 activities' do
+ before do
+ 10.times { push_code_contribution }
+ end
+
+ include_context 'visit overview tab'
+
+ it 'displays 5 entries in the list of activities' do
+ expect(find('#js-overview')).to have_selector('.event-item', count: 5)
+ end
+
+ it 'shows a link to the activity list' do
+ expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: true)
+ end
+
+ it 'links to the activity tab' do
+ page.within('.activities-block') do
+ find('.js-view-all').click
+ wait_for_requests
+ expect(URI.parse(current_url).path).to eq("/users/#{user.username}/activity")
+ end
+ end
+ end
+ end
+
+ describe 'projects section' do
+ shared_context 'visit overview tab' do
+ before do
+ visit user.username
+ page.find('.js-overview-tab a').click
+ wait_for_requests
+ end
+ end
+
+ describe 'user has no personal projects' do
+ include_context 'visit overview tab'
+
+ it 'it shows an empty project list with an info message' do
+ page.within('.projects-block') do
+ expect(page).to have_content('No projects found')
+ expect(page).not_to have_selector('.project-row')
+ end
+ end
+
+ it 'does not show a link to the project list' do
+ expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: false)
+ end
+ end
+
+ describe 'user has a personal project' do
+ let(:private_project) { create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) } }
+ let!(:private_event) { create(:event, project: private_project, author: user) }
+
+ include_context 'visit overview tab'
+
+ it 'it shows one entry in the list of projects' do
+ page.within('.projects-block') do
+ expect(page).to have_selector('.project-row', count: 1)
+ end
+ end
+
+ it 'shows a link to the project list' do
+ expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true)
+ end
+ end
+ end
+end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index bc07ab48c39..86379164cf0 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -8,6 +8,7 @@ describe 'User page' do
visit(user_path(user))
page.within '.nav-links' do
+ expect(page).to have_link('Overview')
expect(page).to have_link('Activity')
expect(page).to have_link('Groups')
expect(page).to have_link('Contributed projects')
@@ -44,6 +45,7 @@ describe 'User page' do
visit(user_path(user))
page.within '.nav-links' do
+ expect(page).to have_link('Overview')
expect(page).to have_link('Activity')
expect(page).to have_link('Groups')
expect(page).to have_link('Contributed projects')
diff --git a/spec/finders/group_labels_finder_spec.rb b/spec/finders/group_labels_finder_spec.rb
index ef68fc105e4..7bdd312eff0 100644
--- a/spec/finders/group_labels_finder_spec.rb
+++ b/spec/finders/group_labels_finder_spec.rb
@@ -4,29 +4,38 @@ require 'spec_helper'
describe GroupLabelsFinder, '#execute' do
let!(:group) { create(:group) }
+ let!(:user) { create(:user) }
let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) }
let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) }
it 'returns all group labels sorted by name if no params' do
- result = described_class.new(group).execute
+ result = described_class.new(user, group).execute
expect(result.to_a).to match_array([label2, label1])
end
it 'returns all group labels sorted by name desc' do
- result = described_class.new(group, sort: 'name_desc').execute
+ result = described_class.new(user, group, sort: 'name_desc').execute
expect(result.to_a).to match_array([label2, label1])
end
- it 'returns group labels that march search' do
- result = described_class.new(group, search: 'Foo').execute
+ it 'returns group labels that match search' do
+ result = described_class.new(user, group, search: 'Foo').execute
expect(result.to_a).to match_array([label1])
end
+ it 'returns group labels user subscribed to' do
+ label2.subscribe(user)
+
+ result = described_class.new(user, group, subscribed: 'true').execute
+
+ expect(result.to_a).to match_array([label2])
+ end
+
it 'returns second page of labels' do
- result = described_class.new(group, page: '2').execute
+ result = described_class.new(user, group, page: '2').execute
expect(result.to_a).to match_array([])
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index f5cec8e349a..9abc52aa664 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -210,5 +210,15 @@ describe LabelsFinder do
expect(finder.execute).to eq [project_label_1]
end
end
+
+ context 'filter by subscription' do
+ it 'returns labels user subscribed to' do
+ project_label_1.subscribe(user)
+
+ finder = described_class.new(user, subscribed: 'true')
+
+ expect(finder.execute).to eq [project_label_1]
+ end
+ end
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 33d01697c75..ff4c6b8dd42 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -3,21 +3,37 @@ require 'spec_helper'
describe MergeRequestsFinder do
include ProjectForksHelper
+ # We need to explicitly permit Gitaly N+1s because of the specs that use
+ # :request_store. Gitaly N+1 detection is only enabled when :request_store is,
+ # but we don't care about potential N+1s when we're just creating several
+ # projects in the setup phase.
+ def create_project_without_n_plus_1(*args)
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ create(:project, :public, *args)
+ end
+ end
+
let(:user) { create :user }
let(:user2) { create :user }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
- let(:project1) { create(:project, :public, group: group) }
- let(:project2) { fork_project(project1, user) }
+ let(:project1) { create_project_without_n_plus_1(group: group) }
+ let(:project2) do
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ fork_project(project1, user)
+ end
+ end
let(:project3) do
- p = fork_project(project1, user)
- p.update!(archived: true)
- p
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ p = fork_project(project1, user)
+ p.update!(archived: true)
+ p
+ end
end
- let(:project4) { create(:project, :public, group: subgroup) }
- let(:project5) { create(:project, :public, group: subgroup) }
- let(:project6) { create(:project, :public, group: subgroup) }
+ let(:project4) { create_project_without_n_plus_1(group: subgroup) }
+ let(:project5) { create_project_without_n_plus_1(group: subgroup) }
+ let(:project6) { create_project_without_n_plus_1(group: subgroup) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') }
diff --git a/spec/fixtures/api/schemas/job/runners.json b/spec/fixtures/api/schemas/job/runners.json
index bebb0c88652..646bfd3a82d 100644
--- a/spec/fixtures/api/schemas/job/runners.json
+++ b/spec/fixtures/api/schemas/job/runners.json
@@ -8,6 +8,5 @@
"online": { "type": "boolean" },
"available": { "type": "boolean" },
"settings_path": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/ssh_host_example_key.pub b/spec/fixtures/ssh_host_example_key.pub
index 6bac42b3ad0..d43315ddae8 100644
--- a/spec/fixtures/ssh_host_example_key.pub
+++ b/spec/fixtures/ssh_host_example_key.pub
@@ -1 +1 @@
-random content
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuRkAgwaap/pXThwCpjX8Wd5tR36Tqx3sW2sVVHs3UKB7kd+xNknw7e4qpuEATv56xHrhKm2+ye/JidTuQ/1EwFhjaz7I5wTslfVawQpeH1ZqAGmvdO/xTw+l7fgEFVlGVx9y0HV3m52y2C9yw82qmg+BohbTVgPtjjutpFc+CwLQxLTnTrRhZf5udQgz+YlwLv+Y0kDx6+DWWOl8N9+TWuGyFKBln79CyBgFcK5NFmF48kYn8W+r7rmawfw9XbuF1aa+6JF+6cNR1mCEonyrRLdXP+vWcxpLKYfejB0NmA1y+W9M/K53AcIHA5zlRQ49tFh0P22eh/Gl8JQ6yyuin foo@bar.mynet
diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js
index 627fb8c490a..8c3376c0eb3 100644
--- a/spec/javascripts/diffs/components/commit_item_spec.js
+++ b/spec/javascripts/diffs/components/commit_item_spec.js
@@ -9,6 +9,8 @@ import getDiffWithCommit from '../mock_data/diff_with_commit';
const TEST_AUTHOR_NAME = 'test';
const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com';
const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`;
+const TEST_SIGNATURE_HTML = '<a>Legit commit</a>';
+const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`;
const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title');
const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description');
@@ -16,6 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e
const getShaElement = vm => vm.$el.querySelector('.commit-sha-group');
const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link');
const getCommitterElement = vm => vm.$el.querySelector('.commiter');
+const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions');
describe('diffs/components/commit_widget', () => {
const Component = Vue.extend(CommitItem);
@@ -125,4 +128,36 @@ describe('diffs/components/commit_widget', () => {
expect(nameElement).toHaveText(TEST_AUTHOR_NAME);
});
});
+
+ describe('with signature', () => {
+ beforeEach(done => {
+ vm.commit.signatureHtml = TEST_SIGNATURE_HTML;
+
+ vm.$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders signature html', () => {
+ const actionsElement = getCommitActionsElement(vm);
+
+ expect(actionsElement).toContainHtml(TEST_SIGNATURE_HTML);
+ });
+ });
+
+ describe('with pipeline status', () => {
+ beforeEach(done => {
+ vm.commit.pipelineStatusPath = TEST_PIPELINE_STATUS_PATH;
+
+ vm.$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders pipeline status', () => {
+ const actionsElement = getCommitActionsElement(vm);
+
+ expect(actionsElement).toContainElement('.ci-status-link');
+ });
+ });
});
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index b29a22da7c2..0ad214ea4a4 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -2,15 +2,13 @@ export default {
id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
position: {
- formatter: {
- old_line: null,
- new_line: 2,
- old_path: 'CHANGELOG',
- new_path: 'CHANGELOG',
- base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
- start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
- head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
- },
+ old_line: null,
+ new_line: 2,
+ old_path: 'CHANGELOG',
+ new_path: 'CHANGELOG',
+ base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
},
line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
expanded: true,
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index aacad7a479b..85c1926fcb1 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -148,12 +148,8 @@ describe('DiffsStoreActions', () => {
},
fileHash: 'ABC',
resolvable: true,
- position: {
- formatter: diffPosition,
- },
- original_position: {
- formatter: diffPosition,
- },
+ position: diffPosition,
+ original_position: diffPosition,
};
const discussions = reduceDiscussionsToLineCodes([singleDiscussion]);
@@ -178,6 +174,7 @@ describe('DiffsStoreActions', () => {
oldLine: 5,
oldPath: 'file2',
lineCode: 'ABC_1_1',
+ positionType: 'text',
},
},
},
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index cc8d5dc4bac..0b712055956 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -194,24 +194,16 @@ describe('DiffsStoreMutations', () => {
line_code: 'ABC_1',
diff_discussion: true,
resolvable: true,
- original_position: {
- formatter: diffPosition,
- },
- position: {
- formatter: diffPosition,
- },
+ original_position: diffPosition,
+ position: diffPosition,
},
{
id: 2,
line_code: 'ABC_1',
diff_discussion: true,
resolvable: true,
- original_position: {
- formatter: diffPosition,
- },
- position: {
- formatter: diffPosition,
- },
+ original_position: diffPosition,
+ position: diffPosition,
},
];
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index e660f94c72e..257270a91ec 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -333,20 +333,12 @@ describe('DiffsStoreUtils', () => {
const discussions = {
upToDateDiscussion1: {
- original_position: {
- formatter: diffPosition,
- },
- position: {
- formatter: wrongDiffPosition,
- },
+ original_position: diffPosition,
+ position: wrongDiffPosition,
},
outDatedDiscussion1: {
- original_position: {
- formatter: wrongDiffPosition,
- },
- position: {
- formatter: wrongDiffPosition,
- },
+ original_position: wrongDiffPosition,
+ position: wrongDiffPosition,
},
};
diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js
index 61ee993f46a..0bcc4ff940f 100644
--- a/spec/javascripts/jobs/components/commit_block_spec.js
+++ b/spec/javascripts/jobs/components/commit_block_spec.js
@@ -56,7 +56,7 @@ describe('Commit block', () => {
props.mergeRequest.path,
);
expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual(
- props.mergeRequest.iid,
+ `!${props.mergeRequest.iid}`,
);
});
});
diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js
index 872cc1e3864..73488eaab9b 100644
--- a/spec/javascripts/jobs/components/empty_state_spec.js
+++ b/spec/javascripts/jobs/components/empty_state_spec.js
@@ -67,7 +67,7 @@ describe('Empty State', () => {
content,
action: {
path: 'runner',
- title: 'Check runner',
+ button_title: 'Check runner',
method: 'post',
},
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index c31fa6f9887..e02eb9723fe 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -37,6 +37,7 @@ describe('Job App ', () => {
available: false,
},
tags: ['docker'],
+ has_trace: true,
};
const props = {
@@ -182,4 +183,142 @@ describe('Job App ', () => {
expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
});
});
+
+ describe('environments block', () => {
+ it('renders environment block when job has environment', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ deployment_status: {
+ environment: {
+ environment_path: '/path',
+ name: 'foo',
+ },
+ },
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
+ });
+
+ it('does not render environment block when job has environment', () => {
+ store.dispatch('receiveJobSuccess', job);
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
+ });
+ });
+
+ describe('erased block', () => {
+ it('renders erased block when `erased` is true', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ erased: true,
+ erased_by: {
+ username: 'root',
+ web_url: 'gitlab.com/root',
+ },
+ erased_at: '2016-11-07T11:11:16.525Z',
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull();
+ });
+
+ it('does not render erased block when `erased` is false', () => {
+ store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false }));
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-erased')).toBeNull();
+ });
+ });
+
+ describe('empty states block', () => {
+ it('renders empty state when job does not have trace and is not running', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ has_trace: false,
+ status: {
+ group: 'pending',
+ icon: 'status_pending',
+ label: 'pending',
+ text: 'pending',
+ details_path: 'path',
+ illustration: {
+ image: 'path',
+ size: '340',
+ title: 'Empty State',
+ content: 'This is an empty state',
+ },
+ action: {
+ button_title: 'Retry job',
+ method: 'post',
+ path: '/path',
+ },
+ },
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
+ });
+
+ it('does not render empty state when job does not have trace but it is running', () => {
+ store.dispatch(
+ 'receiveJobSuccess',
+ Object.assign({}, job, {
+ has_trace: false,
+ status: {
+ group: 'running',
+ icon: 'status_running',
+ label: 'running',
+ text: 'running',
+ details_path: 'path',
+ },
+ }),
+ );
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ });
+
+ it('does not render empty state when job has trace but it is not running', () => {
+ store.dispatch('receiveJobSuccess', Object.assign({}, job, { has_trace: true }));
+
+ vm = mountComponentWithStore(Component, {
+ props,
+ store,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
index 63ef4135d83..160b2f4b34a 100644
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ b/spec/javascripts/jobs/store/getters_spec.js
@@ -99,12 +99,14 @@ describe('Job Store Getters', () => {
expect(getters.hasEnvironment(localState)).toEqual(false);
});
});
+
describe('with an empty object for `deployment_status`', () => {
it('returns false', () => {
localState.job.deployment_status = {};
expect(getters.hasEnvironment(localState)).toEqual(false);
});
});
+
describe('when `deployment_status` is defined and not empty', () => {
it('returns true', () => {
localState.job.deployment_status = {
@@ -118,4 +120,94 @@ describe('Job Store Getters', () => {
});
});
});
+
+ describe('hasTrace', () => {
+ describe('when has_trace is true', () => {
+ it('returns true', () => {
+ localState.job.has_trace = true;
+ localState.job.status = {};
+
+ expect(getters.hasTrace(localState)).toEqual(true);
+ });
+ });
+
+ describe('when job is running', () => {
+ it('returns true', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'running' };
+
+ expect(getters.hasTrace(localState)).toEqual(true);
+ });
+ });
+
+ describe('when has_trace is false and job is not running', () => {
+ it('returns false', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'pending' };
+
+ expect(getters.hasTrace(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('emptyStateIllustration', () => {
+ describe('with defined illustration', () => {
+ it('returns the state illustration object', () => {
+ localState.job.status = {
+ illustration: {
+ path: 'foo',
+ },
+ };
+
+ expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' });
+ });
+ });
+
+ describe('when illustration is not defined', () => {
+ it('returns an empty object', () => {
+ expect(getters.emptyStateIllustration(localState)).toEqual({});
+ });
+ });
+ });
+
+ describe('isJobStuck', () => {
+ describe('when job is pending and runners are not available', () => {
+ it('returns true', () => {
+ localState.job.status = {
+ group: 'pending',
+ };
+ localState.job.runners = {
+ available: false,
+ };
+
+ expect(getters.isJobStuck(localState)).toEqual(true);
+ });
+ });
+
+ describe('when job is not pending', () => {
+ it('returns false', () => {
+ localState.job.status = {
+ group: 'running',
+ };
+ localState.job.runners = {
+ available: false,
+ };
+
+ expect(getters.isJobStuck(localState)).toEqual(false);
+ });
+ });
+
+ describe('when runners are available', () => {
+ it('returns false', () => {
+ localState.job.status = {
+ group: 'pending',
+ };
+ localState.job.runners = {
+ available: true,
+ };
+
+ expect(getters.isJobStuck(localState)).toEqual(false);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 1f030e5af28..9a0e7f34a9c 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1177,10 +1177,8 @@ export const discussion1 = {
file_path: 'about.md',
},
position: {
- formatter: {
- new_line: 50,
- old_line: null,
- },
+ new_line: 50,
+ old_line: null,
},
notes: [
{
@@ -1197,10 +1195,8 @@ export const resolvedDiscussion1 = {
file_path: 'about.md',
},
position: {
- formatter: {
- new_line: 50,
- old_line: null,
- },
+ new_line: 50,
+ old_line: null,
},
notes: [
{
@@ -1217,10 +1213,8 @@ export const discussion2 = {
file_path: 'README.md',
},
position: {
- formatter: {
- new_line: null,
- old_line: 20,
- },
+ new_line: null,
+ old_line: 20,
},
notes: [
{
@@ -1237,10 +1231,8 @@ export const discussion3 = {
file_path: 'README.md',
},
position: {
- formatter: {
- new_line: 21,
- old_line: null,
- },
+ new_line: 21,
+ old_line: null,
},
notes: [
{
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 677eb373d22..2d94356f386 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -5,6 +5,34 @@ describe Gitlab::Diff::Position do
let(:project) { create(:project, :repository) }
+ let(:args_for_img) do
+ {
+ old_path: "files/any.img",
+ new_path: "files/any.img",
+ base_sha: nil,
+ head_sha: nil,
+ start_sha: nil,
+ width: 100,
+ height: 100,
+ x: 1,
+ y: 100,
+ position_type: "image"
+ }
+ end
+
+ let(:args_for_text) do
+ {
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ base_sha: nil,
+ head_sha: nil,
+ start_sha: nil,
+ position_type: "text"
+ }
+ end
+
describe "position for an added text file" do
let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") }
@@ -529,53 +557,49 @@ describe Gitlab::Diff::Position do
end
end
+ describe "#as_json" do
+ shared_examples "diff position json" do
+ let(:diff_position) { described_class.new(args) }
+
+ it "returns the position as JSON" do
+ expect(diff_position.as_json).to eq(args.stringify_keys)
+ end
+ end
+
+ context "for text positon" do
+ let(:args) { args_for_text }
+
+ it_behaves_like "diff position json"
+ end
+
+ context "for image positon" do
+ let(:args) { args_for_img }
+
+ it_behaves_like "diff position json"
+ end
+ end
+
describe "#to_json" do
shared_examples "diff position json" do
+ let(:diff_position) { described_class.new(args) }
+
it "returns the position as JSON" do
- expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys)
+ expect(JSON.parse(diff_position.to_json)).to eq(args.stringify_keys)
end
it "works when nested under another hash" do
- expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys)
+ expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => args.stringify_keys)
end
end
context "for text positon" do
- let(:hash) do
- {
- old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 14,
- base_sha: nil,
- head_sha: nil,
- start_sha: nil,
- position_type: "text"
- }
- end
-
- let(:diff_position) { described_class.new(hash) }
+ let(:args) { args_for_text }
it_behaves_like "diff position json"
end
context "for image positon" do
- let(:hash) do
- {
- old_path: "files/any.img",
- new_path: "files/any.img",
- base_sha: nil,
- head_sha: nil,
- start_sha: nil,
- width: 100,
- height: 100,
- x: 1,
- y: 100,
- position_type: "image"
- }
- end
-
- let(:diff_position) { described_class.new(hash) }
+ let(:args) { args_for_img }
it_behaves_like "diff position json"
end
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index cf9e0f71910..a31f77484d8 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -191,9 +191,7 @@ describe Gitlab::ImportExport::RelationFactory do
"author" => {
"name" => "Administrator"
},
- "events" => [
-
- ]
+ "events" => []
}
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index b19e75a956d..5ca140879c3 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1993,4 +1993,34 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ describe '#default_branch?' do
+ let(:default_branch) { 'master'}
+
+ subject { pipeline.default_branch? }
+
+ before do
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ context 'when pipeline ref is the default branch of the project' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, status: :created, project: project, ref: default_branch)
+ end
+
+ it "returns true" do
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'when pipeline ref is not the default branch of the project' do
+ let(:pipeline) do
+ build(:ci_empty_pipeline, status: :created, project: project, ref: 'another_branch')
+ end
+
+ it "returns false" do
+ expect(subject).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index da26d802688..f8d50e89d40 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -331,11 +331,12 @@ describe CacheMarkdownField do
end
context 'with a project' do
- let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: :project_value) }
+ let(:project) { create(:project, group: create(:group)) }
+ let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) }
it 'sets the project in the context' do
is_expected.to have_key(:project)
- expect(context[:project]).to eq(:project_value)
+ expect(context[:project]).to eq(project)
end
it 'invalidates the cache when project changes' do
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index 34db94920f3..cb3d6c7cda2 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-RSpec.describe InstanceConfiguration do
+describe InstanceConfiguration do
context 'without cache' do
describe '#settings' do
describe '#ssh_algorithms_hashes' do
- let(:md5) { '54:e0:f8:70:d6:4f:4c:b1:b3:02:44:77:cf:cd:0d:fc' }
- let(:sha256) { '9327f0d15a48c4d9f6a3aee65a1825baf9a3412001c98169c5fd022ac27762fc' }
+ let(:md5) { '5a:65:6c:4d:d4:4c:6d:e6:59:25:b8:cf:ba:34:e7:64' }
+ let(:sha256) { 'SHA256:2KJDT7xf2i68mBgJ3TVsjISntg4droLbXYLfQj0VvSY' }
it 'does not return anything if file does not exist' do
stub_pub_file(exist: false)
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 99670af786a..3fc6c06b7fa 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -155,4 +155,40 @@ describe Label do
expect(described_class.search('feature')).to be_empty
end
end
+
+ describe '.subscribed_by' do
+ let!(:user) { create(:user) }
+ let!(:label) { create(:label) }
+ let!(:label2) { create(:label) }
+
+ before do
+ label.subscribe(user)
+ end
+
+ it 'returns subscribed labels' do
+ expect(described_class.subscribed_by(user.id)).to eq([label])
+ end
+
+ it 'returns nothing' do
+ expect(described_class.subscribed_by(0)).to be_empty
+ end
+ end
+
+ describe '.optionally_subscribed_by' do
+ let!(:user) { create(:user) }
+ let!(:label) { create(:label) }
+ let!(:label2) { create(:label) }
+
+ before do
+ label.subscribe(user)
+ end
+
+ it 'returns subscribed labels' do
+ expect(described_class.optionally_subscribed_by(user.id)).to eq([label])
+ end
+
+ it 'returns all labels if user_id is nil' do
+ expect(described_class.optionally_subscribed_by(nil)).to match_array([label, label2])
+ end
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 06ccf383362..98399471f9a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -572,6 +572,20 @@ describe API::Commits do
expect(json_response['title']).to eq(message)
end
+ it 'includes the commit stats' do
+ post api(url, user), valid_mo_params
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to include 'stats'
+ end
+
+ it "doesn't include the commit stats when stats is false" do
+ post api(url, user), valid_mo_params.merge(stats: false)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).not_to include 'stats'
+ end
+
it 'return a 400 bad request if there are any issues' do
post api(url, user), invalid_mo_params
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 5abc6d81958..56df8dddbc1 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -258,10 +258,10 @@ describe 'project routing' do
end
it 'to #logs_tree' do
- expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable')
- expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
- expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
- expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable')
+ expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz')
expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
index 35215e06f5f..b9995818e98 100644
--- a/spec/serializers/commit_entity_spec.rb
+++ b/spec/serializers/commit_entity_spec.rb
@@ -1,10 +1,11 @@
require 'spec_helper'
describe CommitEntity do
+ SIGNATURE_HTML = 'TEST'.freeze
+
let(:entity) do
described_class.new(commit, request: request)
end
-
let(:request) { double('request') }
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
@@ -12,7 +13,11 @@ describe CommitEntity do
subject { entity.as_json }
before do
+ render = double('render')
+ allow(render).to receive(:call).and_return(SIGNATURE_HTML)
+
allow(request).to receive(:project).and_return(project)
+ allow(request).to receive(:render).and_return(render)
end
context 'when commit author is a user' do
@@ -61,7 +66,7 @@ describe CommitEntity do
context 'when type is "full"' do
let(:entity) do
- described_class.new(commit, request: request, type: :full)
+ described_class.new(commit, request: request, type: :full, pipeline_ref: project.default_branch, pipeline_project: project)
end
it 'exposes extra properties' do
@@ -70,6 +75,25 @@ describe CommitEntity do
expect(subject.fetch(:description_html)).not_to be_nil
expect(subject.fetch(:title_html)).not_to be_nil
end
+
+ context 'when commit has signature' do
+ let(:commit) { project.commit(TestEnv::BRANCH_SHA['signed-commits']) }
+
+ it 'exposes "signature_html"' do
+ expect(request.render).to receive(:call)
+ expect(subject.fetch(:signature_html)).to be SIGNATURE_HTML
+ end
+ end
+
+ context 'when commit has pipeline' do
+ before do
+ create(:ci_pipeline, project: project, sha: commit.id)
+ end
+
+ it 'exposes "pipeline_status_path"' do
+ expect(subject.fetch(:pipeline_status_path)).not_to be_nil
+ end
+ end
end
context 'when commit_url_params is set' do
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
index 14ba6b7bed2..cea5ea125b9 100644
--- a/spec/services/notification_recipient_service_spec.rb
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -10,27 +10,50 @@ describe NotificationRecipientService do
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) }
- def create_watcher
- watcher = create(:user)
- create(:notification_setting, source: project, user: watcher, level: :watch)
+ shared_examples 'no N+1 queries' do
+ it 'avoids N+1 queries', :request_store do
+ create_user
- other_projects.each do |other_project|
- create(:notification_setting, source: other_project, user: watcher, level: :watch)
+ service.build_new_note_recipients(note)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ service.build_new_note_recipients(note)
+ end
+
+ create_user
+
+ expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
end
end
- it 'avoids N+1 queries', :request_store do
- create_watcher
+ context 'when there are multiple watchers' do
+ def create_user
+ watcher = create(:user)
+ create(:notification_setting, source: project, user: watcher, level: :watch)
+
+ other_projects.each do |other_project|
+ create(:notification_setting, source: other_project, user: watcher, level: :watch)
+ end
+ end
- service.build_new_note_recipients(note)
+ include_examples 'no N+1 queries'
+ end
- control_count = ActiveRecord::QueryRecorder.new do
- service.build_new_note_recipients(note)
+ context 'when there are multiple subscribers' do
+ def create_user
+ subscriber = create(:user)
+ issue.subscriptions.create(user: subscriber, project: project, subscribed: true)
end
- create_watcher
+ include_examples 'no N+1 queries'
- expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count)
+ context 'when the project is private' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ include_examples 'no N+1 queries'
+ end
end
end
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index e98df375d48..373fe7cb7dd 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -148,7 +148,7 @@ describe Projects::AutocompleteService do
let!(:label1) { create(:label, project: project) }
let!(:label2) { create(:label, project: project) }
let!(:sub_group_label) { create(:group_label, group: sub_group) }
- let!(:parent_group_label) { create(:group_label, group: group.parent) }
+ let!(:parent_group_label) { create(:group_label, group: group.parent, group_id: group.id) }
before do
create(:group_member, group: group, user: user)
@@ -156,7 +156,7 @@ describe Projects::AutocompleteService do
it 'returns labels from project and ancestor groups' do
service = described_class.new(project, user)
- results = service.labels_as_hash
+ results = service.labels_as_hash(nil)
expected_labels = [label1, label2, parent_group_label]
expect_labels_to_equal(results, expected_labels)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index f4b7cb8c90a..a18126ee339 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -324,7 +324,7 @@ describe SystemNoteService do
end
it "posts the 'merge when pipeline succeeds' system note" do
- expect(subject.note).to eq "canceled the automatic merge"
+ expect(subject.note).to eq "canceled the automatic merge"
end
end
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 22f712f3fcf..b0bf942aa09 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -30,10 +30,6 @@ shared_context 'invalid cluster create params' do
end
shared_examples 'create cluster service success' do
- before do
- stub_feature_flags(rbac_clusters: false)
- end
-
it 'creates a cluster object and performs a worker' do
expect(ClusterProvisionWorker).to receive(:perform_async)
diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml
index 4ea5b44c59c..049ffcc3407 100644
--- a/vendor/jupyter/values.yaml
+++ b/vendor/jupyter/values.yaml
@@ -13,6 +13,10 @@ auth:
singleuser:
defaultUrl: "/lab"
+ lifecycleHooks:
+ postStart:
+ exec:
+ command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"]
ingress:
enabled: true
diff --git a/yarn.lock b/yarn.lock
index 579b9408986..89f6391e48b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -29,18 +29,7 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0.tgz#1efd58bffa951dc846449e58ce3a1d7f02d393aa"
- integrity sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==
- dependencies:
- "@babel/types" "^7.0.0"
- jsesc "^2.5.1"
- lodash "^4.17.10"
- source-map "^0.5.0"
- trim-right "^1.0.1"
-
-"@babel/generator@^7.1.2":
+"@babel/generator@^7.0.0", "@babel/generator@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==
@@ -224,12 +213,7 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.0.tgz#a7cd42cb3c12aec52e24375189a47b39759b783e"
- integrity sha512-SmjnXCuPAlai75AFtzv+KCBcJ3sDDWbIn+WytKw1k+wAtEy6phqI2RqKh/zAnw53i1NR8su3Ep/UoqaKcimuLg==
-
-"@babel/parser@^7.1.2":
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
@@ -599,7 +583,7 @@
js-levenshtein "^1.1.3"
semver "^5.3.0"
-"@babel/template@^7.0.0", "@babel/template@^7.1.2":
+"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==
@@ -608,15 +592,6 @@
"@babel/parser" "^7.1.2"
"@babel/types" "^7.1.2"
-"@babel/template@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.0.tgz#58cc9572e1bfe24fe1537fdf99d839d53e517e22"
- integrity sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
-
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
@@ -632,16 +607,7 @@
globals "^11.1.0"
lodash "^4.17.10"
-"@babel/types@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0.tgz#6e191793d3c854d19c6749989e3bc55f0e962118"
- integrity sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==
- dependencies:
- esutils "^2.0.2"
- lodash "^4.17.10"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.1.2":
+"@babel/types@^7.0.0", "@babel/types@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==
@@ -1198,10 +1164,9 @@ babel-loader@^8.0.4:
babel-messages@^6.23.0:
version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
+ resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
dependencies:
- babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-istanbul@^5.1.0:
@@ -1218,11 +1183,6 @@ babel-plugin-rewire@^1.2.0:
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==
-babel-plugin-syntax-object-rest-spread@^6.8.0:
- version "6.13.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
- integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-
babel-polyfill@6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
@@ -2684,12 +2644,12 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
-depd@1.1.1, depd@~1.1.1:
+depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
-depd@~1.1.2:
+depd@~1.1.1, depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
@@ -5219,14 +5179,7 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
-make-dir@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
- integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==
- dependencies:
- pify "^3.0.0"
-
-make-dir@^1.3.0:
+make-dir@^1.0.0, make-dir@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
@@ -6619,12 +6572,7 @@ regenerate-unicode-properties@^7.0.0:
dependencies:
regenerate "^1.4.0"
-regenerate@^1.2.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
- integrity sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=
-
-regenerate@^1.4.0:
+regenerate@^1.2.1, regenerate@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
@@ -6789,20 +6737,13 @@ resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@^1.3.2:
+resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
dependencies:
path-parse "^1.0.5"
-resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
- integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==
- dependencies:
- path-parse "^1.0.5"
-
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"