summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md55
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock19
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue6
-rw-r--r--app/assets/javascripts/pdf/index.vue24
-rw-r--r--app/assets/javascripts/pdf/page/index.vue8
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue23
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue4
-rw-r--r--app/assets/stylesheets/performance_bar.scss11
-rw-r--r--app/controllers/concerns/uploads_actions.rb2
-rw-r--r--app/controllers/concerns/with_performance_bar.rb4
-rw-r--r--app/controllers/projects/badges_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/triggers_controller.rb12
-rw-r--r--app/finders/group_members_finder.rb12
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/helpers/system_note_helper.rb1
-rw-r--r--app/helpers/wiki_helper.rb2
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/application_setting_implementation.rb4
-rw-r--r--app/models/clusters/cluster.rb16
-rw-r--r--app/models/concerns/token_authenticatable.rb4
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/serializers/issue_entity.rb7
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/merge_requests/build_service.rb28
-rw-r--r--app/services/metrics/dashboard/base_service.rb70
-rw-r--r--app/services/metrics/dashboard/default_embed_service.rb63
-rw-r--r--app/services/metrics/dashboard/project_dashboard_service.rb48
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb47
-rw-r--r--app/services/notes/create_service.rb4
-rw-r--r--app/services/projects/destroy_service.rb13
-rw-r--r--app/services/quick_actions/interpret_service.rb31
-rw-r--r--app/services/system_note_service.rb8
-rw-r--r--app/services/zoom_notes_service.rb42
-rw-r--r--app/uploaders/records_uploads.rb2
-rw-r--r--app/validators/qualified_domain_array_validator.rb4
-rw-r--r--app/views/admin/application_settings/_help_page.html.haml2
-rw-r--r--app/views/admin/application_settings/_outbound.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml3
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml4
-rw-r--r--app/views/peek/_bar.html.haml3
-rw-r--r--app/views/profiles/show.html.haml4
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/mirrors/_disabled_mirror_badge.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml4
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml4
-rw-r--r--app/views/projects/triggers/_trigger.html.haml3
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/search/_category.html.haml2
-rw-r--r--app/views/search/results/_blob_data.html.haml6
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml2
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml2
-rw-r--r--app/views/shared/form_elements/_description.html.haml2
-rwxr-xr-xbin/secpick2
-rw-r--r--changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml5
-rw-r--r--changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml5
-rw-r--r--changelogs/unreleased/64091-fix-sprockets-paths.yml5
-rw-r--r--changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml5
-rw-r--r--changelogs/unreleased/64731-fix-project-auto-devops-api.yml5
-rw-r--r--changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml6
-rw-r--r--changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml5
-rw-r--r--changelogs/unreleased/65019-job-templates-dind-tls-fix.yml5
-rw-r--r--changelogs/unreleased/ab-add-index-on-environments.yml5
-rw-r--r--changelogs/unreleased/dm-submodule-helper-routing.yml5
-rw-r--r--changelogs/unreleased/dm-submodule-links-nil.yml5
-rw-r--r--changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml5
-rw-r--r--changelogs/unreleased/optimise-import-performance.yml5
-rw-r--r--changelogs/unreleased/remove-line-profile-from-performance-bar.yml5
-rw-r--r--changelogs/unreleased/security-60551-fix-upload-scope.yml5
-rw-r--r--changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml5
-rw-r--r--changelogs/unreleased/sh-add-index-extern-uid.yml5
-rw-r--r--changelogs/unreleased/sh-ignore-git-errors-delete-project.yml5
-rw-r--r--changelogs/unreleased/sh-remove-pdfjs-deprecations.yml5
-rw-r--r--changelogs/unreleased/sh-support-docker-oci-images.yml5
-rw-r--r--changelogs/unreleased/sh-update-rouge-3-7-0.yml5
-rw-r--r--config/initializers/0_inject_enterprise_edition_module.rb17
-rw-r--r--config/initializers/octokit.rb1
-rw-r--r--config/initializers/peek.rb1
-rw-r--r--config/routes/project.rb6
-rw-r--r--config/webpack.config.js8
-rw-r--r--db/migrate/20190715042813_add_issue_id_to_versions.rb13
-rw-r--r--db/migrate/20190715043954_set_issue_id_for_all_versions.rb19
-rw-r--r--db/migrate/20190729090456_add_index_on_environments_with_state.rb17
-rw-r--r--db/post_migrate/20190715043944_remove_sha_index_from_versions.rb17
-rw-r--r--db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb17
-rw-r--r--db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb19
-rw-r--r--db/schema.rb9
-rw-r--r--doc/administration/custom_hooks.md2
-rw-r--r--doc/administration/geo/replication/index.md10
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin127198 -> 113617 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_line_profiling.pngbin93063 -> 0 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md2
-rw-r--r--doc/administration/raketasks/ldap.md25
-rw-r--r--doc/api/oauth2.md18
-rw-r--r--doc/api/pipeline_triggers.md29
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/ci/docker/using_docker_build.md2
-rw-r--r--doc/ci/docker/using_kaniko.md3
-rw-r--r--doc/ci/examples/README.md2
-rw-r--r--doc/ci/examples/browser_performance.md4
-rw-r--r--doc/ci/examples/code_quality.md8
-rw-r--r--doc/ci/multi_project_pipelines.md5
-rw-r--r--doc/ci/triggers/README.md14
-rw-r--r--doc/ci/yaml/README.md35
-rw-r--r--doc/development/documentation/index.md54
-rw-r--r--doc/development/documentation/styleguide.md87
-rw-r--r--doc/development/ee_features.md46
-rw-r--r--doc/development/fe_guide/components.md45
-rw-r--r--doc/development/fe_guide/performance.md40
-rw-r--r--doc/development/fe_guide/style_guide_js.md859
-rw-r--r--doc/development/fe_guide/vuex.md44
-rw-r--r--doc/development/i18n/externalization.md221
-rw-r--r--doc/development/utilities.md128
-rw-r--r--doc/gitlab-basics/README.md27
-rw-r--r--doc/gitlab-basics/add-file.md92
-rw-r--r--doc/gitlab-basics/add-image.md59
-rw-r--r--doc/gitlab-basics/add-merge-request.md39
-rw-r--r--doc/gitlab-basics/basic-git-commands.md6
-rw-r--r--doc/gitlab-basics/command-line-commands.md148
-rw-r--r--doc/gitlab-basics/create-branch.md20
-rw-r--r--doc/gitlab-basics/create-project.md87
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md27
-rw-r--r--doc/gitlab-basics/fork-project.md19
-rw-r--r--doc/gitlab-basics/start-using-git.md182
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/jenkins.md3
-rw-r--r--doc/public_access/public_access.md2
-rw-r--r--doc/user/application_security/dependency_scanning/index.md11
-rw-r--r--doc/user/clusters/applications.md2
-rw-r--r--doc/user/group/saml_sso/scim_setup.md28
-rw-r--r--doc/user/profile/account/delete_account.md26
-rw-r--r--doc/user/profile/account/two_factor_authentication.md18
-rw-r--r--doc/user/profile/active_sessions.md23
-rw-r--r--doc/user/profile/index.md120
-rw-r--r--doc/user/profile/personal_access_tokens.md16
-rw-r--r--doc/user/profile/preferences.md30
-rw-r--r--doc/user/project/clusters/index.md12
-rw-r--r--doc/user/project/issues/csv_export.md2
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md3
-rw-r--r--doc/user/project/quick_actions.md3
-rw-r--r--doc/user/project/repository/branches/img/compare_branches.png (renamed from doc/user/project/repository/img/compare_branches.png)bin131046 -> 131046 bytes
-rw-r--r--doc/user/project/repository/branches/index.md61
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md75
-rw-r--r--doc/user/project/repository/index.md52
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md16
-rw-r--r--doc/user/project/repository/web_editor.md16
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/triggers.rb21
-rw-r--r--lib/banzai/filter/autolink_filter.rb11
-rw-r--r--lib/banzai/filter/base_sanitization_filter.rb32
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb15
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb8
-rw-r--r--lib/container_registry/client.rb5
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/github_import/client.rb4
-rw-r--r--lib/gitlab/legacy_github_import/client.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/base_service.rb72
-rw-r--r--lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb65
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb10
-rw-r--r--lib/gitlab/metrics/dashboard/project_dashboard_service.rb50
-rw-r--r--lib/gitlab/metrics/dashboard/system_dashboard_service.rb49
-rw-r--r--lib/gitlab/octokit/middleware.rb23
-rw-r--r--lib/gitlab/project_template.rb10
-rw-r--r--lib/gitlab/quick_actions/command_definition.rb21
-rw-r--r--lib/gitlab/quick_actions/commit_actions.rb7
-rw-r--r--lib/gitlab/quick_actions/dsl.rb37
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb116
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb43
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb103
-rw-r--r--lib/gitlab/quick_actions/merge_request_actions.rb28
-rw-r--r--lib/gitlab/url_blocker.rb9
-rw-r--r--lib/gitlab/utils/sanitize_node_link.rb61
-rw-r--r--lib/gitlab/zoom_link_extractor.rb4
-rw-r--r--lib/peek/rblineprof/custom_controller_helpers.rb124
-rw-r--r--lib/peek/views/redis_detailed.rb2
-rw-r--r--lib/tasks/gitlab/update_templates.rake62
-rw-r--r--locale/gitlab.pot206
-rw-r--r--package.json3
-rw-r--r--qa/qa.rb4
-rw-r--r--qa/qa/page/admin/menu.rb12
-rw-r--r--qa/qa/page/main/menu.rb8
-rw-r--r--qa/qa/page/search/results.rb33
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/service/kubernetes_cluster.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb10
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb48
-rwxr-xr-xscripts/review_apps/review-apps.sh8
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb5
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb124
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb98
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb5
-rw-r--r--spec/db/schema_spec.rb2
-rw-r--r--spec/fast_spec_helper.rb1
-rw-r--r--spec/features/admin/admin_settings_spec.rb34
-rw-r--r--spec/features/contextual_sidebar_spec.rb10
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb42
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb2
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb8
-rw-r--r--spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb (renamed from spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb)19
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb2
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb8
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_pages_spec.rb4
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb6
-rw-r--r--spec/features/triggers_spec.rb23
-rw-r--r--spec/features/usage_stats_consent_spec.rb10
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js4
-rw-r--r--spec/helpers/wiki_helper_spec.rb2
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js3
-rw-r--r--spec/javascripts/pdf/page_spec.js5
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb42
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb79
-rw-r--r--spec/lib/container_registry/blob_spec.rb19
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb10
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb68
-rw-r--r--spec/lib/gitlab/project_template_spec.rb6
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb46
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb14
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb44
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb72
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb10
-rw-r--r--spec/migrations/set_issue_id_for_all_versions_spec.rb38
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb45
-rw-r--r--spec/models/project_spec.rb18
-rw-r--r--spec/requests/api/triggers_spec.rb28
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb144
-rw-r--r--spec/serializers/issue_entity_spec.rb33
-rw-r--r--spec/services/issues/update_service_spec.rb9
-rw-r--r--spec/services/merge_requests/build_service_spec.rb37
-rw-r--r--spec/services/merge_requests/update_service_spec.rb3
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb)8
-rw-r--r--spec/services/metrics/dashboard/project_dashboard_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb)2
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb (renamed from spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb)2
-rw-r--r--spec/services/projects/destroy_service_spec.rb17
-rw-r--r--spec/services/projects/download_service_spec.rb9
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb388
-rw-r--r--spec/services/system_note_service_spec.rb24
-rw-r--r--spec/services/zoom_notes_service_spec.rb81
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/helpers/graphql_helpers.rb8
-rw-r--r--spec/support/helpers/stub_requests.rb13
-rw-r--r--spec/support/shared_examples/application_setting_examples.rb11
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb12
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb2
-rw-r--r--spec/tasks/gitlab/update_templates_rake_spec.rb25
-rw-r--r--spec/uploaders/records_uploads_spec.rb21
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb30
-rw-r--r--vendor/licenses.csv1
-rw-r--r--yarn.lock166
278 files changed, 4716 insertions, 2539 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ed244b7ed2..15b55f01c32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,46 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.1.3
+
+### Fixed (11 changes)
+
+- Prevent multiple confirmation modals from opening when deleting a repository. !30532
+- Fix the project auto devops API. !30946
+- Fix "Certificate misses intermediates" UI error when enabling Let's Encrypt integration for pages domain. !30995
+- Fix xterm css not loading for environment terminal. !31023
+- Set DOCKER_TLS_CERTDIR in Auto Dev-Ops CI template to fix jobs using Docker-in-Docker. !31078
+- Set DOCKER_TLS_CERTDIR in CI job templates to fix Docker-in-Docker service. !31080
+- Support Docker OCI images. !31127
+- Fix error rendering submodules in MR diffs when there is no .gitmodules. !31162
+- Fix pdf.js rendering pages in the wrong order. !31222
+- Fix exception handling in Gitaly autodetection. !31285
+- Fix bug that caused diffs not to show on MRs with changes to submodules.
+
+### Performance (1 change)
+
+- Optimise import performance. !31045
+
+
+## 12.1.2
+
+### Security (1 change)
+
+- Use source project as permissions reference for MergeRequestsController#pipelines.
+
+### Security (9 changes)
+
+- Restrict slash commands to users who can log in.
+- Patch XSS issue in wiki links.
+- Queries for Upload should be scoped by model.
+- Filter merge request params on the new merge request page.
+- Fix Server Side Request Forgery mitigation bypass.
+- Show badges if pipelines are public otherwise default to project permissions.
+- Do not allow localhost url redirection in GitHub Integration.
+- Do not show moved issue id for users that cannot read issue.
+- Drop feature to take ownership of trigger token.
+
+
## 12.1.1
- No changes.
@@ -625,6 +665,21 @@ entry.
- Moves snowplow to CE repo.
+## 11.11.7
+
+### Security (9 changes)
+
+- Restrict slash commands to users who can log in.
+- Patch XSS issue in wiki links.
+- Filter merge request params on the new merge request page.
+- Fix Server Side Request Forgery mitigation bypass.
+- Show badges if pipelines are public otherwise default to project permissions.
+- Do not allow localhost url redirection in GitHub Integration.
+- Do not show moved issue id for users that cannot read issue.
+- Use source project as permissions reference for MergeRequestsController#pipelines.
+- Drop feature to take ownership of trigger token.
+
+
## 11.11.4 (2019-06-26)
### Fixed (3 changes)
diff --git a/Gemfile b/Gemfile
index e860ee61cb9..61a6432a953 100644
--- a/Gemfile
+++ b/Gemfile
@@ -134,7 +134,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.9'
-gem 'rouge', '~> 3.5'
+gem 'rouge', '~> 3.7'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.3'
@@ -297,7 +297,6 @@ gem 'batch-loader', '~> 1.4.0'
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
-gem 'peek-rblineprof', '~> 0.2.0'
# Memory benchmarks
gem 'derailed_benchmarks', require: false
@@ -388,7 +387,6 @@ group :test do
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 3.5.1'
gem 'rails-controller-testing'
- gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.2.5'
gem 'rspec_junit_formatter'
diff --git a/Gemfile.lock b/Gemfile.lock
index 24dee04833c..f89654c82fd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -321,7 +321,7 @@ GEM
gitlab-markup (1.7.0)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
- gitlab-styles (2.7.0)
+ gitlab-styles (2.8.0)
rubocop (~> 0.69.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.1.0)
@@ -441,7 +441,7 @@ GEM
jaeger-client (0.10.0)
opentracing (~> 0.3)
thrift
- jaro_winkler (1.5.2)
+ jaro_winkler (1.5.3)
jira-ruby (1.4.1)
activesupport
multipart-post
@@ -643,9 +643,6 @@ GEM
railties (>= 4.0.0)
peek-gc (0.0.2)
peek
- peek-rblineprof (0.2.0)
- peek
- rblineprof
pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
@@ -780,7 +777,7 @@ GEM
retriable (3.1.2)
rinku (2.0.0)
rotp (2.1.2)
- rouge (3.5.1)
+ rouge (3.7.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -840,7 +837,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.17.0)
- ruby-progressbar (1.10.0)
+ ruby-progressbar (1.10.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby_parser (3.13.1)
@@ -848,7 +845,7 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rugged (0.28.1)
+ rugged (0.28.2)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -884,8 +881,6 @@ GEM
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.12.0)
- sham_rack (1.3.6)
- rack
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
sidekiq (5.2.7)
@@ -1179,7 +1174,6 @@ DEPENDENCIES
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
- peek-rblineprof (~> 0.2.0)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.8)
@@ -1208,7 +1202,7 @@ DEPENDENCIES
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
- rouge (~> 3.5)
+ rouge (~> 3.7)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.8.0)
@@ -1232,7 +1226,6 @@ DEPENDENCIES
selenium-webdriver (~> 3.141)
sentry-raven (~> 2.9)
settingslogic (~> 2.0.9)
- sham_rack (~> 1.3.6)
shoulda-matchers (~> 4.0.1)
sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0)
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 745488255ab..45543ef2cc8 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -369,7 +369,7 @@ export default {
</div>
<div v-if="!showEmptyState">
<graph-group
- v-for="groupData in groups"
+ v-for="(groupData, index) in groups"
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
@@ -381,6 +381,7 @@ export default {
:key="`panel-type-${graphIndex}`"
:graph-data="graphData"
:dashboard-width="elWidth"
+ :index="`${index}-${graphIndex}`"
/>
</template>
<template v-else>
@@ -399,6 +400,7 @@ export default {
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
+ :modal-id="`alert-modal-${index}-${graphIndex}`"
@setAlerts="setAlerts"
/>
</monitor-area-chart>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index d7cd2c57871..f1f02964a29 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -20,6 +20,11 @@ export default {
type: Number,
required: true,
},
+ index: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
@@ -64,6 +69,7 @@ export default {
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
+ :modal-id="`alert-modal-${index}`"
@setAlerts="setAlerts"
/>
</monitor-area-chart>
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index 6d39abd4a1f..bbbd9789dc9 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -14,7 +14,6 @@ export default {
},
data() {
return {
- loading: false,
pages: [],
};
},
@@ -35,19 +34,24 @@ export default {
load() {
this.pages = [];
return pdfjsLib
- .getDocument(this.document)
- .then(this.renderPages)
- .then(() => this.$emit('pdflabload'))
- .catch(error => this.$emit('pdflaberror', error))
- .then(() => {
- this.loading = false;
+ .getDocument({
+ url: this.document,
+ cMapUrl: '/assets/webpack/cmaps/',
+ cMapPacked: true,
+ })
+ .promise.then(this.renderPages)
+ .then(pages => {
+ this.pages = pages;
+ this.$emit('pdflabload');
+ })
+ .catch(error => {
+ this.$emit('pdflaberror', error);
});
},
renderPages(pdf) {
const pagePromises = [];
- this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
- pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p)));
+ pagePromises.push(pdf.getPage(num));
}
return Promise.all(pagePromises);
},
@@ -59,8 +63,8 @@ export default {
<div v-if="hasPDF" class="pdf-viewer">
<page
v-for="(page, index) in pages"
+ v-if="page"
:key="index"
- :v-if="!loading"
:page="page"
:number="index + 1"
/>
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
index f16aaca6cd7..65f84e75e86 100644
--- a/app/assets/javascripts/pdf/page/index.vue
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -18,7 +18,7 @@ export default {
},
computed: {
viewport() {
- return this.page.getViewport(this.scale);
+ return this.page.getViewport({ scale: this.scale });
},
context() {
return this.$refs.canvas.getContext('2d');
@@ -36,10 +36,12 @@ export default {
this.rendering = true;
this.page
.render(this.renderContext)
- .then(() => {
+ .promise.then(() => {
this.rendering = false;
})
- .catch(error => this.$emit('pdflaberror', error));
+ .catch(error => {
+ this.$emit('pdflaberror', error);
+ });
},
};
</script>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 769ddb21277..c0ea42ad1a2 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,5 +1,4 @@
<script>
-import $ from 'jquery';
import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
@@ -28,10 +27,6 @@ export default {
type: String,
required: true,
},
- profileUrl: {
- type: String,
- required: true,
- },
},
detailedMetrics: [
{
@@ -74,9 +69,6 @@ export default {
initialRequest() {
return this.currentRequestId === this.requestId;
},
- lineProfileModal() {
- return $('#modal-peek-line-profile');
- },
hasHost() {
return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host;
},
@@ -90,10 +82,6 @@ export default {
},
mounted() {
this.currentRequest = this.requestId;
-
- if (this.lineProfileModal.length) {
- this.lineProfileModal.modal('toggle');
- }
},
methods: {
changeCurrentRequest(newRequestId) {
@@ -124,17 +112,6 @@ export default {
:header="metric.header"
:keys="metric.keys"
/>
- <div v-if="initialRequest" id="peek-view-rblineprof" class="view">
- <button
- v-if="lineProfileModal.length"
- class="btn-link btn-blank"
- data-toggle="modal"
- data-target="#modal-peek-line-profile"
- >
- {{ s__('PerformanceBar|profile') }}
- </button>
- <a v-else :href="profileUrl">{{ s__('PerformanceBar|profile') }}</a>
- </div>
<div id="peek-view-gc" class="view">
<span v-if="currentRequest.details" class="bold">
<span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index accb9d9fef1..98f682c2e8a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -36,7 +36,7 @@ export default {
:disabled="isDisabled"
type="checkbox"
name="squash"
- class="qa-squash-checkbox"
+ class="qa-squash-checkbox js-squash-checkbox"
@change="$emit('input', $event.target.checked)"
/>
{{ __('Squash commits') }}
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 56a16c9e4d6..fcf2f950501 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -123,7 +123,7 @@ export default {
:cursor-offset="4"
:tag-content="lineContent"
icon="doc-code"
- class="qa-suggestion-btn"
+ class="qa-suggestion-btn js-suggestion-btn"
@click="handleSuggestDismissed"
/>
<gl-popover
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index 2eb4ec12a4a..a7cd292e01d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -39,7 +39,7 @@ export default {
<template>
<div class="md-suggestion">
<suggestion-diff-header
- class="qa-suggestion-diff-header"
+ class="qa-suggestion-diff-header js-suggestion-diff-header"
:can-apply="suggestion.appliable && suggestion.current_user.can_apply && !disabled"
:is-applied="suggestion.applied"
:help-page-path="helpPagePath"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 32783b85df4..12de3671477 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -41,7 +41,7 @@ export default {
<template>
<div class="md-suggestion-header border-bottom-0 mt-2">
- <div class="qa-suggestion-diff-header font-weight-bold">
+ <div class="qa-suggestion-diff-header js-suggestion-diff-header font-weight-bold">
{{ __('Suggested change') }}
<a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')" class="js-help-btn">
<icon name="question-o" css-classes="link-highlight" />
@@ -55,7 +55,7 @@ export default {
<gl-button
v-else-if="canApply"
v-gl-tooltip.viewport="__('This also resolves the discussion')"
- class="btn-inverted qa-apply-btn"
+ class="btn-inverted qa-apply-btn js-apply-btn"
:disabled="isApplying"
variant="success"
@click="applySuggestion"
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 5a8940ffd6d..ad7d87f0bf6 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,6 +1,5 @@
@import 'framework/variables';
@import 'framework/variables_overrides';
-@import 'peek/views/rblineprof';
#js-peek {
position: fixed;
@@ -128,13 +127,3 @@
#modal-peek-pg-queries-content {
color: $black;
}
-
-.peek-rblineprof-file {
- pre.duration {
- width: 280px;
- }
-
- .data {
- overflow: visible;
- }
-}
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 59f6d3452a3..f5d35379e10 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -90,7 +90,7 @@ module UploadsActions
return unless uploader = build_uploader
upload_paths = uploader.upload_paths(params[:filename])
- upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths)
+ upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths)
upload&.build_uploader
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index 77c3d476ac6..4e0ae3c59eb 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -3,10 +3,6 @@
module WithPerformanceBar
extend ActiveSupport::Concern
- included do
- include Peek::Rblineprof::CustomControllerHelpers
- end
-
def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 09a384e89ab..66b51b17790 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -3,7 +3,8 @@
class Projects::BadgesController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_admin_project!, only: [:index]
- before_action :no_cache_headers, except: [:index]
+ before_action :no_cache_headers, only: [:pipeline, :coverage]
+ before_action :authorize_read_build!, only: [:pipeline, :coverage]
def pipeline
pipeline_status = Gitlab::Badge::Pipeline::Status
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index dcc272aecff..006731c0e66 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -45,7 +45,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
def set_pipeline_variables
@pipelines =
- if can?(current_user, :read_pipeline, @project)
+ if can?(current_user, :read_pipeline, @merge_request.source_project)
@merge_request.all_pipelines
else
Ci::Pipeline.none
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2aa2508be16..f4d381244d9 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -82,7 +82,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def pipelines
- @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30)
+ set_pipeline_variables
+ @pipelines = @pipelines.page(params[:page]).per(30)
Gitlab::PollingInterval.set_header(response, interval: 10_000)
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 284e119ca06..7159d0243a3 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -4,7 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_build!
before_action :authorize_manage_trigger!, except: [:index, :create]
before_action :authorize_admin_trigger!, only: [:edit, :update]
- before_action :trigger, only: [:take_ownership, :edit, :update, :destroy]
+ before_action :trigger, only: [:edit, :update, :destroy]
layout 'project_settings'
@@ -24,16 +24,6 @@ class Projects::TriggersController < Projects::ApplicationController
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
- def take_ownership
- if trigger.update(owner: current_user)
- flash[:notice] = _('Trigger was re-assigned.')
- else
- flash[:alert] = _('You could not take ownership of trigger.')
- end
-
- redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
- end
-
def edit
end
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index eebc67cfa9e..33ec6a715f9 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GroupMembersFinder
+class GroupMembersFinder < UnionFinder
def initialize(group)
@group = group
end
@@ -8,18 +8,18 @@ class GroupMembersFinder
# rubocop: disable CodeReuse/ActiveRecord
def execute(include_descendants: false)
group_members = @group.members
- wheres = []
+ relations = []
return group_members unless @group.parent || include_descendants
- wheres << "members.id IN (#{group_members.select(:id).to_sql})"
+ relations << group_members
if @group.parent
parents_members = GroupMember.non_request
.where(source_id: @group.ancestors.select(:id))
.where.not(user_id: @group.users.select(:id))
- wheres << "members.id IN (#{parents_members.select(:id).to_sql})"
+ relations << parents_members
end
if include_descendants
@@ -27,10 +27,10 @@ class GroupMembersFinder
.where(source_id: @group.descendants.select(:id))
.where.not(user_id: @group.users.select(:id))
- wheres << "members.id IN (#{descendant_members.select(:id).to_sql})"
+ relations << descendant_members
end
- GroupMember.where(wheres.join(' OR '))
+ find_union(relations, GroupMember)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index d5e5b472115..15f35645c78 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -210,7 +210,7 @@ module SortingHelper
end
def sort_direction_button(reverse_url, reverse_sort, sort_value)
- link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
icon = sort_direction_icon(sort_value)
url = reverse_url
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index ac4e8f54260..3efae0a653c 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -21,6 +21,7 @@ module SystemNoteHelper
'discussion' => 'comment',
'moved' => 'arrow-right',
'outdated' => 'pencil-square',
+ 'pinned_embed' => 'thumbtack',
'duplicate' => 'issue-duplicate',
'locked' => 'lock',
'unlocked' => 'lock-open',
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index edd48f82729..dd8fde2a697 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -50,7 +50,7 @@ module WikiHelper
def wiki_sort_controls(project, sort, direction)
sort ||= ProjectWiki::TITLE_ORDER
- link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
icon_class = direction == 'desc' ? 'highest' : 'lowest'
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index a769a8f07fd..9dbcef8abaa 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -42,9 +42,9 @@ class ApplicationSetting < ApplicationRecord
validates :uuid, presence: true
validates :outbound_local_requests_whitelist,
- length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') }
-
- validates :outbound_local_requests_whitelist, qualified_domain_array: true, allow_blank: true
+ length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') },
+ allow_nil: false,
+ qualified_domain_array: true
validates :session_expire_delay,
presence: true,
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 1e612bd0e78..6efd07a6008 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -163,6 +163,8 @@ module ApplicationSettingImplementation
def outbound_local_requests_whitelist_arrays
strong_memoize(:outbound_local_requests_whitelist_arrays) do
+ next [[], []] unless self.outbound_local_requests_whitelist
+
ip_whitelist = []
domain_whitelist = []
@@ -284,6 +286,8 @@ module ApplicationSettingImplementation
end
def domain_strings_to_array(values)
+ return [] unless values
+
values
.split(DOMAIN_LIST_SEPARATOR)
.reject(&:empty?)
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 8c044c86c47..8bb44b0ce40 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -100,12 +100,6 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
scope :preload_knative, -> {
@@ -161,16 +155,6 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def all_projects
- if project_type?
- projects
- elsif group_type?
- first_group.all_projects
- else
- Project.none
- end
- end
-
def first_project
strong_memoize(:first_project) do
projects.first
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 1293df571a3..4099039dd96 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -3,10 +3,8 @@
module TokenAuthenticatable
extend ActiveSupport::Concern
- private
-
class_methods do
- private # rubocop:disable Lint/UselessAccessModifier
+ private
def add_authentication_token_field(token_field, options = {})
if token_authenticatable_fields.include?(token_field)
diff --git a/app/models/label.rb b/app/models/label.rb
index dd403562bfa..25de26b8384 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -137,6 +137,10 @@ class Label < ApplicationRecord
where(id: ids)
end
+ def self.on_project_board?(project_id, label_id)
+ on_project_boards(project_id).where(id: label_id).exists?
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 4cba69069bb..f6b19317c50 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class GroupMember < Member
+ include FromUnion
+
SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
diff --git a/app/models/project.rb b/app/models/project.rb
index cca7da8c49a..8f234fba04f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -415,12 +415,6 @@ class Project < ApplicationRecord
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 55da37c9545..9a2640db9ca 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -16,7 +16,7 @@ class SystemNoteMetadata < ApplicationRecord
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked
- outdated tag due_date
+ outdated tag due_date pinned_embed
].freeze
validates :note, presence: true
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 36e601f45c5..82139855760 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -16,9 +16,14 @@ class IssueEntity < IssuableEntity
expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic
expose :due_date
- expose :moved_to_id
expose :project_id
+ expose :moved_to_id do |issue|
+ if issue.moved_to_id.present? && can?(request.current_user, :read_issue, issue.moved_to)
+ issue.moved_to_id
+ end
+ end
+
expose :web_url do |issue|
project_issue_path(issue.project, issue)
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index db673cace81..77c2224ee3b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -358,6 +358,7 @@ class IssuableBaseService < BaseService
assignees: issuable.assignees.to_a
}
associations[:total_time_spent] = issuable.total_time_spent if issuable.respond_to?(:total_time_spent)
+ associations[:description] = issuable.description
associations
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 7cd825aa967..c8f4412c9f2 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -61,6 +61,8 @@ module Issues
if added_mentions.present?
notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user)
end
+
+ ZoomNotesService.new(issue, project, current_user, old_description: old_associations[:description]).execute
end
def handle_task_changes(issuable)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 109c964e577..b28f80939ae 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -11,15 +11,18 @@ module MergeRequests
# https://gitlab.com/gitlab-org/gitlab-ce/issues/53658
merge_quick_actions_into_params!(merge_request, only: [:target_branch])
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch)
- merge_request.assign_attributes(params)
+ # Assign the projects first so we can use policies for `filter_params`
merge_request.author = current_user
+ merge_request.source_project = find_source_project
+ merge_request.target_project = find_target_project
+
+ filter_params(merge_request)
+ merge_request.assign_attributes(params.to_h.compact)
+
merge_request.compare_commits = []
- merge_request.source_project = find_source_project
- merge_request.target_project = find_target_project
- merge_request.target_branch = find_target_branch
- merge_request.can_be_created = projects_and_branches_valid?
- ensure_milestone_available(merge_request)
+ merge_request.target_branch = find_target_branch
+ merge_request.can_be_created = projects_and_branches_valid?
# compare branches only if branches are valid, otherwise
# compare_branches may raise an error
@@ -50,12 +53,14 @@ module MergeRequests
to: :merge_request
def find_source_project
+ source_project = project_from_params(:source_project)
return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project)
project
end
def find_target_project
+ target_project = project_from_params(:target_project)
return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)
target_project = project.default_merge_request_target
@@ -65,6 +70,17 @@ module MergeRequests
project
end
+ def project_from_params(param_name)
+ project_from_params = params.delete(param_name)
+
+ id_param_name = :"#{param_name}_id"
+ if project_from_params.nil? && params[id_param_name]
+ project_from_params = Project.find_by_id(params.delete(id_param_name))
+ end
+
+ project_from_params
+ end
+
def find_target_branch
target_branch || target_project.default_branch
end
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
new file mode 100644
index 00000000000..b331bf51874
--- /dev/null
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+module Metrics
+ module Dashboard
+ class BaseService < ::BaseService
+ PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
+ NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
+
+ def get_dashboard
+ return error('Insufficient permissions.', :unauthorized) unless allowed?
+
+ success(dashboard: process_dashboard)
+ rescue NOT_FOUND_ERROR
+ error("#{dashboard_path} could not be found.", :not_found)
+ rescue PROCESSING_ERROR => e
+ error(e.message, :unprocessable_entity)
+ end
+
+ # Summary of all known dashboards for the service.
+ # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
+ def self.all_dashboard_paths(_project)
+ raise NotImplementedError
+ end
+
+ # Returns an un-processed dashboard from the cache.
+ def raw_dashboard
+ Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard }
+ end
+
+ private
+
+ # Determines whether users should be able to view
+ # dashboards at all.
+ def allowed?
+ Ability.allowed?(current_user, :read_environment, project)
+ end
+
+ # Returns a new dashboard Hash, supplemented with DB info
+ def process_dashboard
+ Gitlab::Metrics::Dashboard::Processor
+ .new(project, params[:environment], raw_dashboard)
+ .process(insert_project_metrics: insert_project_metrics?)
+ end
+
+ # @return [String] Relative filepath of the dashboard yml
+ def dashboard_path
+ params[:dashboard_path]
+ end
+
+ # @return [Hash] an unmodified dashboard
+ def get_raw_dashboard
+ raise NotImplementedError
+ end
+
+ # @return [String]
+ def cache_key
+ raise NotImplementedError
+ end
+
+ # Determines whether custom metrics should be included
+ # in the processed output.
+ # @return [Boolean]
+ def insert_project_metrics?
+ false
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/default_embed_service.rb b/app/services/metrics/dashboard/default_embed_service.rb
new file mode 100644
index 00000000000..8b01b44fc98
--- /dev/null
+++ b/app/services/metrics/dashboard/default_embed_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+# Responsible for returning a filtered system dashboard
+# containing only the default embedded metrics. In future,
+# this class may be updated to support filtering to
+# alternate metrics/panels.
+#
+# Why isn't this filtering in a processing stage? By filtering
+# here, we ensure the dynamically-determined dashboard is cached.
+#
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class DefaultEmbedService < ::Metrics::Dashboard::BaseService
+ # For the default filtering for embedded metrics,
+ # uses the 'id' key in dashboard-yml definition for
+ # identification.
+ DEFAULT_EMBEDDED_METRICS_IDENTIFIERS = %w(
+ system_metrics_kubernetes_container_memory_total
+ system_metrics_kubernetes_container_cores_total
+ ).freeze
+
+ # Returns a new dashboard with only the matching
+ # metrics from the system dashboard, stripped of groups.
+ # @return [Hash]
+ def get_raw_dashboard
+ panels = panel_groups.each_with_object([]) do |group, panels|
+ matched_panels = group['panels'].select { |panel| matching_panel?(panel) }
+
+ panels.concat(matched_panels)
+ end
+
+ { 'panel_groups' => [{ 'panels' => panels }] }
+ end
+
+ def cache_key
+ "dynamic_metrics_dashboard_#{metric_identifiers.join('_')}"
+ end
+
+ private
+
+ # Returns an array of the panels groups on the
+ # system dashboard
+ def panel_groups
+ ::Metrics::Dashboard::SystemDashboardService
+ .new(project, nil)
+ .raw_dashboard['panel_groups']
+ end
+
+ # Identifies a panel as "matching" if any metric ids in
+ # the panel is in the list of identifiers to collect.
+ def matching_panel?(panel)
+ panel['metrics'].any? do |metric|
+ metric_identifiers.include?(metric['id'])
+ end
+ end
+
+ def metric_identifiers
+ DEFAULT_EMBEDDED_METRICS_IDENTIFIERS
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/project_dashboard_service.rb b/app/services/metrics/dashboard/project_dashboard_service.rb
new file mode 100644
index 00000000000..756d387c0e6
--- /dev/null
+++ b/app/services/metrics/dashboard/project_dashboard_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class ProjectDashboardService < ::Metrics::Dashboard::BaseService
+ DASHBOARD_ROOT = ".gitlab/dashboards"
+
+ class << self
+ def all_dashboard_paths(project)
+ file_finder(project)
+ .list_files_for(DASHBOARD_ROOT)
+ .map do |filepath|
+ {
+ path: filepath,
+ display_name: name_for_path(filepath),
+ default: false
+ }
+ end
+ end
+
+ def file_finder(project)
+ Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml')
+ end
+
+ # Grabs the filepath after the base directory.
+ def name_for_path(filepath)
+ filepath.delete_prefix("#{DASHBOARD_ROOT}/")
+ end
+ end
+
+ private
+
+ # Searches the project repo for a custom-defined dashboard.
+ def get_raw_dashboard
+ yml = self.class.file_finder(project).read(dashboard_path)
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ "project_#{project.id}_metrics_dashboard_#{dashboard_path}"
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
new file mode 100644
index 00000000000..fcd71aadb03
--- /dev/null
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# Fetches the system metrics dashboard and formats the output.
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class SystemDashboardService < ::Metrics::Dashboard::BaseService
+ SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
+ SYSTEM_DASHBOARD_NAME = 'Default'
+
+ class << self
+ def all_dashboard_paths(_project)
+ [{
+ path: SYSTEM_DASHBOARD_PATH,
+ display_name: SYSTEM_DASHBOARD_NAME,
+ default: true
+ }]
+ end
+
+ def system_dashboard?(filepath)
+ filepath == SYSTEM_DASHBOARD_PATH
+ end
+ end
+
+ private
+
+ def dashboard_path
+ SYSTEM_DASHBOARD_PATH
+ end
+
+ # Returns the base metrics shipped with every GitLab service.
+ def get_raw_dashboard
+ yml = File.read(Rails.root.join(dashboard_path))
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ "metrics_dashboard_#{dashboard_path}"
+ end
+
+ def insert_project_metrics?
+ true
+ end
+ end
+ end
+end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 1b46f6d8a72..194c4a43dbc 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -21,7 +21,7 @@ module Notes
if quick_actions_service.supported?(note)
options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
- content, update_params = quick_actions_service.execute(note, options)
+ content, update_params, message = quick_actions_service.execute(note, options)
only_commands = content.empty?
@@ -52,7 +52,7 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
- note.errors.add(:commands_only, 'Commands applied')
+ note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index b805a7f1211..a1279bfb3a3 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -210,11 +210,20 @@ module Projects
end
def flush_caches(project)
- project.repository.before_delete
+ ignore_git_errors(repo_path) { project.repository.before_delete }
- Repository.new(wiki_path, project, disk_path: repo_path).before_delete
+ ignore_git_errors(wiki_path) { Repository.new(wiki_path, project, disk_path: repo_path).before_delete }
Projects::ForksCountService.new(project).delete_cache
end
+
+ # If we get a Gitaly error, the repository may be corrupted. We can
+ # ignore these errors since we're going to trash the repositories
+ # anyway.
+ def ignore_git_errors(disk_path, &block)
+ yield
+ rescue Gitlab::Git::CommandError => e
+ Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s)
+ end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 8ff73522e5f..7f944e25887 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -31,17 +31,19 @@ module QuickActions
end
# Takes a text and interprets the commands that are extracted from it.
- # Returns the content without commands, and hash of changes to be applied to a record.
+ # Returns the content without commands, a hash of changes to be applied to a record
+ # and a string containing the execution_message to show to the user.
def execute(content, quick_action_target, only: nil)
- return [content, {}] unless current_user.can?(:use_quick_actions)
+ return [content, {}, ''] unless current_user.can?(:use_quick_actions)
@quick_action_target = quick_action_target
@updates = {}
+ @execution_message = {}
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
- [content, @updates]
+ [content, @updates, execution_messages_for(commands)]
end
# Takes a text and interprets the commands that are extracted from it.
@@ -119,8 +121,12 @@ module QuickActions
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
- def find_label_references(labels_param)
- find_labels(labels_param).map(&:to_reference)
+ def find_label_references(labels_param, format = :id)
+ labels_to_reference(find_labels(labels_param), format)
+ end
+
+ def labels_to_reference(labels, format = :id)
+ labels.map { |l| l.to_reference(format: format) }
end
def find_label_ids(labels_param)
@@ -128,11 +134,24 @@ module QuickActions
end
def explain_commands(commands)
+ map_commands(commands, :explain)
+ end
+
+ def execution_messages_for(commands)
+ map_commands(commands, :execute_message).join(' ')
+ end
+
+ def map_commands(commands, method)
commands.map do |name, arg|
definition = self.class.definition_by_name(name)
next unless definition
- definition.explain(self, arg)
+ case method
+ when :explain
+ definition.explain(self, arg)
+ when :execute_message
+ @execution_message[name.to_sym] || definition.execute_message(self, arg)
+ end
end.compact
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e4564bc9b00..e30debbbe75 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -597,6 +597,14 @@ module SystemNoteService
note_text =~ /\A#{cross_reference_note_prefix}/i
end
+ def zoom_link_added(issue, project, author)
+ create_note(NoteSummary.new(issue, project, author, _('a Zoom call was added to this issue'), action: 'pinned_embed'))
+ end
+
+ def zoom_link_removed(issue, project, author)
+ create_note(NoteSummary.new(issue, project, author, _('a Zoom call was removed from this issue'), action: 'pinned_embed'))
+ end
+
private
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/zoom_notes_service.rb b/app/services/zoom_notes_service.rb
new file mode 100644
index 00000000000..983a7fcacd1
--- /dev/null
+++ b/app/services/zoom_notes_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class ZoomNotesService
+ def initialize(issue, project, current_user, old_description: nil)
+ @issue = issue
+ @project = project
+ @current_user = current_user
+ @old_description = old_description
+ end
+
+ def execute
+ return if @issue.description == @old_description
+
+ if zoom_link_added?
+ zoom_link_added_notification
+ elsif zoom_link_removed?
+ zoom_link_removed_notification
+ end
+ end
+
+ private
+
+ def zoom_link_added?
+ has_zoom_link?(@issue.description) && !has_zoom_link?(@old_description)
+ end
+
+ def zoom_link_removed?
+ !has_zoom_link?(@issue.description) && has_zoom_link?(@old_description)
+ end
+
+ def has_zoom_link?(text)
+ Gitlab::ZoomLinkExtractor.new(text).match?
+ end
+
+ def zoom_link_added_notification
+ SystemNoteService.zoom_link_added(@issue, @project, @current_user)
+ end
+
+ def zoom_link_removed_notification
+ SystemNoteService.zoom_link_removed(@issue, @project, @current_user)
+ end
+end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 3b2a9d2f80e..967fcdc704e 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -27,7 +27,7 @@ module RecordsUploads
end
def readd_upload
- uploads.where(path: upload_path).delete_all
+ uploads.where(model: model, path: upload_path).delete_all
upload.delete if upload
self.upload = build_upload.tap(&:save!)
diff --git a/app/validators/qualified_domain_array_validator.rb b/app/validators/qualified_domain_array_validator.rb
index 986c146a9db..c3a79d21ac0 100644
--- a/app/validators/qualified_domain_array_validator.rb
+++ b/app/validators/qualified_domain_array_validator.rb
@@ -23,9 +23,9 @@ class QualifiedDomainArrayValidator < ActiveModel::EachValidator
private
def validate_value_present(record, attribute, value)
- return unless value.blank?
+ return unless value.nil?
- record.errors.add(attribute, _('entries cannot be blank'))
+ record.errors.add(attribute, _('entries cannot be nil'))
end
def validate_host_length(record, attribute, value)
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
index a869f1bd4df..5e5ab1e4269 100644
--- a/app/views/admin/application_settings/_help_page.html.haml
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -16,6 +16,6 @@
.form-group
= f.label :help_page_support_url, _('Support page URL'), class: 'label-bold'
= f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
- %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page')
+ %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page and help dropdown')
= f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index e58bb526c11..4fecdb59e1d 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -13,7 +13,7 @@
= _('Whitelist to allow requests to the local network from hooks and services')
= f.text_area :outbound_local_requests_whitelist_raw, placeholder: "example.com, 192.168.1.1", class: 'form-control', rows: 8
%span.form-text.text-muted
- = _('Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are disabled. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 4000 entries. Domains should use IDNA encoding. Ex: domain.com, 192.168.1.1, 127.0.0.0/28.')
+ = _('Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com.')
.form-group
.form-check
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 9cac8266eaf..bc900992cb0 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,8 @@
tabindex: '1',
autocomplete: 'off',
data: { issues_path: issues_dashboard_path,
- mr_path: merge_requests_dashboard_path },
+ mr_path: merge_requests_dashboard_path,
+ qa_selector: 'search_term_field' },
aria: { label: _('Search or jump to…') }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select.js-dashboard-search-options
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 5643a508ddc..41d7aa3741a 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -2,6 +2,8 @@
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
+ %li
+ = link_to _("Support"), support_url
= render_if_exists "shared/learn_gitlab_menu_item"
%li.divider
%li
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 87133c7ba22..cb39c830170 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -236,7 +236,7 @@
%span
= _('General')
= nav_link(path: 'application_settings#integrations') do
- = link_to integrations_admin_application_settings_path, title: _('Integrations') do
+ = link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
%span
= _('Integrations')
= nav_link(path: 'application_settings#repository') do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 92dd558885a..02ecf816e90 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -9,7 +9,7 @@
= @project.name
%ul.sidebar-top-level-items
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
- = link_to project_path(@project), class: 'shortcuts-project', data: { qa_selector: 'project_link' } do
+ = link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
@@ -163,7 +163,7 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
- = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do
+ = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines' do
.nav-icon-container
= sprite_icon('rocket')
%span.nav-item-name#js-onboarding-pipelines-link
diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml
index 89d3b931f88..5228930293c 100644
--- a/app/views/peek/_bar.html.haml
+++ b/app/views/peek/_bar.html.haml
@@ -2,6 +2,5 @@
#js-peek{ data: { env: Peek.env,
request_id: Peek.request_id,
- peek_url: "#{peek_routes_path}/results",
- profile_url: url_for(safe_params.merge(lineprofiler: 'true')) },
+ peek_url: "#{peek_routes_path}/results" },
class: Peek.env }
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index e36d5192a29..ffb90bbd354 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -89,10 +89,10 @@
.col-lg-8
.row
- if @user.read_only_attribute?(:name)
- = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name' },
+ = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name rspec-full-name' },
help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) }
- else
- = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
+ = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name rspec-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
= render_if_exists 'profiles/email_settings', form: f
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3403564992e..763cc764144 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -23,7 +23,7 @@
.js-project-permissions-form
= f.submit _('Save changes'), class: "btn btn-success"
-%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
+%section.qa-merge-request-settings.rspec-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
@@ -35,7 +35,7 @@
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
= render 'projects/merge_request_settings', form: f
- = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes"
+ = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes rspec-save-merge-request-changes"
= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
diff --git a/app/views/projects/mirrors/_disabled_mirror_badge.html.haml b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
index 356cb43f07f..9c11b650f75 100644
--- a/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
+++ b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
@@ -1 +1 @@
-.badge.badge-warning.qa-disabled-mirror-badge{ data: { toggle: 'tooltip', html: 'true' }, title: _('Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them.') }= _('Disabled')
+.badge.badge-warning.qa-disabled-mirror-badge.rspec-disabled-mirror-badge{ data: { toggle: 'tooltip', html: 'true' }, title: _('Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them.') }= _('Disabled')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index e68fa5d08c7..280ec6d715b 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -50,7 +50,7 @@
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
- %tr.qa-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
+ %tr.qa-mirrored-repository-row.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
%td.qa-mirror-repository-url= mirror.safe_url
%td= _('Push')
%td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
@@ -64,4 +64,4 @@
- if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'))
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
- %button.js-delete-mirror.qa-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
+ %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index 24b53555cdc..ee359a01e74 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -2,13 +2,13 @@
.merge_access_levels-container
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge-select wide',
- dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown capitalize-header',
+ dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown rspec-allowed-to-merge-dropdown capitalize-header',
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
- content_for :push_access_levels do
.push_access_levels-container
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push-select wide',
- dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown capitalize-header',
+ dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header',
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
= render 'projects/protected_branches/shared/create_protected_branch'
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 31a598ccd5e..9899cf9c6de 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -33,10 +33,7 @@
Never
%td.text-right.trigger-actions
- - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
- revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
- - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
- = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
- if can?(current_user, :admin_trigger, trigger)
= link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
%i.fa.fa-pencil
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 5bb69563b51..66a614b0197 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -51,6 +51,6 @@
.float-right
= link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped'
- else
- = f.submit s_("Wiki|Create page"), class: 'btn-success btn qa-create-page-button'
+ = f.submit s_("Wiki|Create page"), class: 'btn-success btn qa-create-page-button rspec-create-page-button'
.float-right
= link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel'
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index ee7d89a9bd8..18613ff4c16 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -12,7 +12,7 @@
%ul.nav-links.search-filter.scrolling-tabs.nav.nav-tabs
- if @project
- if project_search_tabs?(:blobs)
- %li{ class: active_when(@scope == 'blobs') }
+ %li{ class: active_when(@scope == 'blobs'), data: { qa_selector: 'code_tab' } }
= link_to search_filter_path(scope: 'blobs') do
= _("Code")
%span.badge.badge-pill
diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml
index 143e9f91ca3..36b6ea7bd37 100644
--- a/app/views/search/results/_blob_data.html.haml
+++ b/app/views/search/results/_blob_data.html.haml
@@ -1,10 +1,10 @@
-.blob-result
+.blob-result{ data: { qa_selector: 'result_item_content' } }
.file-holder
- .js-file-title.file-title
+ .js-file-title.file-title{ data: { qa_selector: 'file_title_content' } }
= link_to blob_link do
%i.fa.fa-file
%strong
= search_blob_title(project, file_name)
- if blob.data
- .file-content.code.term
+ .file-content.code.term{ data: { qa_selector: 'file_text_content' } }
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 8da2ae5111a..4b39c8b06e9 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -2,5 +2,5 @@
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
= icon("refresh spin")
- elsif remote_mirror.enabled?
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
= icon("refresh")
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
index d499bc0a253..c7546073e5c 100644
--- a/app/views/shared/_sidebar_toggle_button.html.haml
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -1,4 +1,4 @@
-%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
+%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
= sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
= sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
%span.collapse-text= _("Collapse sidebar")
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index b11cb8a3076..be78fd0ccfb 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -15,7 +15,7 @@
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: form, attr: :description,
- classes: 'note-textarea qa-issuable-form-description',
+ classes: 'note-textarea qa-issuable-form-description rspec-issuable-form-description',
placeholder: "Write a comment or drag your files here…",
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
diff --git a/bin/secpick b/bin/secpick
index d01304285b6..8a61356a088 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -45,7 +45,7 @@ module Secpick
def git_commands
["git fetch #{@options[:remote]} #{stable_branch}",
- "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch}",
+ "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track",
"git cherry-pick #{@options[:sha]}",
"git push #{@options[:remote]} #{source_branch}",
"git checkout #{original_branch}"]
diff --git a/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml b/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml
new file mode 100644
index 00000000000..a2fa07c6ed2
--- /dev/null
+++ b/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml
@@ -0,0 +1,5 @@
+---
+title: Make quick action commands applied banner more useful
+merge_request: 26672
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml b/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml
new file mode 100644
index 00000000000..387c01dc135
--- /dev/null
+++ b/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Add system notes for when a Zoom call was added/removed from an issue
+merge_request: 30857
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/64091-fix-sprockets-paths.yml b/changelogs/unreleased/64091-fix-sprockets-paths.yml
deleted file mode 100644
index fcd8b2faa49..00000000000
--- a/changelogs/unreleased/64091-fix-sprockets-paths.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix xterm css not loading for environment terminal
-merge_request: 31023
-author:
-type: fixed
diff --git a/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml b/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml
new file mode 100644
index 00000000000..f86c63a15b6
--- /dev/null
+++ b/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml
@@ -0,0 +1,5 @@
+---
+title: Improve MembersFinder query performance using UNION
+merge_request: 30451
+author: Jacopo Beschi @jacopo-beschi
+type: performance
diff --git a/changelogs/unreleased/64731-fix-project-auto-devops-api.yml b/changelogs/unreleased/64731-fix-project-auto-devops-api.yml
deleted file mode 100644
index 7fe2c036773..00000000000
--- a/changelogs/unreleased/64731-fix-project-auto-devops-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the project auto devops API
-merge_request: 30946
-author:
-type: fixed
diff --git a/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml b/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml
deleted file mode 100644
index 291901d64ed..00000000000
--- a/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix "Certificate misses intermediates" UI error when enabling Let's Encrypt
- integration for pages domain
-merge_request: 30995
-author:
-type: fixed
diff --git a/changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml b/changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml
deleted file mode 100644
index 3eea3e551ce..00000000000
--- a/changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set DOCKER_TLS_CERTDIR in Auto Dev-Ops CI template to fix jobs using Docker-in-Docker
-merge_request: 31078
-author:
-type: fixed
diff --git a/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml b/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml
deleted file mode 100644
index c7c02486d61..00000000000
--- a/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set DOCKER_TLS_CERTDIR in CI job templates to fix Docker-in-Docker service
-merge_request: 31080
-author:
-type: fixed
diff --git a/changelogs/unreleased/ab-add-index-on-environments.yml b/changelogs/unreleased/ab-add-index-on-environments.yml
new file mode 100644
index 00000000000..6c7641912f4
--- /dev/null
+++ b/changelogs/unreleased/ab-add-index-on-environments.yml
@@ -0,0 +1,5 @@
+---
+title: Create index on environments by state
+merge_request: 31231
+author:
+type: performance
diff --git a/changelogs/unreleased/dm-submodule-helper-routing.yml b/changelogs/unreleased/dm-submodule-helper-routing.yml
deleted file mode 100644
index 779d4d167df..00000000000
--- a/changelogs/unreleased/dm-submodule-helper-routing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug that caused diffs not to show on MRs with changes to submodules
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-submodule-links-nil.yml b/changelogs/unreleased/dm-submodule-links-nil.yml
deleted file mode 100644
index c09ca41d01d..00000000000
--- a/changelogs/unreleased/dm-submodule-links-nil.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix error rendering submodules in MR diffs when there is no .gitmodules
-merge_request: 31162
-author:
-type: fixed
diff --git a/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml b/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml
new file mode 100644
index 00000000000..2cddd52212e
--- /dev/null
+++ b/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml
@@ -0,0 +1,5 @@
+---
+title: Add admin-configurable "Support page URL" link to top Help dropdown menu
+merge_request: 30459
+author: Diego Louzán
+type: added
diff --git a/changelogs/unreleased/optimise-import-performance.yml b/changelogs/unreleased/optimise-import-performance.yml
deleted file mode 100644
index c63f44d5109..00000000000
--- a/changelogs/unreleased/optimise-import-performance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Optimise import performance
-merge_request: 31045
-author:
-type: performance
diff --git a/changelogs/unreleased/remove-line-profile-from-performance-bar.yml b/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
new file mode 100644
index 00000000000..c1c7450fbbd
--- /dev/null
+++ b/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Remove line profiler from performance bar
+merge_request:
+author:
+type: removed
diff --git a/changelogs/unreleased/security-60551-fix-upload-scope.yml b/changelogs/unreleased/security-60551-fix-upload-scope.yml
new file mode 100644
index 00000000000..7d7096833a7
--- /dev/null
+++ b/changelogs/unreleased/security-60551-fix-upload-scope.yml
@@ -0,0 +1,5 @@
+---
+title: Queries for Upload should be scoped by model
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml b/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
new file mode 100644
index 00000000000..f4686484e33
--- /dev/null
+++ b/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
@@ -0,0 +1,5 @@
+---
+title: Make pdf.js render CJK characters
+merge_request: 31220
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-add-index-extern-uid.yml b/changelogs/unreleased/sh-add-index-extern-uid.yml
new file mode 100644
index 00000000000..531770237a8
--- /dev/null
+++ b/changelogs/unreleased/sh-add-index-extern-uid.yml
@@ -0,0 +1,5 @@
+---
+title: Add partial index on identities table to speed up LDAP lookups
+merge_request: 26710
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml b/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml
new file mode 100644
index 00000000000..f5e2147f00e
--- /dev/null
+++ b/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore Gitaly errors if cache flushing fails on project destruction
+merge_request: 31164
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml b/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
new file mode 100644
index 00000000000..a00ceedf3f4
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
@@ -0,0 +1,5 @@
+---
+title: Remove pdf.js deprecation warnings
+merge_request: 31253
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-support-docker-oci-images.yml b/changelogs/unreleased/sh-support-docker-oci-images.yml
deleted file mode 100644
index 2dcf980fa50..00000000000
--- a/changelogs/unreleased/sh-support-docker-oci-images.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support Docker OCI images
-merge_request: 31127
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-update-rouge-3-7-0.yml b/changelogs/unreleased/sh-update-rouge-3-7-0.yml
new file mode 100644
index 00000000000..6828f48863c
--- /dev/null
+++ b/changelogs/unreleased/sh-update-rouge-3-7-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update rouge to v3.7.0
+merge_request: 31254
+author:
+type: other
diff --git a/config/initializers/0_inject_enterprise_edition_module.rb b/config/initializers/0_inject_enterprise_edition_module.rb
new file mode 100644
index 00000000000..4b21732e179
--- /dev/null
+++ b/config/initializers/0_inject_enterprise_edition_module.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module InjectEnterpriseEditionModule
+ def prepend_if_ee(constant)
+ prepend(constant.constantize) if Gitlab.ee?
+ end
+
+ def extend_if_ee(constant)
+ extend(constant.constantize) if Gitlab.ee?
+ end
+
+ def include_if_ee(constant)
+ include(constant.constantize) if Gitlab.ee?
+ end
+end
+
+Module.prepend(InjectEnterpriseEditionModule)
diff --git a/config/initializers/octokit.rb b/config/initializers/octokit.rb
new file mode 100644
index 00000000000..b3749258ec5
--- /dev/null
+++ b/config/initializers/octokit.rb
@@ -0,0 +1 @@
+Octokit.middleware.insert_after Octokit::Middleware::FollowRedirects, Gitlab::Octokit::Middleware
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index d51d553c939..4a2b7931f30 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -7,7 +7,6 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Re
Peek.into Peek::Views::Host
Peek.into Peek::Views::ActiveRecord
Peek.into Peek::Views::Gitaly
-Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::RedisDetailed
Peek.into Peek::Views::Rugged
Peek.into Peek::Views::GC
diff --git a/config/routes/project.rb b/config/routes/project.rb
index c202463dadb..1f632765317 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -339,11 +339,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :variables, only: [:show, :update]
- resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
- member do
- post :take_ownership
- end
- end
+ resources :triggers, only: [:index, :create, :edit, :update, :destroy]
resource :mirror, only: [:show, :update] do
member do
diff --git a/config/webpack.config.js b/config/webpack.config.js
index c497a6e88f8..442b4b4c21e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -6,6 +6,7 @@ const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const CompressionPlugin = require('compression-webpack-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+const CopyWebpackPlugin = require('copy-webpack-plugin');
const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
@@ -292,6 +293,13 @@ module.exports = {
}
}),
+ new CopyWebpackPlugin([
+ {
+ from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
+ to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'),
+ },
+ ]),
+
// compression can require a lot of compute time and is disabled in CI
IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(),
diff --git a/db/migrate/20190715042813_add_issue_id_to_versions.rb b/db/migrate/20190715042813_add_issue_id_to_versions.rb
new file mode 100644
index 00000000000..1cefdbc9df2
--- /dev/null
+++ b/db/migrate/20190715042813_add_issue_id_to_versions.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddIssueIdToVersions < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_reference :design_management_versions, :issue, index: true, foreign_key: { on_delete: :cascade }
+ end
+
+ def down
+ remove_reference :design_management_versions, :issue
+ end
+end
diff --git a/db/migrate/20190715043954_set_issue_id_for_all_versions.rb b/db/migrate/20190715043954_set_issue_id_for_all_versions.rb
new file mode 100644
index 00000000000..345b749f1a4
--- /dev/null
+++ b/db/migrate/20190715043954_set_issue_id_for_all_versions.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class SetIssueIdForAllVersions < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ execute('UPDATE design_management_versions as versions SET issue_id = (
+ SELECT design_management_designs.issue_id
+ FROM design_management_designs
+ INNER JOIN design_management_designs_versions ON design_management_designs.id = design_management_designs_versions.design_id
+ WHERE design_management_designs_versions.version_id = versions.id
+ LIMIT 1
+ )')
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190729090456_add_index_on_environments_with_state.rb b/db/migrate/20190729090456_add_index_on_environments_with_state.rb
new file mode 100644
index 00000000000..9a8d8391415
--- /dev/null
+++ b/db/migrate/20190729090456_add_index_on_environments_with_state.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnEnvironmentsWithState < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :environments, [:project_id, :state]
+ end
+
+ def down
+ remove_concurrent_index :environments, [:project_id, :state]
+ end
+end
diff --git a/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb b/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb
new file mode 100644
index 00000000000..b23abb80dda
--- /dev/null
+++ b/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RemoveShaIndexFromVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :design_management_versions, :sha
+ end
+
+ def down
+ add_concurrent_index :design_management_versions, :sha, unique: true, using: :btree
+ end
+end
diff --git a/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb b/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb
new file mode 100644
index 00000000000..27b0c9648f9
--- /dev/null
+++ b/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddUniqueIssueIdShaIndexToVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :design_management_versions, [:sha, :issue_id], unique: true, using: :btree
+ end
+
+ def down
+ remove_concurrent_index :design_management_versions, [:sha, :issue_id]
+ end
+end
diff --git a/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb b/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb
new file mode 100644
index 00000000000..36ecca4821f
--- /dev/null
+++ b/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexOnIdentitiesLowerExternUidAndProvider < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = "index_on_identities_lower_extern_uid_and_provider"
+
+ def up
+ add_concurrent_index(:identities, 'lower(extern_uid), provider', name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:identities, INDEX_NAME)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1b5272179f5..6f5fc6c65eb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_07_25_012225) do
+ActiveRecord::Schema.define(version: 2019_07_29_090456) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -1116,7 +1116,9 @@ ActiveRecord::Schema.define(version: 2019_07_25_012225) do
create_table "design_management_versions", force: :cascade do |t|
t.binary "sha", null: false
- t.index ["sha"], name: "index_design_management_versions_on_sha", unique: true
+ t.bigint "issue_id"
+ t.index ["issue_id"], name: "index_design_management_versions_on_issue_id"
+ t.index ["sha", "issue_id"], name: "index_design_management_versions_on_sha_and_issue_id", unique: true
end
create_table "draft_notes", force: :cascade do |t|
@@ -1172,6 +1174,7 @@ ActiveRecord::Schema.define(version: 2019_07_25_012225) do
t.index ["name"], name: "index_environments_on_name_varchar_pattern_ops", opclass: :varchar_pattern_ops
t.index ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true
t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true
+ t.index ["project_id", "state"], name: "index_environments_on_project_id_and_state"
end
create_table "epic_issues", id: :serial, force: :cascade do |t|
@@ -1577,6 +1580,7 @@ ActiveRecord::Schema.define(version: 2019_07_25_012225) do
t.datetime "updated_at"
t.integer "saml_provider_id"
t.string "secondary_extern_uid"
+ t.index "lower((extern_uid)::text), provider", name: "index_on_identities_lower_extern_uid_and_provider"
t.index ["saml_provider_id"], name: "index_identities_on_saml_provider_id", where: "(saml_provider_id IS NOT NULL)"
t.index ["user_id"], name: "index_identities_on_user_id"
end
@@ -3701,6 +3705,7 @@ ActiveRecord::Schema.define(version: 2019_07_25_012225) do
add_foreign_key "design_management_designs", "projects", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_versions", column: "version_id", name: "fk_f4d25ba00c", on_delete: :cascade
+ add_foreign_key "design_management_versions", "issues", on_delete: :cascade
add_foreign_key "draft_notes", "merge_requests", on_delete: :cascade
add_foreign_key "draft_notes", "users", column: "author_id", on_delete: :cascade
add_foreign_key "elasticsearch_indexed_namespaces", "namespaces", on_delete: :cascade
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 113514e1ee8..32462a95a1a 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -21,7 +21,7 @@ administrators can add custom git hooks to any GitLab project.
## Create a custom Git hook for a repository
Server-side Git hooks are typically placed in the repository's `hooks`
-subdirectory. In GitLab, hook directories are are symlinked to the gitlab-shell
+subdirectory. In GitLab, hook directories are symlinked to the gitlab-shell
`hooks` directory for ease of maintenance between gitlab-shell upgrades.
Custom hooks are implemented differently, but the behavior is exactly the same
once the hook is created. Follow the steps below to set up a custom hook for a
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index f0d329d5296..b5789c87e47 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -1,5 +1,11 @@
# Geo Replication **(PREMIUM ONLY)**
+> - Introduced in GitLab Enterprise Edition 8.9.
+> - Using Geo in combination with
+> [High Availability](../../high_availability/README.md)
+> is considered **Generally Available** (GA) in
+> [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
+
Geo is the solution for widely distributed development teams.
## Overview
@@ -8,14 +14,14 @@ Fetching large repositories can take a long time for teams located far from a si
Geo provides local, read-only instances of your GitLab instances, reducing the time it takes to clone and fetch large repositories and speeding up development.
+> **Notes:**
+>
> - Geo is part of [GitLab Premium](https://about.gitlab.com/pricing/#self-managed).
-> - Introduced in GitLab Enterprise Edition 8.9.
> - We recommend you use:
> - At least GitLab Enterprise Edition 10.0 for basic Geo features.
> - The latest version for a better experience.
> - Make sure that all nodes run the same GitLab version.
> - Geo requires PostgreSQL 9.6 and Git 2.9, in addition to GitLab's usual [minimum requirements](../../../install/requirements.md).
-> - Using Geo in combination with [High Availability](../../high_availability/README.md) is considered **Generally Available** (GA) in GitLab [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
For a video introduction to Geo, see [Introduction to GitLab Geo - GitLab Features](https://www.youtube.com/watch?v=-HDLxSjEh6w).
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
index 8a6f8b3b273..89b09054d46 100644
--- a/doc/administration/monitoring/performance/img/performance_bar.png
+++ b/doc/administration/monitoring/performance/img/performance_bar.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
deleted file mode 100644
index a55ce753101..00000000000
--- a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index 15eab7dcab0..2cc78ccc03c 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -14,8 +14,6 @@ It allows you to see (from left to right):
![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
- time taken and number of [Rugged] calls, click through for details of these calls
![Rugged profiling using the Performance Bar](img/performance_bar_rugged_calls.png)
-- profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
- ![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
- time taken and number of Redis calls, click through for details of these calls
![Redis profiling using the Performance Bar](img/performance_bar_redis_calls.png)
- time taken and number of Ruby GC calls
diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md
index 91fc0133d56..e880d76e756 100644
--- a/doc/administration/raketasks/ldap.md
+++ b/doc/administration/raketasks/ldap.md
@@ -28,6 +28,31 @@ limit by passing a number to the check task:
rake gitlab:ldap:check[50]
```
+## Run a Group Sync
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14735) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.3.
+
+The following task will run a [group sync](../auth/ldap-ee.md#group-sync) immediately. This is valuable
+when you'd like to update all configured group memberships against LDAP without
+waiting for the next scheduled group sync to be run.
+
+NOTE: **NOTE:**
+If you'd like to change the frequency at which a group sync is performed,
+[adjust the cron schedule](../auth/ldap-ee.md#adjusting-ldap-group-sync-schedule)
+instead.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:ldap:group_sync
+```
+
+**Source Installation**
+
+```bash
+bundle exec rake gitlab:ldap:group_sync
+```
+
## Rename a provider
If you change the LDAP server ID in `gitlab.yml` or `gitlab.rb` you will need
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 76e3a0fa1a4..f9382361187 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -50,11 +50,14 @@ The web application flow is:
`/oauth/authorize` endpoint with the following GET parameters:
```
- https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
+ https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES
```
- This will ask the user to approve the applications access to their account and
- then redirect back to the `REDIRECT_URI` you provided. The redirect will
+ This will ask the user to approve the applications access to their account
+ based on the scopes specified in `REQUESTED_SCOPES` and then redirect back to
+ the `REDIRECT_URI` you provided. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
+ is a space separated list of scopes you want to have access to (e.g. `scope=read_user+profile`
+ would request `read_user` and `profile` scopes). The redirect will
include the GET `code` parameter, for example:
```
@@ -110,11 +113,14 @@ To request the access token, you should redirect the user to the
`/oauth/authorize` endpoint using `token` response type:
```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES
```
-This will ask the user to approve the application's access to their account and
-then redirect them back to the `REDIRECT_URI` you provided. The redirect
+This will ask the user to approve the applications access to their account
+based on the scopes specified in `REQUESTED_SCOPES` and then redirect back to
+the `REDIRECT_URI` you provided. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
+ is a space separated list of scopes you want to have access to (e.g. `scope=read_user+profile`
+would request `read_user` and `profile` scopes). The redirect
will include a fragment with `access_token` as well as token details in GET
parameters, for example:
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index 736312df116..e207ff8e98a 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -120,35 +120,6 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form descript
}
```
-## Take ownership of a project trigger
-
-Update an owner of a project trigger.
-
-```
-POST /projects/:id/triggers/:trigger_id/take_ownership
-```
-
-| Attribute | Type | required | Description |
-|---------------|---------|----------|--------------------------|
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `trigger_id` | integer | yes | The trigger id |
-
-```
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
-```
-
-```json
-{
- "id": 10,
- "description": "my trigger",
- "created_at": "2016-01-07T09:53:58.235Z",
- "last_used": null,
- "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
- "updated_at": "2016-01-07T09:53:58.235Z",
- "owner": null
-}
-```
-
## Remove a project trigger
Remove a project's build trigger.
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 68da88af698..c3ac70f0579 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -230,7 +230,7 @@ are listed in the descriptions of the relevant settings.
| `gravatar_enabled` | boolean | no | Enable Gravatar. |
| `hashed_storage_enabled` | boolean | no | Create new projects using hashed storage paths: Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance. (EXPERIMENTAL) |
| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help. |
-| `help_page_support_url` | string | no | Alternate support URL for help page. |
+| `help_page_support_url` | string | no | Alternate support URL for help page and help dropdown. |
| `help_page_text` | string | no | Custom text displayed on the help page. |
| `help_text` | string | no | **(PREMIUM)** GitLab server administrator information |
| `hide_third_party_offers` | boolean | no | Do not display offers from third parties within GitLab. |
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 9f1ce1fc230..10a21edbd34 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -91,7 +91,7 @@ NOTE: **Note:**
By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
For more information please read [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful).
-### Use docker-in-docker executor
+### Use docker-in-docker workflow with Docker executor
The second approach is to use the special docker-in-docker (dind)
[Docker image](https://hub.docker.com/_/docker/) with all tools installed
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index 50f1ac3d54a..925653f9fdf 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -11,7 +11,8 @@ Requires GitLab Runner 11.2 and above.
container images from a Dockerfile, inside a container or Kubernetes cluster.
kaniko solves two problems with using the
-[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
+[docker-in-docker
+build](using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor) method:
- Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
in order to function, which is a significant security concern.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 9295dcfd4e0..00995f881da 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -33,7 +33,7 @@ The following table lists examples with step-by-step tutorials that are containe
| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
-| PHP with Laravel, Ennvoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
+| PHP with Laravel, Envoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index 8ecac4a5a4f..3266e5dc62e 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -15,7 +15,7 @@ your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
using Docker-in-Docker.
First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+[docker-in-docker build](../docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
generates the expected report:
@@ -155,4 +155,4 @@ performance:
paths:
- performance.json
- sitespeed-results/
-``` \ No newline at end of file
+```
diff --git a/doc/ci/examples/code_quality.md b/doc/ci/examples/code_quality.md
index 43f773dab7c..69bad6b4c25 100644
--- a/doc/ci/examples/code_quality.md
+++ b/doc/ci/examples/code_quality.md
@@ -14,7 +14,7 @@ This example shows how to run Code Quality on your code by using GitLab CI/CD
and Docker.
First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
Once you set up the Runner, include the CodeQuality template in your CI config:
@@ -34,6 +34,12 @@ For [GitLab Starter][ee] users, this information will be automatically
extracted and shown right in the merge request widget.
[Learn more on Code Quality in merge requests](../../user/project/merge_requests/code_quality.md).
+CAUTION: **Caution:**
+On self-managed instances, if a malicious actor compromises the Code Quality job
+definition they will be able to execute privileged docker commands on the Runner
+host. Having proper access control policies mitigates this attack vector by
+allowing access only to trusted actors.
+
## Previous job definitions
CAUTION: **Caution:**
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 463b9194c58..ced4344a0b0 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -171,6 +171,11 @@ In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the
upstream pipeline will be passed to the `downstream-job` job, and will be available
within the context of all downstream builds.
+NOTE: **Tip:**
+Upstream pipelines take precedence over downstream ones. If there are two
+variables with the same name defined in both upstream and downstream projects,
+the ones defined in the upstream project will take precedence.
+
### Limitations
Because bridge jobs are a little different to regular jobs, it is not
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index d1f9aa03b6b..2a382f18038 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -97,17 +97,6 @@ overview of the time the triggers were last used.
![Triggers page overview](img/triggers_page.png)
-## Taking ownership of a trigger
-
-> **Note**:
-GitLab 9.0 introduced a trigger ownership to solve permission problems.
-
-Each created trigger when run will impersonate their associated user including
-their access to projects and their project permissions.
-
-You can take ownership of existing triggers by clicking *Take ownership*.
-From now on the trigger will be run as you.
-
## Revoking a trigger
You can revoke a trigger any time by going at your project's
@@ -282,8 +271,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
-removed with one of the future versions of GitLab. You are advised to
-[take ownership](#taking-ownership-of-a-trigger) of any legacy triggers.
+removed with one of the future versions of GitLab.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 09b9fc87986..201047b70b9 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1771,18 +1771,41 @@ sequentially from `job_name 1/N` to `job_name N/N`.
For every job, `CI_NODE_INDEX` and `CI_NODE_TOTAL` [environment variables](../variables/README.md#predefined-environment-variables) are set.
-A simple example:
+Marking a job to be run in parallel requires only a simple addition to your configuration file:
-```yaml
-test:
- script: rspec
- parallel: 5
+```diff
+ test:
+ script: rspec
++ parallel: 5
```
-
TIP: **Tip:**
Parallelize tests suites across parallel jobs.
Different languages have different tools to facilitate this.
+A simple example using [Sempahore Test Boosters](https://github.com/renderedtext/test-boosters) and RSpec to run some Ruby tests:
+
+```ruby
+# Gemfile
+source 'https://rubygems.org'
+
+gem 'rspec'
+gem 'semaphore_test_boosters'
+```
+
+```yaml
+test:
+ parallel: 3
+ script:
+ - bundle
+ - bundle exec rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL
+```
+
+CAUTION: **Caution:**
+Please be aware that semaphore_test_boosters reports usages statistics to the author.
+
+You can then navigate to the **Jobs** tab of a new pipeline build and see your RSpec
+job split into three separate jobs.
+
### `trigger` **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index d717b17136e..19b26618882 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -111,18 +111,18 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
- ```md
- This document was moved to [another location](../../administration/lfs.md).
- ```
+ ```md
+ This document was moved to [another location](../../administration/lfs.md).
+ ```
1. Find and replace any occurrences of the old location with the new one.
A quick way to find them is to use `git grep`. First go to the root directory
where you cloned the `gitlab-ce` repository and then do:
- ```sh
- git grep -n "workflow/lfs/lfs_administration"
- git grep -n "lfs/lfs_administration"
- ```
+ ```sh
+ git grep -n "workflow/lfs/lfs_administration"
+ git grep -n "lfs/lfs_administration"
+ ```
NOTE: **Note:**
If the document being moved has any Disqus comments on it, there are extra steps
@@ -296,45 +296,45 @@ You can combine one or more of the following:
1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
method:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
+ ```
1. **Opening links in a new tab.** This should be the default behavior:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
+ ```
1. **Linking to a circle icon.** Usually used in settings where a long
description cannot be used, like near checkboxes. You can basically use
any font awesome icon, but prefer the `question-circle`:
- ```haml
- = link_to icon('question-circle'), help_page_path('user/permissions')
- ```
+ ```haml
+ = link_to icon('question-circle'), help_page_path('user/permissions')
+ ```
1. **Using a button link.** Useful in places where text would be out of context
with the rest of the page layout:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
+ ```
1. **Using links inline of some text.**
- ```haml
- Description to #{link_to 'Help page', help_page_path('user/permissions')}.
- ```
+ ```haml
+ Description to #{link_to 'Help page', help_page_path('user/permissions')}.
+ ```
1. **Adding a period at the end of the sentence.** Useful when you don't want
the period to be part of the link:
- ```haml
- = succeed '.' do
- Learn more in the
- = link_to 'Help page', help_page_path('user/permissions')
- ```
+ ```haml
+ = succeed '.' do
+ Learn more in the
+ = link_to 'Help page', help_page_path('user/permissions')
+ ```
### GitLab `/help` tests
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index dd798777c12..e84d65f424e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -36,8 +36,8 @@ For the Troubleshooting sections, people in GitLab Support can merge additions t
Include any media types/sources if the content is relevant to readers. You can freely include or link presentations, diagrams, videos, etc.; no matter who it was originally composed for, if it is helpful to any of our audiences, we can include it.
- - If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
- - Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
+- If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
+- Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
### No special types
@@ -237,14 +237,14 @@ Do not include the same information in multiple places. [Link to a SSOT instead.
- Use sentence case for titles, headings, labels, menu items, and buttons.
- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
- ```md
- ## Header
+ ```md
+ ## Header
- Paragraph.
+ Paragraph.
- - List item 1
- - List item 2
- ```
+ - List item 1
+ - List item 2
+ ```
### Tables overlapping the TOC
@@ -303,12 +303,12 @@ Check specific punctuation rules for [list items](#list-items) below.
- Be consistent throughout the list: if the majority of the items do not end in a period, do not end any of the items in a period, even if they consist of a complete sentence. The opposite is also valid: if the majority of the items end with a period, end all with a period.
- Separate list items from explanatory text with a colon (`:`). For example:
- ```md
- The list is as follows:
+ ```md
+ The list is as follows:
- - First item: this explains the first item.
- - Second item: this explains the second item.
- ```
+ - First item: this explains the first item.
+ - Second item: this explains the second item.
+ ```
**Examples:**
@@ -520,16 +520,16 @@ To embed a video, follow the instructions below and make sure
you have your MR reviewed and approved by a technical writer.
1. Copy the code below and paste it into your markdown file.
- Leave a blank line above and below it. Do NOT edit the code
- (don't remove or add any spaces, etc).
+ Leave a blank line above and below it. Do NOT edit the code
+ (don't remove or add any spaces, etc).
1. On YouTube, visit the video URL you want to display. Copy
- the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
- and replace the video title and link in the line under `<div class="video-fallback">`.
+ the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
+ and replace the video title and link in the line under `<div class="video-fallback">`.
1. On YouTube, click **Share**, then **Embed**.
1. Copy the `<iframe>` source (`src`) **URL only**
- (`https://www.youtube.com/embed/VIDEO-ID`),
- and paste it, replacing the content of the `src` field in the
- `iframe` tag.
+ (`https://www.youtube.com/embed/VIDEO-ID`),
+ and paste it, replacing the content of the `src` field in the
+ `iframe` tag.
```html
leave a blank line here
@@ -611,7 +611,7 @@ In most cases, content considered for a note should be included:
#### When to use
Use a note when there is a reason that most or all readers who browse the
-section should see the content. That is, if missed, it’s likely to cause
+section should see the content. That is, if missed, it’s likely to cause
major trouble for a minority of users or significant trouble for a majority
of users.
@@ -747,24 +747,24 @@ a helpful link back to how the feature was developed.
- For features that need to declare the GitLab version that the feature was introduced. Text similar
to the following should be added immediately below the heading as a blockquote:
- ```md
- > Introduced in GitLab 11.3.
- ```
+ ```md
+ > Introduced in GitLab 11.3.
+ ```
- Whenever possible, version text should have a link to the issue, merge request, or epic that introduced the feature.
An issue is preferred over a merge request, and a merge request is preferred over an epic. For example:
- ```md
- > [Introduced](<link-to-issue>) in GitLab 11.3.
- ```
+ ```md
+ > [Introduced](<link-to-issue>) in GitLab 11.3.
+ ```
- If the feature is only available in GitLab Enterprise Edition, mention
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the feature is available in:
- ```md
- > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
- ```
+ ```md
+ > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
+ ```
### Removing version text
@@ -871,14 +871,14 @@ When there is a list of steps to perform, usually that entails editing the
configuration file and reconfiguring/restarting GitLab. In such case, follow
the style below as a guide:
-```md
+````md
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- external_url "https://gitlab.example.com"
- ```
+ ```ruby
+ external_url "https://gitlab.example.com"
+ ```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
@@ -888,17 +888,16 @@ the style below as a guide:
1. Edit `config/gitlab.yml`:
- ```yaml
- gitlab:
- host: "gitlab.example.com"
- ```
+ ```yaml
+ gitlab:
+ host: "gitlab.example.com"
+ ```
1. Save the file and [restart] GitLab for the changes to take effect.
-
[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/restart_gitlab.md#installations-from-source
-```
+````
In this case:
@@ -917,9 +916,9 @@ on this document. Further explanation is given below.
- Every method must have the REST API request. For example:
- ```
- GET /projects/:id/repository/branches
- ```
+ ```
+ GET /projects/:id/repository/branches
+ ```
- Every method must have a detailed
[description of the parameters](#method-description).
@@ -971,7 +970,7 @@ You can use the following fake tokens as examples.
| Token type | Token value |
|:----------------------|:-------------------------------------------------------------------|
-| Private user token | `<your_access_token>` |
+| Private user token | `<your_access_token>` |
| Personal access token | `n671WNGecHugsdEDPsyo` |
| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` |
| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` |
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 7131b717353..2217dedccd3 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -125,20 +125,24 @@ This also applies to views.
### EE features based on CE features
For features that build on existing CE features, write a module in the `EE`
-namespace and `prepend` it in the CE class, on the last line of the file that
-the class resides in. This makes conflicts less likely to happen during CE to EE
-merges because only one line is added to the CE class - the `prepend` line. For
-example, to prepend a module into the `User` class you would use the following
-approach:
+namespace and inject it in the CE class, on the last line of the file that the
+class resides in. This makes conflicts less likely to happen during CE to EE
+merges because only one line is added to the CE class - the line that injects
+the module. For example, to prepend a module into the `User` class you would use
+the following approach:
```ruby
class User < ActiveRecord::Base
# ... lots of code here ...
end
-User.prepend(EE::User)
+User.prepend_if_ee('EE::User')
```
+Do not use methods such as `prepend`, `extend`, and `include`. Instead, use
+`prepend_if_ee`, `extend_if_ee`, or `include_if_ee`. These methods take a
+_String_ containing the full module name as the argument, not the module itself.
+
Since the module would require an `EE` namespace, the file should also be
put in an `ee/` sub-directory. For example, we want to extend the user model
in EE, so we have a module called `::EE::User` put inside
@@ -255,7 +259,7 @@ class ApplicationController < ActionController::Base
# ...
end
-ApplicationController.prepend(EE::ApplicationController)
+ApplicationController.prepend_if_ee('EE::ApplicationController')
```
And create a new file in the `ee/` sub-directory with the altered
@@ -504,9 +508,9 @@ EE-specific LDAP classes in `ee/lib/ee/gitlab/ldap`.
### Code in `lib/api/`
-It can be very tricky to extend EE features by a single line of `prepend`,
-and for each different [Grape](https://github.com/ruby-grape/grape) feature,
-we might need different strategies to extend it. To apply different strategies
+It can be very tricky to extend EE features by a single line of `prepend_if_ee`,
+and for each different [Grape](https://github.com/ruby-grape/grape) feature, we
+might need different strategies to extend it. To apply different strategies
easily, we would use `extend ActiveSupport::Concern` in the EE module.
Put the EE module files following
@@ -543,12 +547,12 @@ constants.
We can define `params` and utilize `use` in another `params` definition to
include params defined in EE. However, we need to define the "interface" first
in CE in order for EE to override it. We don't have to do this in other places
-due to `prepend`, but Grape is complex internally and we couldn't easily do
-that, so we'll follow regular object-oriented practices that we define the
+due to `prepend_if_ee`, but Grape is complex internally and we couldn't easily
+do that, so we'll follow regular object-oriented practices that we define the
interface first here.
For example, suppose we have a few more optional params for EE. We can move the
-params out of the `Grape::API` class to a helper module, so we can `prepend` it
+params out of the `Grape::API` class to a helper module, so we can inject it
before it would be used in the class.
```ruby
@@ -583,7 +587,7 @@ module API
end
end
-API::Helpers::ProjectsHelpers.prepend(EE::API::Helpers::ProjectsHelpers)
+API::Helpers::ProjectsHelpers.prepend_if_ee('EE::API::Helpers::ProjectsHelpers')
```
We could override it in EE module:
@@ -624,7 +628,7 @@ module API
end
end
-API::JobArtifacts.prepend(EE::API::JobArtifacts)
+API::JobArtifacts.prepend_if_ee('EE::API::JobArtifacts')
```
And then we can follow regular object-oriented practices to override it:
@@ -677,7 +681,7 @@ module API
end
end
-API::MergeRequests.prepend(EE::API::MergeRequests)
+API::MergeRequests.prepend_if_ee('EE::API::MergeRequests')
```
Note that `update_merge_request_ee` doesn't do anything in CE, but
@@ -717,8 +721,8 @@ Sometimes we need to use different arguments for a particular API route, and we
can't easily extend it with an EE module because Grape has different context in
different blocks. In order to overcome this, we need to move the data to a class
method that resides in a separate module or class. This allows us to extend that
-module or class before its data is used, without having to place a `prepend` in
-the middle of CE code.
+module or class before its data is used, without having to place a
+`prepend_if_ee` in the middle of CE code.
For example, in one place we need to pass an extra argument to
`at_least_one_of` so that the API could consider an EE-only argument as the
@@ -739,7 +743,7 @@ module API
end
end
-API::MergeRequests::Parameters.prepend(EE::API::MergeRequests::Parameters)
+API::MergeRequests::Parameters.prepend_if_ee('EE::API::MergeRequests::Parameters')
# api/merge_requests.rb
module API
@@ -789,7 +793,7 @@ class Identity < ActiveRecord::Base
[:provider]
end
- prepend EE::Identity
+ prepend_if_ee('EE::Identity')
validates :extern_uid,
allow_blank: true,
@@ -841,7 +845,7 @@ class Identity < ActiveRecord::Base
end
end
-Identity::UniquenessScopes.prepend(EE::Identity::UniquenessScopes)
+Identity::UniquenessScopes.prepend_if_ee('EE::Identity::UniquenessScopes')
# app/models/identity.rb
class Identity < ActiveRecord::Base
diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md
index 52462a4bec9..096ce8ca25a 100644
--- a/doc/development/fe_guide/components.md
+++ b/doc/development/fe_guide/components.md
@@ -14,28 +14,29 @@ See also the [corresponding UX guide](https://design.gitlab.com/#/components/dro
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
- ```Haml
- .dropdown.my-dropdown
- %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
- %span.dropdown-toggle-text
- Toggle Dropdown
- = icon('chevron-down')
-
- %ul.dropdown-menu
- %li
- %a
- item!
- ```
-
- Or use the helpers
- ```Haml
- .dropdown.my-dropdown
- = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
- = dropdown_content
- %li
- %a
- item!
- ```
+ ```Haml
+ .dropdown.my-dropdown
+ %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
+ %span.dropdown-toggle-text
+ Toggle Dropdown
+ = icon('chevron-down')
+
+ %ul.dropdown-menu
+ %li
+ %a
+ item!
+ ```
+
+ Or use the helpers
+
+ ```Haml
+ .dropdown.my-dropdown
+ = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
+ = dropdown_content
+ %li
+ %a
+ item!
+ ```
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 2628e95dbc1..676bce32998 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -30,8 +30,8 @@ To improve the time to first render we are using lazy loading for images. This w
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
-- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
-- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
+- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
@@ -96,26 +96,26 @@ bundle and included on the page.
DOM has loaded, you should attach an event handler to the `DOMContentLoaded`
event with:
- ```javascript
- import initMyWidget from './my_widget';
+ ```javascript
+ import initMyWidget from './my_widget';
- document.addEventListener('DOMContentLoaded', () => {
- initMyWidget();
- });
- ```
+ document.addEventListener('DOMContentLoaded', () => {
+ initMyWidget();
+ });
+ ```
- **Supporting Module Placement:**
- - If a class or a module is _specific to a particular route_, try to locate
- it close to the entry point it will be used. For instance, if
- `my_widget.js` is only imported within `pages/widget/show/index.js`, you
- should place the module at `pages/widget/show/my_widget.js` and import it
- with a relative path (e.g. `import initMyWidget from './my_widget';`).
- - If a class or module is _used by multiple routes_, place it within a
- shared directory at the closest common parent directory for the entry
- points that import it. For example, if `my_widget.js` is imported within
- both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
- place the module at `pages/widget/shared/my_widget.js` and import it with
- a relative path if possible (e.g. `../shared/my_widget`).
+ - If a class or a module is _specific to a particular route_, try to locate
+ it close to the entry point it will be used. For instance, if
+ `my_widget.js` is only imported within `pages/widget/show/index.js`, you
+ should place the module at `pages/widget/show/my_widget.js` and import it
+ with a relative path (e.g. `import initMyWidget from './my_widget';`).
+ - If a class or module is _used by multiple routes_, place it within a
+ shared directory at the closest common parent directory for the entry
+ points that import it. For example, if `my_widget.js` is imported within
+ both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
+ place the module at `pages/widget/shared/my_widget.js` and import it with
+ a relative path if possible (e.g. `../shared/my_widget`).
- **Enterprise Edition Caveats:**
For GitLab Enterprise Edition, page-specific entry points will override their
@@ -161,7 +161,7 @@ General tips:
- Use code-splitting dynamic imports wherever possible to lazy-load code that is not needed initially.
- [High Performance Animations][high-perf-animations]
--------
+---
## Additional Resources
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 18ef754642d..b0bbb4cc4b2 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -20,33 +20,33 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. **Never Ever EVER** disable eslint globally for a file
- ```javascript
- // bad
- /* eslint-disable */
+ ```javascript
+ // bad
+ /* eslint-disable */
- // better
- /* eslint-disable some-rule, some-other-rule */
+ // better
+ /* eslint-disable some-rule, some-other-rule */
- // best
- // nothing :)
- ```
+ // best
+ // nothing :)
+ ```
1. If you do need to disable a rule for a single violation, try to do it as locally as possible
- ```javascript
- // bad
- /* eslint-disable no-new */
+ ```javascript
+ // bad
+ /* eslint-disable no-new */
- import Foo from 'foo';
+ import Foo from 'foo';
- new Foo();
+ new Foo();
- // better
- import Foo from 'foo';
+ // better
+ import Foo from 'foo';
- // eslint-disable-next-line no-new
- new Foo();
- ```
+ // eslint-disable-next-line no-new
+ new Foo();
+ ```
1. There are few rules that we need to disable due to technical debt. Which are:
1. [no-new][eslint-new]
@@ -55,113 +55,113 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. When they are needed _always_ place ESlint directive comment blocks on the first line of a script,
followed by any global declarations, then a blank newline prior to any imports or code.
- ```javascript
- // bad
- /* global Foo */
- /* eslint-disable no-new */
- import Bar from './bar';
+ ```javascript
+ // bad
+ /* global Foo */
+ /* eslint-disable no-new */
+ import Bar from './bar';
- // good
- /* eslint-disable no-new */
- /* global Foo */
+ // good
+ /* eslint-disable no-new */
+ /* global Foo */
- import Bar from './bar';
- ```
+ import Bar from './bar';
+ ```
1. **Never** disable the `no-undef` rule. Declare globals with `/* global Foo */` instead.
1. When declaring multiple globals, always use one `/* global [name] */` line per variable.
- ```javascript
- // bad
- /* globals Flash, Cookies, jQuery */
+ ```javascript
+ // bad
+ /* globals Flash, Cookies, jQuery */
- // good
- /* global Flash */
- /* global Cookies */
- /* global jQuery */
- ```
+ // good
+ /* global Flash */
+ /* global Cookies */
+ /* global jQuery */
+ ```
1. Use up to 3 parameters for a function or class. If you need more accept an Object instead.
- ```javascript
- // bad
- fn(p1, p2, p3, p4) {}
+ ```javascript
+ // bad
+ fn(p1, p2, p3, p4) {}
- // good
- fn(options) {}
- ```
+ // good
+ fn(options) {}
+ ```
#### Modules, Imports, and Exports
1. Use ES module syntax to import modules
- ```javascript
- // bad
- const SomeClass = require('some_class');
+ ```javascript
+ // bad
+ const SomeClass = require('some_class');
- // good
- import SomeClass from 'some_class';
+ // good
+ import SomeClass from 'some_class';
- // bad
- module.exports = SomeClass;
+ // bad
+ module.exports = SomeClass;
- // good
- export default SomeClass;
- ```
+ // good
+ export default SomeClass;
+ ```
- Import statements are following usual naming guidelines, for example object literals use camel case:
+ Import statements are following usual naming guidelines, for example object literals use camel case:
- ```javascript
- // some_object file
- export default {
- key: 'value',
- };
+ ```javascript
+ // some_object file
+ export default {
+ key: 'value',
+ };
- // bad
- import ObjectLiteral from 'some_object';
+ // bad
+ import ObjectLiteral from 'some_object';
- // good
- import objectLiteral from 'some_object';
- ```
+ // good
+ import objectLiteral from 'some_object';
+ ```
1. Relative paths: when importing a module in the same directory, a child
directory, or an immediate parent directory prefer relative paths. When
importing a module which is two or more levels up, prefer either `~/` or `ee/`.
- In **app/assets/javascripts/my-feature/subdir**:
+ In **app/assets/javascripts/my-feature/subdir**:
- ```javascript
- // bad
- import Foo from '~/my-feature/foo';
- import Bar from '~/my-feature/subdir/bar';
- import Bin from '~/my-feature/subdir/lib/bin';
+ ```javascript
+ // bad
+ import Foo from '~/my-feature/foo';
+ import Bar from '~/my-feature/subdir/bar';
+ import Bin from '~/my-feature/subdir/lib/bin';
- // good
- import Foo from '../foo';
- import Bar from './bar';
- import Bin from './lib/bin';
- ```
+ // good
+ import Foo from '../foo';
+ import Bar from './bar';
+ import Bin from './lib/bin';
+ ```
- In **spec/javascripts**:
+ In **spec/javascripts**:
- ```javascript
- // bad
- import Foo from '../../app/assets/javascripts/my-feature/foo';
+ ```javascript
+ // bad
+ import Foo from '../../app/assets/javascripts/my-feature/foo';
- // good
- import Foo from '~/my-feature/foo';
- ```
+ // good
+ import Foo from '~/my-feature/foo';
+ ```
- When referencing an **EE component**:
+ When referencing an **EE component**:
- ```javascript
- // bad
- import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
+ ```javascript
+ // bad
+ import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
- // good
- import Foo from 'ee/my-feature/foo';
- ```
+ // good
+ import Foo from 'ee/my-feature/foo';
+ ```
1. Avoid using IIFE. Although we have a lot of examples of files which wrap their
contents in IIFEs (immediately-invoked function expressions),
@@ -170,136 +170,136 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. Avoid adding to the global namespace.
- ```javascript
- // bad
- window.MyClass = class { /* ... */ };
+ ```javascript
+ // bad
+ window.MyClass = class { /* ... */ };
- // good
- export default class MyClass { /* ... */ }
- ```
+ // good
+ export default class MyClass { /* ... */ }
+ ```
1. Side effects are forbidden in any script which contains export
- ```javascript
- // bad
- export default class MyClass { /* ... */ }
+ ```javascript
+ // bad
+ export default class MyClass { /* ... */ }
- document.addEventListener("DOMContentLoaded", function(event) {
- new MyClass();
- }
- ```
+ document.addEventListener("DOMContentLoaded", function(event) {
+ new MyClass();
+ }
+ ```
#### Data Mutation and Pure functions
1. Strive to write many small pure functions, and minimize where mutations occur.
- ```javascript
- // bad
- const values = {foo: 1};
+ ```javascript
+ // bad
+ const values = {foo: 1};
- function impureFunction(items) {
- const bar = 1;
+ function impureFunction(items) {
+ const bar = 1;
- items.foo = items.a * bar + 2;
+ items.foo = items.a * bar + 2;
- return items.a;
- }
+ return items.a;
+ }
- const c = impureFunction(values);
+ const c = impureFunction(values);
- // good
- var values = {foo: 1};
+ // good
+ var values = {foo: 1};
- function pureFunction (foo) {
- var bar = 1;
+ function pureFunction (foo) {
+ var bar = 1;
- foo = foo * bar + 2;
+ foo = foo * bar + 2;
- return foo;
- }
+ return foo;
+ }
- var c = pureFunction(values.foo);
+ var c = pureFunction(values.foo);
```
1. Avoid constructors with side-effects.
Although we aim for code without side-effects we need some side-effects for our code to run.
- If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
-
- ```javascript
- // Bad
- export class Foo {
- constructor() {
- this.init();
- }
- init() {
- document.addEventListener('click', this.handleCallback)
- },
- handleCallback() {
-
- }
- }
-
- // Good
- export class Foo {
- constructor() {
- document.addEventListener()
- }
- handleCallback() {
- }
- }
- ```
-
- On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
+ If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
+
+ ```javascript
+ // Bad
+ export class Foo {
+ constructor() {
+ this.init();
+ }
+ init() {
+ document.addEventListener('click', this.handleCallback)
+ },
+ handleCallback() {
+
+ }
+ }
+
+ // Good
+ export class Foo {
+ constructor() {
+ document.addEventListener()
+ }
+ handleCallback() {
+ }
+ }
+ ```
+
+ On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
`.reduce` or `.filter`
- ```javascript
- const users = [ { name: 'Foo' }, { name: 'Bar' } ];
+ ```javascript
+ const users = [ { name: 'Foo' }, { name: 'Bar' } ];
- // bad
- users.forEach((user, index) => {
- user.id = index;
- });
+ // bad
+ users.forEach((user, index) => {
+ user.id = index;
+ });
- // good
- const usersWithId = users.map((user, index) => {
- return Object.assign({}, user, { id: index });
- });
- ```
+ // good
+ const usersWithId = users.map((user, index) => {
+ return Object.assign({}, user, { id: index });
+ });
+ ```
#### Parse Strings into Numbers
1. `parseInt()` is preferable over `Number()` or `+`
- ```javascript
- // bad
- +'10' // 10
+ ```javascript
+ // bad
+ +'10' // 10
- // good
- Number('10') // 10
+ // good
+ Number('10') // 10
- // better
- parseInt('10', 10);
- ```
+ // better
+ parseInt('10', 10);
+ ```
#### CSS classes used for JavaScript
1. If the class is being used in Javascript it needs to be prepend with `js-`
- ```html
- // bad
- <button class="add-user">
- Add User
- </button>
+ ```html
+ // bad
+ <button class="add-user">
+ Add User
+ </button>
- // good
- <button class="js-add-user">
- Add User
- </button>
- ```
+ // good
+ <button class="js-add-user">
+ Add User
+ </button>
+ ```
### Vue.js
@@ -314,43 +314,44 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. The store has it's own file
1. Use a function in the bundle file to instantiate the Vue component:
- ```javascript
- // bad
- class {
- init() {
- new Component({})
- }
- }
-
- // good
- document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#element',
- components: {
- componentName
- },
- render: createElement => createElement('component-name'),
- }));
- ```
+ ```javascript
+ // bad
+ class {
+ init() {
+ new Component({})
+ }
+ }
+
+ // good
+ document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#element',
+ components: {
+ componentName
+ },
+ render: createElement => createElement('component-name'),
+ }));
+ ```
1. Do not use a singleton for the service or the store
- ```javascript
- // bad
- class Store {
- constructor() {
- if (!this.prototype.singleton) {
- // do something
- }
- }
- }
-
- // good
- class Store {
- constructor() {
- // do something
- }
- }
- ```
+ ```javascript
+ // bad
+ class Store {
+ constructor() {
+ if (!this.prototype.singleton) {
+ // do something
+ }
+ }
+ }
+
+ // good
+ class Store {
+ constructor() {
+ // do something
+ }
+ }
+ ```
+
1. Use `.vue` for Vue templates. Do not use `%template` in HAML.
#### Naming
@@ -358,38 +359,38 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371]).
1. **Reference Naming**: Use PascalCase for their instances:
- ```javascript
- // bad
- import cardBoard from 'cardBoard.vue'
+ ```javascript
+ // bad
+ import cardBoard from 'cardBoard.vue'
- components: {
- cardBoard,
- };
+ components: {
+ cardBoard,
+ };
- // good
- import CardBoard from 'cardBoard.vue'
+ // good
+ import CardBoard from 'cardBoard.vue'
- components: {
- CardBoard,
- };
- ```
+ components: {
+ CardBoard,
+ };
+ ```
1. **Props Naming:** Avoid using DOM component prop names.
1. **Props Naming:** Use kebab-case instead of camelCase to provide props in templates.
- ```javascript
- // bad
- <component class="btn">
+ ```javascript
+ // bad
+ <component class="btn">
- // good
- <component css-class="btn">
+ // good
+ <component css-class="btn">
- // bad
- <component myProp="prop" />
+ // bad
+ <component myProp="prop" />
- // good
- <component my-prop="prop" />
- ```
+ // good
+ <component my-prop="prop" />
+ ```
[#34371]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34371
@@ -399,205 +400,205 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. With more than one attribute, all attributes should be on a new line:
- ```javascript
- // bad
- <component v-if="bar"
- param="baz" />
+ ```javascript
+ // bad
+ <component v-if="bar"
+ param="baz" />
- <button class="btn">Click me</button>
+ <button class="btn">Click me</button>
- // good
- <component
- v-if="bar"
- param="baz"
- />
+ // good
+ <component
+ v-if="bar"
+ param="baz"
+ />
- <button class="btn">
- Click me
- </button>
- ```
+ <button class="btn">
+ Click me
+ </button>
+ ```
1. The tag can be inline if there is only one attribute:
- ```javascript
- // good
- <component bar="bar" />
+ ```javascript
+ // good
+ <component bar="bar" />
- // good
- <component
- bar="bar"
- />
+ // good
+ <component
+ bar="bar"
+ />
- // bad
- <component
- bar="bar" />
- ```
+ // bad
+ <component
+ bar="bar" />
+ ```
#### Quotes
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
- ```javascript
- // bad
- template: `
- <button :class='style'>Button</button>
- `
+ ```javascript
+ // bad
+ template: `
+ <button :class='style'>Button</button>
+ `
- // good
- template: `
- <button :class="style">Button</button>
- `
- ```
+ // good
+ template: `
+ <button :class="style">Button</button>
+ `
+ ```
#### Props
1. Props should be declared as an object
- ```javascript
- // bad
- props: ['foo']
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
- ```
+ ```javascript
+ // bad
+ props: ['foo']
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
+ }
+ }
+ ```
1. Required key should always be provided when declaring a prop
- ```javascript
- // bad
- props: {
- foo: {
- type: String,
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
- ```
+ ```javascript
+ // bad
+ props: {
+ foo: {
+ type: String,
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
+ }
+ }
+ ```
1. Default key should be provided if the prop is not required.
_Note:_ There are some scenarios where we need to check for the existence of the property.
On those a default key should not be provided.
- ```javascript
- // good
- props: {
- foo: {
- type: String,
- required: false,
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: true
- }
- }
- ```
+ ```javascript
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: true
+ }
+ }
+ ```
#### Data
1. `data` method should always be a function
- ```javascript
- // bad
- data: {
- foo: 'foo'
- }
-
- // good
- data() {
- return {
- foo: 'foo'
- };
- }
- ```
+ ```javascript
+ // bad
+ data: {
+ foo: 'foo'
+ }
+
+ // good
+ data() {
+ return {
+ foo: 'foo'
+ };
+ }
+ ```
#### Directives
1. Shorthand `@` is preferable over `v-on`
- ```javascript
- // bad
- <component v-on:click="eventHandler"/>
+ ```javascript
+ // bad
+ <component v-on:click="eventHandler"/>
- // good
- <component @click="eventHandler"/>
- ```
+ // good
+ <component @click="eventHandler"/>
+ ```
1. Shorthand `:` is preferable over `v-bind`
- ```javascript
- // bad
- <component v-bind:class="btn"/>
+ ```javascript
+ // bad
+ <component v-bind:class="btn"/>
- // good
- <component :class="btn"/>
- ```
+ // good
+ <component :class="btn"/>
+ ```
1. Shorthand `#` is preferable over `v-slot`
- ```javascript
- // bad
- <template v-slot:header></template>
+ ```javascript
+ // bad
+ <template v-slot:header></template>
- // good
- <template #header></template>
- ```
+ // good
+ <template #header></template>
+ ```
#### Closing tags
1. Prefer self closing component tags
- ```javascript
- // bad
- <component></component>
+ ```javascript
+ // bad
+ <component></component>
- // good
- <component />
- ```
+ // good
+ <component />
+ ```
#### Ordering
1. Tag order in `.vue` file
- ```
- <script>
- // ...
- </script>
-
- <template>
- // ...
- </template>
-
- // We don't use scoped styles but there are few instances of this
- <style>
- // ...
- </style>
- ```
+ ```
+ <script>
+ // ...
+ </script>
+
+ <template>
+ // ...
+ </template>
+
+ // We don't use scoped styles but there are few instances of this
+ <style>
+ // ...
+ </style>
+ ```
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
@@ -608,50 +609,50 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
1. If the elements of the array being iterated have an unique `id` it is advised to use it:
- ```html
- <div
- v-for="item in items"
- :key="item.id"
- >
- <!-- content -->
- </div>
- ```
+ ```html
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <!-- content -->
+ </div>
+ ```
1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
- ```html
- <div
- v-for="(item, index) in items"
- :key="index"
- >
- <!-- content -->
- </div>
- ```
+ ```html
+ <div
+ v-for="(item, index) in items"
+ :key="index"
+ >
+ <!-- content -->
+ </div>
+ ```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
- ```html
- <template v-for="(item, index) in items">
- <span :key="`span-${index}`"></span>
- <button :key="`button-${index}`"></button>
- </template>
- ```
+ ```html
+ <template v-for="(item, index) in items">
+ <span :key="`span-${index}`"></span>
+ <button :key="`button-${index}`"></button>
+ </template>
+ ```
1. When dealing with nested `v-for` use the same guidelines as above.
- ```html
- <div
- v-for="item in items"
- :key="item.id"
- >
- <span
- v-for="element in array"
- :key="element.id"
- >
- <!-- content -->
- </span>
- </div>
- ```
+ ```html
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <span
+ v-for="element in array"
+ :key="element.id"
+ >
+ <!-- content -->
+ </span>
+ </div>
+ ```
Useful links:
@@ -662,35 +663,35 @@ Useful links:
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
- ```javascript
- // bad
- <span
- class="has-tooltip"
- title="Some tooltip text">
- Text
- </span>
-
- // good
- <span
- v-tooltip
- title="Some tooltip text">
- Text
- </span>
- ```
+ ```javascript
+ // bad
+ <span
+ class="has-tooltip"
+ title="Some tooltip text">
+ Text
+ </span>
+
+ // good
+ <span
+ v-tooltip
+ title="Some tooltip text">
+ Text
+ </span>
+ ```
1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js`
1. Don't change `data-original-title`.
- ```javascript
- // bad
- <span data-original-title="tooltip text">Foo</span>
+ ```javascript
+ // bad
+ <span data-original-title="tooltip text">Foo</span>
- // good
- <span title="tooltip text">Foo</span>
+ // good
+ <span title="tooltip text">Foo</span>
- $('span').tooltip('_fixTitle');
- ```
+ $('span').tooltip('_fixTitle');
+ ```
### The Javascript/Vue Accord
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index bf248b7f8af..9eeaee4482f 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -1,15 +1,18 @@
# Vuex
+
To manage the state of an application you should use [Vuex][vuex-docs].
_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
## Separation of concerns
+
Vuex is composed of State, Getters, Mutations, Actions and Modules.
When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state.
_Note:_ The action itself will not update the state, only a mutation should update the state.
## File structure
+
When using Vuex at GitLab, separate this concerns into different files to improve readability:
```
@@ -21,10 +24,12 @@ When using Vuex at GitLab, separate this concerns into different files to improv
├── state.js # state
└── mutation_types.js # mutation types
```
+
The following example shows an application that lists and adds users to the state.
(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store))
### `index.js`
+
This is the entry point for our store. You can use the following as a guide:
```javascript
@@ -47,6 +52,7 @@ export default createStore();
```
### `state.js`
+
The first thing you should do before writing any code is to design the state.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
@@ -66,9 +72,11 @@ Often we need to provide data from haml to our Vue application. Let's store it i
```
#### Access `state` properties
+
You can use `mapState` to access state properties in the components.
### `actions.js`
+
An action is a payload of information to send data from our application to our store.
An action is usually composed by a `type` and a `payload` and they describe what happened.
@@ -110,6 +118,7 @@ In this file, we will write the actions that will call the respective mutations:
```
#### Actions Pattern: `request` and `receive` namespaces
+
When a request is made we often want to show a loading state to the user.
Instead of creating an action to toggle the loading state and dispatch it in the component,
@@ -136,6 +145,7 @@ By following this pattern we guarantee:
1. Actions are simple and straightforward
#### Dispatching actions
+
To dispatch an action from a component, use the `mapActions` helper:
```javascript
@@ -154,6 +164,7 @@ import { mapActions } from 'vuex';
```
### `mutations.js`
+
The mutations specify how the application state changes in response to actions sent to the store.
The only way to change state in a Vuex store should be by committing a mutation.
@@ -193,6 +204,7 @@ Remember that actions only describe that something happened, they don't describe
```
### `getters.js`
+
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
This can be done through the `getters`:
@@ -219,6 +231,7 @@ import { mapGetters } from 'vuex';
```
### `mutation_types.js`
+
From [vuex mutations docs][vuex-mutations]:
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
@@ -227,6 +240,7 @@ export const ADD_USER = 'ADD_USER';
```
### How to include the store in your application
+
The store should be included in the main component of your application:
```javascript
@@ -241,6 +255,7 @@ The store should be included in the main component of your application:
```
### Communicating with the Store
+
```javascript
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
@@ -298,29 +313,33 @@ export default {
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
- > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
+ > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
- ```javascript
- // component.vue
+ ```javascript
+ // component.vue
- // bad
- created() {
- this.$store.commit('mutation');
- }
+ // bad
+ created() {
+ this.$store.commit('mutation');
+ }
+
+ // good
+ created() {
+ this.$store.dispatch('action');
+ }
+ ```
- // good
- created() {
- this.$store.dispatch('action');
- }
- ```
1. Use mutation types instead of hardcoding strings. It will be less error prone.
1. The State will be accessible in all components descending from the use where the store is instantiated.
### Testing Vuex
+
#### Testing Vuex concerns
+
Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations.
#### Testing components that need a store
+
Smaller components might use `store` properties to access the data.
In order to write unit tests for those components, we need to include the store and provide the correct state:
@@ -363,6 +382,7 @@ describe('component', () => {
```
#### Testing Vuex actions and getters
+
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 17462887162..141f5a8d6d9 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -21,18 +21,18 @@ The following tools are used:
1. [`gettext_i18n_rails`](https://github.com/grosser/gettext_i18n_rails): this
gem allow us to translate content from models, views and controllers. Also
it gives us access to the following raketasks:
- - `rake gettext:find`: Parses almost all the files from the
- Rails application looking for content that has been marked for
- translation. Finally, it updates the PO files with the new content that
- it has found.
- - `rake gettext:pack`: Processes the PO files and generates the
- MO files that are binary and are finally used by the application.
+ - `rake gettext:find`: Parses almost all the files from the
+ Rails application looking for content that has been marked for
+ translation. Finally, it updates the PO files with the new content that
+ it has found.
+ - `rake gettext:pack`: Processes the PO files and generates the
+ MO files that are binary and are finally used by the application.
1. [`gettext_i18n_rails_js`](https://github.com/webhippie/gettext_i18n_rails_js):
this gem is useful to make the translations available in JavaScript. It
provides the following raketask:
- - `rake gettext:po_to_json`: Reads the contents from the PO files and
- generates JSON files containing all the available translations.
+ - `rake gettext:po_to_json`: Reads the contents from the PO files and
+ generates JSON files containing all the available translations.
1. PO editor: there are multiple applications that can help us to work with PO
files, a good option is [Poedit](https://poedit.net/download) which is
@@ -139,60 +139,61 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
- In Ruby/HAML:
- ```ruby
- _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe'
- ```
+ ```ruby
+ _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe'
+ ```
- In JavaScript:
- ```js
- import { __, sprintf } from '~/locale';
+ ```js
+ import { __, sprintf } from '~/locale';
- sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
- ```
+ sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
+ ```
- By default, `sprintf` escapes the placeholder values.
- If you want to take care of that yourself, you can pass `false` as third argument.
+ By default, `sprintf` escapes the placeholder values.
+ If you want to take care of that yourself, you can pass `false` as third argument.
- ```js
- import { __, sprintf } from '~/locale';
+ ```js
+ import { __, sprintf } from '~/locale';
- sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
- sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
- ```
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
+ ```
### Plurals
- In Ruby/HAML:
- ```ruby
- n_('Apple', 'Apples', 3)
- # => 'Apples'
- ```
+ ```ruby
+ n_('Apple', 'Apples', 3)
+ # => 'Apples'
+ ```
- Using interpolation:
- ```ruby
- n_("There is a mouse.", "There are %d mice.", size) % size
- # => When size == 1: 'There is a mouse.'
- # => When size == 2: 'There are 2 mice.'
- ```
+ Using interpolation:
- Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
+ ```ruby
+ n_("There is a mouse.", "There are %d mice.", size) % size
+ # => When size == 1: 'There is a mouse.'
+ # => When size == 2: 'There are 2 mice.'
+ ```
+
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
- ```js
- n__('Apple', 'Apples', 3)
- // => 'Apples'
- ```
+ ```js
+ n__('Apple', 'Apples', 3)
+ // => 'Apples'
+ ```
- Using interpolation:
+ Using interpolation:
- ```js
- n__('Last day', 'Last %d days', x)
- // => When x == 1: 'Last day'
- // => When x == 2: 'Last 2 days'
- ```
+ ```js
+ n__('Last day', 'Last %d days', x)
+ // => When x == 1: 'Last day'
+ // => When x == 2: 'Last 2 days'
+ ```
### Namespaces
@@ -202,17 +203,17 @@ Namespaces should be PascalCase.
- In Ruby/HAML:
- ```ruby
- s_('OpenedNDaysAgo|Opened')
- ```
+ ```ruby
+ s_('OpenedNDaysAgo|Opened')
+ ```
- In case the translation is not found it will return `Opened`.
+ In case the translation is not found it will return `Opened`.
- In JavaScript:
- ```js
- s__('OpenedNDaysAgo|Opened')
- ```
+ ```js
+ s__('OpenedNDaysAgo|Opened')
+ ```
Note: The namespace should be removed from the translation. See the [translation
guidelines for more details](translation.md#namespaced-strings).
@@ -235,12 +236,12 @@ This makes use of [`Intl.DateTimeFormat`].
- In Ruby/HAML, we have two ways of adding format to dates and times:
1. **Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
- [dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
- If you need to add a new format, because other parts of the code could benefit from it,
- you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
+ [dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
+ If you need to add a new format, because other parts of the code could benefit from it,
+ you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
1. **Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
- defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
- specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
+ defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
+ specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
## Best practices
@@ -268,40 +269,40 @@ should be externalized as follows:
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
- In Ruby/HAML, instead of:
-
- ```haml
- - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
- ```
-
- Set the link starting and ending HTML fragments as variables like so:
-
- ```haml
- - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
- - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
- = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
- ```
+
+ ```haml
+ - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```haml
+ - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
+ - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
+ = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
+ ```
- In JavaScript, instead of:
- ```js
- {{
- sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
- link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
- })
- }}
- ```
-
- Set the link starting and ending HTML fragments as variables like so:
-
- ```js
- {{
- sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
- linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
- linkEnd: '</a>',
- })
- }}
- ```
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
+ link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
+ })
+ }}
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
+ linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
+ linkEnd: '</a>',
+ })
+ }}
+ ```
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
@@ -374,29 +375,29 @@ Let's suppose you want to add translations for a new language, let's say French.
1. The first step is to register the new language in `lib/gitlab/i18n.rb`:
- ```ruby
- ...
- AVAILABLE_LANGUAGES = {
- ...,
- 'fr' => 'Français'
- }.freeze
- ...
- ```
+ ```ruby
+ ...
+ AVAILABLE_LANGUAGES = {
+ ...,
+ 'fr' => 'Français'
+ }.freeze
+ ...
+ ```
1. Next, you need to add the language:
- ```sh
- bin/rake gettext:add_language[fr]
- ```
+ ```sh
+ bin/rake gettext:add_language[fr]
+ ```
- If you want to add a new language for a specific region, the command is similar,
- you just need to separate the region with an underscore (`_`). For example:
+ If you want to add a new language for a specific region, the command is similar,
+ you just need to separate the region with an underscore (`_`). For example:
- ```sh
- bin/rake gettext:add_language[en_GB]
- ```
+ ```sh
+ bin/rake gettext:add_language[en_GB]
+ ```
- Please note that you need to specify the region part in capitals.
+ Please note that you need to specify the region part in capitals.
1. Now that the language is added, a new directory has been created under the
path: `locale/fr/`. You can now start using your PO editor to edit the PO file
@@ -406,9 +407,9 @@ Let's suppose you want to add translations for a new language, let's say French.
in order to generate the binary MO files and finally update the JSON files
containing the translations:
- ```sh
- bin/rake gettext:compile
- ```
+ ```sh
+ bin/rake gettext:compile
+ ```
1. In order to see the translated content we need to change our preferred language
which can be found under the user's **Settings** (`/profile`).
@@ -416,7 +417,7 @@ Let's suppose you want to add translations for a new language, let's say French.
1. After checking that the changes are ok, you can proceed to commit the new files.
For example:
- ```sh
- git add locale/fr/ app/assets/javascripts/locale/fr/
- git commit -m "Add French translations for Cycle Analytics page"
- ```
+ ```sh
+ git add locale/fr/ app/assets/javascripts/locale/fr/
+ git commit -m "Add French translations for Cycle Analytics page"
+ ```
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 0e396baccff..4021756343c 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -6,44 +6,44 @@ We developed a number of utilities to ease development.
- Deep merges an array of hashes:
- ``` ruby
- Gitlab::Utils::MergeHash.merge(
- [{ hello: ["world"] },
- { hello: "Everyone" },
- { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
- "Goodbye", "Hallo"]
- )
- ```
-
- Gives:
-
- ``` ruby
- [
- {
- hello:
- [
- "world",
- "Everyone",
- { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
- ]
- },
- "Goodbye"
- ]
- ```
+ ``` ruby
+ Gitlab::Utils::MergeHash.merge(
+ [{ hello: ["world"] },
+ { hello: "Everyone" },
+ { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
+ "Goodbye", "Hallo"]
+ )
+ ```
+
+ Gives:
+
+ ``` ruby
+ [
+ {
+ hello:
+ [
+ "world",
+ "Everyone",
+ { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
+ ]
+ },
+ "Goodbye"
+ ]
+ ```
- Extracts all keys and values from a hash into an array:
- ``` ruby
- Gitlab::Utils::MergeHash.crush(
- { hello: "world", this: { crushes: ["an entire", "hash"] } }
- )
- ```
+ ``` ruby
+ Gitlab::Utils::MergeHash.crush(
+ { hello: "world", this: { crushes: ["an entire", "hash"] } }
+ )
+ ```
- Gives:
+ Gives:
- ``` ruby
- [:hello, "world", :this, :crushes, "an entire", "hash"]
- ```
+ ``` ruby
+ [:hello, "world", :this, :crushes, "an entire", "hash"]
+ ```
## [`Override`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/override.rb)
@@ -53,9 +53,9 @@ We developed a number of utilities to ease development.
`ENV['STATIC_VERIFICATION']` is set to avoid production runtime overhead.
This is useful to check:
- - If we have typos in overriding methods.
- - If we renamed the overridden methods, making original overriding methods
- overrides nothing.
+ - If we have typos in overriding methods.
+ - If we renamed the overridden methods, making original overriding methods
+ overrides nothing.
Here's a simple example:
@@ -94,47 +94,47 @@ We developed a number of utilities to ease development.
- Memoize the value even if it is `nil` or `false`.
- We often do `@value ||= compute`, however this doesn't work well if
- `compute` might eventually give `nil` and we don't want to compute again.
- Instead we could use `defined?` to check if the value is set or not.
- However it's tedious to write such pattern, and `StrongMemoize` would
- help us use such pattern.
+ We often do `@value ||= compute`, however this doesn't work well if
+ `compute` might eventually give `nil` and we don't want to compute again.
+ Instead we could use `defined?` to check if the value is set or not.
+ However it's tedious to write such pattern, and `StrongMemoize` would
+ help us use such pattern.
- Instead of writing patterns like this:
+ Instead of writing patterns like this:
- ``` ruby
- class Find
- def result
- return @result if defined?(@result)
+ ``` ruby
+ class Find
+ def result
+ return @result if defined?(@result)
- @result = search
- end
+ @result = search
end
- ```
+ end
+ ```
- We could write it like:
+ We could write it like:
- ``` ruby
- class Find
- include Gitlab::Utils::StrongMemoize
+ ``` ruby
+ class Find
+ include Gitlab::Utils::StrongMemoize
- def result
- strong_memoize(:result) do
- search
- end
+ def result
+ strong_memoize(:result) do
+ search
end
end
- ```
+ end
+ ```
- Clear memoization
- ``` ruby
- class Find
- include Gitlab::Utils::StrongMemoize
- end
+ ``` ruby
+ class Find
+ include Gitlab::Utils::StrongMemoize
+ end
- Find.new.clear_memoization(:result)
- ```
+ Find.new.clear_memoization(:result)
+ ```
## [`RequestCache`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/cache/request_cache.rb)
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index fd16047b8e4..fc3d36910f2 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -1,15 +1,18 @@
---
comments: false
+type: index
---
# GitLab basics guides
-This section provides resources to help you start with GitLab by focusing on basic functionality.
+This section provides resources to help you start working with GitLab and Git by focusing
+on the basic features that you will need to use.
This documentation is split into the following groups:
- [GitLab-specific functionality](#gitlab-basics), for basic GitLab features.
-- [General Git functionality](#git-basics), for working with Git in conjunction with GitLab.
+- [General Git functionality](#working-with-git-from-the-command-line), for working
+ with Git in conjunction with GitLab.
## GitLab basics
@@ -17,22 +20,26 @@ The following are guides to basic GitLab functionality:
- [Create and add your SSH public key](create-your-ssh-keys.md), for enabling Git over SSH.
- [Create a project](create-project.md), to start using GitLab.
-- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer projects together.
+- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer
+ projects together.
- [Create a branch](create-branch.md), to make changes to files stored in a project's repository.
- [Fork a project](fork-project.md), to duplicate projects so they can be worked on in parallel.
- [Add a file](add-file.md), to add new files to a project's repository.
-- [Add an image](add-image.md), to add new images to a project's repository.
-- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue), to start collaborating within a project.
-- [Create a merge request](add-merge-request.md), to request changes made in a branch be merged into a project's repository.
-- See how these features come together in the [GitLab Flow introduction video](https://youtu.be/InKNIvky2KE) and [GitLab Flow page](../workflow/gitlab_flow.md).
+- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue),
+ to start collaborating within a project.
+- [Create a merge request](add-merge-request.md), to request changes made in a branch
+ be merged into a project's repository.
+- See how these features come together in the [GitLab Flow introduction video](https://youtu.be/InKNIvky2KE)
+ and [GitLab Flow page](../workflow/gitlab_flow.md).
-## Git basics
+## Working with Git from the command line
-If you're familiar with Git on the command line, you can interact with your GitLab projects just as you would with any other Git repository.
+If you're familiar with Git on the command line, you can interact with your GitLab
+projects just as you would with any other Git repository.
These resources will help get further acclimated to working on the command line.
- [Start using Git on the command line](start-using-git.md), for some simple Git commands.
- [Command line basics](command-line-commands.md), to create and edit files using the command line.
-More Git resources are available at GitLab's [Git documentation](../topics/git/index.md).
+More Git resources are available in GitLab's [Git documentation](../topics/git/index.md).
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index e9fbcbc23a9..41cc8bc4aeb 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -1,5 +1,91 @@
-# How to add a file
+---
+type: howto
+---
-You can create a file in your [terminal](command-line-commands.md) and push
-to GitLab or you can use the
+# Add a file to a repository
+
+Adding files to a repository is a small, but key task. Bringing files in to a repository,
+such as code, images, or documents, allows them to be tracked by Git, even though they
+may have been created elsewhere.
+
+You can add a file to a repository in your [terminal](#add-a-file-using-the-command-line), and
+then push to GitLab. You can also use the [web interface](../user/project/repository/web_editor.md#upload-a-file),
+which may be a simpler solution.
+
+If you need to create a file first, for example a `README.md` text file, that can
+also be done from the [terminal](command-line-commands.md#create-a-text-file-in-the-current-directory) or
[web interface](../user/project/repository/web_editor.md#create-a-file).
+
+## Add a file using the command line
+
+Open a [terminal/shell](command-line-commands.md), and change into the folder of your
+GitLab project. This usually means running the following command until you get
+to the desired destination:
+
+```sh
+cd <destination folder>
+```
+
+[Create a branch](create-branch.md) to add your file to, before it is added to the master
+(main) branch of the project. It is not strictly necessary, but working directly in
+the `master` branch is not recommended unless your project is very small, and you are
+the only person working on it. You can [switch to an existing branch](start-using-git.md#work-on-an-existing-branch),
+if you have one already.
+
+Using your standard tool for copying files (for example, Finder in macOS, or File Explorer
+in Windows), put the file into a directory within the GitLab project.
+
+Check if your file is actually present in the directory (if you are in Windows,
+use `dir` instead):
+
+```sh
+ls
+```
+
+You should see the name of the file in the list shown.
+
+Check the status:
+
+```sh
+git status
+```
+
+Your file's name should appear in red, so `git` took notice of it! Now add it
+to the repository:
+
+```sh
+git add <name of file>
+```
+
+Check the status again, your file's name should have turned green:
+
+```sh
+git status
+```
+
+Commit (save) your file to the repository:
+
+```sh
+git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
+```
+
+Now you can push (send) your changes (in the branch `<branch-name>`) to GitLab
+(the git remote named 'origin'):
+
+```sh
+git push origin <branch-name>
+```
+
+Your image will be added to your branch in your repository in GitLab.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/add-image.md b/doc/gitlab-basics/add-image.md
index 17c210c43e0..11a4a6bbd97 100644
--- a/doc/gitlab-basics/add-image.md
+++ b/doc/gitlab-basics/add-image.md
@@ -1,56 +1,5 @@
-# How to add an image
+---
+redirect_to: 'add-file.md'
+---
-Using your standard tool for copying files (e.g. Finder in Mac OS, or Explorer
-in Windows, or...), put the image file into the GitLab project. You can find the
-project as a regular folder in your files.
-
-Go to your [shell](command-line-commands.md), and move into the folder of your
-GitLab project. This usually means running the following command until you get
-to the desired destination:
-
-```
-cd NAME-OF-FOLDER-YOU'D-LIKE-TO-OPEN
-```
-
-Check if your image is actually present in the directory (if you are in Windows,
-use `dir` instead):
-
-```
-ls
-```
-
-You should see the name of the image in the list shown.
-
-Check the status:
-
-```
-git status
-```
-
-Your image's name should appear in red, so `git` took notice of it! Now add it
-to the repository:
-
-```
-git add NAME-OF-YOUR-IMAGE
-```
-
-Check the status again, your image's name should have turned green:
-
-```
-git status
-```
-
-Commit:
-
-```
-git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
-```
-
-Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab
-(the git remote named 'origin'):
-
-```
-git push origin NAME-OF-BRANCH
-```
-
-Your image will be added to your branch in your repository in GitLab.
+This document was moved to [another location](add-file.md).
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 7bb2e014738..1a6a26152fa 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -1,9 +1,16 @@
+---
+type: howto
+---
+
# How to create a merge request
-Merge requests are useful to integrate separate changes that you've made to a
-project, on different branches. This is a brief guide on how to create a merge
-request. For more information, check the
-[merge requests documentation](../user/project/merge_requests/index.md).
+Merge requests are how you integrate separate changes that you've made in a
+[branch](create-branch.md) to a [project](create-project.md).
+
+This is a brief guide on how to create a merge request. For more detailed information,
+check the [merge requests documentation](../user/project/merge_requests/index.md), or
+you can watch our [GitLab Flow video](https://www.youtube.com/watch?v=InKNIvky2KE) for
+a quick overview of working with merge requests.
---
@@ -12,19 +19,31 @@ request. For more information, check the
1. Go to the project where you'd like to merge your changes and click on the
**Merge requests** tab.
1. Click on **New merge request** on the right side of the screen.
-1. From there on, you have the option to select the source branch and the target
+1. From there, you have the option to select the source branch and the target
branch you'd like to compare to. The default target project is the upstream
repository, but you can choose to compare across any of its forks.
- ![Select a branch](img/merge_request_select_branch.png)
+ ![Select a branch](img/merge_request_select_branch.png)
1. When ready, click on the **Compare branches and continue** button.
1. At a minimum, add a title and a description to your merge request. Optionally,
- select a user to review your merge request and to accept or close it. You may
- also select a milestone and labels.
+ select a user to review your merge request. You may also select a milestone and
+ labels.
- ![New merge request page](img/merge_request_page.png)
+ ![New merge request page](img/merge_request_page.png)
1. When ready, click on the **Submit merge request** button.
-Your merge request will be ready to be approved and merged.
+Your merge request will be ready to be reviewed, approved, and merged.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
index c2a3415cbc4..4e0cc2de2bc 100644
--- a/doc/gitlab-basics/basic-git-commands.md
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -1,3 +1,5 @@
-# Basic Git commands
+---
+redirect_to: 'start-using-git.md'
+---
-This section is now merged into [Start using Git](start-using-git.md).
+This document was moved to [another location](start-using-git.md).
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index b8ebbbea9d4..ed70d3ce598 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -1,75 +1,47 @@
-# Command Line basic commands
-
-## Start working on your project
-
-In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab.
-
-When you are on your Dashboard, click on the project that you'd like to clone.
-To work in the project, you can copy a link to the Git repository through a SSH
-or a HTTPS protocol. SSH is easier to use after it's been
-[set up](create-your-ssh-keys.md). While you are at the **Project** tab, select
-HTTPS or SSH from the dropdown menu and copy the link using the _Copy URL to clipboard_
-button (you'll have to paste it on your shell in the next step).
-
-![Copy the HTTPS or SSH](img/project_clone_url.png)
-
-## Working with project files on the command line
-
-This section has examples of some basic shell commands that you might find useful. For more information, search the web for _bash commands_.
-
-Alternatively, you can edit files using your choice of editor (IDE) or the GitLab user interface.
-
-### Clone your project
-
-Go to your computer's shell and type the following command with your SSH or HTTPS URL:
+---
+type: howto, reference
+---
-```
-git clone PASTE HTTPS OR SSH HERE
-```
-
-A clone of the project will be created in your computer.
-
-NOTE: **Note:**
-If you clone your project via a URL that contains special characters, make sure
-that characters are URL-encoded.
-
-### Go into a project directory to work in it
-
-```
-cd NAME-OF-PROJECT
-```
+# Command Line basic commands
-### Go back one directory
+When [working with Git from the command line](start-using-git.md), you will need to
+use more than just the Git commands. There are several basic commands that you should
+learn, in order to make full use of the command line.
-```
-cd ..
-```
+## Start working on your project
-### List what’s in the current directory
+To work on a git project locally (from your own computer), with the command line,
+first you will need to [clone (copy) it](start-using-git.md#clone-a-repository) to
+your computer.
-```
-ls
-```
+## Working with files on the command line
-### List what’s in the current directory that starts with `a`
+This section has examples of some basic shell commands that you might find useful.
+For more information, search the web for _bash commands_.
-```
-ls a*
-```
+Alternatively, you can edit files using your choice of editor (IDE), or the GitLab user
+interface (not locally).
-### List what’s in the current directory that ends with `.md`
+### Common commands
-```
-ls *.md
-```
+The list below is not exhaustive, but contains many of the most commonly used commands.
-### Create a new directory
+| Command | Description |
+|--------------------------------|---------------------------------------------|
+| `cd NAME-OF-DIRECTORY` | Go into a directory to work in it |
+| `cd ..` | Go back one directory |
+| `ls` | List what’s in the current directory |
+| `ls a*` | List what’s in the current directory that starts with `a` |
+| `ls *.md` | List what’s in the current directory that ends with `.md` |
+| `mkdir NAME-OF-YOUR-DIRECTORY` | Create a new directory |
+| `cat README.md` | Display the contents of a [text file you created previously](#create-a-text-file-in-the-current-directory) |
+| `pwd` | Show the current directory |
+| `clear` | Clear the shell window |
-```
-mkdir NAME-OF-YOUR-DIRECTORY
-```
+### Create a text file in the current directory
-### Create a README.md file in the current directory
+To create a text file from the command line, for example `README.md`, follow these
+steps:
```
touch README.md
@@ -80,37 +52,37 @@ nano README.md
#### Press: enter
```
-### Show the contents of the README.md file
+### Remove a file or directory
-```
-cat README.md
-```
-
-### Remove a file
+It is easy to delete (remove) a file or directory, but be careful:
DANGER: **Danger:**
-This will permanently delete the file.
+This will **permanently** delete a file.
```
rm NAME-OF-FILE
```
-### Remove a directory and all of its contents
-
DANGER: **Danger:**
-This will permanently delete the directory and all of its contents.
+This will **permanently** delete a directory and **all** of its contents.
```
rm -r NAME-OF-DIRECTORY
```
-### View command history
+### View and Execute commands from history
+
+You can view the history of all the commands you executed from the command line,
+and then execute any of them again, if needed.
+
+First, list the commands you executed previously:
```
history
```
-### Execute command 123 from history
+Then, choose a command from the list and check the number next to the command (`123`,
+for example) . Execute the same full command with:
```
!123
@@ -118,28 +90,32 @@ history
### Carry out commands for which the account you are using lacks authority
-You will be asked for an administrator’s password.
+Not all commands can be executed from a basic user account on a computer, you may
+need administrator's rights to execute commands that affect the system, or try to access
+protected data, for example. You can use `sudo` to execute these commands, but you
+will likely be asked for an administrator password.
```
-sudo COMMAND
+sudo RESTRICTED-COMMAND
```
CAUTION: **Caution:**
Be careful of the commands you run with `sudo`. Certain commands may cause
-damage to your data and system.
+damage to your data or system.
-### Show which directory I am in
+## Sample Git taskflow
-```
-pwd
-```
-
-### Clear the shell window
+If you are completely new to Git, looking through some [sample taskflows](https://rogerdudler.github.io/git-guide/)
+will help you understand the best practices for using these commands as you work.
-```
-clear
-```
+<!-- ## Troubleshooting
-### Sample Git taskflow
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-If you are completely new to Git, looking through some [sample taskflows](https://rogerdudler.github.io/git-guide/) will help you understand best practices for using these commands as you work.
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
index ad94f0dad29..e2a2fb52af8 100644
--- a/doc/gitlab-basics/create-branch.md
+++ b/doc/gitlab-basics/create-branch.md
@@ -1,12 +1,16 @@
+---
+type: howto
+---
+
# How to create a branch
-A branch is an independent line of development.
+A branch is an independent line of development in a [project](../user/project/index.md).
-New commits are recorded in the history for the current branch, which results
-in taking the source from someone’s repository (the place where the history of
-your work is stored) at certain point in time, and apply your own changes to it
-in the history of the project.
+When you create a new branch (in your [terminal](basic-git-commands.md) or with
+[the web interface](../user/project/repository/web_editor.md#create-a-new-branch)),
+you are creating a snapshot of a certain branch, usually the main `master` branch,
+at it's current state. From there, you can start to make your own changes without
+affecting the main codebase. The history of your changes will be tracked in your branch.
-To add changes to your GitLab project, you should create a branch. You can do
-it in your [terminal](basic-git-commands.md) or by
-[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch).
+When your changes are ready, you then merge them into the rest of the codebase with a
+[merge request](add-merge-request.md).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 2caf7dbbc7a..1833b27a292 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,10 +1,13 @@
-# Create a project
+---
+type: howto
+---
-[Projects](../user/project/index.md) combine many features of GitLab together.
+# Creating projects
-NOTE: **Note:**
-For a list of words that cannot be used as project names see
-[Reserved project and group names](../user/reserved_names.md).
+Most work in GitLab is done within a [Project](../user/project/index.md). Files and
+code are saved in projects, and most features are used within the scope of projects.
+
+## Create a project in GitLab
To create a project in GitLab:
@@ -14,11 +17,14 @@ To create a project in GitLab:
- Create a [blank project](#blank-projects).
- Create a project using with one of the available [project templates](#project-templates).
- [Import a project](../user/project/import/index.md) from a different repository,
- if enabled on your GitLab instance. Contact your GitLab admin if this
- is unavailable.
+ if enabled on your GitLab instance. Contact your GitLab admin if this is unavailable.
- Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **(PREMIUM)**
-## Blank projects
+NOTE: **Note:**
+For a list of words that cannot be used as project names see
+[Reserved project and group names](../user/reserved_names.md).
+
+### Blank projects
To create a new blank project on the **New project** page:
@@ -37,23 +43,25 @@ To create a new blank project on the **New project** page:
can be cloned.
1. Click **Create project**.
-## Project templates
+### Project templates
-Project templates can pre-populate your project with necessary files to get you started quickly.
+Project templates can pre-populate a new project with the necessary files to get you
+started quickly.
There are two types of project templates:
- [Built-in templates](#built-in-templates), sourced from the following groups:
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
- [`pages`](https://gitlab.com/pages)
-- [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users.
+- [Custom project templates](#custom-project-templates-premium-only), for custom templates
+ configured by GitLab administrators and users.
-### Built-in templates
+#### Built-in templates
Built-in templates are project templates that are:
-- Developed and maintained in the
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
+- Developed and maintained in the [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
+ and [`pages`](https://gitlab.com/pages) groups.
- Released with GitLab.
To use a built-in template on the **New project** page:
@@ -62,22 +70,25 @@ To use a built-in template on the **New project** page:
1. From the list of available built-in templates, click the:
- **Preview** button to look at the template source itself.
- **Use template** button to start creating the project.
-1. Finish creating the project by filling out the project's details. The process is the same as for
- using a [blank project](#blank-projects).
+1. Finish creating the project by filling out the project's details. The process is
+ the same as creating a [blank project](#blank-projects).
TIP: **Tip:**
-You can improve the existing built-in templates or contribute new ones on the
-[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
+You can improve the existing built-in templates or contribute new ones in the
+[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and
+[`pages`](https://gitlab.com/pages) groups.
-### Custom project templates **(PREMIUM ONLY)**
+#### Custom project templates **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in
[GitLab Premium](https://about.gitlab.com/pricing) 11.2.
-Creating new projects based on custom project templates is a convenient option to bootstrap a project.
+Creating new projects based on custom project templates is a convenient option to
+bootstrap a project.
-Custom projects are available from the **Instance** or **Group** tabs under the **Create from template** tab,
-depending on the type of template.
+Custom projects are available at the [instance-level](../user/admin_area/custom_project_templates.md)
+from the **Instance** tab, or at the [group-level](../user/group/custom_project_templates.md)
+from the **Group** tab, under the **Create from template** tab.
To use a custom project template on the **New project** page:
@@ -85,23 +96,19 @@ To use a custom project template on the **New project** page:
1. From the list of available custom templates, click the:
- **Preview** button to look at the template source itself.
- **Use template** button to start creating the project.
-1. Finish creating the project by filling out the project's details. The process is the same as for
- using a [blank project](#blank-projects).
-
-For information on configuring custom project templates, see:
-
-- [Custom instance-level project templates](../user/admin_area/custom_project_templates.md), for instance-level templates.
-- [Custom group-level project templates](../user/group/custom_project_templates.md), for group-level templates.
+1. Finish creating the project by filling out the project's details. The process is
+ the same as creating a [blank project](#blank-projects).
## Push to create a new project
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5.
-When you create a new repo locally, instead of going to GitLab to manually
-create a new project and then push the repo, you can directly push it to
-GitLab to create the new project, all without leaving your terminal. If you have access to that
-namespace, we will automatically create a new project under that GitLab namespace with its
-visibility set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
+When you create a new repository locally, instead of going to GitLab to manually
+create a new project and then [clone the repo](start-using-git.md#clone-a-repository)
+locally, you can directly push it to GitLab to create the new project, all without leaving
+your terminal. If you have access rights to the associated namespace, GitLab will
+automatically create a new project under that GitLab namespace with its visibility
+set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
This can be done by using either SSH or HTTPS:
@@ -127,3 +134,15 @@ remote: To view the project, visit:
remote: https://gitlab.example.com/namespace/nonexistent-project
remote:
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index aac73d4c9c5..338b96374aa 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,22 +1,27 @@
-# Create and add your SSH public key
+---
+type: howto
+---
-This topic describes how to:
+# Create and add your SSH public key
-- Create an SSH key pair to use with GitLab.
-- Add the SSH public key to your GitLab account.
+It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
+In order to use SSH, you will need to
-You do this to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
+1. [Create an SSH key pair](#creating-your-ssh-key-pair) on your local computer.
+1. [Add the key to GitLab](#adding-your-ssh-public-key-to-gitlab).
## Creating your SSH key pair
-1. Go to your [command line](start-using-git.md).
-1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate your SSH key pair.
+1. Go to your [command line](start-using-git.md#open-a-shell).
+1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate
+ your SSH key pair.
## Adding your SSH public key to GitLab
-To add the SSH public key to GitLab,
-see [Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
+To add the SSH public key to GitLab, see
+[Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
NOTE: **Note:**
-Once you add a key, you cannot edit it. If the paste
-[didn't work](../ssh/README.md#testing-that-everything-is-set-up-correctly), you need to remove the key and re-add it.
+Once you add a key, you cannot edit it. If it didn't paste properly, it
+[will not work](../ssh/README.md#testing-that-everything-is-set-up-correctly), and
+you will need to remove the key from GitLab and try adding it again.
diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md
index a128a7c7dd3..5c19985121d 100644
--- a/doc/gitlab-basics/fork-project.md
+++ b/doc/gitlab-basics/fork-project.md
@@ -1,20 +1,11 @@
+---
+type: howto
+---
+
# How to fork a project
A fork is a copy of an original repository that you put in another namespace
where you can experiment and apply changes that you can later decide whether or
not to share, without affecting the original project.
-It takes just a few steps to fork a project in GitLab.
-
-1. Go to a project's dashboard under the **Project** tab and click on the
- **Fork** button.
-
- ![Click on Fork button](img/fork_new.png)
-
-1. You will be asked where to fork the repository. Click on the user or group
- to where you'd like to add the forked project.
-
- ![Choose namespace](img/fork_choose_namespace.png)
-
-1. After a few moments, depending on the repository's size, the forking will
- complete.
+It takes just a few steps to [fork a project in GitLab](../workflow/forking_workflow.md#creating-a-fork).
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index b3c5d32f2f5..3e3f96fb31f 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,38 +1,43 @@
+---
+type: howto, tutorial
+---
+
# Start using Git on the command line
-If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab.
+While GitLab has a powerful user interface, if you want to use Git itself, you will
+have to do so from the command line. If you want to start using Git and GitLab together,
+make sure that you have created and/or signed into an account on GitLab.
## Open a shell
-Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions:
-
-- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
+Depending on your operating system, you will need to use a shell of your preference.
+Here are some suggestions:
+- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on macOS
- [GitBash](https://msysgit.github.io) on Windows
-
- [Linux Terminal](http://www.howtogeek.com/140679/beginner-geek-how-to-start-using-the-linux-terminal/) on Linux
## Check if Git has already been installed
-Git is usually preinstalled on Mac and Linux.
-
-Type the following command and then press enter:
+Git is usually preinstalled on Mac and Linux, so run the following command:
```bash
git --version
```
-You should receive a message that tells you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+You should receive a message that tells you which Git version you have on your computer.
+If you don’t receive a "Git version" message, it means that you need to
+[download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
-If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
-
-After you are finished installing Git, open a new shell and type `git --version` again to verify that it was correctly installed.
+After you are finished installing Git, open a new shell and type `git --version` again
+to verify that it was correctly installed.
## Add your Git username and set your email
-It is important to configure your Git username and email address, since every Git commit will use this information to identify you as the author.
+It is important to configure your Git username and email address, since every Git
+commit will use this information to identify you as the author.
-On your shell, type the following command to add your username:
+In your shell, type the following command to add your username:
```bash
git config --global user.name "YOUR_USERNAME"
@@ -56,7 +61,10 @@ To verify that you entered your email correctly, type:
git config --global user.email
```
-You'll need to do this only once, since you are using the `--global` option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the `--global` option when you’re in that project.
+You'll need to do this only once, since you are using the `--global` option. It tells
+Git to always use this information for anything you do on that system. If you want
+to override this with a different username or email address for specific projects,
+you can run the command without the `--global` option when you’re in that project.
## Check your information
@@ -68,12 +76,12 @@ git config --global --list
## Basic Git commands
-Start using Git via the command line with the most basic
-commands as described below.
+Start using Git via the command line with the most basic commands as described below.
-## Initialize a local directory for Git version control
+### Initialize a local directory for Git version control
-If you have an existing local directory that you want to *initialize* for version control, use the `init` command to instruct Git to begin tracking the directory:
+If you have an existing local directory that you want to *initialize* for version
+control, use the `init` command to instruct Git to begin tracking the directory:
```bash
git init
@@ -81,34 +89,33 @@ git init
This creates a `.git` directory that contains the Git configuration files.
-Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository) and [send changes to GitLab.com](#send-changes-to-gitlabcom). View the instructions on [Create a project](../gitlab-basics/create-project.html#push-to-create-a-new-project) to create a new project on GitLab with your changes.
+Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository)
+and [send changes to GitLab.com](#send-changes-to-gitlabcom). You will also need to
+[create a new project in GitLab](../gitlab-basics/create-project.html#push-to-create-a-new-project)
+for your Git repository.
### Clone a repository
-To start working locally on an existing remote repository,
-clone it with the command `git clone <repository path>`.
-By cloning a repository, you'll download a copy of its
-files into your local computer, preserving the Git
-connection with the remote repository.
+To start working locally on an existing remote repository, clone it with the command
+`git clone <repository path>`. By cloning a repository, you'll download a copy of its
+files to your local computer, automatically preserving the Git connection with the
+remote repository.
-You can either clone it via HTTPS or [SSH](../ssh/README.md).
-If you chose to clone it via HTTPS, you'll have to enter your
-credentials every time you pull and push. With SSH, you enter
-your credentials once and can pull and push straightaway.
+You can either clone it via HTTPS or [SSH](../ssh/README.md). If you chose to clone
+it via HTTPS, you'll have to enter your credentials every time you pull and push.
+With SSH, you enter your credentials only once.
-You can find both paths (HTTPS and SSH) by navigating to
-your project's landing page and clicking **Clone**. GitLab
-will prompt you with both paths, from which you can copy
+You can find both paths (HTTPS and SSH) by navigating to your project's landing page
+and clicking **Clone**. GitLab will prompt you with both paths, from which you can copy
and paste in your command line.
-As an example, consider a repository path:
+As an example, consider this repository path:
- HTTPS: `https://gitlab.com/gitlab-org/gitlab-ce.git`
-- SSH: `` git@gitlab.com:gitlab-org/gitlab-ce.git ``
+- SSH: `git@gitlab.com:gitlab-org/gitlab-ce.git`
-To get started, open a terminal window in the directory
-you wish to clone the repository files into, and run one
-of the following commands.
+To get started, open a terminal window in the directory you wish to clone the repository
+files into, and run one of the following commands.
Clone via HTTPS:
@@ -122,13 +129,15 @@ Clone via SSH:
git clone git@gitlab.com:gitlab-org/gitlab-ce.git
```
-Both commands will download a copy of the files in a
-folder named after the project's name.
-
-You can then navigate to the directory and start working
+Both commands will download a copy of the files in a folder named after the project's
+name. You can then navigate to the directory and start working
on it locally.
-### Go to the master branch to pull the latest changes from there
+### Switch to the master branch
+
+You are always in a branch when working with Git. The main branch is the master branch,
+but you can use the same command to switch to a different branch by changing `master`
+to the branch name.
```bash
git checkout master
@@ -136,13 +145,20 @@ git checkout master
### Download the latest changes in the project
-This is for you to work on an up-to-date copy (it is important to do this every time you start working on a project), while you set up tracking branches. You pull from remote repositories to get all the changes made by users since the last time you cloned or pulled the project. Later, you can push your local commits to the remote repositories.
+To work on an up-to-date copy of the project (it is important to do this every time
+you start working on a project), you `pull` to get all the changes made by users since
+the last time you cloned or pulled the project. Use `master` for the `<name-of-branch>`
+to get the main branch code, or the branch name of the branch you are currently working
+in.
```bash
-git pull REMOTE NAME-OF-BRANCH
+git pull REMOTE <name-of-branch>
```
-When you first clone a repository, REMOTE is typically "origin". This is where the repository came from, and it indicates the SSH or HTTPS URL of the repository on the remote server. NAME-OF-BRANCH is usually "master", but it may be any existing branch.
+When you first clone a repository, REMOTE is typically `origin`. This is where the
+repository was cloned from, and it indicates the SSH or HTTPS URL of the repository
+on the remote server. `<name-of-branch>` is usually `master`, but it may be any existing
+branch.
### View your remote repositories
@@ -157,17 +173,20 @@ git remote -v
To add a link to a remote repository:
```bash
-git remote add SOURCE-NAME REPOSITORY-PATH
+git remote add <source-name> <repository-path>
```
-You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom), so use something easy to remember and type.
+You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom),
+so use something easy to remember and type.
### Create a branch
-To create a branch, type the following (spaces won't be recognized in the branch name, so you will need to use a hyphen or underscore):
+To create a new branch, to work from without affecting the `master` branch, type the
+following (spaces won't be recognized in the branch name, so you will need to use a
+hyphen or underscore):
```bash
-git checkout -b NAME-OF-BRANCH
+git checkout -b <name-of-branch>>
```
### Work on an existing branch
@@ -175,12 +194,14 @@ git checkout -b NAME-OF-BRANCH
To switch to an existing branch, so you can work on it:
```bash
-git checkout NAME-OF-BRANCH
+git checkout <name-of-branch>
```
### View the changes you've made
-It's important to be aware of what's happening and the status of your changes. When you add, change, or delete files/folders, Git knows about it. To check the status of your changes:
+It's important to be aware of what's happening and the status of your changes. When
+you add, change, or delete files/folders, Git knows about it. To check the status of
+your changes:
```bash
git status
@@ -188,7 +209,8 @@ git status
### View differences
-To view the differences between your local, unstaged changes and the repository versions that you cloned or pulled, type:
+To view the differences between your local, unstaged changes and the repository versions
+that you cloned or pulled, type:
```bash
git diff
@@ -196,16 +218,19 @@ git diff
### Add and commit local changes
-You'll see your local changes in red when you type `git status`. These changes may be new, modified, or deleted files/folders. Use `git add` to stage a local file/folder for committing. Then use `git commit` to commit the staged files:
+You'll see any local changes in red when you type `git status`. These changes may
+be new, modified, or deleted files/folders. Use `git add` to first stage (prepare)
+a local file/folder for committing. Then use `git commit` to commit (save) the staged
+files:
```bash
-git add FILE OR FOLDER
+git add <file-name OR folder-name>
git commit -m "COMMENT TO DESCRIBE THE INTENTION OF THE COMMIT"
```
### Add all changes to commit
-To add and commit all local changes in one command:
+To add and commit (save) all local changes quickly:
```bash
git add .
@@ -217,35 +242,32 @@ The `.` character typically means _all_ in Git.
### Send changes to GitLab.com
-To push all local commits to the remote repository:
+To push all local commits (saved changes) to the remote repository:
```bash
-git push REMOTE NAME-OF-BRANCH
+git push <remote> <name-of-branch>
```
-For example, to push your local commits to the _master_ branch of the _origin_ remote:
+For example, to push your local commits to the _`master`_ branch of the _`origin`_ remote:
```bash
git push origin master
```
-### Delete all changes in the Git repository
+### Delete all changes in the branch
-To delete all local changes in the repository that have not been added to the staging area, and leave unstaged files/folders, type:
+To delete all local changes in the branch that have not been added to the staging
+area, and leave unstaged files/folders, type:
```bash
git checkout .
```
-### Delete all untracked changes in the Git repository
-
-```bash
-git clean -f
-```
+Note that this removes *changes* to files, not the files themselves.
### Unstage all changes that have been added to the staging area
-To undo the most recent add, but not committed, files/folders:
+To undo the most recently added, but not committed, changes to files/folders:
```bash
git reset .
@@ -259,25 +281,31 @@ To undo the most recent commit, type:
git reset HEAD~1
```
-This leaves the files and folders unstaged in your local repository.
+This leaves the changed files and folders unstaged in your local repository.
CAUTION: **Warning:**
-A Git commit is mostly irreversible, particularly if you already pushed it to the remote repository. Although you can undo a commit, the best option is to avoid the situation altogether.
+A Git commit should not usually be reverse, particularly if you already pushed it
+to the remote repository. Although you can undo a commit, the best option is to avoid
+the situation altogether by working carefully.
-### Merge created branch with master branch
+### Merge a branch with master branch
-You need to be in the created branch.
+When you are ready to make all the changes in a branch a permanent addition to
+the master branch, you `merge` the two together:
```bash
-git checkout NAME-OF-BRANCH
+git checkout <name-of-branch>
git merge master
```
-### Merge master branch with created branch
+<!-- ## Troubleshooting
-You need to be in the master branch.
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-```bash
-git checkout master
-git merge NAME-OF-BRANCH
-```
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 06ec00cecc4..46ee980841b 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -6,7 +6,7 @@ type: howto
This is the official installation guide to set up a production GitLab server
using the source files. To set up a **development installation** or for many
-other installation options, see the [main installation page](index.md).
+other installation options, see the [main installation page](README.md).
It was created for and tested on **Debian/Ubuntu** operating systems.
Read [requirements.md](requirements.md) for hardware and operating system requirements.
If you want to install on RHEL/CentOS, we recommend using the
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index 50cb3d50009..6755640d39c 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -35,6 +35,9 @@ and [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF
For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/).
+NOTE: **Moving from a traditional CI plug-in to a single application for the entire software development lifecycle can decrease hours spent on maintaining toolchains by 10% or more.**
+Visit the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html) to learn how our built-in CI compares to Jenkins.
+
## Requirements
- [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index d04428fdbfe..ea4702acc41 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -37,7 +37,7 @@ visibility setting keep this setting. You can read more about the change in the
### Private projects
-Private projects can only be cloned and viewed by project members.
+Private projects can only be cloned and viewed by project members (except for guests).
They will appear in the public access directory (`/public`) for project members only.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 6a810757a28..10b4d9d4c7c 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -52,14 +52,15 @@ The following languages and dependency managers are supported.
| Language (package managers) | Supported | Scan tool(s) |
|----------------------------- | --------- | ------------ |
+| Java ([Gradle](https://gradle.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/13075 "Dependency Scanning for Gradle" )) | not available |
+| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) |
+| Go ([Golang](https://golang.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
+| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
+| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
+| Python ([poetry](https://poetry.eustace.io/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
| Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) |
-| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
-| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
-| Python ([poetry](https://poetry.eustace.io/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
-| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
-| Go ([Golang](https://golang.org/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
## Remote checks
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index e1a6f8fcf71..1bc43f4b7a4 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -95,7 +95,7 @@ CI/CD](../../ci/README.md), 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](../project/clusters/index.md/#security-implications) before doing so.
+implications](../project/clusters/index.md#security-implications) before doing so.
NOTE: **Note:**
The
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index bc74725bbc9..64ac4f15d5f 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -78,7 +78,7 @@ During this configuration, note the following:
- It is recommended to set a notification email and check the **Send an email notification when a failure occurs** checkbox.
- For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled.
-You can then test the connection by clicking on **Test Connection**. If the connection is successful, be sure to save your configuration before moving on.
+You can then test the connection by clicking on **Test Connection**. If the connection is successful, be sure to save your configuration before moving on. See below for [troubleshooting](#troubleshooting).
#### Configure attribute mapping
@@ -118,14 +118,22 @@ You can then test the connection by clicking on **Test Connection**. If the conn
Once enabled, the synchronization details and any errors will appear on the
bottom of the **Provisioning** screen, together with a link to the audit logs.
-<!-- ## Troubleshooting
+## Troubleshooting
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+### Testing Azure connection: invalid credentials
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+When testing the connection, you may encounter an error: **You appear to have entered invalid credentials. Please confirm you are using the correct information for an administrative account**. If `Tenant URL` and `secret token` are correct, check whether your group path contains characters that may be considered invalid JSON primitives (such as `.`). Removing such characters from the group path typically resolves the error.
+
+### Azure: (Field) can't be blank sync error
+
+When checking the Audit Logs for the Provisioning, you can sometimes see the
+error `Namespace can't be blank, Name can't be blank, and User can't be blank.`
+
+This is likely caused because not all required fields (such as first name and
+last name) are present for all users being mapped.
+
+As a workaround, try an alternate mapping:
+
+1. Follow the Azure mapping instructions from above.
+1. Delete the `name.formatted` target attribute entry.
+1. Change the `displayName` source attribute to have `name.formatted` target attribute.
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index cbee79de493..5b0954195f8 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Deleting a User account
Users can be deleted from a GitLab instance, either by:
@@ -64,8 +68,20 @@ for such records. Any commits made by a deleted user will still display the
username of the original user.
When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md)
-or spam log, these associated records are not ghosted and will be removed, along
-with any groups the user is a sole owner of.
-
-Administrators can also request this behavior when deleting users from the
-[API](../../../api/users.md#user-deletion) or the Admin Area.
+or spam log, these associated
+records are not ghosted and will be removed, along with any groups the user
+is a sole owner of. Administrators can also request this behavior when
+deleting users from the [API](../../../api/users.md#user-deletion) or the
+Admin Area.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index d3a634c7b9d..e2b1a20a605 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Two-Factor Authentication
Two-factor Authentication (2FA) provides an additional level of security to your
@@ -15,7 +19,7 @@ When you enable 2FA, don't forget to back up your [recovery codes](#recovery-cod
In addition to time-based one time passwords (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
+enabled, in addition to supplying your username and password to log in, 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.
@@ -238,3 +242,15 @@ Sign in and re-enable two-factor authentication as soon as possible.
- The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md
index 28e3f4904a9..2d7bd25fc27 100644
--- a/doc/user/profile/active_sessions.md
+++ b/doc/user/profile/active_sessions.md
@@ -1,14 +1,31 @@
+---
+type: howto
+---
+
# Active Sessions
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17867)
> in GitLab 10.8.
GitLab lists all devices that have logged into your account. This allows you to
-review the sessions.
+review the sessions, and revoke any you don't recognize.
## Listing all active sessions
-1. On the upper right corner, click on your avatar and go to your **Settings**.
-1. Navigate to the **Active Sessions** tab.
+1. Click your avatar.
+1. Select **Settings**.
+1. Click **Active Sessions** in the sidebar.
![Active sessions list](img/active_sessions_list.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index fc5eb8c7b56..06710065dfd 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -1,7 +1,12 @@
+---
+type: index, howto
+---
+
# User account
-When signed into their GitLab account, users can customize their
-experience according to the best approach to their cases.
+Each GitLab account has a user profile, and settings. Your [profile](#user-profile)
+contains information about you, and your GitLab activity. Your [settings](#profile-settings)
+allow you to customize some aspects of GitLab to suit yourself.
## Signing in
@@ -10,8 +15,10 @@ See the [authentication topic](../../topics/authentication/index.md) for more de
## User profile
-Your profile is available from the up-right corner menu bar (user's avatar) > **Profile**,
-or from `https://example.gitlab.com/username`.
+To access your profile:
+
+1. Click on your avatar.
+1. Select **Profile**.
On your profile page, you will see the following information:
@@ -24,8 +31,10 @@ On your profile page, you will see the following information:
## Profile settings
-You can edit your account settings by navigating from the up-right corner menu bar
-(user's avatar) > **Settings**, or visiting `https://example.gitlab.com/profile`.
+To access your profile settings:
+
+1. Click on your avatar.
+1. Select **Settings**.
From there, you can:
@@ -56,8 +65,8 @@ before proceeding.
To change your `username`:
1. Navigate to your [profile's](#profile-settings) **Settings > Account**.
-1. Enter a new username under "Change username".
-1. Hit **Update username**.
+1. Enter a new username under **Change username**.
+1. Click **Update username**.
CAUTION: **Caution:**
It is currently not possible to change your username if it contains a
@@ -86,12 +95,15 @@ The following information will be hidden from the user profile page (`https://gi
To enable private profile:
-1. Navigate to your personal [profile settings](#profile-settings).
-1. Check the "Private profile" option.
-1. Hit **Update profile settings**.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Check the **Private profile** option in the **Main settings** section.
+1. Click **Update profile settings**.
NOTE: **Note:**
-You and GitLab admins can see your the abovementioned information on your profile even if it is private.
+All your profile information can be seen by yourself, and GitLab admins, even if
+the **Private profile** option is enabled.
## Add details of external accounts
@@ -99,9 +111,15 @@ GitLab allows you to add links to certain other external accounts you might have
To add links to other accounts:
-1. Navigate to your **User Settings > Profile**.
-1. In the **Main settings** section, locate and fill out fields for links to external accounts like Skype and Twitter.
-1. Click the **Update profile settings** button.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Complete the desired fields for external accounts, in the **Main settings**
+ section:
+ - Skype
+ - Twitter
+ - LinkedIn
+1. Click **Update profile settings**.
## Private contributions
@@ -111,9 +129,11 @@ Enabling private contributions will include contributions to private projects, i
To enable private contributions:
-1. Navigate to your personal [profile settings](#profile-settings).
-1. Check the "Private contributions" option.
-1. Hit **Update profile settings**.
+1. Click on your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Check the **Private contributions** option.
+1. Click **Update profile settings**.
## Current status
@@ -124,22 +144,24 @@ This may be helpful when you are out of office or otherwise not available.
Other users can then take your status into consideration when responding to your issues or assigning work to you.
Please be aware that your status is publicly visible even if your [profile is private](#private-profile).
+Status messages are restricted to 100 characters of plain text.
+They may however contain emoji codes such as `I'm on vacation :palm_tree:`.
+
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.
+1. Click your avatar.
+1. Click **Set status**, or **Edit status** if you have already set a status.
+1. Set the desired emoji and/or status message.
+1. Click **Set status**. Alternatively, you can click **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.
-1. Hit **Update profile settings**.
-
-Status messages are restricted to 100 characters of plain text.
-They may however contain emoji codes such as `I'm on vacation :palm_tree:`.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Enter your status message in the **Your status** text field.
+1. Click **Add status emoji** (smiley face), and select the desired emoji.
+1. Click **Update profile settings**.
You can also set your current status [using the API](../../api/users.md#user-status).
@@ -153,34 +175,36 @@ Any of your own verified email addresses can be used as the commit email.
To change your commit email:
-1. Click on your avatar at the top-right corner of the navigation bar.
-1. From the menu that appears, click **Settings**.
-1. In the **Main settings** section, locate **Commit email** dropdown.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Click **Commit email** dropdown.
1. Select any of the verified emails.
-1. Press **Update profile settings**.
+1. Click **Update profile settings**.
### Private commit email
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22560) in GitLab 11.5.
-GitLab provides users with an automatically generated private commit email option,
-which allows the user to not make their email information public.
+GitLab provides the user with an automatically generated private commit email option,
+which allows the user to keep their email information private.
To enable this option:
-1. Click on your avatar at the top-right corner of the navigation bar.
-1. From the menu that appears, click **Settings**.
-1. In the **Main settings** section, locate **Commit email** dropdown.
-1. Select the "Use a private email" option.
-1. Press **Update profile settings**.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Click **Commit email** dropdown.
+1. Select **Use a private email** option.
+1. Click **Update profile settings**.
Once this option is enabled, every Git-related action will be performed using the private commit email.
-In order to stay fully anonymous, you can also copy this private commit email
+To stay fully anonymous, you can also copy this private commit email
and configure it on your local machine using the following command:
```sh
-git config --global user.email <YOUR_PRIVATE_COMMIT_EMAIL>
+git config --global user.email <your email address>
```
## Troubleshooting
@@ -203,3 +227,15 @@ to get you a new `_gitlab_session` and keep you signed in through browser restar
After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
you will be asked to sign in again to verify your identity (which is for security reasons).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 0b224fc7e01..248188a6457 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Personal access tokens
> [Introduced][ce-3749] in GitLab 8.8.
@@ -52,3 +56,15 @@ the following table.
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#personal-access-tokens
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index b1fde3b577b..5bd4b263a58 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -1,11 +1,17 @@
+---
+type: concepts, howto
+---
+
# Profile preferences
A user's profile preferences page allows the user to customize various aspects
of GitLab to their liking.
-To navigate to your profile's preferences, click your avatar icon in the top
-right corner, select **Settings** and then choose **Preferences** from the
-left sidebar.
+To navigate to your profile's preferences:
+
+1. Click your avatar.
+1. Select **Settings**.
+1. Click **Preferences** in the sidebar.
## Navigation theme
@@ -15,7 +21,7 @@ and left side navigation.
Using individual color themes might help you differentiate between your different
GitLab instances.
-The default palette is Indigo. You can choose between 10 different themes:
+The default theme is Indigo. You can choose between 10 themes:
- Indigo
- Light Indigo
@@ -39,7 +45,7 @@ for syntax highlighting. For a list of supported languages visit the rouge websi
Changing this setting allows you to customize the color theme when viewing any
syntax highlighted code on GitLab.
-The default syntax theme is White, and you can choose among 5 different colors:
+The default syntax theme is White, and you can choose among 5 different themes:
- White
- Dark
@@ -102,7 +108,7 @@ Select your preferred language from a list of supported languages.
### First day of the week
-The first day of the week can be customised for calendar views and date pickers.
+The first day of the week can be customized for calendar views and date pickers.
You can choose one of the following options as the first day of the week:
@@ -111,3 +117,15 @@ You can choose one of the following options as the first day of the week:
- Monday
If you select **System Default**, the system-wide default setting will be used.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 918944d72a1..f0d80dad94f 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -620,8 +620,16 @@ use an A record. If your external endpoint is a hostname, use a CNAME record.
## Deploying to a Kubernetes cluster
-A Kubernetes cluster can be the destination for a deployment job using special
-[deployment variables](#deployment-variables).
+A Kubernetes cluster can be the destination for a deployment job. If
+
+- The cluster is integrated with GitLab, special
+ [deployment variables](#deployment-variables) are made available to your job
+ and configuration is not required. You can immediately begin interacting with
+ the cluster from your jobs using tools such as `kubectl` or `helm`.
+- You don't use GitLab's cluster integration you can still deploy to your
+ cluster. However, you will need configure Kubernetes tools yourself
+ using [environment variables](../../../ci/variables/README.md#creating-a-custom-environment-variable)
+ before you can interact with the cluster from your jobs.
### Deployment variables
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
index d3f53c09fb3..27ce3a814f9 100644
--- a/doc/user/project/issues/csv_export.md
+++ b/doc/user/project/issues/csv_export.md
@@ -72,4 +72,4 @@ Data will be encoded with a comma as the column delimiter, with `"` used to quot
## Limitations
-As the issues will be sent as an email attachment, there is a limit on how much data can be exported. Currently this limit is 20MB to ensure successful delivery across a range of email providers. If this limit is reached we suggest narrowing the search before export, perhaps by exporting open and closed issues separately.
+As the issues will be sent as an email attachment, there is a limit on how much data can be exported. Currently this limit is 15MB to ensure successful delivery across a range of email providers. If this limit is reached we suggest narrowing the search before export, perhaps by exporting open and closed issues separately.
diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
index ba120783430..0fe86e6f410 100644
--- a/doc/user/project/issues/sorting_issue_lists.md
+++ b/doc/user/project/issues/sorting_issue_lists.md
@@ -9,7 +9,7 @@ similar to [issue boards](../issue_board.md#issue-ordering-in-a-list).
## Manual sorting
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/62178) in GitLab 12.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/62178) in GitLab 12.2.
When you select **Manual** sorting, you can change
the order by dragging and dropping the issues. The changed order will persist. Everyone who visits the same list will see the reordered list, with some exceptions.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 494dd539da7..03ae24242e3 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -90,8 +90,7 @@ to steal the tokens of other jobs.
Since 9.0 [pipeline triggers][triggers] do support the new permission model.
The new triggers do impersonate their associated user including their access
-to projects and their project permissions. To migrate trigger to use new permission
-model use **Take ownership**.
+to projects and their project permissions.
## Before GitLab 8.12
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 40fba8fb111..b8e0ef8d12f 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -9,7 +9,8 @@ and commits that are usually done by clicking buttons or dropdowns in GitLab's U
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
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.
+
+> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26672), an alert is displayed when a quick action is successfully applied.
## Quick Actions for issues and merge requests
diff --git a/doc/user/project/repository/img/compare_branches.png b/doc/user/project/repository/branches/img/compare_branches.png
index 52d5c518c45..52d5c518c45 100644
--- a/doc/user/project/repository/img/compare_branches.png
+++ b/doc/user/project/repository/branches/img/compare_branches.png
Binary files differ
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index a81c9197ec1..a864665602e 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -1,17 +1,41 @@
+---
+type: concepts, howto
+---
+
# Branches
-Read through GiLab's branching documentation:
+A branch is a version of a project's working tree. You create a branch for each
+set of related changes you make. This keeps each set of changes separate from
+each other, allowing changes to be made in parallel, without affecting each
+other.
+
+After pushing your changes to a new branch, you can:
+
+- Create a [merge request](../../merge_requests/index.md)
+- Perform inline code review
+- [Discuss](../../../discussions/index.md) your implementation with your team
+- Preview changes submitted to a new branch with [Review Apps](../../../../ci/review_apps/index.md).
+
+With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
+[approval](../../merge_requests/merge_request_approvals.md) from your managers.
+
+For more information on managing branches using the GitLab UI, see:
+
+- [Default branches](#default-branch)
+- [Create a branch](../web_editor.md#create-a-new-branch)
+- [Protected branches](../../protected_branches.md#protected-branches)
+- [Delete merged branches](#delete-merged-branches)
+- [Branch filter search box](#branch-filter-search-box)
+
+You can also manage branches using the
+[command line](../../../../gitlab-basics/start-using-git.md#create-a-branch).
-- [Create a branch](../web_editor.md#create-a-new-branch).
-- [Default branch](#default-branch).
-- [Protected branches](../../protected_branches.md#protected-branches).
-- [Delete merged branches](#delete-merged-branches).
-- [Branch filter search box](#branch-filter-search-box).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>Watch the video [GitLab Flow](https://www.youtube.com/watch?v=InKNIvky2KE).
See also:
- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
-- [GitLab Flow](../../../../university/training/gitlab_flow.md). Use the best of GitLab for your branching strategies.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md) documentation.
- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
@@ -29,6 +53,17 @@ The default branch is also protected against accidental deletion. Read through
the documentation on [protected branches](../../protected_branches.md#protected-branches)
to learn more.
+## Compare
+
+To compare branches in a repository:
+
+1. Navigate to your project's repository.
+1. Select **Repository > Compare** in the sidebar.
+1. Select branches to compare using the [branch filter search box](#branch-filter-search-box)
+1. Click **Compare** to view the changes inline:
+
+![compare branches](img/compare_branches.png)
+
## Delete merged branches
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449) in GitLab 8.14.
@@ -57,3 +92,15 @@ Sometimes when you have hundreds of branches you may want a more flexible matchi
- `^feature` will only match branch names that begin with 'feature'.
- `feature$` will only match branch names that end with 'feature'.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index 3b0a045ef9c..b929c6a681a 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -1,38 +1,39 @@
-# Signing commits with GPG
+---
+type: concepts, howto
+---
-NOTE: **Note:**
-The term GPG is used for all OpenPGP/PGP/GPG related material and
-implementations.
+# Signing commits with GPG
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546) in GitLab 9.5.
> - Subkeys support was added in GitLab 10.1.
-GitLab can show whether a commit is verified or not when signed with a GPG key.
-All you need to do is upload the public GPG key in your profile settings.
+You can use a GPG key to sign Git commits made in a GitLab repository. Signed
+commits are labeled **Verified** if the identity of the committer can be
+verified. To verify the identity of a committer, GitLab requires their public
+GPG key.
-GPG verified tags are not supported yet.
+NOTE: **Note:**
+The term GPG is used for all OpenPGP/PGP/GPG related material and
+implementations.
-## Getting started with GPG
+GPG verified tags are not supported yet.
-Here are a few guides to get you started with GPG:
-
-- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
-- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
-- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
-- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
+See the [further reading](#further-reading) section for more details on GPG.
## How GitLab handles GPG
GitLab uses its own keyring to verify the GPG signature. It does not access any
public key server.
-In order to have a commit verified on GitLab the corresponding public key needs
-to be uploaded to GitLab. For a signature to be verified three conditions need
-to be met:
+For a commit to be verified by GitLab:
-1. The public key needs to be added your GitLab account
-1. One of the emails in the GPG key matches a **verified** email address you use in GitLab
-1. The committer's email matches the verified email from the gpg key
+- The committer must have a GPG public/private key pair.
+- The committer's public key must have been uploaded to their GitLab
+ account.
+- One of the emails in the GPG key must match a **verified** email address
+ used by the committer in GitLab.
+- The committer's email address must match the verified email address from the
+ GPG key.
## Generating a GPG key
@@ -65,8 +66,7 @@ started:
Your selection? 1
```
-1. The next question is key length. We recommend to choose the highest value
- which is `4096`:
+1. The next question is key length. We recommend you choose `4096`:
```
RSA keys may be between 1024 and 4096 bits long.
@@ -74,8 +74,8 @@ started:
Requested keysize is 4096 bits
```
-1. Next, you need to specify the validity period of your key. This is something
- subjective, and you can use the default value which is to never expire:
+1. Specify the validity period of your key. This is something
+ subjective, and you can use the default value, which is to never expire:
```
Please specify how long the key should be valid.
@@ -94,9 +94,9 @@ started:
Is this correct? (y/N) y
```
-1. Enter you real name, the email address to be associated with this key (should
- match a verified email address you use in GitLab) and an optional comment
- (press <kbd>Enter</kbd> to skip):
+1. Enter your real name, the email address to be associated with this key
+ (should match a verified email address you use in GitLab) and an optional
+ comment (press <kbd>Enter</kbd> to skip):
```
GnuPG needs to construct a user ID to identify your key.
@@ -270,3 +270,24 @@ via [push rules](../../../../push_rules/push_rules.md).
## GPG signing API
Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit).
+
+## Further reading
+
+For more details about GPG, see:
+
+- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
+- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
+- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
+- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 5b5685b3418..84d63d29929 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Repository
A [repository](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository)
@@ -111,33 +115,7 @@ GitLab.
## Branches
-When you submit changes in a new [branch](branches/index.md), you create a new version
-of that project's file tree. Your branch contains all the changes
-you are presenting, which are detected by Git line by line.
-
-To continue your workflow, once you pushed your changes to a new branch,
-you can create a [merge request](../merge_requests/index.md), perform
-inline code review, and [discuss](../../discussions/index.md)
-your implementation with your team.
-You can live preview changes submitted to a new branch with
-[Review Apps](../../../ci/review_apps/index.md).
-
-With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
-[approval](../merge_requests/merge_request_approvals.md) from your managers.
-
-To create, delete, and view [branches](branches/index.md) via GitLab's UI:
-
-- [Default branches](branches/index.md#default-branch)
-- [Create a branch](web_editor.md#create-a-new-branch)
-- [Protected branches](../protected_branches.md#protected-branches)
-- [Delete merged branches](branches/index.md#delete-merged-branches)
-- [Branch filter search box](branches/index.md#branch-filter-search-box)
-
-Alternatively, you can use the
-[command line](../../../gitlab-basics/start-using-git.md#create-a-branch).
-
-To learn more about branching strategies read through the
-[GitLab Flow](../../../university/training/gitlab_flow.md) documentation.
+For details, see [Branches](branches/index.md).
## Commits
@@ -213,14 +191,6 @@ detected, add the following to `.gitattributes` in the root of your repository.
> *.proto linguist-detectable=true
-## Compare
-
-Select branches to compare using the [branch filter search box](branches/index.md#branch-filter-search-box), then click the **Compare** button to view the changes inline:
-
-![compare branches](img/compare_branches.png)
-
-Find it under your project's **Repository > Compare**.
-
## Locked files **(PREMIUM)**
Use [File Locking](../file_lock.md) to
@@ -256,3 +226,15 @@ By clicking the download icon, a dropdown will open with links to download the f
`tar`, `tar.gz`, and `tar.bz2`.
- **Artifacts:**
allows users to download the artifacts of the latest CI build.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 7c711bc0b3b..3adf66e4b6f 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Reducing the repository size using Git
A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md)
@@ -139,3 +143,15 @@ purposes!
```
Your repository should now be below the size limit.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 0116f0fe7ca..09a5cdabc00 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# GitLab Web Editor
Sometimes it's easier to make quick changes directly from the GitLab interface
@@ -169,3 +173,15 @@ through the web editor, you can choose to use another of your linked email
addresses from the **User Settings > Edit Profile** page.
[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 4275d911708..aa9e879160d 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -59,7 +59,7 @@ module API
optional :grafana_url, type: String, desc: 'Grafana URL'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help'
- optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page'
+ optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page and help dropdown'
optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 0e829c5699b..eeecc390256 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -112,27 +112,6 @@ module API
end
end
- desc 'Take ownership of trigger' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- end
- post ':id/triggers/:trigger_id/take_ownership' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- if trigger.update(owner: current_user)
- status :ok
- present trigger, with: Entities::Trigger, current_user: current_user
- else
- render_validation_error!(trigger)
- end
- end
-
desc 'Delete a trigger' do
success Entities::Trigger
end
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 56214043d87..5f2cbc24c60 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -18,6 +18,7 @@ module Banzai
#
class AutolinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper
+ include Gitlab::Utils::SanitizeNodeLink
# Pattern to match text that should be autolinked.
#
@@ -72,19 +73,11 @@ module Banzai
private
- # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
- def contains_unsafe?(scheme)
- return false unless scheme
-
- scheme = scheme.strip.downcase
- Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
- end
-
def autolink_match(match)
# start by stripping out dangerous links
begin
uri = Addressable::URI.parse(match)
- return match if contains_unsafe?(uri.scheme)
+ return match unless safe_protocol?(uri.scheme)
rescue Addressable::URI::InvalidURIError
return match
end
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
index 420e92cb1e8..2dabca3552d 100644
--- a/lib/banzai/filter/base_sanitization_filter.rb
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -11,6 +11,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with common rules.
class BaseSanitizationFilter < HTML::Pipeline::SanitizationFilter
include Gitlab::Utils::StrongMemoize
+ extend Gitlab::Utils::SanitizeNodeLink
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
@@ -40,7 +41,7 @@ module Banzai
# Allow any protocol in `a` elements
# and then remove links with unsafe protocols
whitelist[:protocols].delete('a')
- whitelist[:transformers].push(self.class.remove_unsafe_links)
+ whitelist[:transformers].push(self.class.method(:remove_unsafe_links))
# Remove `rel` attribute from `a` elements
whitelist[:transformers].push(self.class.remove_rel)
@@ -54,35 +55,6 @@ module Banzai
end
class << self
- def remove_unsafe_links
- lambda do |env|
- node = env[:node]
-
- return unless node.name == 'a'
- return unless node.has_attribute?('href')
-
- begin
- node['href'] = node['href'].strip
- uri = Addressable::URI.parse(node['href'])
-
- return unless uri.scheme
-
- # Remove all invalid scheme characters before checking against the
- # list of unsafe protocols.
- #
- # See https://tools.ietf.org/html/rfc3986#section-3.1
- scheme = uri.scheme
- .strip
- .downcase
- .gsub(/[^A-Za-z0-9\+\.\-]+/, '')
-
- node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
- rescue Addressable::URI::InvalidURIError
- node.remove_attribute('href')
- end
- end
- end
-
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 1728a442533..18947679b69 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -8,15 +8,19 @@ module Banzai
# Context options:
# :project_wiki
class WikiLinkFilter < HTML::Pipeline::Filter
+ include Gitlab::Utils::SanitizeNodeLink
+
def call
return doc unless project_wiki?
- doc.search('a:not(.gfm)').each { |el| process_link_attr(el.attribute('href')) }
- doc.search('video').each { |el| process_link_attr(el.attribute('src')) }
+ doc.search('a:not(.gfm)').each { |el| process_link(el.attribute('href'), el) }
+
+ doc.search('video').each { |el| process_link(el.attribute('src'), el) }
+
doc.search('img').each do |el|
attr = el.attribute('data-src') || el.attribute('src')
- process_link_attr(attr)
+ process_link(attr, el)
end
doc
@@ -24,6 +28,11 @@ module Banzai
protected
+ def process_link(link_attr, node)
+ process_link_attr(link_attr)
+ remove_unsafe_links({ node: node }, remove_invalid_links: false)
+ end
+
def project_wiki?
!context[:project_wiki].nil?
end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index 77b5053f38c..f4cc8beeb52 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -4,8 +4,6 @@ module Banzai
module Filter
class WikiLinkFilter < HTML::Pipeline::Filter
class Rewriter
- UNSAFE_SLUG_REGEXES = [/\Ajavascript:/i].freeze
-
def initialize(link_string, wiki:, slug:)
@uri = Addressable::URI.parse(link_string)
@wiki_base_path = wiki && wiki.wiki_base_path
@@ -37,8 +35,6 @@ module Banzai
# Of the form `./link`, `../link`, or similar
def apply_hierarchical_link_rules!
- return if slug_considered_unsafe?
-
@uri = Addressable::URI.join(@slug, @uri) if @uri.to_s[0] == '.'
end
@@ -58,10 +54,6 @@ module Banzai
def repository_upload?
@uri.relative? && @uri.path.starts_with?(Wikis::CreateAttachmentService::ATTACHMENT_PATH)
end
-
- def slug_considered_unsafe?
- UNSAFE_SLUG_REGEXES.any? { |r| r.match?(@slug) }
- end
end
end
end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c3a19af7a94..82810ea4076 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -82,7 +82,10 @@ module ContainerRegistry
def redirect_response(location)
return unless location
- faraday_redirect.get(location)
+ uri = URI(@base_uri).merge(location)
+ raise ArgumentError, "Invalid scheme for #{location}" unless %w[http https].include?(uri.scheme)
+
+ faraday_redirect.get(uri)
end
def faraday
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index c98de722fe1..4783832961d 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -413,7 +413,7 @@ module Gitlab
metadata_file = File.read(storage_metadata_file_path(storage))
metadata_hash = JSON.parse(metadata_file)
metadata_hash['gitaly_filesystem_id']
- rescue Errno::ENOENT, Errno::ACCESS, JSON::ParserError
+ rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError
nil
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index a61beafae0d..826b35d685c 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -40,7 +40,7 @@ module Gitlab
# otherwise hitting the rate limit will result in a thread
# being blocked in a `sleep()` call for up to an hour.
def initialize(token, per_page: 100, parallel: true)
- @octokit = Octokit::Client.new(
+ @octokit = ::Octokit::Client.new(
access_token: token,
per_page: per_page,
api_endpoint: api_endpoint
@@ -139,7 +139,7 @@ module Gitlab
begin
yield
- rescue Octokit::TooManyRequests
+ rescue ::Octokit::TooManyRequests
raise_or_wait_for_rate_limit
# This retry will only happen when running in sequential mode as we'll
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index bbdd094e33b..b23efd64dee 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -101,7 +101,7 @@ module Gitlab
# GitHub Rate Limit API returns 404 when the rate limit is
# disabled. In this case we just want to return gracefully
# instead of spitting out an error.
- rescue Octokit::NotFound
+ rescue ::Octokit::NotFound
nil
end
diff --git a/lib/gitlab/metrics/dashboard/base_service.rb b/lib/gitlab/metrics/dashboard/base_service.rb
deleted file mode 100644
index 0628e82e592..00000000000
--- a/lib/gitlab/metrics/dashboard/base_service.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-# Searches a projects repository for a metrics dashboard and formats the output.
-# Expects any custom dashboards will be located in `.gitlab/dashboards`
-module Gitlab
- module Metrics
- module Dashboard
- class BaseService < ::BaseService
- PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
- NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
-
- def get_dashboard
- return error('Insufficient permissions.', :unauthorized) unless allowed?
-
- success(dashboard: process_dashboard)
- rescue NOT_FOUND_ERROR
- error("#{dashboard_path} could not be found.", :not_found)
- rescue PROCESSING_ERROR => e
- error(e.message, :unprocessable_entity)
- end
-
- # Summary of all known dashboards for the service.
- # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
- def self.all_dashboard_paths(_project)
- raise NotImplementedError
- end
-
- # Returns an un-processed dashboard from the cache.
- def raw_dashboard
- Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard }
- end
-
- private
-
- # Determines whether users should be able to view
- # dashboards at all.
- def allowed?
- Ability.allowed?(current_user, :read_environment, project)
- end
-
- # Returns a new dashboard Hash, supplemented with DB info
- def process_dashboard
- Gitlab::Metrics::Dashboard::Processor
- .new(project, params[:environment], raw_dashboard)
- .process(insert_project_metrics: insert_project_metrics?)
- end
-
- # @return [String] Relative filepath of the dashboard yml
- def dashboard_path
- params[:dashboard_path]
- end
-
- # @return [Hash] an unmodified dashboard
- def get_raw_dashboard
- raise NotImplementedError
- end
-
- # @return [String]
- def cache_key
- raise NotImplementedError
- end
-
- # Determines whether custom metrics should be included
- # in the processed output.
- # @return [Boolean]
- def insert_project_metrics?
- false
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb b/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb
deleted file mode 100644
index 81ed8922e17..00000000000
--- a/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-# Responsible for returning a filtered system dashboard
-# containing only the default embedded metrics. In future,
-# this class may be updated to support filtering to
-# alternate metrics/panels.
-#
-# Why isn't this filtering in a processing stage? By filtering
-# here, we ensure the dynamically-determined dashboard is cached.
-#
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class DynamicDashboardService < Gitlab::Metrics::Dashboard::BaseService
- # For the default filtering for embedded metrics,
- # uses the 'id' key in dashboard-yml definition for
- # identification.
- DEFAULT_EMBEDDED_METRICS_IDENTIFIERS = %w(
- system_metrics_kubernetes_container_memory_total
- system_metrics_kubernetes_container_cores_total
- ).freeze
-
- # Returns a new dashboard with only the matching
- # metrics from the system dashboard, stripped of groups.
- # @return [Hash]
- def raw_dashboard
- panels = panel_groups.each_with_object([]) do |group, panels|
- matched_panels = group['panels'].select { |panel| matching_panel?(panel) }
-
- panels.concat(matched_panels)
- end
-
- { 'panel_groups' => [{ 'panels' => panels }] }
- end
-
- def cache_key
- "dynamic_metrics_dashboard_#{metric_identifiers.join('_')}"
- end
-
- private
-
- # Returns an array of the panels groups on the
- # system dashboard
- def panel_groups
- Gitlab::Metrics::Dashboard::SystemDashboardService
- .new(project, nil)
- .raw_dashboard['panel_groups']
- end
-
- # Identifies a panel as "matching" if any metric ids in
- # the panel is in the list of identifiers to collect.
- def matching_panel?(panel)
- panel['metrics'].any? do |metric|
- metric_identifiers.include?(metric['id'])
- end
- end
-
- def metric_identifiers
- DEFAULT_EMBEDDED_METRICS_IDENTIFIERS
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
index d7491d1553d..1373830844b 100644
--- a/lib/gitlab/metrics/dashboard/finder.rb
+++ b/lib/gitlab/metrics/dashboard/finder.rb
@@ -47,22 +47,22 @@ module Gitlab
private
def service_for_path(dashboard_path, embedded:)
- return dynamic_service if embedded
+ return embed_service if embedded
return system_service if system_dashboard?(dashboard_path)
project_service
end
def system_service
- Gitlab::Metrics::Dashboard::SystemDashboardService
+ ::Metrics::Dashboard::SystemDashboardService
end
def project_service
- Gitlab::Metrics::Dashboard::ProjectDashboardService
+ ::Metrics::Dashboard::ProjectDashboardService
end
- def dynamic_service
- Gitlab::Metrics::Dashboard::DynamicDashboardService
+ def embed_service
+ ::Metrics::Dashboard::DefaultEmbedService
end
def system_dashboard?(filepath)
diff --git a/lib/gitlab/metrics/dashboard/project_dashboard_service.rb b/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
deleted file mode 100644
index 5a1c4ecf886..00000000000
--- a/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-# Searches a projects repository for a metrics dashboard and formats the output.
-# Expects any custom dashboards will be located in `.gitlab/dashboards`
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class ProjectDashboardService < Gitlab::Metrics::Dashboard::BaseService
- DASHBOARD_ROOT = ".gitlab/dashboards"
-
- class << self
- def all_dashboard_paths(project)
- file_finder(project)
- .list_files_for(DASHBOARD_ROOT)
- .map do |filepath|
- {
- path: filepath,
- display_name: name_for_path(filepath),
- default: false
- }
- end
- end
-
- def file_finder(project)
- Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml')
- end
-
- # Grabs the filepath after the base directory.
- def name_for_path(filepath)
- filepath.delete_prefix("#{DASHBOARD_ROOT}/")
- end
- end
-
- private
-
- # Searches the project repo for a custom-defined dashboard.
- def get_raw_dashboard
- yml = self.class.file_finder(project).read(dashboard_path)
-
- YAML.safe_load(yml)
- end
-
- def cache_key
- "project_#{project.id}_metrics_dashboard_#{dashboard_path}"
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/system_dashboard_service.rb b/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
deleted file mode 100644
index 82421572f4a..00000000000
--- a/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-# Fetches the system metrics dashboard and formats the output.
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class SystemDashboardService < Gitlab::Metrics::Dashboard::BaseService
- SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
- SYSTEM_DASHBOARD_NAME = 'Default'
-
- class << self
- def all_dashboard_paths(_project)
- [{
- path: SYSTEM_DASHBOARD_PATH,
- display_name: SYSTEM_DASHBOARD_NAME,
- default: true
- }]
- end
-
- def system_dashboard?(filepath)
- filepath == SYSTEM_DASHBOARD_PATH
- end
- end
-
- private
-
- def dashboard_path
- SYSTEM_DASHBOARD_PATH
- end
-
- # Returns the base metrics shipped with every GitLab service.
- def get_raw_dashboard
- yml = File.read(Rails.root.join(dashboard_path))
-
- YAML.safe_load(yml)
- end
-
- def cache_key
- "metrics_dashboard_#{dashboard_path}"
- end
-
- def insert_project_metrics?
- true
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/octokit/middleware.rb b/lib/gitlab/octokit/middleware.rb
new file mode 100644
index 00000000000..2f762957d1b
--- /dev/null
+++ b/lib/gitlab/octokit/middleware.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Octokit
+ class Middleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ Gitlab::UrlBlocker.validate!(env[:url], { allow_localhost: allow_local_requests?, allow_local_network: allow_local_requests? })
+
+ @app.call(env)
+ end
+
+ private
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 99885be8755..dbf469a44c1 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -13,11 +13,15 @@ module Gitlab
end
def archive_path
- Rails.root.join("vendor/project_templates/#{name}.tar.gz")
+ self.class.archive_directory.join(archive_filename)
+ end
+
+ def archive_filename
+ "#{name}.tar.gz"
end
def clone_url
- "https://gitlab.com/gitlab-org/project-templates/#{name}.git"
+ "#{preview}.git"
end
def ==(other)
@@ -54,7 +58,7 @@ module Gitlab
end
def archive_directory
- Rails.root.join("vendor_directory/project_templates")
+ Rails.root.join("vendor/project_templates")
end
end
end
diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb
index 93030fd454e..ebdae139315 100644
--- a/lib/gitlab/quick_actions/command_definition.rb
+++ b/lib/gitlab/quick_actions/command_definition.rb
@@ -3,8 +3,8 @@
module Gitlab
module QuickActions
class CommandDefinition
- attr_accessor :name, :aliases, :description, :explanation, :params,
- :condition_block, :parse_params_block, :action_block, :warning, :types
+ attr_accessor :name, :aliases, :description, :explanation, :execution_message,
+ :params, :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {})
@name = name
@@ -13,6 +13,7 @@ module Gitlab
@description = attributes[:description] || ''
@warning = attributes[:warning] || ''
@explanation = attributes[:explanation] || ''
+ @execution_message = attributes[:execution_message] || ''
@params = attributes[:params] || []
@condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block]
@@ -48,13 +49,23 @@ module Gitlab
end
def execute(context, arg)
- return if noop? || !available?(context)
+ return unless executable?(context)
count_commands_executed_in(context)
execute_block(action_block, context, arg)
end
+ def execute_message(context, arg)
+ return unless executable?(context)
+
+ if execution_message.respond_to?(:call)
+ execute_block(execution_message, context, arg)
+ else
+ execution_message
+ end
+ end
+
def to_h(context)
desc = description
if desc.respond_to?(:call)
@@ -77,6 +88,10 @@ module Gitlab
private
+ def executable?(context)
+ !noop? && available?(context)
+ end
+
def count_commands_executed_in(context)
return unless context.respond_to?(:commands_executed_count=)
diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb
index 1018910e8e9..49f5ddf24eb 100644
--- a/lib/gitlab/quick_actions/commit_actions.rb
+++ b/lib/gitlab/quick_actions/commit_actions.rb
@@ -16,6 +16,13 @@ module Gitlab
_("Tags this commit to %{tag_name}.") % { tag_name: tag_name }
end
end
+ execution_message do |tag_name, message|
+ if message.present?
+ _("Tagged this commit to %{tag_name} with \"%{message}\".") % { tag_name: tag_name, message: message }
+ else
+ _("Tagged this commit to %{tag_name}.") % { tag_name: tag_name }
+ end
+ end
params 'v1.2.3 <message>'
parse_params do |tag_name_and_message|
tag_name_and_message.split(' ', 2)
diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb
index ecb2169151e..5abbd377642 100644
--- a/lib/gitlab/quick_actions/dsl.rb
+++ b/lib/gitlab/quick_actions/dsl.rb
@@ -66,6 +66,35 @@ module Gitlab
@explanation = block_given? ? block : text
end
+ # Allows to provide a message about quick action execution result, success or failure.
+ # This message is shown after quick action execution and after saving the note.
+ #
+ # Example:
+ #
+ # execution_message do |arguments|
+ # "Added label(s) #{arguments.join(' ')}"
+ # end
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # end
+ #
+ # Note: The execution_message won't be executed unless the condition block returns true.
+ # execution_message block is executed always after the command block has run,
+ # for this reason if the condition block doesn't return true after the command block has
+ # run you need to set the @execution_message variable inside the command block instead as
+ # shown in the following example.
+ #
+ # Example using instance variable:
+ #
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # @execution_message[:command_key] = 'command_key executed successfully'
+ # end
+ #
+ def execution_message(text = '', &block)
+ @execution_message = block_given? ? block : text
+ end
+
# Allows to define type(s) that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
#
@@ -121,10 +150,16 @@ module Gitlab
# comment.
# It accepts aliases and takes a block.
#
+ # You can also set the @execution_message instance variable, on conflicts with
+ # execution_message method the instance variable has precedence.
+ #
# Example:
#
# command :my_command, :alias_for_my_command do |arguments|
# # Awesome code block
+ # @updates[:my_command] = 'foo'
+ #
+ # @execution_message[:my_command] = 'my_command executed successfully'
# end
def command(*command_names, &block)
define_command(CommandDefinition, *command_names, &block)
@@ -158,6 +193,7 @@ module Gitlab
description: @description,
warning: @warning,
explanation: @explanation,
+ execution_message: @execution_message,
params: @params,
condition_block: @condition_block,
parse_params_block: @parse_params_block,
@@ -173,6 +209,7 @@ module Gitlab
@description = nil
@explanation = nil
+ @execution_message = nil
@params = nil
@condition_block = nil
@warning = nil
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index f7f89d4e897..b975a967d03 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -12,10 +12,16 @@ module Gitlab
included do
# Issue, MergeRequest, Epic: quick actions definitions
desc do
- "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ _('Close this %{quick_action_target}') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
- "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Closes this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Closed this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -28,10 +34,16 @@ module Gitlab
end
desc do
- "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ _('Reopen this %{quick_action_target}') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
- "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Reopens this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Reopened this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -45,7 +57,10 @@ module Gitlab
desc _('Change title')
explanation do |title_param|
- _("Changes the title to \"%{title_param}\".") % { title_param: title_param }
+ _('Changes the title to "%{title_param}".') % { title_param: title_param }
+ end
+ execution_message do |title_param|
+ _('Changed the title to "%{title_param}".') % { title_param: title_param }
end
params '<New title>'
types Issuable
@@ -61,7 +76,10 @@ module Gitlab
explanation do |labels_param|
labels = find_label_references(labels_param)
- "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ if labels.any?
+ _("Adds %{labels} %{label_text}.") %
+ { labels: labels.join(' '), label_text: 'label'.pluralize(labels.count) }
+ end
end
params '~label1 ~"label 2"'
types Issuable
@@ -71,21 +89,15 @@ module Gitlab
find_labels.any?
end
command :label do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:add_label_ids] ||= []
- @updates[:add_label_ids] += label_ids
-
- @updates[:add_label_ids].uniq!
- end
+ run_label_command(labels: find_labels(labels_param), command: :label, updates_key: :add_label_ids)
end
desc _('Remove all or specific label(s)')
explanation do |labels_param = nil|
- if labels_param.present?
- labels = find_label_references(labels_param)
- "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ label_references = labels_param.present? ? find_label_references(labels_param) : []
+ if label_references.any?
+ _("Removes %{label_references} %{label_text}.") %
+ { label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
else
_('Removes all labels.')
end
@@ -99,7 +111,9 @@ module Gitlab
end
command :unlabel do |labels_param = nil|
if labels_param.present?
- label_ids = find_label_ids(labels_param)
+ labels = find_labels(labels_param)
+ label_ids = labels.map(&:id)
+ label_references = labels_to_reference(labels, :name)
if label_ids.any?
@updates[:remove_label_ids] ||= []
@@ -109,7 +123,10 @@ module Gitlab
end
else
@updates[:label_ids] = []
+ label_references = []
end
+
+ @execution_message[:unlabel] = remove_label_message(label_references)
end
desc _('Replace all label(s)')
@@ -125,18 +142,12 @@ module Gitlab
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
end
command :relabel do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:label_ids] ||= []
- @updates[:label_ids] += label_ids
-
- @updates[:label_ids].uniq!
- end
+ run_label_command(labels: find_labels(labels_param), command: :relabel, updates_key: :label_ids)
end
desc _('Add a todo')
explanation _('Adds a todo.')
+ execution_message _('Added a todo.')
types Issuable
condition do
quick_action_target.persisted? &&
@@ -148,6 +159,7 @@ module Gitlab
desc _('Mark to do as done')
explanation _('Marks to do as done.')
+ execution_message _('Marked to do as done.')
types Issuable
condition do
quick_action_target.persisted? &&
@@ -159,7 +171,12 @@ module Gitlab
desc _('Subscribe')
explanation do
- "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Subscribes to this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Subscribed to this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -172,7 +189,12 @@ module Gitlab
desc _('Unsubscribe')
explanation do
- "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Unsubscribes from this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Unsubscribed from this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -187,6 +209,9 @@ module Gitlab
explanation do |name|
_("Toggles :%{name}: emoji award.") % { name: name } if name
end
+ execution_message do |name|
+ _("Toggled :%{name}: emoji award.") % { name: name } if name
+ end
params ':emoji:'
types Issuable
condition do
@@ -215,6 +240,41 @@ module Gitlab
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
+
+ private
+
+ def run_label_command(labels:, command:, updates_key:)
+ return if labels.empty?
+
+ @updates[updates_key] ||= []
+ @updates[updates_key] += labels.map(&:id)
+ @updates[updates_key].uniq!
+
+ label_references = labels_to_reference(labels, :name)
+ @execution_message[command] = case command
+ when :relabel
+ _('Replaced all labels with %{label_references} %{label_text}.') %
+ {
+ label_references: label_references.join(' '),
+ label_text: 'label'.pluralize(label_references.count)
+ }
+ when :label
+ _('Added %{label_references} %{label_text}.') %
+ {
+ label_references: label_references.join(' '),
+ label_text: 'label'.pluralize(labels.count)
+ }
+ end
+ end
+
+ def remove_label_message(label_references)
+ if label_references.any?
+ _("Removed %{label_references} %{label_text}.") %
+ { label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
+ else
+ _('Removed all labels.')
+ end
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index 85e62f950c8..0868bd85600 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -12,6 +12,9 @@ module Gitlab
explanation do |due_date|
_("Sets the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
end
+ execution_message do |due_date|
+ _("Set the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
+ end
params '<in 2 days | this Friday | December 31st>'
types Issue
condition do
@@ -27,6 +30,7 @@ module Gitlab
desc _('Remove due date')
explanation _('Removes the due date.')
+ execution_message _('Removed the due date.')
types Issue
condition do
quick_action_target.persisted? &&
@@ -49,22 +53,27 @@ module Gitlab
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) &&
quick_action_target.project.boards.count == 1
end
- # rubocop: disable CodeReuse/ActiveRecord
command :board_move do |target_list_name|
- label_ids = find_label_ids(target_list_name)
+ labels = find_labels(target_list_name)
+ label_ids = labels.map(&:id)
if label_ids.size == 1
label_id = label_ids.first
# Ensure this label corresponds to a list on the board
- next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists?
+ next unless Label.on_project_board?(quick_action_target.project_id, label_id)
@updates[:remove_label_ids] =
- quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
+ quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
@updates[:add_label_ids] = [label_id]
+
+ message = _("Moved issue to %{label} column in the board.") % { label: labels_to_reference(labels).first }
+ else
+ message = _('Move this issue failed because you need to specify only one label.')
end
+
+ @execution_message[:board_move] = message
end
- # rubocop: enable CodeReuse/ActiveRecord
desc _('Mark this issue as a duplicate of another issue')
explanation do |duplicate_reference|
@@ -81,7 +90,13 @@ module Gitlab
if canonical_issue.present?
@updates[:canonical_issue_id] = canonical_issue.id
+
+ message = _("Marked this issue as a duplicate of %{duplicate_param}.") % { duplicate_param: duplicate_param }
+ else
+ message = _('Mark as duplicate failed because referenced issue was not found')
end
+
+ @execution_message[:duplicate] = message
end
desc _('Move this issue to another project.')
@@ -99,13 +114,22 @@ module Gitlab
if target_project.present?
@updates[:target_project] = target_project
+
+ message = _("Moved this issue to %{path_to_project}.") % { path_to_project: target_project_path }
+ else
+ message = _("Move this issue failed because target project doesn't exists")
end
+
+ @execution_message[:move] = message
end
desc _('Make issue confidential.')
explanation do
_('Makes this issue confidential')
end
+ execution_message do
+ _('Made this issue confidential')
+ end
types Issue
condition do
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
@@ -119,7 +143,14 @@ module Gitlab
if branch_name
_("Creates branch '%{branch_name}' and a merge request to resolve this issue") % { branch_name: branch_name }
else
- "Creates a branch and a merge request to resolve this issue"
+ _('Creates a branch and a merge request to resolve this issue')
+ end
+ end
+ execution_message do |branch_name = nil|
+ if branch_name
+ _("Created branch '%{branch_name}' and a merge request to resolve this issue") % { branch_name: branch_name }
+ else
+ _('Created a branch and a merge request to resolve this issue')
end
end
params "<branch name>"
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index e1579cfddc0..41ffd51cde8 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -9,12 +9,9 @@ module Gitlab
included do
# Issue, MergeRequest: quick actions definitions
desc _('Assign')
- # rubocop: disable CodeReuse/ActiveRecord
explanation do |users|
- users = quick_action_target.allows_multiple_assignees? ? users : users.take(1)
- "Assigns #{users.map(&:to_reference).to_sentence}."
+ _('Assigns %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
- # rubocop: enable CodeReuse/ActiveRecord
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
@@ -26,7 +23,10 @@ module Gitlab
extract_users(assignee_param)
end
command :assign do |users|
- next if users.empty?
+ if users.empty?
+ @execution_message[:assign] = _("Assign command failed because no user was found")
+ next
+ end
if quick_action_target.allows_multiple_assignees?
@updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
@@ -34,6 +34,8 @@ module Gitlab
else
@updates[:assignee_ids] = [users.first.id]
end
+
+ @execution_message[:assign] = _('Assigned %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
desc do
@@ -44,9 +46,14 @@ module Gitlab
end
end
explanation do |users = nil|
- assignees = quick_action_target.assignees
- assignees &= users if users.present? && quick_action_target.allows_multiple_assignees?
- "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
+ assignees = assignees_for_removal(users)
+ _("Removes %{assignee_text} %{assignee_references}.") %
+ { assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
+ end
+ execution_message do |users = nil|
+ assignees = assignees_for_removal(users)
+ _("Removed %{assignee_text} %{assignee_references}.") %
+ { assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
end
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
@@ -74,6 +81,9 @@ module Gitlab
explanation do |milestone|
_("Sets the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
end
+ execution_message do |milestone|
+ _("Set the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
+ end
params '%"milestone"'
types Issue, MergeRequest
condition do
@@ -92,6 +102,9 @@ module Gitlab
explanation do
_("Removes %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
end
+ execution_message do
+ _("Removed %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
+ end
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -116,17 +129,22 @@ module Gitlab
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
- if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id
+ if can_copy_metadata?(source_issuable)
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
+
+ @execution_message[:copy_metadata] = _("Copied labels and milestone from %{source_issuable_reference}.") % { source_issuable_reference: source_issuable.to_reference }
end
end
desc _('Set time estimate')
explanation do |time_estimate|
- time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
-
- _("Sets time estimate to %{time_estimate}.") % { time_estimate: time_estimate } if time_estimate
+ formatted_time_estimate = format_time_estimate(time_estimate)
+ _("Sets time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
+ end
+ execution_message do |time_estimate|
+ formatted_time_estimate = format_time_estimate(time_estimate)
+ _("Set time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
end
params '<1w 3d 2h 14m>'
types Issue, MergeRequest
@@ -144,18 +162,12 @@ module Gitlab
desc _('Add or subtract spent time')
explanation do |time_spent, time_spent_date|
- if time_spent
- if time_spent > 0
- verb = _('Adds')
- value = time_spent
- else
- verb = _('Subtracts')
- value = -time_spent
- end
-
- _("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: Gitlab::TimeTrackingFormatter.output(value) }
- end
+ spend_time_message(time_spent, time_spent_date, false)
end
+ execution_message do |time_spent, time_spent_date|
+ spend_time_message(time_spent, time_spent_date, true)
+ end
+
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
types Issue, MergeRequest
condition do
@@ -176,6 +188,7 @@ module Gitlab
desc _('Remove time estimate')
explanation _('Removes time estimate.')
+ execution_message _('Removed time estimate.')
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -187,6 +200,7 @@ module Gitlab
desc _('Remove spent time')
explanation _('Removes spent time.')
+ execution_message _('Removed spent time.')
condition do
quick_action_target.persisted? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
@@ -198,6 +212,7 @@ module Gitlab
desc _("Lock the discussion")
explanation _("Locks the discussion")
+ execution_message _("Locked the discussion")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -210,6 +225,7 @@ module Gitlab
desc _("Unlock the discussion")
explanation _("Unlocks the discussion")
+ execution_message _("Unlocked the discussion")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -219,6 +235,47 @@ module Gitlab
command :unlock do
@updates[:discussion_locked] = false
end
+
+ private
+
+ def assignee_users_sentence(users)
+ if quick_action_target.allows_multiple_assignees?
+ users
+ else
+ [users.first]
+ end.map(&:to_reference).to_sentence
+ end
+
+ def assignees_for_removal(users)
+ assignees = quick_action_target.assignees
+ if users.present? && quick_action_target.allows_multiple_assignees?
+ assignees & users
+ else
+ assignees
+ end
+ end
+
+ def can_copy_metadata?(source_issuable)
+ source_issuable.present? && source_issuable.project_id == quick_action_target.project_id
+ end
+
+ def format_time_estimate(time_estimate)
+ Gitlab::TimeTrackingFormatter.output(time_estimate)
+ end
+
+ def spend_time_message(time_spent, time_spent_date, paste_tense)
+ return unless time_spent
+
+ if time_spent > 0
+ verb = paste_tense ? _('Added') : _('Adds')
+ value = time_spent
+ else
+ verb = paste_tense ? _('Subtracted') : _('Subtracts')
+ value = -time_spent
+ end
+
+ _("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: format_time_estimate(value) }
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
index bade59182a1..e9127095a0d 100644
--- a/lib/gitlab/quick_actions/merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -8,8 +8,9 @@ module Gitlab
included do
# MergeRequest only quick actions definitions
- desc 'Merge (when the pipeline succeeds)'
- explanation 'Merges this merge request when the pipeline succeeds.'
+ desc _('Merge (when the pipeline succeeds)')
+ explanation _('Merges this merge request when the pipeline succeeds.')
+ execution_message _('Scheduled to merge this merge request when the pipeline succeeds.')
types MergeRequest
condition do
last_diff_sha = params && params[:merge_request_diff_head_sha]
@@ -22,10 +23,22 @@ module Gitlab
desc 'Toggle the Work In Progress status'
explanation do
- verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks'
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
- "#{verb} this #{noun} as Work In Progress."
+ if quick_action_target.work_in_progress?
+ _("Unmarks this %{noun} as Work In Progress.")
+ else
+ _("Marks this %{noun} as Work In Progress.")
+ end % { noun: noun }
end
+ execution_message do
+ noun = quick_action_target.to_ability_name.humanize(capitalize: false)
+ if quick_action_target.work_in_progress?
+ _("Unmarked this %{noun} as Work In Progress.")
+ else
+ _("Marked this %{noun} as Work In Progress.")
+ end % { noun: noun }
+ end
+
types MergeRequest
condition do
quick_action_target.respond_to?(:work_in_progress?) &&
@@ -36,9 +49,12 @@ module Gitlab
@updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
end
- desc 'Set target branch'
+ desc _('Set target branch')
explanation do |branch_name|
- "Sets target branch to #{branch_name}."
+ _('Sets target branch to %{branch_name}.') % { branch_name: branch_name }
+ end
+ execution_message do |branch_name|
+ _('Set target branch to %{branch_name}.') % { branch_name: branch_name }
end
params '<Local branch name>'
types MergeRequest
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index eab6762cab7..9c35d200dcb 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -115,6 +115,15 @@ module Gitlab
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
+ # In the test suite we use a lot of mocked urls that are either invalid or
+ # don't exist. In order to avoid modifying a ton of tests and factories
+ # we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS
+ # is not true
+ return if Rails.env.test? && ENV['RSPEC_ALLOW_INVALID_URLS'] == 'true'
+
+ # If the addr can't be resolved or the url is invalid (i.e http://1.1.1.1.1)
+ # we block the url
+ raise BlockedUrlError, "Host cannot be resolved or invalid"
end
def validate_local_request(
diff --git a/lib/gitlab/utils/sanitize_node_link.rb b/lib/gitlab/utils/sanitize_node_link.rb
new file mode 100644
index 00000000000..620d71a7814
--- /dev/null
+++ b/lib/gitlab/utils/sanitize_node_link.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require_dependency 'gitlab/utils'
+
+module Gitlab
+ module Utils
+ module SanitizeNodeLink
+ UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+ ATTRS_TO_SANITIZE = %w(href src data-src).freeze
+
+ def remove_unsafe_links(env, remove_invalid_links: true)
+ node = env[:node]
+
+ sanitize_node(node: node, remove_invalid_links: remove_invalid_links)
+
+ # HTML entities such as <video></video> have scannable attrs in
+ # children elements, which also need to be sanitized.
+ #
+ node.children.each do |child_node|
+ sanitize_node(node: child_node, remove_invalid_links: remove_invalid_links)
+ end
+ end
+
+ # Remove all invalid scheme characters before checking against the
+ # list of unsafe protocols.
+ #
+ # See https://tools.ietf.org/html/rfc3986#section-3.1
+ #
+ def safe_protocol?(scheme)
+ return false unless scheme
+
+ scheme = scheme
+ .strip
+ .downcase
+ .gsub(/[^A-Za-z\+\.\-]+/, '')
+
+ UNSAFE_PROTOCOLS.none?(scheme)
+ end
+
+ private
+
+ def sanitize_node(node:, remove_invalid_links: true)
+ ATTRS_TO_SANITIZE.each do |attr|
+ next unless node.has_attribute?(attr)
+
+ begin
+ node[attr] = node[attr].strip
+ uri = Addressable::URI.parse(node[attr])
+
+ next unless uri.scheme
+ next if safe_protocol?(uri.scheme)
+
+ node.remove_attribute(attr)
+ rescue Addressable::URI::InvalidURIError
+ node.remove_attribute(attr) if remove_invalid_links
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/zoom_link_extractor.rb b/lib/gitlab/zoom_link_extractor.rb
index d9994898a08..7ac14eb2d4f 100644
--- a/lib/gitlab/zoom_link_extractor.rb
+++ b/lib/gitlab/zoom_link_extractor.rb
@@ -17,5 +17,9 @@ module Gitlab
def links
@text.scan(ZOOM_REGEXP)
end
+
+ def match?
+ ZOOM_REGEXP.match?(@text)
+ end
end
end
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
deleted file mode 100644
index 581cc6a37b4..00000000000
--- a/lib/peek/rblineprof/custom_controller_helpers.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-module Peek
- module Rblineprof
- module CustomControllerHelpers
- extend ActiveSupport::Concern
-
- # This will become useless once https://github.com/peek/peek-rblineprof/pull/5
- # is merged
- def pygmentize(file_name, code, lexer = nil)
- if lexer.present?
- Gitlab::Highlight.highlight(file_name, code)
- else
- "<pre>#{Rack::Utils.escape_html(code)}</pre>"
- end
- end
-
- # rubocop:disable all
- def inject_rblineprof
- ret = nil
- profile = lineprof(rblineprof_profiler_regex) do
- ret = yield
- end
-
- if response.content_type =~ %r|text/html|
- sort = params[:lineprofiler_sort]
- mode = params[:lineprofiler_mode] || 'cpu'
- min = (params[:lineprofiler_min] || 5).to_i * 1000
- summary = params[:lineprofiler_summary]
-
- # Sort each file by the longest calculated time
- per_file = profile.map do |file, lines|
- total, child, excl, total_cpu, child_cpu, excl_cpu = lines[0]
-
- wall = summary == 'exclusive' ? excl : total
- cpu = summary == 'exclusive' ? excl_cpu : total_cpu
- idle = summary == 'exclusive' ? (excl - excl_cpu) : (total - total_cpu)
-
- [
- file, lines,
- wall, cpu, idle,
- sort == 'idle' ? idle : sort == 'cpu' ? cpu : wall
- ]
- end.sort_by{ |a,b,c,d,e,f| -f }
-
- output = ["<div class='modal-dialog modal-xl'><div class='modal-content'>"]
- output << "<div class='modal-header'>"
- output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
- output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>&times;</span></button>"
- output << "</div>"
- output << "<div class='modal-body'>"
-
- per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
- output << "<div class='peek-rblineprof-file'><div class='heading'>"
-
- show_src = file_sort > min
- tmpl = show_src ? "<a href='#' class='js-lineprof-file'>%s</a>" : "%s"
-
- if mode == 'cpu'
- output << sprintf("<span class='duration'>% 8.1fms + % 8.1fms</span> #{tmpl}", file_cpu / 1000.0, file_idle / 1000.0, file_name.sub(Rails.root.to_s + '/', ''))
- else
- output << sprintf("<span class='duration'>% 8.1fms</span> #{tmpl}", file_wall/1000.0, file_name.sub(Rails.root.to_s + '/', ''))
- end
-
- output << "</div>" # .heading
-
- next unless show_src
-
- output << "<div class='data'>"
- code = []
- times = []
- File.readlines(file_name).each_with_index do |line, i|
- code << line
- wall, cpu, calls = lines[i + 1]
-
- if calls && calls > 0
- if mode == 'cpu'
- idle = wall - cpu
- times << sprintf("% 8.1fms + % 8.1fms (% 5d)", cpu / 1000.0, idle / 1000.0, calls)
- else
- times << sprintf("% 8.1fms (% 5d)", wall / 1000.0, calls)
- end
- else
- times << ' '
- end
- end
- output << "<pre class='duration'>#{times.join("\n")}</pre>"
- # The following line was changed from
- # https://github.com/peek/peek-rblineprof/blob/8d3b7a283a27de2f40abda45974516693d882258/lib/peek/rblineprof/controller_helpers.rb#L125
- # This will become useless once https://github.com/peek/peek-rblineprof/pull/16
- # is merged and is implemented.
- output << "<pre class='code highlight white'>#{pygmentize(file_name, code.join, 'ruby')}</pre>"
- output << "</div></div>" # .data then .peek-rblineprof-file
- end
-
- output << "</div></div></div>"
-
- response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output.join}</div>".html_safe
- end
-
- ret
- end
-
- private
-
- def human_description(lineprofiler_param)
- case lineprofiler_param
- when 'app'
- 'app/ & lib/'
- when 'views'
- 'app/view/'
- when 'gems'
- 'vendor/gems'
- when 'all'
- 'everything in Rails.root'
- when 'stdlib'
- 'everything in the Ruby standard library'
- else
- 'app/, config/, lib/, vendor/ & plugin/'
- end
- end
- end
- end
-end
diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb
index a64ea672895..b95307deddb 100644
--- a/lib/peek/views/redis_detailed.rb
+++ b/lib/peek/views/redis_detailed.rb
@@ -22,7 +22,7 @@ module Gitlab
detail_store << {
cmd: args.first,
duration: duration,
- backtrace: Gitlab::Profiler.clean_backtrace(caller)
+ backtrace: ::Gitlab::Profiler.clean_backtrace(caller)
}
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index e058e9fe069..8267c235a7f 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -5,25 +5,43 @@ namespace :gitlab do
end
desc "GitLab | Update project templates"
- task :update_project_templates do
- include Gitlab::ImportExport::CommandLineUtil
+ task :update_project_templates, [] => :environment do |_task, args|
+ # we need an instance method from Gitlab::ImportExport::CommandLineUtil and don't
+ # want to include it in the task, as this would affect subsequent tasks as well
+ downloader = Class.new do
+ extend Gitlab::ImportExport::CommandLineUtil
+
+ def self.call(uploader, upload_path)
+ download_or_copy_upload(uploader, upload_path)
+ end
+ end
+
+ template_names = args.extras.to_set
if Rails.env.production?
- puts "This rake task is not meant fo production instances".red
- exit(1)
+ raise "This rake task is not meant for production instances"
end
admin = User.find_by(admin: true)
unless admin
- puts "No admin user could be found".red
- exit(1)
+ raise "No admin user could be found"
end
- Gitlab::ProjectTemplate.all.each do |template|
+ tmp_namespace_path = "tmp-project-import-#{Time.now.to_i}"
+ puts "Creating temporary namespace #{tmp_namespace_path}"
+ tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path)
+
+ templates = if template_names.empty?
+ Gitlab::ProjectTemplate.all
+ else
+ Gitlab::ProjectTemplate.all.select { |template| template_names.include?(template.name) }
+ end
+
+ templates.each do |template|
params = {
import_url: template.clone_url,
- namespace_id: admin.namespace.id,
+ namespace_id: tmp_namespace.id,
path: template.name,
skip_wiki: true
}
@@ -32,19 +50,17 @@ namespace :gitlab do
project = Projects::CreateService.new(admin, params).execute
unless project.persisted?
- puts project.errors.messages
- exit(1)
+ raise "Failed to create project: #{project.errors.messages}"
end
loop do
- if project.finished?
+ if project.import_finished?
puts "Import finished for #{template.name}"
break
end
- if project.failed?
- puts "Failed to import from #{project_params[:import_url]}".red
- exit(1)
+ if project.import_failed?
+ raise "Failed to import from #{project_params[:import_url]}"
end
puts "Waiting for the import to finish"
@@ -54,11 +70,23 @@ namespace :gitlab do
end
Projects::ImportExport::ExportService.new(project, admin).execute
- download_or_copy_upload(project.export_file, template.archive_path)
- Projects::DestroyService.new(admin, project).execute
+ downloader.call(project.export_file, template.archive_path)
+
+ unless Projects::DestroyService.new(project, admin).execute
+ puts "Failed to destroy project #{template.name} (but namespace will be cleaned up later)"
+ end
+
puts "Exported #{template.name}".green
end
- puts "Done".green
+
+ success = true
+ ensure
+ if tmp_namespace
+ puts "Destroying temporary namespace #{tmp_namespace_path}"
+ tmp_namespace.destroy
+ end
+
+ puts "Done".green if success
end
def update(template)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5397404b630..878ee5bc344 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -735,6 +735,15 @@ msgstr ""
msgid "AddMember|Too many users specified (limit is %{user_limit})"
msgstr ""
+msgid "Added"
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a todo."
+msgstr ""
+
msgid "Added at"
msgstr ""
@@ -744,6 +753,9 @@ msgstr ""
msgid "Adds"
msgstr ""
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
msgid "Adds a todo."
msgstr ""
@@ -954,7 +966,7 @@ msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
-msgid "Alternate support URL for help page"
+msgid "Alternate support URL for help page and help dropdown"
msgstr ""
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
@@ -1332,6 +1344,9 @@ msgstr ""
msgid "Assign"
msgstr ""
+msgid "Assign command failed because no user was found"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -1353,6 +1368,9 @@ msgstr ""
msgid "Assign yourself to this issue"
msgstr ""
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
msgid "Assigned Issues"
msgstr ""
@@ -1370,6 +1388,9 @@ msgstr[1] ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -2005,6 +2026,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
msgid "Changes"
msgstr ""
@@ -2341,9 +2365,18 @@ msgstr ""
msgid "Close sidebar"
msgstr ""
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
msgid "Closed"
msgstr ""
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -2887,6 +2920,9 @@ msgstr ""
msgid "Commands applied"
msgstr ""
+msgid "Commands did not apply"
+msgstr ""
+
msgid "Comment"
msgstr ""
@@ -3186,6 +3222,9 @@ msgstr ""
msgid "Copied"
msgstr ""
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
msgid "Copy %{http_label} clone URL"
msgstr ""
@@ -3396,6 +3435,12 @@ msgstr ""
msgid "Created At"
msgstr ""
+msgid "Created a branch and a merge request to resolve this issue"
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue"
+msgstr ""
+
msgid "Created by me"
msgstr ""
@@ -3405,6 +3450,9 @@ msgstr ""
msgid "Created on:"
msgstr ""
+msgid "Creates a branch and a merge request to resolve this issue"
+msgstr ""
+
msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue"
msgstr ""
@@ -6337,6 +6385,9 @@ msgstr ""
msgid "Locked by %{fileLockUserName}"
msgstr ""
+msgid "Locked the discussion"
+msgstr ""
+
msgid "Locked to current projects"
msgstr ""
@@ -6355,6 +6406,9 @@ msgstr ""
msgid "MRDiff|Show full file"
msgstr ""
+msgid "Made this issue confidential"
+msgstr ""
+
msgid "Make and review changes in the browser with the Web IDE"
msgstr ""
@@ -6436,6 +6490,9 @@ msgstr ""
msgid "Mark as done"
msgstr ""
+msgid "Mark as duplicate failed because referenced issue was not found"
+msgstr ""
+
msgid "Mark as resolved"
msgstr ""
@@ -6454,6 +6511,18 @@ msgstr ""
msgid "Markdown enabled"
msgstr ""
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marked to do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
@@ -6499,6 +6568,9 @@ msgstr ""
msgid "Merge"
msgstr ""
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -6622,6 +6694,9 @@ msgstr ""
msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -6805,6 +6880,12 @@ msgstr ""
msgid "Move issue from one column of the board to another"
msgstr ""
+msgid "Move this issue failed because target project doesn't exists"
+msgstr ""
+
+msgid "Move this issue failed because you need to specify only one label."
+msgstr ""
+
msgid "Move this issue to another project."
msgstr ""
@@ -6814,6 +6895,12 @@ msgstr ""
msgid "MoveIssue|Cannot move issue to project it originates from!"
msgstr ""
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
msgid "Moves issue to %{label} column in the board."
msgstr ""
@@ -7517,9 +7604,6 @@ msgstr ""
msgid "PerformanceBar|SQL queries"
msgstr ""
-msgid "PerformanceBar|profile"
-msgstr ""
-
msgid "PerformanceBar|trace"
msgstr ""
@@ -8986,12 +9070,39 @@ msgstr ""
msgid "Remove time estimate"
msgstr ""
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
msgid "Removed projects cannot be restored!"
msgstr ""
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
msgid "Removes %{milestone_reference} milestone."
msgstr ""
@@ -9025,12 +9136,24 @@ msgstr ""
msgid "Reopen milestone"
msgstr ""
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
msgid "Replace"
msgstr ""
msgid "Replace all label(s)"
msgstr ""
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
msgid "Reply by email"
msgstr ""
@@ -9115,7 +9238,7 @@ msgstr ""
msgid "Requests Profiles"
msgstr ""
-msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are disabled. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 4000 entries. Domains should use IDNA encoding. Ex: domain.com, 192.168.1.1, 127.0.0.0/28."
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
msgstr ""
msgid "Require all users in this group to setup Two-factor authentication"
@@ -9369,6 +9492,9 @@ msgstr ""
msgid "Scheduled"
msgstr ""
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
msgid "Schedules"
msgstr ""
@@ -9696,18 +9822,33 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
msgstr ""
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
msgstr ""
msgid "Set the maximum file size for each job's artifacts"
msgstr ""
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -9753,6 +9894,9 @@ msgstr ""
msgid "SetStatusModal|What's your status?"
msgstr ""
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
msgid "Sets the due date to %{due_date}."
msgstr ""
@@ -10334,9 +10478,18 @@ msgstr ""
msgid "Subscribed"
msgstr ""
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
msgid "Subscription"
msgstr ""
+msgid "Subtracted"
+msgstr ""
+
msgid "Subtracts"
msgstr ""
@@ -10433,6 +10586,9 @@ msgstr ""
msgid "Sunday"
msgstr ""
+msgid "Support"
+msgstr ""
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
@@ -10484,6 +10640,12 @@ msgstr ""
msgid "Tag this commit."
msgstr ""
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
msgid "Tags"
msgstr ""
@@ -11543,6 +11705,9 @@ msgstr ""
msgid "ToggleButton|Toggle Status: ON"
msgstr ""
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
msgid "Toggles :%{name}: emoji award."
msgstr ""
@@ -11627,9 +11792,6 @@ msgstr ""
msgid "Trigger was created successfully."
msgstr ""
-msgid "Trigger was re-assigned."
-msgstr ""
-
msgid "Trigger was successfully updated."
msgstr ""
@@ -11753,9 +11915,18 @@ msgstr ""
msgid "Unlocked"
msgstr ""
+msgid "Unlocked the discussion"
+msgstr ""
+
msgid "Unlocks the discussion"
msgstr ""
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
+msgstr ""
+
msgid "Unresolve discussion"
msgstr ""
@@ -11798,6 +11969,12 @@ msgstr ""
msgid "Unsubscribe from %{type}"
msgstr ""
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -12583,9 +12760,6 @@ msgstr ""
msgid "You could not create a new trigger."
msgstr ""
-msgid "You could not take ownership of trigger."
-msgstr ""
-
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -12856,6 +13030,12 @@ msgstr ""
msgid "Your request for access has been queued for review."
msgstr ""
+msgid "a Zoom call was added to this issue"
+msgstr ""
+
+msgid "a Zoom call was removed from this issue"
+msgstr ""
+
msgid "a deleted user"
msgstr ""
@@ -12957,10 +13137,10 @@ msgstr ""
msgid "encrypted: needs to be a :required, :optional or :migrating!"
msgstr ""
-msgid "entries cannot be blank"
+msgid "entries cannot be larger than 255 characters"
msgstr ""
-msgid "entries cannot be larger than 255 characters"
+msgid "entries cannot be nil"
msgstr ""
msgid "entries cannot contain HTML tags"
diff --git a/package.json b/package.json
index 773918524f9..c97046b0e84 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.9.0",
+ "@gitlab/ui": "^5.11.1",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -56,6 +56,7 @@
"clipboard": "^1.7.1",
"codesandbox-api": "^0.0.20",
"compression-webpack-plugin": "^2.0.0",
+ "copy-webpack-plugin": "^5.0.4",
"core-js": "^3.1.3",
"cropper": "^2.3.0",
"css-loader": "^1.0.0",
diff --git a/qa/qa.rb b/qa/qa.rb
index be73776425b..18fb4509dce 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -314,6 +314,10 @@ module QA
autoload :Login, 'qa/page/mattermost/login'
end
+ module Search
+ autoload :Results, 'qa/page/search/results'
+ end
+
##
# Classes describing components that are used by several pages.
#
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index eea5717f5a7..61ec9854726 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -13,6 +13,10 @@ module QA
element :admin_settings_metrics_and_profiling_item
end
+ view 'app/views/layouts/nav/sidebar/_admin.html.haml' do
+ element :integration_settings_link
+ end
+
def go_to_repository_settings
hover_settings do
within_submenu do
@@ -21,6 +25,14 @@ module QA
end
end
+ def go_to_integration_settings
+ hover_settings do
+ within_submenu do
+ click_element :integration_settings_link
+ end
+ end
+ end
+
def go_to_general_settings
hover_settings do
within_submenu do
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index d86d554356e..e3039149ab4 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -27,6 +27,10 @@ module QA
element :your_projects_link
end
+ view 'app/views/layouts/_search.html.haml' do
+ element :search_term_field
+ end
+
def go_to_groups
within_top_menu do
click_element :groups_dropdown
@@ -71,6 +75,10 @@ module QA
click_element :snippets_link
end
+ def search_for(term)
+ fill_element :search_term_field, "#{term}\n"
+ end
+
def has_personal_area?(wait: Capybara.default_max_wait_time)
has_element?(:user_avatar, wait: wait)
end
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
new file mode 100644
index 00000000000..b9b18abf660
--- /dev/null
+++ b/qa/qa/page/search/results.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA::Page
+ module Search
+ class Results < QA::Page::Base
+ view 'app/views/search/_category.html.haml' do
+ element :code_tab
+ end
+
+ view 'app/views/search/results/_blob_data.html.haml' do
+ element :result_item_content
+ element :file_title_content
+ element :file_text_content
+ end
+
+ def switch_to_code
+ click_element(:code_tab)
+ end
+
+ def has_file_in_project?(file_name, project_name)
+ has_element? :result_item_content, text: "#{project_name}: #{file_name}"
+ end
+
+ def has_file_with_content?(file_name, file_text)
+ within_element_by_index :result_item_content, 0 do
+ false unless has_element? :file_title_content, text: file_name
+
+ has_element? :file_text_content, text: file_text
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index e78b9bece19..4e5610d69b7 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -181,6 +181,10 @@ module QA
ENV.fetch('GCLOUD_REGION')
end
+ def gcloud_num_nodes
+ ENV.fetch('GCLOUD_NUM_NODES', 3)
+ end
+
def has_gcloud_credentials?
%w[GCLOUD_ACCOUNT_KEY GCLOUD_ACCOUNT_EMAIL].none? { |var| ENV[var].to_s.empty? }
end
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index 40263e94065..ac0b6313167 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -29,6 +29,8 @@ module QA
#{auth_options}
--enable-basic-auth
--region #{Runtime::Env.gcloud_region}
+ --disk-size 10GB
+ --num-nodes #{Runtime::Env.gcloud_num_nodes}
&& gcloud container clusters
get-credentials
--region #{Runtime::Env.gcloud_region}
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index 67610b62ed7..0a999cf00fa 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -7,11 +7,13 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
- Page::Mattermost::Login.perform(&:sign_in_using_oauth)
+ Support::Retrier.retry_on_exception do
+ Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
+ Page::Mattermost::Login.perform(&:sign_in_using_oauth)
- Page::Mattermost::Main.perform do |page|
- expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ Page::Mattermost::Main.perform do |page|
+ expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ end
end
end
end
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index 1d37b1bd12d..e0e1b2d6c7d 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -6,23 +6,39 @@ module RuboCop
# the last line of a file. Injecting a module in the middle of a file will
# cause merge conflicts, while placing it on the last line will not.
class InjectEnterpriseEditionModule < RuboCop::Cop::Cop
- MSG = 'Injecting EE modules must be done on the last line of this file' \
- ', outside of any class or module definitions'
+ INVALID_LINE = 'Injecting EE modules must be done on the last line of this file' \
+ ', outside of any class or module definitions'
- METHODS = Set.new(%i[include extend prepend]).freeze
+ DISALLOWED_METHOD =
+ 'EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`'
+
+ INVALID_ARGUMENT = 'EE modules to inject must be specified as a String'
+
+ CHECK_LINE_METHODS =
+ Set.new(%i[include_if_ee extend_if_ee prepend_if_ee]).freeze
+
+ DISALLOW_METHODS = Set.new(%i[include extend prepend]).freeze
def ee_const?(node)
line = node.location.expression.source_line
# We use `match?` here instead of RuboCop's AST matching, as this makes
# it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
- line.match?(/(\s|\()(::)?EE::/)
+ line.match?(/(\s|\()('|")?(::)?EE::/)
end
def on_send(node)
- return unless METHODS.include?(node.children[1])
- return unless ee_const?(node.children[2])
+ return unless check_method?(node)
+ if DISALLOW_METHODS.include?(node.children[1])
+ add_offense(node, message: DISALLOWED_METHOD)
+ else
+ verify_line_number(node)
+ verify_argument_type(node)
+ end
+ end
+
+ def verify_line_number(node)
line = node.location.line
buffer = node.location.expression.source_buffer
last_line = buffer.last_line
@@ -32,7 +48,25 @@ module RuboCop
# the expression is the last line _of code_.
last_line -= 1 if buffer.source.end_with?("\n")
- add_offense(node) if line < last_line
+ add_offense(node, message: INVALID_LINE) if line < last_line
+ end
+
+ def verify_argument_type(node)
+ argument = node.children[2]
+
+ return if argument.str_type?
+
+ add_offense(argument, message: INVALID_ARGUMENT)
+ end
+
+ def check_method?(node)
+ name = node.children[1]
+
+ if CHECK_LINE_METHODS.include?(name) || DISALLOW_METHODS.include?(name)
+ ee_const?(node.children[2])
+ else
+ false
+ end
end
# Automatically correcting these offenses is not always possible, as
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 4f617b15a6e..408a0595f9e 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -189,17 +189,11 @@ function deploy() {
gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
- # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
- if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previous_deploy_failed "$CI_ENVIRONMENT_SLUG" ; then
- echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
- delete
- fi
-
create_application_secret
HELM_CMD=$(cat << EOF
helm upgrade --install \
- --wait \
+ --atomic \
--timeout 900 \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set global.appConfig.enableUsagePing=false \
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 0f99a957581..60342bf8e3d 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Groups::UploadsController do
{ group_id: model }
end
+ let(:other_model) { create(:group, :public) }
+ let(:other_params) do
+ { group_id: other_model }
+ end
+
it_behaves_like 'handle uploads' do
let(:uploader_class) { NamespaceFileUploader }
end
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 5ec8d8d41d7..4ae29ba7f54 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -7,51 +7,115 @@ describe Projects::BadgesController do
let!(:pipeline) { create(:ci_empty_pipeline) }
let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ shared_examples 'a badge resource' do |badge_type|
+ context 'when pipelines are public' do
+ before do
+ project.update!(public_builds: true)
+ end
+
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it "returns the #{badge_type} badge to unauthenticated users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when project is restricted' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it "returns the #{badge_type} badge to guest users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
- it 'requests the pipeline badge successfully' do
- get_badge(:pipeline)
+ context 'format' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'renders the `flat` badge layout by default' do
+ get_badge(badge_type)
- it 'requests the coverage badge successfully' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(badge_type, 'flat')
- it 'renders the `flat` badge layout by default' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- expect(response).to render_template('projects/badges/badge')
- end
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(badge_type, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- context 'when style param is set to `flat`' do
- it 'renders the `flat` badge layout' do
- get_badge(:coverage, 'flat')
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(badge_type, 'flat-square')
- expect(response).to render_template('projects/badges/badge')
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
end
- end
- context 'when style param is set to an invalid type' do
- it 'renders the `flat` (default) badge layout' do
- get_badge(:coverage, 'xxx')
+ context 'when pipelines are not public' do
+ before do
+ project.update!(public_builds: false)
+ end
- expect(response).to render_template('projects/badges/badge')
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'returns 404 to unauthenticated users' do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when project is restricted to the user' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it 'defaults to project permissions' do
+ get_badge(:coverage)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
- context 'when style param is set to `flat-square`' do
- it 'renders the `flat-square` badge layout' do
- get_badge(:coverage, 'flat-square')
+ describe '#pipeline' do
+ it_behaves_like 'a badge resource', :pipeline
+ end
- expect(response).to render_template('projects/badges/badge_flat-square')
- end
+ describe '#coverage' do
+ it_behaves_like 'a badge resource', :coverage
end
def get_badge(badge, style = nil)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index ebbbebf1bc0..8872e8d38e7 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -551,7 +551,7 @@ describe Projects::EnvironmentsController do
end
context 'when the specified dashboard is the default dashboard' do
- let(:dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH }
+ let(:dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH }
it_behaves_like 'the default dashboard'
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index fa71d9b61b1..57a432de3f5 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -621,10 +621,100 @@ describe Projects::MergeRequestsController do
format: :json
end
- it 'responds with serialized pipelines' do
- expect(json_response['pipelines']).not_to be_empty
- expect(json_response['count']['all']).to eq 1
- expect(response).to include_pagination_headers
+ context 'with "enabled" builds on a public project' do
+ let(:project) { create(:project, :repository, :public) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with private builds on a public project' do
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'from a project fork' do
+ let(:fork_user) { create :user }
+ let(:forked_project) { fork_project(project, fork_user, repository: true) } # Forked project carries over :builds_private
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: forked_project) }
+
+ context 'with private builds' do
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with public builds' do
+ let(:forked_project) do
+ fork_project(project, fork_user, repository: true).tap do |new_project|
+ new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED)
+ end
+ end
+
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 776c1270977..661ed9840b1 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Projects::UploadsController do
{ namespace_id: model.namespace.to_param, project_id: model }
end
+ let(:other_model) { create(:project, :public) }
+ let(:other_params) do
+ { namespace_id: other_model.namespace.to_param, project_id: other_model }
+ end
+
it_behaves_like 'handle uploads'
context 'when the URL the old style, without /-/system' do
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 6cfec5f4017..13fad1b6dc9 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
+require Rails.root.join('ee', 'spec', 'db', 'schema_support') if Gitlab.ee?
describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
# Use if you are certain that this column should not have a foreign key
+ # EE: edit the ee/spec/db/schema_support.rb
IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_site_id],
diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb
index 91ef7653822..f5a487b4d57 100644
--- a/spec/fast_spec_helper.rb
+++ b/spec/fast_spec_helper.rb
@@ -4,6 +4,7 @@ ENV['GITLAB_ENV'] = 'test'
ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true'
require 'active_support/dependencies'
+require_relative '../config/initializers/0_inject_enterprise_edition_module'
require_relative '../config/settings'
require_relative 'support/rspec'
require 'active_support/all'
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index a074bbcff0a..c77605f3869 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -356,16 +356,18 @@ describe 'Admin updates settings' do
end
it 'Change Help page' do
+ new_support_url = 'http://example.com/help'
+
page.within('.as-help-page') do
fill_in 'Help page text', with: 'Example text'
check 'Hide marketing-related entries from help'
- fill_in 'Support page URL', with: 'http://example.com/help'
+ fill_in 'Support page URL', with: new_support_url
click_button 'Save changes'
end
expect(current_settings.help_page_text).to eq "Example text"
expect(current_settings.help_page_hide_commercial_content).to be_truthy
- expect(current_settings.help_page_support_url).to eq "http://example.com/help"
+ expect(current_settings.help_page_support_url).to eq new_support_url
expect(page).to have_content "Application settings saved successfully"
end
@@ -415,6 +417,34 @@ describe 'Admin updates settings' do
end
end
+ context 'Nav bar' do
+ it 'Shows default help links in nav' do
+ default_support_url = 'https://about.gitlab.com/getting-help/'
+
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Help', href: help_path)
+ expect(page).to have_link(text: 'Support', href: default_support_url)
+ end
+ end
+
+ it 'Shows custom support url in nav when set' do
+ new_support_url = 'http://example.com/help'
+ stub_application_setting(help_page_support_url: new_support_url)
+
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Support', href: new_support_url)
+ end
+ end
+ end
+
def check_all_events
page.check('Active')
page.check('Push')
diff --git a/spec/features/contextual_sidebar_spec.rb b/spec/features/contextual_sidebar_spec.rb
index 9807818bef7..e250e8cc90a 100644
--- a/spec/features/contextual_sidebar_spec.rb
+++ b/spec/features/contextual_sidebar_spec.rb
@@ -16,21 +16,21 @@ describe 'Contextual sidebar', :js do
it 'shows flyout navs when collapsed or expanded apart from on the active item when expanded' do
expect(page).not_to have_selector('.js-sidebar-collapsed')
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('a[data-qa-selector="project_link"]').hover
+ find('.rspec-project-link').hover
expect(page).not_to have_selector('.is-showing-fly-out')
- find('.qa-toggle-sidebar').click
+ find('.rspec-toggle-sidebar').click
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('a[data-qa-selector="project_link"]').hover
+ find('.rspec-project-link').hover
expect(page).to have_selector('.is-showing-fly-out')
end
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 5c20d50edc0..f273e416597 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -6,23 +6,36 @@ describe 'Dashboard > User filters todos', :js do
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }
- let(:project_1) { create(:project, name: 'project_1') }
- let(:project_2) { create(:project, name: 'project_2') }
+ let(:group1) { create(:group) }
+ let(:group2) { create(:group) }
- let(:issue) { create(:issue, title: 'issue', project: project_1) }
+ let(:project_1) { create(:project, name: 'project_1', namespace: group1) }
+ let(:project_2) { create(:project, name: 'project_2', namespace: group1) }
+ let(:project_3) { create(:project, name: 'project_3', namespace: group2) }
+
+ let(:issue1) { create(:issue, title: 'issue', project: project_1) }
+ let(:issue2) { create(:issue, title: 'issue', project: project_3) }
let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') }
before do
- create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_1, target: issue1, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_3, target: issue2, action: 1)
create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2)
project_1.add_developer(user_1)
project_2.add_developer(user_1)
+ project_3.add_developer(user_1)
sign_in(user_1)
visit dashboard_todos_path
end
+ it 'displays all todos without a filter' do
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).to have_content issue2.to_reference(full: true)
+ end
+
it 'filters by project' do
click_button 'Project'
within '.dropdown-menu-project' do
@@ -36,6 +49,20 @@ describe 'Dashboard > User filters todos', :js do
expect(page).not_to have_content project_2.full_name
end
+ it 'filters by group' do
+ click_button 'Group'
+ within '.dropdown-menu-group' do
+ fill_in 'Search groups', with: group1.full_name
+ click_link group1.full_name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).not_to have_content issue2.to_reference(full: true)
+ end
+
context 'Author filter' do
it 'filters by author' do
click_button 'Author'
@@ -65,7 +92,7 @@ describe 'Dashboard > User filters todos', :js do
it 'shows only authors of existing done todos' do
user_3 = create :user
user_4 = create :user
- create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
+ create(:todo, user: user_1, author: user_3, project: project_1, target: issue1, action: 1, state: :done)
create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done)
project_1.add_developer(user_3)
@@ -94,14 +121,15 @@ describe 'Dashboard > User filters todos', :js do
wait_for_requests
- expect(find('.todos-list')).to have_content issue.to_reference
+ expect(find('.todos-list')).to have_content issue1.to_reference
+ expect(find('.todos-list')).to have_content issue2.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
describe 'filter by action' do
before do
create(:todo, :build_failed, user: user_1, author: user_2, project: project_1)
- create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue)
+ create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1)
end
it 'filters by Assigned' do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 3a46a4e0167..b4531f5da4e 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -102,7 +102,7 @@ describe 'Sort Issuable List' do
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_merge_request).to include(first_updated_issuable.title)
expect(last_merge_request).to include(last_updated_issuable.title)
@@ -204,7 +204,7 @@ describe 'Sort Issuable List' do
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_issue).to include(first_updated_issuable.title)
expect(last_issue).to include(last_updated_issuable.title)
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 2789d574156..a71395c0e47 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -28,7 +28,7 @@ describe "User creates issue" do
fill_in("Title", with: issue_title)
first('.js-md').click
- first('.qa-issuable-form-description').native.send_keys('Description')
+ first('.rspec-issuable-form-description').native.send_keys('Description')
click_button("Submit issue")
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 609d8d9976b..c098a1b3e3a 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -418,8 +418,8 @@ describe 'Copy as GFM', :js do
html = <<~HTML
<div class="md-suggestion">
- <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header">
- <div class="qa-suggestion-diff-header font-weight-bold">
+ <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header js-suggestion-diff-header">
+ <div class="qa-suggestion-diff-header js-suggestion-diff-header font-weight-bold">
Suggested change
<a href="/gitlab/help/user/discussions/index.md#suggest-changes" aria-label="Help" class="js-help-btn">
<svg aria-hidden="true" class="s16 ic-question-o link-highlight">
@@ -428,7 +428,7 @@ describe 'Copy as GFM', :js do
</a>
</div>
<!---->
- <button type="button" class="btn qa-apply-btn">Apply suggestion</button>
+ <button type="button" class="btn qa-apply-btn js-apply-btn">Apply suggestion</button>
</div>
<table class="mb-3 md-suggestion-diff js-syntax-highlight code white">
<tbody>
@@ -798,7 +798,7 @@ describe 'Copy as GFM', :js do
context 'selecting one word of text' do
it 'copies as inline code' do
verify(
- '.line[id="LC27"] .s2',
+ '.line[id="LC27"] .nl',
'`"bio"`'
)
diff --git a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
index d0dba4b9761..1ebe9e2e409 100644
--- a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb
+++ b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Merge Request > Tries to access private repo of public project' do
+describe 'Merge Request > User tries to access private project information through the new mr page' do
let(:current_user) { create(:user) }
let(:private_project) do
create(:project, :public, :repository,
@@ -35,5 +35,22 @@ describe 'Merge Request > Tries to access private repo of public project' do
it "does not mention the project the user can't see the repo of" do
expect(page).not_to have_content('nothing-to-see-here')
end
+
+ context 'when the user enters label information from the private project in the querystring' do
+ let(:inaccessible_label) { create(:label, project: private_project) }
+ let(:mr_path) do
+ project_new_merge_request_path(
+ owned_project,
+ merge_request: {
+ label_ids: [inaccessible_label.id],
+ source_branch: 'feature'
+ }
+ )
+ end
+
+ it 'does not expose the label name' do
+ expect(page).not_to have_content(inaccessible_label.name)
+ 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 7a53e2d19f7..1ab7742b36e 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -42,7 +42,7 @@ describe 'User edit profile' do
simulate_input('#user_name', 'Martin 😀')
submit_settings
- page.within('.qa-full-name') do
+ page.within('.rspec-full-name') do
expect(page).to have_css '.gl-field-error-outline'
expect(find('.gl-field-error')).not_to have_selector('.hidden')
expect(find('.gl-field-error')).to have_content('Using emojis in names seems fun, but please try to set a status message instead')
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 45adcc00a3c..1294c8822b6 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -250,11 +250,11 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
- mirror = find('.qa-mirrored-repository-row')
+ mirror = find('.rspec-mirrored-repository-row')
- expect(mirror).to have_selector('.qa-delete-mirror')
- expect(mirror).to have_selector('.qa-disabled-mirror-badge')
- expect(mirror).not_to have_selector('.qa-update-now-button')
+ expect(mirror).to have_selector('.rspec-delete-mirror')
+ expect(mirror).to have_selector('.rspec-disabled-mirror-badge')
+ expect(mirror).not_to have_selector('.rspec-update-now-button')
end
end
end
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 0739726f52c..9f09c5c4501 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -94,7 +94,7 @@ describe 'Projects > Settings > User manages merge request settings' do
it 'when unchecked sets :printing_merge_request_link_enabled to false' do
uncheck('project_printing_merge_request_link_enabled')
within('.merge-request-settings-form') do
- find('.qa-save-merge-request-changes')
+ find('.rspec-save-merge-request-changes')
click_on('Save changes')
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 6be0565eaf5..1080976f7ce 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -142,7 +142,7 @@ describe "User creates wiki page" do
# blur. Just not `click`. But only when you manually insert \n or \r - if you manually insert any other sequence
# then `click` is fired normally. And it's only Capybara. Browsers and JSDOM don't have this issue.
# So that's why the next line performs the click via JS.
- page.execute_script("document.querySelector('.qa-create-page-button').click()")
+ page.execute_script("document.querySelector('.rspec-create-page-button').click()")
page.within ".md" do
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
diff --git a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
index 5c16d7783f0..6740df1d4ed 100644
--- a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
@@ -42,7 +42,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
@@ -75,7 +75,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index d1e2d17e9cc..67ae26d8d1e 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -387,7 +387,7 @@ describe 'Project' do
end
it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
- { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ { form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
def remove_with_confirm(button_text, confirm_with)
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 7844d6ac6d3..3322a747cf5 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -16,6 +16,7 @@ describe 'Protected Tags', :js do
it "allows creating explicit protected tags" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('some-tag') }
@@ -29,6 +30,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content(commit.id[0..7]) }
@@ -37,6 +39,7 @@ describe 'Protected Tags', :js do
it "displays an error message if the named tag does not exist" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('tag was removed') }
@@ -47,6 +50,7 @@ describe 'Protected Tags', :js do
it "allows creating protected tags with a wildcard" do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('*-stable') }
@@ -60,6 +64,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") do
@@ -75,6 +80,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
visit project_protected_tags_path(project)
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 9661e204dfb..19cd21e4161 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -83,29 +83,6 @@ describe 'Triggers', :js do
end
end
- describe 'trigger "Take ownership" workflow' do
- before do
- create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit project_settings_ci_cd_path(@project)
- end
-
- it 'button "Take ownership" has correct alert' do
- expected_alert = 'By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?'
- expect(page.find('a.btn-trigger-take-ownership')['data-confirm']).to eq expected_alert
- end
-
- it 'take trigger ownership' do
- # See if "Take ownership" on trigger works post trigger creation
- page.accept_confirm do
- first(:link, "Take ownership").send_keys(:return)
- end
-
- expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
- expect(page.find('.triggers-list')).to have_content trigger_title
- expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
- end
- end
-
describe 'trigger "Revoke" workflow' do
before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb
index dd8f3179895..14232b1b370 100644
--- a/spec/features/usage_stats_consent_spec.rb
+++ b/spec/features/usage_stats_consent_spec.rb
@@ -8,7 +8,15 @@ describe 'Usage stats consent' do
let(:message) { 'To help improve GitLab, we would like to periodically collect usage information.' }
before do
- allow(user).to receive(:has_current_license?).and_return false
+ if Gitlab.ee?
+ allow_any_instance_of(EE::User)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ else
+ allow(user)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ end
gitlab_sign_in(user)
end
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index aa0b544f948..48f2ee86619 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -101,7 +101,7 @@ describe('Markdown field header component', () => {
vm.canSuggest = false;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null);
+ expect(vm.$el.querySelector('.js-suggestion-btn')).toBe(null);
});
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index d69b4c7c162..6716e5cd794 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -28,8 +28,8 @@ describe('Suggestion Diff component', () => {
wrapper.destroy();
});
- const findApplyButton = () => wrapper.find('.qa-apply-btn');
- const findHeader = () => wrapper.find('.qa-suggestion-diff-header');
+ const findApplyButton = () => wrapper.find('.js-apply-btn');
+ const findHeader = () => wrapper.find('.js-suggestion-diff-header');
const findHelpButton = () => wrapper.find('.js-help-btn');
const findLoading = () => wrapper.find(GlLoadingIcon);
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 8eab40aeaf3..ee977e37ec1 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -22,7 +22,7 @@ describe WikiHelper do
describe '#wiki_sort_controls' do
let(:project) { create(:project) }
let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) }
- let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" }
+ let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
def expected_link(sort, direction, icon_class)
path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js
index 504bc51778c..473cc0612ea 100644
--- a/spec/javascripts/boards/components/boards_selector_spec.js
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -76,7 +76,8 @@ describe('BoardsSelector', () => {
document.querySelector('.js-boards-selector'),
);
- vm.$el.querySelector('.js-dropdown-toggle').click();
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ vm.$children[0].$emit('show');
Promise.all([allBoardsResponse, recentBoardsResponse])
.then(() => vm.$nextTick())
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index 6dea570266b..efeb65acf87 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -17,7 +17,7 @@ describe('Page component', () => {
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
- .then(pdf => pdf.getPage(1))
+ .promise.then(pdf => pdf.getPage(1))
.then(page => {
testPage = page;
})
@@ -31,7 +31,8 @@ describe('Page component', () => {
it('renders the page when mounting', done => {
const promise = Promise.resolve();
- spyOn(testPage, 'render').and.callFake(() => promise);
+ spyOn(testPage, 'render').and.returnValue({ promise });
+
vm = mountComponent(Component, {
page: testPage,
number: 1,
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index d6d8eecfcb9..cb656525f06 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -21,7 +21,7 @@ describe('Squash before merge component', () => {
});
describe('checkbox', () => {
- const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+ const findCheckbox = () => wrapper.find('.js-squash-checkbox');
it('is unchecked if passed value prop is false', () => {
createComponent({
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index ea74cb9eb21..dc929e83eb7 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
describe('init', () => {
it('renders a suggestion header', () => {
- expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull();
});
it('renders a diff table with syntax highlighting', () => {
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 9694c44c17a..d2d539a62fc 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -72,47 +72,5 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
-
- context "when the slug is deemed unsafe or invalid" do
- let(:link) { "alert(1);" }
-
- invalid_slugs = [
- "javascript:",
- "JaVaScRiPt:",
- "\u0001java\u0003script:",
- "javascript :",
- "javascript: ",
- "javascript : ",
- ":javascript:",
- "javascript&#58;",
- "javascript&#0058;",
- "javascript&#x3A;",
- "javascript&#x003A;",
- "java\0script:",
- " &#14; javascript:"
- ]
-
- invalid_slugs.each do |slug|
- context "with the slug #{slug}" do
- it "doesn't rewrite a (.) relative link" do
- filtered_link = filter(
- "<a href='.#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
-
- it "doesn't rewrite a (..) relative link" do
- filtered_link = filter(
- "<a href='..#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
- end
- end
- end
end
end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index cb94944fdfb..015af20f220 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -179,6 +179,85 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe "checking slug validity when assembling links" do
+ context "with a valid slug" do
+ let(:valid_slug) { "http://example.com" }
+
+ it "includes the slug in a (.) relative link" do
+ output = described_class.to_html(
+ "[Link](./alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+
+ it "includeds the slug in a (..) relative link" do
+ output = described_class.to_html(
+ "[Link](../alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+ end
+
+ context "when the slug is deemed unsafe or invalid" do
+ invalid_slugs = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ "javascript&#x3A;",
+ "javascript&#x003A;",
+ "java\0script:",
+ " &#14; javascript:"
+ ]
+
+ invalid_js_links = [
+ "alert(1);",
+ "alert(document.location);"
+ ]
+
+ invalid_slugs.each do |slug|
+ context "with the invalid slug #{slug}" do
+ invalid_js_links.each do |link|
+ it "doesn't include a prohibited slug in a (.) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](./#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+
+ it "doesn't include a prohibited slug in a (..) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](../#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+ end
+ end
+ end
+ end
+ end
end
describe 'videos' do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index ba7f76cfa3b..be7be2f3719 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -112,11 +112,28 @@ describe ContainerRegistry::Blob do
end
end
+ context 'for a relative address' do
+ before do
+ stub_request(:get, 'http://registry.gitlab/relative')
+ .with { |request| !request.headers.include?('Authorization') }
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: '{"key":"value"}')
+ end
+
+ let(:location) { '/relative' }
+
+ it 'returns correct data' do
+ expect(blob.data).to eq '{"key":"value"}'
+ end
+ end
+
context 'for invalid file' do
let(:location) { 'file:///etc/passwd' }
it 'raises an error' do
- expect { blob.data }.to raise_error(ArgumentError, 'invalid address')
+ expect { blob.data }.to raise_error(ArgumentError, 'Invalid scheme for file:///etc/passwd')
end
end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index e1d24ae8977..e9fb6c0125c 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -17,6 +17,16 @@ describe Gitlab::GitalyClient do
})
end
+ describe '.filesystem_id_from_disk' do
+ it 'catches errors' do
+ [Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
+ allow(File).to receive(:read).with(described_class.storage_metadata_file_path('default')).and_raise(error)
+
+ expect(described_class.filesystem_id_from_disk('default')).to be_nil
+ end
+ end
+ end
+
describe '.stub_class' do
it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 930d1f62272..1532fd1103e 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -3,7 +3,13 @@
require 'spec_helper'
describe Gitlab::HTTPConnectionAdapter do
+ include StubRequests
+
describe '#connection' do
+ before do
+ stub_all_dns('https://example.org', ip_address: '93.184.216.34')
+ end
+
context 'when local requests are not allowed' do
it 'sets up the connection' do
uri = URI('https://example.org')
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index d8ed54c0248..e57c7326320 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
set(:project) { build(:project) }
set(:user) { create(:user) }
set(:environment) { create(:environment, project: project) }
- let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
+ let(:system_dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
before do
project.add_maintainer(user)
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
new file mode 100644
index 00000000000..7f2b523f5b7
--- /dev/null
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe Gitlab::Octokit::Middleware do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+
+ shared_examples 'Public URL' do
+ it 'does not raise an error' do
+ expect(app).to receive(:call).with(env)
+
+ expect { middleware.call(env) }.not_to raise_error
+ end
+ end
+
+ shared_examples 'Local URL' do
+ it 'raises an error' do
+ expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ end
+ end
+
+ describe '#call' do
+ context 'when the URL is a public URL' do
+ let(:env) { { url: 'https://public-url.com' } }
+
+ it_behaves_like 'Public URL'
+ end
+
+ context 'when the URL is a localhost adresss' do
+ let(:env) { { url: 'http://127.0.0.1' } }
+
+ context 'when localhost requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when localhost requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+
+ context 'when the URL is a local network address' do
+ let(:env) { { url: 'http://172.16.0.0' } }
+
+ context 'when local network requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when local network requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 8c2fc048a54..8b82ea7faa5 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -44,6 +44,12 @@ describe Gitlab::ProjectTemplate do
end
end
+ describe '.archive_directory' do
+ subject { described_class.archive_directory }
+
+ it { is_expected.to be_a Pathname }
+ end
+
describe 'instance methods' do
subject { described_class.new('phoenix', 'Phoenix Framework', 'Phoenix description', 'link-to-template') }
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index b6e0adbc1c2..21f2c87a755 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -219,6 +219,52 @@ describe Gitlab::QuickActions::CommandDefinition do
end
end
+ describe "#execute_message" do
+ context "when the command is a noop" do
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is not a noop" do
+ before do
+ subject.action_block = proc { self.run = true }
+ end
+
+ context "when the command is not available" do
+ before do
+ subject.condition_block = proc { false }
+ end
+
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is available" do
+ context 'when the execution_message is a static string' do
+ before do
+ subject.execution_message = 'Assigned jacopo'
+ end
+
+ it 'returns this static string' do
+ expect(subject.execute_message({}, nil)).to eq('Assigned jacopo')
+ end
+ end
+
+ context 'when the explanation is dynamic' do
+ before do
+ subject.execution_message = proc { |arg| "Assigned #{arg}" }
+ end
+
+ it 'invokes the proc' do
+ expect(subject.execute_message({}, 'Jacopo')).to eq('Assigned Jacopo')
+ end
+ end
+ end
+ end
+ end
+
describe '#explain' do
context 'when the command is not available' do
before do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 185adab1ff6..78b9b3804c3 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -20,6 +20,9 @@ describe Gitlab::QuickActions::Dsl do
desc do
"A dynamic description for #{noteable.upcase}"
end
+ execution_message do |arg|
+ "A dynamic execution message for #{noteable.upcase} passing #{arg}"
+ end
params 'The first argument', 'The second argument'
command :dynamic_description do |args|
args.split
@@ -30,6 +33,7 @@ describe Gitlab::QuickActions::Dsl do
explanation do |arg|
"Action does something with #{arg}"
end
+ execution_message 'Command applied correctly'
condition do
project == 'foo'
end
@@ -67,6 +71,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.aliases).to eq([:none])
expect(no_args_def.description).to eq('A command with no args')
expect(no_args_def.explanation).to eq('')
+ expect(no_args_def.execution_message).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
expect(no_args_def.types).to eq([])
@@ -78,6 +83,8 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.aliases).to eq([:once, :first])
expect(explanation_with_aliases_def.description).to eq('')
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
+ expect(explanation_with_aliases_def.execution_message).to eq('')
+ expect(no_args_def.params).to eq([])
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
expect(explanation_with_aliases_def.types).to eq([])
@@ -88,7 +95,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.name).to eq(:dynamic_description)
expect(dynamic_description_def.aliases).to eq([])
expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
- expect(dynamic_description_def.explanation).to eq('')
+ expect(dynamic_description_def.execute_message(OpenStruct.new(noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
expect(dynamic_description_def.types).to eq([])
@@ -100,6 +107,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.aliases).to eq([])
expect(cc_def.description).to eq('')
expect(cc_def.explanation).to eq('')
+ expect(cc_def.execution_message).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
expect(cc_def.types).to eq([])
@@ -111,6 +119,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.aliases).to eq([])
expect(cond_action_def.description).to eq('')
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
+ expect(cond_action_def.execution_message).to eq('Command applied correctly')
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
expect(cond_action_def.types).to eq([])
@@ -122,6 +131,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.aliases).to eq([])
expect(with_params_parsing_def.description).to eq('')
expect(with_params_parsing_def.explanation).to eq('')
+ expect(with_params_parsing_def.execution_message).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
expect(with_params_parsing_def.types).to eq([])
@@ -133,6 +143,7 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.aliases).to eq([])
expect(substitution_def.description).to eq('')
expect(substitution_def.explanation).to eq('')
+ expect(substitution_def.execution_message).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
expect(substitution_def.types).to eq([])
@@ -144,6 +155,7 @@ describe Gitlab::QuickActions::Dsl do
expect(has_types.aliases).to eq([])
expect(has_types.description).to eq('A command with types')
expect(has_types.explanation).to eq('')
+ expect(has_types.execution_message).to eq('')
expect(has_types.params).to eq([])
expect(has_types.condition_block).to be_nil
expect(has_types.types).to eq([Issue, Commit])
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 93194de4a1b..45d9022abeb 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -68,6 +68,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://example.org'))
expect(hostname).to eq(nil)
end
+
+ context 'when it cannot be resolved' do
+ let(:import_url) { 'http://foobar.x' }
+
+ it 'raises error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
context 'when the URL hostname is an IP address' do
@@ -79,6 +89,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to be(nil)
end
+
+ context 'when it is invalid' do
+ let(:import_url) { 'http://1.1.1.1.1' }
+
+ it 'raises an error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
end
end
@@ -180,8 +200,6 @@ describe Gitlab::UrlBlocker do
end
it 'returns true for a non-alphanumeric hostname' do
- stub_resolv
-
aggregate_failures do
expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
@@ -454,10 +472,6 @@ describe Gitlab::UrlBlocker do
end
context 'when enforce_user is' do
- before do
- stub_resolv
- end
-
context 'false (default)' do
it 'does not block urls with a non-alphanumeric username' do
expect(described_class).not_to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
@@ -505,6 +519,18 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://git‌lab.com/foo/foo.bar', ascii_only: true)).to be true
end
end
+
+ it 'blocks urls with invalid ip address' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://8.8.8.8.8')
+ end
+
+ it 'blocks urls whose hostname cannot be resolved' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://foobar.x')
+ end
end
describe '#validate_hostname' do
@@ -536,10 +562,4 @@ describe Gitlab::UrlBlocker do
end
end
end
-
- # Resolv does not support resolving UTF-8 domain names
- # See https://bugs.ruby-lang.org/issues/4270
- def stub_resolv
- allow(Resolv).to receive(:getaddresses).and_return([])
- end
end
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
new file mode 100644
index 00000000000..064c2707d06
--- /dev/null
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::Utils::SanitizeNodeLink do
+ let(:klass) do
+ struct = Struct.new(:value)
+ struct.include(described_class)
+
+ struct
+ end
+
+ subject(:object) { klass.new(:value) }
+
+ invalid_schemes = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ " &#14; javascript:"
+ ]
+
+ invalid_schemes.each do |scheme|
+ context "with the scheme: #{scheme}" do
+ describe "#remove_unsafe_links" do
+ tags = {
+ a: {
+ doc: HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>"),
+ attr: "href",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ img: {
+ doc: HTML::Pipeline.parse("<img src='#{scheme}alert(1);'>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ video: {
+ doc: HTML::Pipeline.parse("<video><source src='#{scheme}alert(1);'></video>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first.children.filter("source").first }
+ }
+ }
+
+ tags.each do |tag, opts|
+ context "<#{tag}> tags" do
+ it "removes the unsafe link" do
+ node = opts[:node_to_check].call(opts[:doc])
+
+ expect { object.remove_unsafe_links({ node: node }, remove_invalid_links: true) }
+ .to change { node[opts[:attr]] }
+
+ expect(node[opts[:attr]]).to be_blank
+ end
+ end
+ end
+ end
+
+ describe "#safe_protocol?" do
+ let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
+ let(:node) { doc.children.first }
+ let(:uri) { Addressable::URI.parse(node['href'])}
+
+ it "returns false" do
+ expect(object.safe_protocol?(scheme)).to be_falsy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
index 52387fc3688..c3d1679d031 100644
--- a/spec/lib/gitlab/zoom_link_extractor_spec.rb
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -20,5 +20,15 @@ describe Gitlab::ZoomLinkExtractor do
it { is_expected.to eq(links) }
end
+
+ describe '#match?' do
+ it 'is true when a zoom link found' do
+ expect(described_class.new('issue text https://zoom.us/j/123')).to be_match
+ end
+
+ it 'is false when no zoom link found' do
+ expect(described_class.new('issue text')).not_to be_match
+ end
+ end
end
end
diff --git a/spec/migrations/set_issue_id_for_all_versions_spec.rb b/spec/migrations/set_issue_id_for_all_versions_spec.rb
new file mode 100644
index 00000000000..bfc2731181b
--- /dev/null
+++ b/spec/migrations/set_issue_id_for_all_versions_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190715043954_set_issue_id_for_all_versions.rb')
+
+describe SetIssueIdForAllVersions, :migration do
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:designs) { table(:design_management_designs) }
+ let(:designs_versions) { table(:design_management_designs_versions) }
+ let(:versions) { table(:design_management_versions) }
+
+ before do
+ @project = projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+
+ @issue_1 = issues.create!(description: 'first', project_id: @project.id)
+ @issue_2 = issues.create!(description: 'second', project_id: @project.id)
+
+ @design_1 = designs.create!(issue_id: @issue_1.id, filename: 'homepage-1.jpg', project_id: @project.id)
+ @design_2 = designs.create!(issue_id: @issue_2.id, filename: 'homepage-2.jpg', project_id: @project.id)
+
+ @version_1 = versions.create!(sha: 'foo')
+ @version_2 = versions.create!(sha: 'bar')
+
+ designs_versions.create!(version_id: @version_1.id, design_id: @design_1.id)
+ designs_versions.create!(version_id: @version_2.id, design_id: @design_2.id)
+ end
+
+ it 'correctly sets issue_id' do
+ expect(versions.where(issue_id: nil).count).to eq(2)
+
+ migrate!
+
+ expect(versions.where(issue_id: nil).count).to eq(0)
+ expect(versions.find(@version_1.id).issue_id).to eq(@issue_1.id)
+ expect(versions.find(@version_2.id).issue_id).to eq(@issue_2.id)
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index bd87bbd8d68..db80b85360f 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -45,7 +45,7 @@ describe ApplicationSetting do
it { is_expected.to allow_value(['xn--itlab-j1a.com']).for(:outbound_local_requests_whitelist) }
it { is_expected.not_to allow_value(['<h1></h1>']).for(:outbound_local_requests_whitelist) }
it { is_expected.to allow_value(['gitlab.com']).for(:outbound_local_requests_whitelist) }
- it { is_expected.to allow_value(nil).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(nil).for(:outbound_local_requests_whitelist) }
it { is_expected.to allow_value([]).for(:outbound_local_requests_whitelist) }
context "when user accepted let's encrypt terms of service" do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 8f2f1b200e4..761695bfe91 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -121,26 +121,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '.missing_kubernetes_namespace' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
- let(:project) { cluster.project }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject do
- described_class.joins(:projects).where(projects: { id: project.id }).missing_kubernetes_namespace(kubernetes_namespaces)
- end
-
- it { is_expected.to contain_exactly(cluster) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validations' do
subject { cluster.valid? }
@@ -423,31 +403,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '#all_projects' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, projects: [project]) }
-
- subject { cluster.all_projects }
-
- context 'project cluster' do
- it 'returns project' do
- is_expected.to eq([project])
- end
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group) }
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subproject) { create(:project, group: subgroup) }
-
- it 'returns all projects for group' do
- is_expected.to contain_exactly(project, subproject)
- end
- end
- end
-
describe '#first_project' do
subject { cluster.first_project }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 24dfd0c5605..32fb0c0ff73 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -173,24 +173,6 @@ describe Project do
it { is_expected.to include_module(Sortable) }
end
- describe '.missing_kubernetes_namespace' do
- let!(:project) { create(:project) }
- let!(:cluster) { create(:cluster, :provided_by_user, :group) }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject { described_class.missing_kubernetes_namespace(kubernetes_namespaces) }
-
- it { is_expected.to contain_exactly(project) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validation' do
let!(:project) { create(:project) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index f0f01e97f1d..8ea3d16a41f 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -270,34 +270,6 @@ describe API::Triggers do
end
end
- describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
- context 'authenticated user with valid permissions' do
- it 'updates owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to include('owner')
- expect(trigger.reload.owner).to eq(user)
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
- end
-
describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 0ff777388e5..27df42c0aee 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -10,91 +10,91 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
subject(:cop) { described_class.new }
- it 'flags the use of `prepend EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ it 'does not flag the use of `prepend_if_ee EEFoo` in the middle of a file' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend EEFoo
+ prepend_if_ee 'EEFoo'
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar
- ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee(EE::Foo::Bar)` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend(EE::Foo::Bar)
- ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee('EE::Foo::Bar')
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar::Baz` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar::Baz
- ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar::Baz'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend ::EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include ::EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend EE::Foo
- ^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend ::EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend ::EE::Foo
- ^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
@@ -102,7 +102,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag prepending of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend Foo
+ prepend_if_ee 'Foo'
end
SOURCE
end
@@ -110,7 +110,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag including of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- include Foo
+ include_if_ee 'Foo'
end
SOURCE
end
@@ -118,51 +118,111 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag extending using regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- extend Foo
+ extend_if_ee 'Foo'
end
SOURCE
end
- it 'does not flag the use of `prepend EE` on the last line' do
+ it 'does not flag the use of `prepend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.prepend(EE::Foo)
+ Foo.prepend_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `include EE` on the last line' do
+ it 'does not flag the use of `include_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.include(EE::Foo)
+ Foo.include_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `extend EE` on the last line' do
+ it 'does not flag the use of `extend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.extend(EE::Foo)
+ Foo.extend_if_ee('EE::Foo')
SOURCE
end
it 'autocorrects offenses by just disabling the Cop' do
source = <<~SOURCE
class Foo
- prepend EE::Foo
- include Bar
+ prepend_if_ee 'EE::Foo'
+ include_if_ee 'Bar'
end
SOURCE
expect(autocorrect_source(source)).to eq(<<~SOURCE)
class Foo
- prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule
- include Bar
+ prepend_if_ee 'EE::Foo' # rubocop: disable Cop/InjectEnterpriseEditionModule
+ include_if_ee 'Bar'
+ end
+ SOURCE
+ end
+
+ it 'disallows the use of prepend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of extend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
end
+
+ Foo.extend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of include to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of prepend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of include_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of extend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.extend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
SOURCE
end
end
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index caa3e41402b..0e05b3c84f4 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -17,4 +17,37 @@ describe IssueEntity do
it 'has time estimation attributes' do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
+
+ context 'when issue got moved' do
+ let(:public_project) { create(:project, :public) }
+ let(:member) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:issue) { create(:issue, project: public_project) }
+
+ before do
+ project.add_developer(member)
+ public_project.add_developer(member)
+ Issues::MoveService.new(public_project, member).execute(issue, project)
+ end
+
+ context 'when user cannot read target project' do
+ it 'does not return moved_to_id' do
+ request = double('request', current_user: non_member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to be_nil
+ end
+ end
+
+ context 'when user can read target project' do
+ it 'returns moved moved_to_id' do
+ request = double('request', current_user: member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to eq(issue.moved_to_id)
+ end
+ end
+ end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 3ae7c7b1c1d..d9f35afee06 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -226,6 +226,15 @@ describe Issues::UpdateService, :mailer do
end
end
+ it 'creates zoom_link_added system note when a zoom link is added to the description' do
+ update_issue(description: 'Changed description https://zoom.us/j/5873603787')
+
+ note = find_note('a Zoom call was added')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq('a Zoom call was added to this issue')
+ end
+
context 'when issue turns confidential' do
let(:opts) do
{
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 5c3b209086c..f18239f6d39 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe MergeRequests::BuildService do
@@ -225,6 +224,11 @@ describe MergeRequests::BuildService do
let(:label_ids) { [label2.id] }
let(:milestone_id) { milestone2.id }
+ before do
+ # Guests are not able to assign labels or milestones to an issue
+ project.add_developer(user)
+ end
+
it 'assigns milestone_id and label_ids instead of issue labels and milestone' do
expect(merge_request.milestone).to eq(milestone2)
expect(merge_request.labels).to match_array([label2])
@@ -479,4 +483,35 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'when assigning labels' do
+ let(:label_ids) { [create(:label, project: project).id] }
+
+ context 'for members with less than developer access' do
+ it 'is not allowed' do
+ expect(merge_request.label_ids).to be_empty
+ end
+ end
+
+ context 'for users allowed to assign labels' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'for labels in the project' do
+ it 'is allowed for developers' do
+ expect(merge_request.label_ids).to contain_exactly(*label_ids)
+ end
+ end
+
+ context 'for unrelated labels' do
+ let(:project_label) { create(:label, project: project) }
+ let(:label_ids) { [create(:label).id, project_label.id] }
+
+ it 'only assigns related labels' do
+ expect(merge_request.label_ids).to contain_exactly(project_label.id)
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2e58da894e5..9688e02d6ac 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -91,7 +91,8 @@ describe MergeRequests::UpdateService, :mailer do
labels: [],
mentioned_users: [user2],
assignees: [user3],
- total_time_spent: 0
+ total_time_spent: 0,
+ description: "FYI #{user2.to_reference}"
}
)
end
diff --git a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
index 79a78df44ae..803b9a93be7 100644
--- a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:project) { build(:project) }
@@ -14,14 +14,16 @@ describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_m
end
describe '#get_dashboard' do
- let(:service_params) { [project, user, { environment: environment, dashboard_path: nil }] }
+ let(:service_params) { [project, user, { environment: environment }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid embedded dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it 'caches the unprocessed dashboard for subsequent calls' do
- expect(YAML).to receive(:safe_load).once.and_call_original
+ system_service = Metrics::Dashboard::SystemDashboardService
+
+ expect(system_service).to receive(:new).once.and_call_original
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
index 468e8ec9ef2..1357914be2a 100644
--- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'rails_helper'
-describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:user) { create(:user) }
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
index 13f22dd01c5..8be3e7f6064 100644
--- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:user) { create(:user) }
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 3af7ee3ad50..e436af77ed4 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -121,7 +121,22 @@ describe Projects::DestroyService do
it { expect(Dir.exist?(remove_path)).to be_truthy }
end
- context 'when flushing caches fail' do
+ context 'when flushing caches fail due to Git errors' do
+ before do
+ allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
+ allow(Gitlab::GitLogger).to receive(:warn).with(
+ class: described_class.name,
+ project_id: project.id,
+ disk_path: project.disk_path,
+ message: 'Gitlab::Git::CommandError').and_call_original
+
+ perform_enqueued_jobs { destroy_project(project, user, {}) }
+ end
+
+ it_behaves_like 'deleting the project'
+ end
+
+ context 'when flushing caches fail due to Redis' do
before do
new_user = create(:user)
project.team.add_user(new_user, Gitlab::Access::DEVELOPER)
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f25233ceeb1..06efc2ff825 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -20,13 +20,8 @@ describe Projects::DownloadService do
context 'for URLs that are on the whitelist' do
before do
- sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
- sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
- sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
- end
-
- after do
- ShamRack.unmount_all
+ stub_request(:get, 'http://mycompany.fogbugz.com/rails_sample.jpg').to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'))
+ stub_request(:get, 'http://mycompany.fogbugz.com/doc_sample.txt').to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt'))
end
context 'an image file' do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 71c4c3ad0d7..bf5f211b11c 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -28,61 +28,108 @@ describe QuickActions::InterpretService do
shared_examples 'reopen command' do
it 'returns state_event: "reopen" if content contains /reopen' do
issuable.close!
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'reopen')
end
+
+ it 'returns the reopen message' do
+ issuable.close!
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Reopened this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'close command' do
it 'returns state_event: "close" if content contains /close' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'close')
end
+
+ it 'returns the close message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Closed this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'title command' do
it 'populates title: "A brand new title" if content contains /title A brand new title' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(title: 'A brand new title')
end
+
+ it 'returns the title message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq(%{Changed the title to "A brand new title".})
+ end
end
shared_examples 'milestone command' do
it 'fetches milestone and populates milestone_id if content contains /milestone' do
milestone # populate the milestone
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: milestone.id)
end
+
+ it 'returns the milestone message' do
+ milestone # populate the milestone
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the milestone to #{milestone.to_reference}.")
+ end
+
+ it 'returns empty milestone message when milestone is wrong' do
+ _, _, message = service.execute('/milestone %wrong-milestone', issuable)
+
+ expect(message).to be_empty
+ end
end
shared_examples 'remove_milestone command' do
it 'populates milestone_id: nil if content contains /remove_milestone' do
issuable.update!(milestone_id: milestone.id)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: nil)
end
+
+ it 'returns removed milestone message' do
+ issuable.update!(milestone_id: milestone.id)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{milestone.to_reference} milestone.")
+ end
end
shared_examples 'label command' do
it 'fetches label ids and populates add_label_ids if content contains /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [bug.id, inprogress.id])
end
+
+ it 'returns the label message' do
+ bug # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.")
+ end
end
shared_examples 'multiple label command' do
it 'fetches label ids and populates add_label_ids if content contains multiple /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id, bug.id])
end
@@ -91,7 +138,7 @@ describe QuickActions::InterpretService do
shared_examples 'multiple label with same argument' do
it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id])
end
@@ -120,16 +167,23 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id])
end
+
+ it 'returns the unlabel message' do
+ issuable.update!(label_ids: [inprogress.id]) # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'multiple unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do
issuable.update!(label_ids: [inprogress.id, bug.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id])
end
@@ -138,7 +192,7 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command with no argument' do
it 'populates label_ids: [] if content contains /unlabel with no arguments' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [])
end
@@ -148,91 +202,161 @@ describe QuickActions::InterpretService do
it 'populates label_ids: [] if content contains /relabel' do
issuable.update!(label_ids: [bug.id]) # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [inprogress.id])
end
+
+ it 'returns the relabel message' do
+ issuable.update!(label_ids: [bug.id]) # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Replaced all labels with #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'todo command' do
it 'populates todo_event: "add" if content contains /todo' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'add')
end
+
+ it 'returns the todo message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Added a todo.')
+ end
end
shared_examples 'done command' do
it 'populates todo_event: "done" if content contains /done' do
TodoService.new.mark_todo(issuable, developer)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'done')
end
+
+ it 'returns the done message' do
+ TodoService.new.mark_todo(issuable, developer)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Marked to do as done.')
+ end
end
shared_examples 'subscribe command' do
it 'populates subscription_event: "subscribe" if content contains /subscribe' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'subscribe')
end
+
+ it 'returns the subscribe message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Subscribed to this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'unsubscribe command' do
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
issuable.subscribe(developer, project)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'unsubscribe')
end
+
+ it 'returns the unsubscribe message' do
+ issuable.subscribe(developer, project)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unsubscribed from this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'due command' do
+ let(:expected_date) { Date.new(2016, 8, 28) }
+
it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
+
+ expect(updates).to eq(due_date: expected_date)
+ end
- expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28))
+ it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.")
end
end
shared_examples 'remove_due_date command' do
- it 'populates due_date: nil if content contains /remove_due_date' do
+ before do
issuable.update!(due_date: Date.today)
- _, updates = service.execute(content, issuable)
+ end
+
+ it 'populates due_date: nil if content contains /remove_due_date' do
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(due_date: nil)
end
+
+ it 'returns Removed the due date' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed the due date.')
+ end
end
shared_examples 'wip command' do
it 'returns wip_event: "wip" if content contains /wip' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'wip')
end
+
+ it 'returns the wip message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'unwip command' do
it 'returns wip_event: "unwip" if content contains /wip' do
issuable.update!(title: issuable.wip_title)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'unwip')
end
+
+ it 'returns the unwip message' do
+ issuable.update!(title: issuable.wip_title)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unmarked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'estimate command' do
it 'populates time_estimate: 3600 if content contains /estimate 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 3600)
end
+
+ it 'returns the time_estimate formatted message' do
+ _, _, message = service.execute('/estimate 79d', issuable)
+
+ expect(message).to eq('Set time estimate to 3mo 3w 4d.')
+ end
end
shared_examples 'spend command' do
it 'populates spend_time: 3600 if content contains /spend 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 3600,
@@ -240,11 +364,17 @@ describe QuickActions::InterpretService do
spent_at: DateTime.now.to_date
})
end
+
+ it 'returns the spend_time message including the formatted duration and verb' do
+ _, _, message = service.execute('/spend -120m', issuable)
+
+ expect(message).to eq('Subtracted 2h spent time.')
+ end
end
shared_examples 'spend command with negative time' do
it 'populates spend_time: -1800 if content contains /spend -30m' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: -1800,
@@ -256,7 +386,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with valid date' do
it 'populates spend time: 1800 with date in date type format' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 1800,
@@ -268,7 +398,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with invalid date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -276,7 +406,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with future date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -284,18 +414,30 @@ describe QuickActions::InterpretService do
shared_examples 'remove_estimate command' do
it 'populates time_estimate: 0 if content contains /remove_estimate' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 0)
end
+
+ it 'returns the remove_estimate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed time estimate.')
+ end
end
shared_examples 'remove_time_spent command' do
it 'populates spend_time: :reset if content contains /remove_time_spent' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id })
end
+
+ it 'returns the remove_time_spent message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed spent time.')
+ end
end
shared_examples 'lock command' do
@@ -303,10 +445,16 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) }
it 'returns discussion_locked: true if content contains /lock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: true)
end
+
+ it 'returns the lock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Locked the discussion')
+ end
end
shared_examples 'unlock command' do
@@ -314,45 +462,79 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) }
it 'returns discussion_locked: true if content contains /unlock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: false)
end
+
+ it 'returns the unlock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Unlocked the discussion')
+ end
end
- shared_examples 'empty command' do
+ shared_examples 'empty command' do |error_msg|
it 'populates {} if content contains an unsupported command' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to be_empty
end
+
+ it "returns #{error_msg || 'an empty'} message" do
+ _, _, message = service.execute(content, issuable)
+
+ if error_msg
+ expect(message).to eq(error_msg)
+ else
+ expect(message).to be_empty
+ end
+ end
end
shared_examples 'merge command' do
let(:project) { create(:project, :repository) }
it 'runs merge command if content contains /merge' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(merge: merge_request.diff_head_sha)
end
+
+ it 'returns them merge message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Scheduled to merge this merge request when the pipeline succeeds.')
+ end
end
shared_examples 'award command' do
it 'toggle award 100 emoji if content contains /award :100:' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(emoji_award: "100")
end
+
+ it 'returns the award message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Toggled :100: emoji award.')
+ end
end
shared_examples 'duplicate command' do
it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do
issue_duplicate # populate the issue
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(canonical_issue_id: issue_duplicate.id)
end
+
+ it 'returns the duplicate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this issue as a duplicate of #{issue_duplicate.to_reference(project)}.")
+ end
end
shared_examples 'copy_metadata command' do
@@ -360,7 +542,7 @@ describe QuickActions::InterpretService do
source_issuable # populate the issue
todo_label # populate this label
inreview_label # populate this label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates[:add_label_ids]).to match_array([inreview_label.id, todo_label.id])
@@ -370,19 +552,45 @@ describe QuickActions::InterpretService do
expect(updates).not_to have_key(:milestone_id)
end
end
+
+ it 'returns the copy metadata message' do
+ _, _, message = service.execute("/copy_metadata #{source_issuable.to_reference}", issuable)
+
+ expect(message).to eq("Copied labels and milestone from #{source_issuable.to_reference}.")
+ end
+ end
+
+ describe 'move issue command' do
+ it 'returns the move issue message' do
+ _, _, message = service.execute("/move #{project.full_path}", issue)
+
+ expect(message).to eq("Moved this issue to #{project.full_path}.")
+ end
+
+ it 'returns move issue failure message when the referenced issue is not found' do
+ _, _, message = service.execute('/move invalid', issue)
+
+ expect(message).to eq("Move this issue failed because target project doesn't exists")
+ end
end
shared_examples 'confidential command' do
it 'marks issue as confidential if content contains /confidential' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(confidential: true)
end
+
+ it 'returns the confidential message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Made this issue confidential')
+ end
end
shared_examples 'shrug command' do
it 'appends ¯\_(ツ)_/¯ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::SHRUG)
end
@@ -390,7 +598,7 @@ describe QuickActions::InterpretService do
shared_examples 'tableflip command' do
it 'appends (╯°□°)╯︵ ┻━┻ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::TABLEFLIP)
end
@@ -398,18 +606,34 @@ describe QuickActions::InterpretService do
shared_examples 'tag command' do
it 'tags a commit' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(tag_name: tag_name, tag_message: tag_message)
end
+
+ it 'returns the tag message' do
+ _, _, message = service.execute(content, issuable)
+
+ if tag_message.present?
+ expect(message).to eq(%{Tagged this commit to #{tag_name} with "#{tag_message}".})
+ else
+ expect(message).to eq("Tagged this commit to #{tag_name}.")
+ end
+ end
end
shared_examples 'assign command' do
it 'assigns to a single user' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(assignee_ids: [developer.id])
end
+
+ it 'returns the assign message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Assigned #{developer.to_reference}.")
+ end
end
it_behaves_like 'reopen command' do
@@ -463,7 +687,7 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(project, developer, {}) }
it 'precheck passes and returns merge command' do
- _, updates = service.execute('/merge', merge_request)
+ _, updates, _ = service.execute('/merge', merge_request)
expect(updates).to eq(merge: nil)
end
@@ -559,7 +783,7 @@ describe QuickActions::InterpretService do
end
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', "Assign command failed because no user was found" do
let(:content) { '/assign @abcd1234' }
let(:issuable) { issue }
end
@@ -575,19 +799,33 @@ describe QuickActions::InterpretService do
context 'Issue' do
it 'populates assignee_ids: [] if content contains /unassign' do
issue.update!(assignee_ids: [developer.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ issue.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
context 'Merge Request' do
it 'populates assignee_ids: [] if content contains /unassign' do
merge_request.update!(assignee_ids: [developer.id])
- _, updates = service.execute(content, merge_request)
+ _, updates, _ = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ merge_request.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, merge_request)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
end
@@ -979,12 +1217,12 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do
let(:content) { "/duplicate imaginary#1234" }
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do
let(:other_project) { create(:project, :private) }
let(:issue_duplicate) { create(:issue, project: other_project) }
@@ -1049,7 +1287,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do
let(:content) { '/duplicate #{issue.to_reference}' }
let(:issuable) { issue }
end
@@ -1132,13 +1370,13 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(non_empty_project, developer)}
it 'updates target_branch if /target_branch command is executed' do
- _, updates = service.execute('/target_branch merge-test', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
it 'handles blanks around param' do
- _, updates = service.execute('/target_branch merge-test ', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test ', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
@@ -1156,6 +1394,12 @@ describe QuickActions::InterpretService do
let(:issuable) { another_merge_request }
end
end
+
+ it 'returns the target_branch message' do
+ _, _, message = service.execute('/target_branch merge-test', merge_request)
+
+ expect(message).to eq('Set target branch to merge-test.')
+ end
end
context '/board_move command' do
@@ -1171,13 +1415,13 @@ describe QuickActions::InterpretService do
it 'populates remove_label_ids for all current board columns' do
issue.update!(label_ids: [todo.id, inprogress.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id, inprogress.id])
end
it 'populates add_label_ids with the id of the given label' do
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:add_label_ids]).to eq([inreview.id])
end
@@ -1185,7 +1429,7 @@ describe QuickActions::InterpretService do
it 'does not include the given label id in remove_label_ids' do
issue.update!(label_ids: [todo.id, inreview.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
@@ -1193,11 +1437,19 @@ describe QuickActions::InterpretService do
it 'does not remove label ids that are not lists on the board' do
issue.update!(label_ids: [todo.id, bug.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
+ it 'returns board_move message' do
+ issue.update!(label_ids: [todo.id, inprogress.id])
+
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Moved issue to ~#{inreview.id} column in the board.")
+ end
+
context 'if the project has multiple boards' do
let(:issuable) { issue }
@@ -1211,13 +1463,13 @@ describe QuickActions::InterpretService do
context 'if the given label does not exist' do
let(:issuable) { issue }
let(:content) { '/board_move ~"Fake Label"' }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Move this issue failed because you need to specify only one label.'
end
context 'if multiple labels are given' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Move this issue failed because you need to specify only one label.'
end
context 'if the given label is not a list on the board' do
@@ -1292,10 +1544,16 @@ describe QuickActions::InterpretService do
end
it 'populates create_merge_request with branch_name and issue iid' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(create_merge_request: { branch_name: branch_name, issue_iid: issuable.iid })
end
+
+ it 'returns the create_merge_request message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Created branch '#{branch_name}' and a merge request to resolve this issue")
+ end
end
end
@@ -1558,6 +1816,12 @@ describe QuickActions::InterpretService do
expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue'])
end
+
+ it 'returns the execution message using the default branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq('Created a branch and a merge request to resolve this issue')
+ end
end
context 'with a branch name' do
@@ -1568,6 +1832,12 @@ describe QuickActions::InterpretService do
expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue"])
end
+
+ it 'returns the execution message using the given branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Created branch 'foo' and a merge request to resolve this issue")
+ end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 157cfc46e69..486d0ca0c56 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -513,6 +513,30 @@ describe SystemNoteService do
end
end
+ describe '.zoom_link_added' do
+ subject { described_class.zoom_link_added(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link added note text' do
+ expect(subject.note).to eq('a Zoom call was added to this issue')
+ end
+ end
+
+ describe '.zoom_link_removed' do
+ subject { described_class.zoom_link_removed(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link removed note text' do
+ expect(subject.note).to eq('a Zoom call was removed from this issue')
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
diff --git a/spec/services/zoom_notes_service_spec.rb b/spec/services/zoom_notes_service_spec.rb
new file mode 100644
index 00000000000..419ecf3f374
--- /dev/null
+++ b/spec/services/zoom_notes_service_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ZoomNotesService do
+ describe '#execute' do
+ let(:issue) { OpenStruct.new(description: description) }
+ let(:project) { Object.new }
+ let(:user) { Object.new }
+ let(:description) { 'an issue description' }
+ let(:old_description) { nil }
+
+ subject { described_class.new(issue, project, user, old_description: old_description) }
+
+ shared_examples 'no notifications' do
+ it "doesn't create notifications" do
+ expect(SystemNoteService).not_to receive(:zoom_link_added)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ it_behaves_like 'no notifications'
+
+ context 'when the zoom link exists in both description and old_description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context "when the zoom link doesn't exist in both description and old_description" do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when description == old_description' do
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when the description contains a zoom link and old_description is nil' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been added to the description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been removed from the description' do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_removed notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 6ef5f46c81b..6994b6687fc 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,6 +3,7 @@ SimpleCovEnv.start!
ENV["RAILS_ENV"] = 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
+ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index ae1b859ae3f..d86371d70b9 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -3,6 +3,8 @@
module GraphqlHelpers
MutationDefinition = Struct.new(:query, :variables)
+ NoData = Class.new(StandardError)
+
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
@@ -158,8 +160,9 @@ module GraphqlHelpers
post_graphql(mutation.query, current_user: current_user, variables: mutation.variables)
end
+ # Raises an error if no data is found
def graphql_data
- json_response['data']
+ json_response['data'] || (raise NoData, graphql_errors)
end
def graphql_errors
@@ -173,8 +176,9 @@ module GraphqlHelpers
end
end
+ # Raises an error if no response is found
def graphql_mutation_response(mutation_name)
- graphql_data[GraphqlHelpers.fieldnamerize(mutation_name)]
+ graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name))
end
def nested_fields?(field)
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index 6eb8007ed26..473f07dd413 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -28,6 +28,19 @@ module StubRequests
.and_return([addr])
end
+ def stub_all_dns(url, ip_address:)
+ url = URI(url)
+ port = 80 # arbitarily chosen, does not matter as we are not going to connect
+ socket = Socket.sockaddr_in(port, ip_address)
+ addr = Addrinfo.new(socket)
+
+ # See Gitlab::UrlBlocker
+ allow(Addrinfo).to receive(:getaddrinfo).and_call_original
+ allow(Addrinfo).to receive(:getaddrinfo)
+ .with(url.hostname, anything, nil, :STREAM)
+ .and_return([addr])
+ end
+
def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
url = parse_url(url)
url.hostname = hostname
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb
index 2c600785ad3..0e6a8059376 100644
--- a/spec/support/shared_examples/application_setting_examples.rb
+++ b/spec/support/shared_examples/application_setting_examples.rb
@@ -40,6 +40,11 @@ RSpec.shared_examples 'string of domains' do |attribute|
setting.method("#{attribute}_raw=").call("example;34543:garbage:fdh5654;")
expect(setting.method(attribute).call).to contain_exactly('example', '34543:garbage:fdh5654')
end
+
+ it 'does not raise error with nil' do
+ setting.method("#{attribute}_raw=").call(nil)
+ expect(setting.method(attribute).call).to eq([])
+ end
end
RSpec.shared_examples 'application settings examples' do
@@ -75,6 +80,12 @@ RSpec.shared_examples 'application settings examples' do
expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(ip_whitelist, domain_whitelist)
end
+
+ it 'does not raise error with nil' do
+ setting.outbound_local_requests_whitelist = nil
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], [])
+ end
end
describe 'usage ping settings' do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 97b2a01576c..39d13cccb13 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -76,6 +76,16 @@ shared_examples 'handle uploads' do
UploadService.new(model, jpg, uploader_class).execute
end
+ context 'when accessing a specific upload via different model' do
+ it 'responds with status 404' do
+ params.merge!(other_params)
+
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context "when the model is public" do
before do
model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index 867a1774aa9..db83d6f0793 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -8,7 +8,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -34,13 +34,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -80,7 +80,7 @@ shared_examples "protected branches > access control > CE" do
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -97,13 +97,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
index b337a1c18d8..f5a86e4dc2c 100644
--- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples 'tag quick action' do
it 'tags this commit' do
add_note("/tag #{tag_name} #{tag_message}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".}
expect(page).to have_content "tagged commit #{truncated_commit_sha}"
expect(page).to have_content tag_name
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
index a79a61bc708..6e7eb78261a 100644
--- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type|
it "does not close the #{issuable_type}" do
add_note('/close')
- expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}."
expect(issuable).to be_open
end
end
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 34dba5dbc31..3e9ee9a633f 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -2,8 +2,14 @@
shared_examples 'create_merge_request quick action' do
context 'create a merge request starting from an issue' do
- def expect_mr_quickaction(success)
- expect(page).to have_content 'Commands applied'
+ def expect_mr_quickaction(success, branch_name = nil)
+ command_message = if branch_name
+ "Created branch '#{branch_name}' and a merge request to resolve this issue"
+ else
+ "Created a branch and a merge request to resolve this issue"
+ end
+
+ expect(page).to have_content command_message
if success
expect(page).to have_content 'created merge request'
@@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do
end
it "doesn't create a merge request when the branch name is invalid" do
- add_note("/create_merge_request invalid branch name")
+ branch_name = 'invalid branch name'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it "doesn't create a merge request when a branch with that name already exists" do
- add_note("/create_merge_request feature")
+ branch_name = 'feature'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
@@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do
branch_name = '1-feature'
add_note("/create_merge_request #{branch_name}")
- expect_mr_quickaction(true)
+ expect_mr_quickaction(true, branch_name)
created_mr = project.merge_requests.last
expect(created_mr.source_branch).to eq(branch_name)
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
index 633c7135fbc..3834b8b2b87 100644
--- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
@@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do
add_note("/duplicate ##{original_issue.to_reference}")
expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
- expect(page).to have_content 'Commands applied'
expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_closed
@@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do
it 'does not create a note, and does not mark the issue as a duplicate' do
add_note("/duplicate ##{original_issue.to_reference}")
- expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_open
diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
index a0b0d888769..85682b4919d 100644
--- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -12,7 +12,7 @@ shared_examples 'move quick action' do
it 'moves the issue' do
add_note("/move #{target_project.full_path}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
@@ -29,7 +29,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}."
expect(issue.reload).to be_open
end
end
@@ -40,7 +40,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Move this issue failed because target project doesn't exists"
expect(issue.reload).to be_open
end
end
@@ -56,7 +56,7 @@ shared_examples 'move quick action' do
shared_examples 'applies the commands to issues in both projects, target and source' do
it "applies quick actions" do
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index c454ddc4bba..ac7c17915de 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -10,7 +10,7 @@ shared_examples 'merge quick action' do
it 'merges the MR' do
add_note("/merge")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.'
expect(merge_request.reload).to be_merged
end
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
new file mode 100644
index 00000000000..7b17549b8c7
--- /dev/null
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe 'gitlab:update_project_templates rake task' do
+ let!(:tmpdir) { Dir.mktmpdir }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/update_templates'
+ create(:admin)
+ allow(Gitlab::ProjectTemplate)
+ .to receive(:archive_directory)
+ .and_return(Pathname.new(tmpdir))
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ it 'updates valid project templates' do
+ expect { run_rake_task('gitlab:update_project_templates', ['rails']) }
+ .to change { Dir.entries(tmpdir) }
+ .by(['rails.tar.gz'])
+ end
+end
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 42352f9b9f8..6134137d2b7 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -85,6 +85,27 @@ describe RecordsUploads do
expect { existing.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(Upload.count).to eq(1)
end
+
+ it 'does not affect other uploads with different model but the same path' do
+ project = create(:project)
+ other_project = create(:project)
+
+ uploader = RecordsUploadsExampleUploader.new(other_project)
+
+ upload_for_project = Upload.create!(
+ path: File.join('uploads', 'rails_sample.jpg'),
+ size: 512.kilobytes,
+ model: project,
+ uploader: uploader.class.to_s
+ )
+
+ uploader.store!(upload_fixture('rails_sample.jpg'))
+
+ upload_for_project_fresh = Upload.find(upload_for_project.id)
+
+ expect(upload_for_project).to eq(upload_for_project_fresh)
+ expect(Upload.count).to eq(2)
+ end
end
describe '#destroy_upload callback' do
diff --git a/spec/validators/qualified_domain_array_validator_spec.rb b/spec/validators/qualified_domain_array_validator_spec.rb
index a96b00bfd1d..6beb4c67f6f 100644
--- a/spec/validators/qualified_domain_array_validator_spec.rb
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -19,20 +19,19 @@ describe QualifiedDomainArrayValidator do
subject { validator.validate(record) }
- shared_examples 'cannot be blank' do
- it 'returns error when attribute is blank' do
- record.domain_array = []
+ shared_examples 'can be nil' do
+ it 'allows when attribute is nil' do
+ record.domain_array = nil
subject
- expect(record.errors).to be_present
- expect(record.errors.first[1]).to eq 'entries cannot be blank'
+ expect(record.errors).to be_empty
end
end
- shared_examples 'can be nil' do
- it 'allows when attribute is nil' do
- record.domain_array = nil
+ shared_examples 'can be blank' do
+ it 'allows when attribute is blank' do
+ record.domain_array = []
subject
@@ -43,7 +42,7 @@ describe QualifiedDomainArrayValidator do
describe 'validations' do
let(:validator) { described_class.new(attributes: [:domain_array]) }
- it_behaves_like 'cannot be blank'
+ it_behaves_like 'can be blank'
it 'returns error when attribute is nil' do
record.domain_array = nil
@@ -51,6 +50,7 @@ describe QualifiedDomainArrayValidator do
subject
expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq('entries cannot be nil')
end
it 'allows when domain is valid' do
@@ -91,21 +91,13 @@ describe QualifiedDomainArrayValidator do
let(:validator) { described_class.new(attributes: [:domain_array], allow_nil: true) }
it_behaves_like 'can be nil'
-
- it_behaves_like 'cannot be blank'
+ it_behaves_like 'can be blank'
end
context 'when allow_blank is set to true' do
let(:validator) { described_class.new(attributes: [:domain_array], allow_blank: true) }
it_behaves_like 'can be nil'
-
- it 'allows when attribute is blank' do
- record.domain_array = []
-
- subject
-
- expect(record.errors).to be_empty
- end
+ it_behaves_like 'can be blank'
end
end
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index bf6d05c6b6e..0c445aeac88 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -816,7 +816,6 @@ pbkdf2,3.0.14,MIT
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
peek-mysql2,1.1.0,MIT
-peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
pify,3.0.0,MIT
diff --git a/yarn.lock b/yarn.lock
index 7095ff706c2..6643b011dab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -996,17 +996,16 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
-"@gitlab/ui@^5.9.0":
- version "5.9.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.9.0.tgz#a38b1b57c365608b100b95969ae7a57ce1707542"
- integrity sha512-cgvEPWVerYZNLqkHjg5dd0VhEDBWj8aNoISZCaGOWI9K4yVtpMPVRUv19o/xYm4vUexfFsG9vg9lBgd+4ZU6Yw==
+"@gitlab/ui@^5.11.1":
+ version "5.11.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.11.1.tgz#10ee8a4410eb249032142f85f6180b6c465c48d7"
+ integrity sha512-bxIB3//aaYZIT6fpDKhIW60gvVvOCbw6inqC8xffQmklFYFKgcZjEIBu3RH5oJ6t3zFxeelcPQG7+t2F+p3bIg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
bootstrap "4.3.1"
- bootstrap-vue "^2.0.0-rc.26"
+ bootstrap-vue "2.0.0-rc.27"
copy-to-clipboard "^3.0.8"
- core-js "^2.6.9"
echarts "^4.2.0-rc.2"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
@@ -2180,10 +2179,10 @@ bfj@^6.1.1:
hoopy "^0.1.2"
tryer "^1.0.0"
-big.js@^3.1.3:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
- integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
+big.js@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+ integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
binary-extensions@^1.0.0:
version "1.11.0"
@@ -2207,10 +2206,10 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
- integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.5, bluebird@~3.5.0:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
+ integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
@@ -2255,10 +2254,10 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-bootstrap-vue@^2.0.0-rc.26:
- version "2.0.0-rc.26"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.26.tgz#2b8b9116a452584ae20ba8310d143635bc9e1375"
- integrity sha512-AzN+IRTmfR9rLFWNGt+v2XPmQjZiAlH4x5z2kStA3UoJ5LR91K34iZ8apaa6isDo+DfWDcwH4q7OwHM+VNxWwg==
+bootstrap-vue@2.0.0-rc.27:
+ version "2.0.0-rc.27"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.27.tgz#884a46a71948d13c9729134cb564467f79a7b2b9"
+ integrity sha512-gXdpt2IsKbmC3SU0bf/RgldWwgrXK7G47yslOtkk4OA1z6fOzb2orM2vU5L8NCNZQomYax9LapRucv+8PByfdA==
dependencies:
"@nuxt/opencollective" "^0.2.2"
bootstrap "^4.3.1"
@@ -2469,22 +2468,22 @@ bytes@3.0.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
-cacache@^11.0.2, cacache@^11.2.0:
- version "11.3.2"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
- integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
+cacache@^11.0.2, cacache@^11.2.0, cacache@^11.3.3:
+ version "11.3.3"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
+ integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
dependencies:
- bluebird "^3.5.3"
+ bluebird "^3.5.5"
chownr "^1.1.1"
figgy-pudding "^3.5.1"
- glob "^7.1.3"
+ glob "^7.1.4"
graceful-fs "^4.1.15"
lru-cache "^5.1.1"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
promise-inflight "^1.0.1"
- rimraf "^2.6.2"
+ rimraf "^2.6.3"
ssri "^6.0.1"
unique-filename "^1.1.1"
y18n "^4.0.0"
@@ -3204,6 +3203,24 @@ copy-to-clipboard@^3.0.8:
dependencies:
toggle-selection "^1.0.3"
+copy-webpack-plugin@^5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
+ integrity sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==
+ dependencies:
+ cacache "^11.3.3"
+ find-cache-dir "^2.1.0"
+ glob-parent "^3.1.0"
+ globby "^7.1.1"
+ is-glob "^4.0.1"
+ loader-utils "^1.2.3"
+ minimatch "^3.0.4"
+ normalize-path "^3.0.0"
+ p-limit "^2.2.0"
+ schema-utils "^1.0.0"
+ serialize-javascript "^1.7.0"
+ webpack-log "^2.0.0"
+
core-js-compat@^3.1.1:
version "3.1.4"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.1.4.tgz#e4d0c40fbd01e65b1d457980fe4112d4358a7408"
@@ -3218,21 +3235,16 @@ core-js-pure@3.1.4:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.1.4.tgz#5fa17dc77002a169a3566cc48dc774d2e13e3769"
integrity sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==
-core-js@^2.2.0, core-js@^2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
- integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
+core-js@^2.2.0, core-js@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
+ integrity sha1-+rg/uwstjchfpjbEudNMdUIMbWU=
core-js@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.3.tgz#95700bca5f248f5f78c0ec63e784eca663ec4138"
integrity sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==
-core-js@~2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
- integrity sha1-+rg/uwstjchfpjbEudNMdUIMbWU=
-
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4063,7 +4075,7 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
-dir-glob@^2.2.2:
+dir-glob@^2.0.0, dir-glob@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
@@ -5164,13 +5176,13 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
-find-cache-dir@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
- integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==
+find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+ integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
dependencies:
commondir "^1.0.1"
- make-dir "^1.0.0"
+ make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-root@^1.0.0, find-root@^1.1.0:
@@ -5528,7 +5540,7 @@ glob-to-regexp@^0.4.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
+"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@@ -5621,6 +5633,18 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+globby@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+ integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA=
+ dependencies:
+ array-union "^1.0.1"
+ dir-glob "^2.0.0"
+ glob "^7.1.2"
+ ignore "^3.3.5"
+ pify "^3.0.0"
+ slash "^1.0.0"
+
globby@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
@@ -6120,6 +6144,11 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
+ignore@^3.3.5:
+ version "3.3.10"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
+ integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
+
ignore@^4.0.3, ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -6482,10 +6511,10 @@ is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
-is-glob@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
- integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+is-glob@^4.0.0, is-glob@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+ integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
@@ -7379,10 +7408,12 @@ json5@2.x, json5@^2.1.0:
dependencies:
minimist "^1.2.0"
-json5@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
- integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+json5@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+ integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+ dependencies:
+ minimist "^1.2.0"
jsonparse@^1.2.0:
version "1.3.1"
@@ -7682,14 +7713,14 @@ loader-runner@^2.3.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
-loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
- integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
+loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
+ integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
- big.js "^3.1.3"
+ big.js "^5.2.2"
emojis-list "^2.0.0"
- json5 "^0.5.0"
+ json5 "^1.0.1"
locate-path@^2.0.0:
version "2.0.0"
@@ -7862,7 +7893,7 @@ make-dir@^1.0.0, make-dir@^1.3.0:
dependencies:
pify "^3.0.0"
-make-dir@^2.1.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -8956,7 +8987,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
-p-limit@^2.0.0:
+p-limit@^2.0.0, p-limit@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
@@ -9245,9 +9276,9 @@ pbkdf2@^3.0.3:
sha.js "^2.4.8"
pdfjs-dist@^2.0.943:
- version "2.0.943"
- resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.0.943.tgz#32fb9a2d863df5a1d89521a0b3cd900c16e7edde"
- integrity sha512-iLhNcm4XceTHRaSU5o22ZGCm4YpuW5+rf4+BJFH/feBhMQLbCGBry+Jet8Q419QDI4qgARaIQzXuiNrsNWS8Yw==
+ version "2.1.266"
+ resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.1.266.tgz#cded02268b389559e807f410d2a729db62160026"
+ integrity sha512-Jy7o1wE3NezPxozexSbq4ltuLT0Z21ew/qrEiAEeUZzHxMHGk4DUV1D7RuCXg5vJDvHmjX1YssN+we9QfRRgXQ==
dependencies:
node-ensure "^0.0.0"
worker-loader "^2.0.0"
@@ -10542,7 +10573,7 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
-rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
+rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -10760,10 +10791,10 @@ send@0.16.2:
range-parser "~1.2.0"
statuses "~1.4.0"
-serialize-javascript@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
- integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
+serialize-javascript@^1.4.0, serialize-javascript@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
+ integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
serve-index@^1.7.2:
version "1.9.1"
@@ -10881,6 +10912,11 @@ sisteransi@^1.0.0:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+ integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
+
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"