summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/merge_request_templates/Change documentation location.md2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js3
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js2
-rw-r--r--app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue2
-rw-r--r--app/assets/javascripts/reports/components/issue_status_icon.vue2
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue9
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue4
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue14
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue186
-rw-r--r--app/assets/javascripts/repository/index.js50
-rw-r--r--app/assets/javascripts/repository/queries/getPermissions.query.graphql9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue4
-rw-r--r--app/assets/stylesheets/framework/animations.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss128
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss17
-rw-r--r--app/assets/stylesheets/framework/icons.scss3
-rw-r--r--app/assets/stylesheets/framework/panels.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/issues.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss27
-rw-r--r--app/assets/stylesheets/pages/note_form.scss1
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss5
-rw-r--r--app/assets/stylesheets/pages/reports.scss9
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/boards/issues_controller.rb7
-rw-r--r--app/controllers/projects/environments_controller.rb16
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/tree_helper.rb32
-rw-r--r--app/models/ci/pipeline_schedule.rb3
-rw-r--r--app/models/ci/trigger.rb11
-rw-r--r--app/models/clusters/applications/runner.rb15
-rw-r--r--app/models/clusters/concerns/application_core.rb10
-rw-r--r--app/models/clusters/concerns/application_status.rb24
-rw-r--r--app/models/concerns/stepable.rb35
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/namespace.rb3
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/policies/ci/trigger_policy.rb2
-rw-r--r--app/services/boards/issues/move_service.rb39
-rw-r--r--app/services/clusters/applications/check_uninstall_progress_service.rb1
-rw-r--r--app/services/self_monitoring/project/create_service.rb132
-rw-r--r--app/views/devise/shared/_signup_box.html.haml14
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/projects/_files.html.haml3
-rw-r--r--app/views/projects/_new_project_fields.html.haml4
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml2
-rw-r--r--app/views/projects/issues/import_csv/_modal.html.haml2
-rw-r--r--app/views/projects/new.html.haml8
-rw-r--r--app/views/projects/project_templates/_built_in_templates.html.haml4
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/projects/triggers/_content.html.haml17
-rw-r--r--app/views/projects/triggers/_trigger.html.haml7
-rw-r--r--app/views/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml8
-rw-r--r--changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml5
-rw-r--r--changelogs/unreleased/61145-fix-button-dimensions.yml5
-rw-r--r--changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml5
-rw-r--r--changelogs/unreleased/61613-spacing-mr-widgets.yml5
-rw-r--r--changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml5
-rw-r--r--changelogs/unreleased/bjk-fix_prom_example.yml5
-rw-r--r--changelogs/unreleased/button-bug-fixes.yml5
-rw-r--r--changelogs/unreleased/jc-remove-catfile-flag.yml5
-rw-r--r--changelogs/unreleased/mh-mermaid-linebreaks.yml5
-rw-r--r--changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml5
-rw-r--r--config/gitlab.yml.example13
-rw-r--r--config/initializers/zz_metrics.rb2
-rw-r--r--danger/commit_messages/Dangerfile23
-rw-r--r--doc/administration/auth/README.md37
-rw-r--r--doc/administration/auth/authentiq.md16
-rw-r--r--doc/administration/auth/crowd.md6
-rw-r--r--doc/administration/auth/google_secure_ldap.md16
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md20
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md22
-rw-r--r--doc/administration/auth/jwt.md16
-rw-r--r--doc/administration/auth/ldap-ee.md4
-rw-r--r--doc/administration/auth/ldap.md22
-rw-r--r--doc/administration/auth/oidc.md6
-rw-r--r--doc/administration/auth/okta.md16
-rw-r--r--doc/administration/auth/smartcard.md18
-rw-r--r--doc/administration/geo/disaster_recovery/background_verification.md17
-rw-r--r--doc/administration/high_availability/README.md4
-rw-r--r--doc/administration/high_availability/alpha_database.md3
-rw-r--r--doc/administration/high_availability/consul.md6
-rw-r--r--doc/administration/high_availability/database.md7
-rw-r--r--doc/administration/high_availability/gitaly.md16
-rw-r--r--doc/administration/high_availability/gitlab.md6
-rw-r--r--doc/administration/high_availability/load_balancer.md16
-rw-r--r--doc/administration/high_availability/monitoring_node.md18
-rw-r--r--doc/administration/high_availability/nfs.md38
-rw-r--r--doc/administration/high_availability/nfs_host_client_setup.md16
-rw-r--r--doc/administration/high_availability/pgbouncer.md6
-rw-r--r--doc/administration/high_availability/redis.md4
-rw-r--r--doc/administration/high_availability/redis_source.md4
-rw-r--r--doc/administration/monitoring/prometheus/index.md54
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/dependencies.md50
-rw-r--r--doc/api/jobs.md14
-rw-r--r--doc/api/merge_requests.md6
-rw-r--r--doc/api/releases/img/upcoming_release_v12_1.pngbin0 -> 22635 bytes
-rw-r--r--doc/api/releases/index.md18
-rw-r--r--doc/api/users.md24
-rw-r--r--doc/ci/docker/using_docker_images.md37
-rw-r--r--doc/ci/examples/README.md1
-rw-r--r--doc/ci/yaml/README.md75
-rw-r--r--doc/development/fe_guide/event_tracking.md2
-rw-r--r--doc/integration/elasticsearch.md6
-rw-r--r--doc/security/information_exclusivity.md1
-rw-r--r--doc/security/password_length_limits.md30
-rw-r--r--doc/security/rack_attack.md1
-rw-r--r--doc/security/reset_root_password.md1
-rw-r--r--doc/security/ssh_keys_restrictions.md1
-rw-r--r--doc/security/two_factor_authentication.md1
-rw-r--r--doc/security/unlock_user.md47
-rw-r--r--doc/security/user_email_confirmation.md1
-rw-r--r--doc/security/user_file_uploads.md1
-rw-r--r--doc/security/webhooks.md1
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md20
-rw-r--r--doc/user/admin_area/settings/img/admin_required_pipeline.pngbin0 -> 64548 bytes
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/clusters/applications.md1
-rw-r--r--doc/user/project/autocomplete_characters.md48
-rwxr-xr-xdoc/user/project/img/autocomplete_characters_example1_v12_0.pngbin0 -> 17510 bytes
-rwxr-xr-xdoc/user/project/img/autocomplete_characters_example2_v12_0.pngbin0 -> 14623 bytes
-rw-r--r--doc/user/project/index.md3
-rw-r--r--doc/user/project/integrations/prometheus.md115
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md21
-rw-r--r--doc/user/project/pipelines/settings.md16
-rw-r--r--doc/user/project/quick_actions.md20
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/releases.rb2
-rw-r--r--lib/api/users.rb8
-rw-r--r--lib/banzai/filter/ascii_doc_sanitization_filter.rb30
-rw-r--r--lib/banzai/filter/reference_redactor_filter.rb (renamed from lib/banzai/filter/redactor_filter.rb)4
-rw-r--r--lib/banzai/object_renderer.rb2
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb2
-rw-r--r--lib/banzai/reference_redactor.rb (renamed from lib/banzai/redactor.rb)2
-rw-r--r--lib/banzai/renderer.rb2
-rw-r--r--lib/feature/gitaly.rb6
-rw-r--r--lib/gitlab/asciidoc.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb6
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb4
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb11
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb17
-rw-r--r--lib/gitlab/ci/pipeline/expression/parser.rb39
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/gitaly_client.rb3
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb9
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--lib/gitlab/usage_data_counters/redis_counter.rb19
-rw-r--r--lib/gitlab/usage_data_counters/web_ide_commits_counter.rb13
-rw-r--r--lib/gitlab/web_ide_commits_counter.rb17
-rw-r--r--lib/tasks/gitlab/features.rake14
-rw-r--r--lib/tasks/gitlab/seed.rake7
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/qa/page/main/oauth.rb4
-rw-r--r--qa/qa/page/main/sign_up.rb26
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb8
-rw-r--r--qa/qa/page/project/sub_menus/common.rb6
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb2
-rw-r--r--qa/qa/resource/merge_request.rb3
-rwxr-xr-xscripts/lint-doc.sh4
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb24
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb4
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb4
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/health_check_controller_spec.rb1
-rw-r--r--spec/controllers/health_controller_spec.rb1
-rw-r--r--spec/controllers/metrics_controller_spec.rb1
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb4
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb4
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb9
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb4
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb9
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb10
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb199
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb5
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb20
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb4
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb9
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb29
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb6
-rw-r--r--spec/controllers/snippets_controller_spec.rb2
-rw-r--r--spec/controllers/users_controller_spec.rb2
-rw-r--r--spec/factories/projects.rb22
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/factories/services_data.rb8
-rw-r--r--spec/features/markdown/mermaid_spec.rb18
-rw-r--r--spec/features/triggers_spec.rb78
-rw-r--r--spec/frontend/create_merge_request_dropdown_spec.js34
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js20
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb (renamed from spec/lib/banzai/filter/redactor_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb8
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb6
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb (renamed from spec/lib/banzai/redactor_spec.rb)2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb69
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb246
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb11
-rw-r--r--spec/lib/gitlab/import_export/project.json8
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb11
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb54
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/web_ide_commits_counter_spec.rb19
-rw-r--r--spec/models/ci/trigger_spec.rb20
-rw-r--r--spec/models/clusters/applications/runner_spec.rb33
-rw-r--r--spec/models/concerns/stepable_spec.rb111
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb94
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/search_spec.rb4
-rw-r--r--spec/requests/api/services_spec.rb13
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/requests/lfs_locks_api_spec.rb4
-rw-r--r--spec/services/boards/issues/move_service_spec.rb4
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb8
-rw-r--r--spec/services/self_monitoring/project/create_service_spec.rb201
-rw-r--r--spec/support/features/rss_shared_examples.rb4
-rw-r--r--spec/support/json_response.rb2
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb2
249 files changed, 2897 insertions, 1109 deletions
diff --git a/.gitlab/merge_request_templates/Change documentation location.md b/.gitlab/merge_request_templates/Change documentation location.md
index c80af95d5e5..7dc80a641c4 100644
--- a/.gitlab/merge_request_templates/Change documentation location.md
+++ b/.gitlab/merge_request_templates/Change documentation location.md
@@ -22,7 +22,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen
- [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app,
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
-- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
+- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread.
- [ ] Update the link in `features.yml` (if applicable)
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
diff --git a/Gemfile b/Gemfile
index 20eeb7cc5ea..7845e9d57fa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -431,7 +431,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.36.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.37.0', require: 'gitaly'
gem 'grpc', '~> 1.19.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index bf469de3835..beded888ffd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -310,7 +310,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.36.0)
+ gitaly-proto (1.37.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-labkit (0.3.0)
@@ -1119,7 +1119,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.36.0)
+ gitaly-proto (~> 1.37.0)
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.3.0)
gitlab-markup (~> 1.7.0)
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index b23de36f860..dbc28beffbe 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -36,7 +36,8 @@ export default function renderMermaid($els) {
});
$els.each((i, el) => {
- const source = el.textContent;
+ // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
+ const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
/**
* Restrict the rendering to a certain amount of character to
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index 052168bb21c..dce9c1a5410 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -182,7 +182,7 @@ export default class CreateMergeRequestDropdown {
}
enable() {
- if (!canCreateConfidentialMergeRequest()) return;
+ if (isConfidentialIssue() && !canCreateConfidentialMergeRequest()) return;
this.createMergeRequestButton.classList.remove('disabled');
this.createMergeRequestButton.removeAttribute('disabled');
diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
index 6d908524da9..f0112a5a623 100644
--- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
+++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
@@ -65,7 +65,7 @@ export default {
<template>
<div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)">
- <div id="merge-requests" class="card-slim mt-3">
+ <div id="merge-requests" class="card card-slim mt-3">
<div class="card-header">
<div class="card-title mt-0 mb-0 h5 merge-requests-title">
<span class="mr-1">
diff --git a/app/assets/javascripts/reports/components/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index 04fba43b2f3..386653b9444 100644
--- a/app/assets/javascripts/reports/components/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -16,7 +16,7 @@ export default {
statusIconSize: {
type: Number,
required: false,
- default: 32,
+ default: 24,
},
},
computed: {
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 2be9c37b00a..d477fafd3f5 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -27,7 +27,7 @@ export default {
statusIconSize: {
type: Number,
required: false,
- default: 32,
+ default: 24,
},
isNew: {
type: Boolean,
@@ -43,12 +43,15 @@ export default {
};
</script>
<template>
- <li :class="{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue">
+ <li
+ :class="{ 'is-dismissed': issue.isDismissed }"
+ class="report-block-list-issue justify-content-center align-items-center"
+ >
<issue-status-icon
v-if="showReportSectionStatusIcon"
:status="status"
:status-icon-size="statusIconSize"
- class="append-right-5"
+ class="append-right-default"
/>
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 3d576caaf8f..9bc3e6388e3 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -165,8 +165,8 @@ export default {
<template>
<section class="media-section">
<div class="media">
- <status-icon :status="statusIconName" />
- <div class="media-body d-flex flex-align-self-center">
+ <status-icon :status="statusIconName" :size="24" />
+ <div class="media-body d-flex flex-align-self-center prepend-left-default">
<span class="js-code-text code-text">
{{ headerText }}
<slot :name="slotName"></slot>
diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index 97a68531d29..1caf52431e2 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -44,10 +44,16 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue report-block-list-issue-parent">
- <div class="report-block-list-icon append-right-10 prepend-left-5">
- <gl-loading-icon v-if="statusIcon === 'loading'" css-class="report-block-list-loading-icon" />
- <ci-icon v-else :status="iconStatus" />
+ <div
+ class="report-block-list-issue report-block-list-issue-parent justify-content-center align-items-center"
+ >
+ <div class="report-block-list-icon append-right-default">
+ <gl-loading-icon
+ v-if="statusIcon === 'loading'"
+ css-class="report-block-list-loading-icon"
+ size="md"
+ />
+ <ci-icon v-else :status="iconStatus" :size="24" />
</div>
<div class="report-block-list-issue-description">
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 67963dc1923..afb58a60155 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -1,12 +1,41 @@
<script>
+import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
+import { __ } from '../../locale';
+import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref';
import getProjectShortPath from '../queries/getProjectShortPath.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
+import getPermissions from '../queries/getPermissions.query.graphql';
+
+const ROW_TYPES = {
+ header: 'header',
+ divider: 'divider',
+};
export default {
+ components: {
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownHeader,
+ GlDropdownItem,
+ Icon,
+ },
apollo: {
projectShortPath: {
query: getProjectShortPath,
},
+ projectPath: {
+ query: getProjectPath,
+ },
+ userPermissions: {
+ query: getPermissions,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update: data => data.project.userPermissions,
+ },
},
mixins: [getRefMixin],
props: {
@@ -15,10 +44,52 @@ export default {
required: false,
default: '/',
},
+ canCollaborate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ canEditTree: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ newBranchPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newTagPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkNewBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkNewDirectoryPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkUploadBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
projectShortPath: '',
+ projectPath: '',
+ userPermissions: {},
};
},
computed: {
@@ -39,11 +110,112 @@ export default {
[{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }],
);
},
+ canCreateMrFromFork() {
+ return this.userPermissions.forkProject && this.userPermissions.createMergeRequestIn;
+ },
+ dropdownItems() {
+ const items = [];
+
+ if (this.canEditTree) {
+ items.push(
+ {
+ type: ROW_TYPES.header,
+ text: __('This directory'),
+ },
+ {
+ attrs: {
+ href: this.newBlobPath,
+ class: 'qa-new-file-option',
+ },
+ text: __('New file'),
+ },
+ {
+ attrs: {
+ href: '#modal-upload-blob',
+ 'data-target': '#modal-upload-blob',
+ 'data-toggle': 'modal',
+ },
+ text: __('Upload file'),
+ },
+ {
+ attrs: {
+ href: '#modal-create-new-dir',
+ 'data-target': '#modal-create-new-dir',
+ 'data-toggle': 'modal',
+ },
+ text: __('New directory'),
+ },
+ );
+ } else if (this.canCreateMrFromFork) {
+ items.push(
+ {
+ attrs: {
+ href: this.forkNewBlobPath,
+ 'data-method': 'post',
+ },
+ text: __('New file'),
+ },
+ {
+ attrs: {
+ href: this.forkUploadBlobPath,
+ 'data-method': 'post',
+ },
+ text: __('Upload file'),
+ },
+ {
+ attrs: {
+ href: this.forkNewDirectoryPath,
+ 'data-method': 'post',
+ },
+ text: __('New directory'),
+ },
+ );
+ }
+
+ if (this.userPermissions.pushCode) {
+ items.push(
+ {
+ type: ROW_TYPES.divider,
+ },
+ {
+ type: ROW_TYPES.header,
+ text: __('This repository'),
+ },
+ {
+ attrs: {
+ href: this.newBranchPath,
+ },
+ text: __('New branch'),
+ },
+ {
+ attrs: {
+ href: this.newTagPath,
+ },
+ text: __('New tag'),
+ },
+ );
+ }
+
+ return items;
+ },
+ renderAddToTreeDropdown() {
+ return this.canCollaborate || this.canCreateMrFromFork;
+ },
},
methods: {
isLast(i) {
return i === this.pathLinks.length - 1;
},
+ getComponent(type) {
+ switch (type) {
+ case ROW_TYPES.divider:
+ return 'gl-dropdown-divider';
+ case ROW_TYPES.header:
+ return 'gl-dropdown-header';
+ default:
+ return 'gl-dropdown-item';
+ }
+ },
},
};
</script>
@@ -56,6 +228,20 @@ export default {
{{ link.name }}
</router-link>
</li>
+ <li v-if="renderAddToTreeDropdown" class="breadcrumb-item">
+ <gl-dropdown toggle-class="add-to-tree qa-add-to-tree ml-1">
+ <template slot="button-content">
+ <span class="sr-only">{{ __('Add to tree') }}</span>
+ <icon name="plus" :size="16" class="float-left" />
+ <icon name="arrow-down" :size="16" class="float-left" />
+ </template>
+ <template v-for="(item, i) in dropdownItems">
+ <component :is="getComponent(item.type)" :key="i" v-bind="item.attrs">
+ {{ item.text }}
+ </component>
+ </template>
+ </gl-dropdown>
+ </li>
</ol>
</nav>
</template>
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index ea051eaa414..f9727960040 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -5,6 +5,7 @@ import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue';
import apolloProvider from './graphql';
import { setTitle } from './utils/title';
+import { parseBoolean } from '../lib/utils/common_utils';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
@@ -36,19 +37,42 @@ export default function setupVueRepositoryList() {
.forEach(elem => elem.classList.toggle('hidden', !isRoot));
});
- // eslint-disable-next-line no-new
- new Vue({
- el: document.getElementById('js-repo-breadcrumb'),
- router,
- apolloProvider,
- render(h) {
- return h(Breadcrumbs, {
- props: {
- currentPath: this.$route.params.pathMatch,
- },
- });
- },
- });
+ const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
+
+ if (breadcrumbEl) {
+ const {
+ canCollaborate,
+ canEditTree,
+ newBranchPath,
+ newTagPath,
+ newBlobPath,
+ forkNewBlobPath,
+ forkNewDirectoryPath,
+ forkUploadBlobPath,
+ } = breadcrumbEl.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: breadcrumbEl,
+ router,
+ apolloProvider,
+ render(h) {
+ return h(Breadcrumbs, {
+ props: {
+ currentPath: this.$route.params.pathMatch,
+ canCollaborate: parseBoolean(canCollaborate),
+ canEditTree: parseBoolean(canEditTree),
+ newBranchPath,
+ newTagPath,
+ newBlobPath,
+ forkNewBlobPath,
+ forkNewDirectoryPath,
+ forkUploadBlobPath,
+ },
+ });
+ },
+ });
+ }
// eslint-disable-next-line no-new
new Vue({
diff --git a/app/assets/javascripts/repository/queries/getPermissions.query.graphql b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
new file mode 100644
index 00000000000..092fa44e2d0
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
@@ -0,0 +1,9 @@
+query getPermissions($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ userPermissions {
+ pushCode
+ forkProject
+ createMergeRequestIn
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
index 4b57693e8f1..57d4d8b7ae6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
@@ -14,6 +14,6 @@ export default {
<template>
<div class="circle-icon-container append-right-default align-self-start align-self-lg-center">
- <icon :name="name" />
+ <icon :name="name" :size="24" />
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index f5fa68308bc..40c095aa954 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -96,16 +96,14 @@ export default {
<template>
<div class="ci-widget media js-ci-widget">
<template v-if="!hasPipeline || hasCIError">
- <div
- class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default"
- >
- <icon :size="32" name="status_failed_borderless" />
+ <div class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error">
+ <icon :size="24" name="status_failed_borderless" />
</div>
- <div class="media-body" v-html="errorText"></div>
+ <div class="media-body prepend-left-default" v-html="errorText"></div>
</template>
<template v-else-if="hasPipeline">
<a :href="status.details_path" class="align-self-start append-right-default">
- <ci-icon :status="status" :size="32" :borderless="true" class="add-border" />
+ <ci-icon :status="status" :size="24" :borderless="true" class="add-border" />
</a>
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 392eb6fb425..8dbd9e52cfe 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -32,8 +32,8 @@ export default {
};
</script>
<template>
- <div class="space-children d-flex append-right-10 widget-status-icon">
- <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="md" /></div>
+ <div class="d-flex widget-status-icon">
+ <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="sm" /></div>
<ci-icon v-else :status="statusObj" :size="24" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index 0312b147b62..01524f4b650 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -83,7 +83,7 @@ export default {
<gl-button
:aria-label="ariaLabel"
variant="blank"
- class="commit-edit-toggle square s24 mr-2"
+ class="commit-edit-toggle square s24 append-right-default"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 7312b31c01c..4d7d49398eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -18,7 +18,9 @@ export default {
<template>
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
- <div class="artwork col-md-5 order-md-last col-12 text-center">
+ <div
+ class="artwork col-md-5 order-md-last col-12 text-center d-flex justify-content-center align-items-center"
+ >
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-md-7 order-md-first col-12">
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 6bc5632365f..6f5a2e561af 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -104,7 +104,7 @@
}
.btn {
- @include transition(border-color);
+ @include transition(background-color, border-color, color, box-shadow);
}
.dropdown-menu-toggle,
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e0b6da31261..767832e242c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -24,11 +24,12 @@
border-radius: $border-radius-default;
font-size: $gl-font-size;
font-weight: $gl-font-weight-normal;
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: $gl-vert-padding $gl-btn-padding;
&:focus,
&:active {
background-color: $btn-active-gray;
+ box-shadow: $gl-btn-active-background;
}
}
@@ -49,89 +50,77 @@
color: $text;
}
- &:not(:disabled):not(.disabled) {
- &:hover {
- box-shadow: inset 0 0 0 1px $hover-border, 0 2px 2px 0 $gl-btn-hover-shadow-light;
- }
+ &:hover,
+ &:focus {
+ background-color: $hover-background;
+ border-color: $hover-border;
+ color: $hover-text;
- &:focus {
- box-shadow: inset 0 0 0 1px $hover-border, 0 0 4px 1px $blue-300;
+ > .icon {
+ color: $hover-text;
}
+ }
- &:hover,
- &:focus {
- background-color: $hover-background;
- border-color: $hover-border;
- color: $hover-text;
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
- > .icon {
- color: $hover-text;
- }
- }
+ &:active {
+ background-color: $active-background;
+ border-color: $active-border;
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
+ color: $active-text;
- &:active,
- &:active:focus {
- background-color: $active-background;
- border-color: $active-border;
- box-shadow: inset 0 0 0 1px $hover-border, inset 0 2px 4px 0 rgba($black, 0.2);
+ > .icon {
color: $active-text;
+ }
- > .icon {
- color: $active-text;
- }
+ &:focus {
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
}
}
}
-@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color, $hover-shadow-color: $gl-btn-hover-shadow-dark) {
+@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light;
border-color: $border-light;
color: $color;
- &:not(:disabled):not(.disabled) {
- &:hover {
- box-shadow: inset 0 0 0 1px $border-normal, 0 2px 2px 0 $hover-shadow-color;
- }
-
- &:focus {
- box-shadow: inset 0 0 0 1px $border-normal, 0 0 4px 1px $blue-300;
- }
+ &:hover,
+ &:focus {
+ background-color: $normal;
+ border-color: $border-normal;
+ color: $color;
+ }
- &:hover,
- &:focus {
- background-color: $normal;
- border-color: $border-normal;
- color: $color;
- }
+ &:active,
+ &.active {
+ box-shadow: $gl-btn-active-background;
- &:active,
- &.active {
- box-shadow: inset 0 2px 4px 0 $gl-btn-hover-shadow-dark;
- background-color: $dark;
- border-color: $border-dark;
- color: $color;
- }
+ background-color: $dark;
+ border-color: $border-dark;
+ color: $color;
}
}
@mixin btn-green {
- @include btn-color($green-500, $green-600, $green-500, $green-700, $green-600, $green-800, $white-light);
+ @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light);
}
@mixin btn-blue {
- @include btn-color($blue-500, $blue-600, $blue-500, $blue-700, $blue-600, $blue-800, $white-light);
+ @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light);
}
@mixin btn-orange {
- @include btn-color($orange-500, $orange-600, $orange-500, $orange-700, $orange-600, $orange-800, $white-light);
+ @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light);
}
@mixin btn-red {
- @include btn-color($red-500, $red-600, $red-500, $red-700, $red-600, $red-800, $white-light);
+ @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light);
}
@mixin btn-white {
- @include btn-color($white-light, $gray-400, $gray-200, $gray-400, $gl-gray-200, $gray-500, $gl-text-color, $gl-btn-hover-shadow-light);
+ @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-gray-dark, $gl-text-color);
}
@mixin btn-with-margin {
@@ -160,20 +149,21 @@
color: $gl-text-color;
white-space: nowrap;
- line-height: $gl-btn-line-height;
&:focus:active {
outline: 0;
}
- &.btn-xs {
- font-size: $gl-btn-xs-font-size;
- line-height: $gl-btn-xs-line-height;
+ &.btn-sm {
+ padding: 4px 10px;
+ font-size: $gl-btn-small-font-size;
+ line-height: $gl-btn-small-line-height;
}
- &.btn-sm,
&.btn-xs {
- padding: 3px $gl-bordered-btn-vert-padding;
+ padding: 2px $gl-btn-padding;
+ font-size: $gl-btn-xs-font-size;
+ line-height: $gl-btn-xs-line-height;
}
&.btn-success,
@@ -249,7 +239,7 @@
&.dropdown-toggle {
.fa-caret-down {
- margin: 0;
+ margin-left: 3px;
}
}
@@ -282,7 +272,10 @@
}
svg {
- @include btn-svg;
+ height: 15px;
+ width: 15px;
+ position: relative;
+ top: 2px;
}
svg,
@@ -337,12 +330,6 @@
&.btn-grouped {
@include btn-with-margin;
}
-
- .btn {
- border-radius: $border-radius-default;
- font-size: $gl-font-size;
- line-height: $gl-btn-line-height;
- }
}
.btn-clipboard {
@@ -500,25 +487,18 @@
&:active,
&:focus {
color: $gl-text-color-secondary;
- border: 1px solid $border-gray-normal-dashed;
background-color: $white-normal;
}
}
-.btn-svg {
- padding: $gl-bordered-btn-vert-padding;
-
- svg {
- @include btn-svg;
- display: block;
- }
+.btn-svg svg {
+ @include btn-svg;
}
// All disabled buttons, regardless of color, type, etc
%disabled {
background-color: $gray-light !important;
border-color: $gray-200 !important;
- box-shadow: none;
color: $gl-text-color-disabled !important;
opacity: 1 !important;
cursor: default !important;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 05afcecca05..29f63e9578d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -8,6 +8,12 @@
}
}
+@mixin chevron-active {
+ .fa-chevron-down {
+ color: $gray-darkest;
+ }
+}
+
@mixin set-visible {
transform: translateY(0);
display: block;
@@ -43,6 +49,7 @@
.dropdown-toggle,
.dropdown-menu-toggle {
+ @include chevron-active;
border-color: $gray-darkest;
}
@@ -58,12 +65,12 @@
.dropdown-toggle,
.confidential-merge-request-fork-group .dropdown-toggle {
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: 6px 8px 6px 10px;
background-color: $white-light;
color: $gl-text-color;
font-size: 14px;
- line-height: $gl-btn-line-height;
text-align: left;
+ border: 1px solid $border-color;
border-radius: $border-radius-base;
white-space: nowrap;
@@ -96,6 +103,10 @@
padding-right: 25px;
}
+ .fa {
+ color: $gray-darkest;
+ }
+
.fa-chevron-down {
font-size: $dropdown-chevron-size;
position: relative;
@@ -104,10 +115,12 @@
}
&:hover {
+ @include chevron-active;
border-color: $gray-darkest;
}
&:focus:active {
+ @include chevron-active;
border-color: $dropdown-toggle-active-border-color;
outline: 0;
}
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 1be5ef276fd..7332c4981d2 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -88,8 +88,5 @@
display: flex;
align-items: center;
justify-content: center;
- border: $border-size solid $gray-400;
- border-radius: 50%;
- padding: $gl-padding-8 - $border-size;
color: $gray-700;
}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index cd3d6f8297e..d9c93fed1c4 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -3,7 +3,6 @@
}
.card-slim {
- @extend .card;
margin-bottom: $gl-vert-padding;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 047a9799c3f..c108f45622f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -405,8 +405,6 @@ $tanuki-yellow: #fca326;
*/
$green-500-focus: rgba($green-500, 0.4);
$gl-btn-active-background: rgba(0, 0, 0, 0.16);
-$gl-btn-hover-shadow-dark: rgba($black, 0.2);
-$gl-btn-hover-shadow-light: rgba($black, 0.1);
$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
@@ -483,8 +481,6 @@ $gl-btn-padding: 10px;
$gl-btn-line-height: 16px;
$gl-btn-vert-padding: 8px;
$gl-btn-horz-padding: 12px;
-$gl-bordered-btn-vert-padding: $gl-btn-vert-padding - 1px;
-$gl-bordered-btn-horz-padding: $gl-btn-horz-padding - 1px;
$gl-btn-small-font-size: 13px;
$gl-btn-small-line-height: 18px;
$gl-btn-xs-font-size: 13px;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index ffc6e433988..0b0a4e50146 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -167,7 +167,7 @@
min-width: 0;
.project-namespace {
- color: $gl-text-color-secondary;
+ color: $gl-text-color-tertiary;
}
}
@@ -214,10 +214,10 @@
.label,
.btn {
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: $gl-vert-padding $gl-btn-padding;
border: 1px $border-color solid;
font-size: $gl-font-size;
- line-height: $gl-btn-line-height;
+ line-height: $line-height-base;
border-radius: 0;
display: flex;
align-items: center;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 66ea70e79da..6a0127eb51c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -929,6 +929,10 @@
margin: 0;
}
}
+
+ .dropdown-toggle > .icon {
+ margin: 0 3px;
+ }
}
.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index e51ca44476c..8359a60ec9f 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -267,6 +267,7 @@ ul.related-merge-requests > li {
.fa-caret-down {
pointer-events: none;
color: inherit;
+ margin-left: 0;
}
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3917937f4af..2780afa11fa 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -10,8 +10,8 @@
float: left;
}
- > *:not(:last-child) {
- margin-right: 10px;
+ > *:not(:first-child) {
+ margin-left: 10px;
}
}
@@ -69,7 +69,7 @@
content: '';
border-left: 1px solid $gray-200;
position: absolute;
- left: 32px;
+ left: 28px;
top: -17px;
height: 16px;
}
@@ -114,7 +114,7 @@
padding: $gl-padding;
@include media-breakpoint-up(md) {
- padding-left: $gl-padding-50;
+ padding-left: $gl-padding-8 * 7;
}
}
}
@@ -264,6 +264,10 @@
.widget-status-icon {
align-self: flex-start;
+
+ button {
+ margin-left: $gl-padding;
+ }
}
.mr-widget-body {
@@ -271,8 +275,8 @@
@include clearfix;
- &.media > *:first-child {
- margin-right: 10px;
+ button {
+ margin-left: $gl-padding;
}
.approve-btn {
@@ -312,6 +316,7 @@
.bold {
font-weight: $gl-font-weight-bold;
color: $gl-gray-light;
+ margin-left: 10px;
}
.state-label {
@@ -377,9 +382,13 @@
&.mr-widget-empty-state {
line-height: 20px;
+ padding: $gl-padding;
.artwork {
- margin-bottom: $gl-padding;
+
+ @include media-breakpoint-down(md) {
+ margin-bottom: $gl-padding;
+ }
}
.text {
@@ -395,7 +404,7 @@
}
.mr-widget-help {
- padding: 10px 16px 10px $gl-padding-50;
+ padding: 10px 16px 10px ($gl-padding-8 * 7);
font-style: italic;
}
@@ -913,7 +922,7 @@
.media-body {
min-width: 0;
font-size: 12px;
- margin-left: 48px;
+ margin-left: 40px;
}
&:not(:last-child) {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 1d57a4a4784..c6bac33e888 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -417,6 +417,7 @@ table {
i {
color: $white-light;
+ padding-right: 2px;
margin-top: 2px;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index aa6bbc8e473..ff4fa8aacdc 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -588,8 +588,8 @@
}
.ci-status-icon svg {
- height: 20px;
- width: 20px;
+ height: 24px;
+ width: 24px;
}
.dropdown-menu-toggle {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 09a576c11cb..c80beceae52 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -303,7 +303,7 @@
.count-badge-count,
.count-badge-button {
- border: 1px solid $gray-400;
+ border: 1px solid $border-color;
line-height: 1;
}
@@ -429,7 +429,7 @@
padding: 0;
background: transparent;
border: 0;
- line-height: 2;
+ line-height: 34px;
margin: 0;
> li + li::before {
@@ -792,6 +792,7 @@
.btn {
margin-top: $gl-padding;
+ padding: $gl-btn-vert-padding $gl-btn-padding;
line-height: $gl-btn-line-height;
.icon {
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index 94da72622af..85e9f303dde 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -57,7 +57,7 @@
.report-block-container {
border-top: 1px solid $border-color;
- padding: $gl-padding-top;
+ padding: $gl-padding - 2;
background-color: $gray-light;
// Clean MR widget CSS
@@ -96,17 +96,14 @@
.ci-status-icon {
svg {
- width: 16px;
- height: 16px;
- left: -2px;
+ width: 24px;
+ height: 24px;
}
}
}
.report-block-list-issue {
display: flex;
- align-items: flex-start;
- align-content: flex-start;
}
.is-dismissed .report-block-list-issue-description,
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5c732ab0d1f..5664f46484e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -90,7 +90,7 @@
.add-to-tree {
vertical-align: top;
- padding: $gl-bordered-btn-vert-padding;
+ padding: 8px;
svg {
top: 0;
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 353a9806fd1..90528f75ffd 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -58,11 +58,8 @@ module Boards
service = Boards::Issues::MoveService.new(board_parent, current_user, move_params(true))
issues = Issue.find(params[:ids])
- if service.execute_multiple(issues)
- head :ok
- else
- head :unprocessable_entity
- end
+
+ render json: service.execute_multiple(issues)
end
def update
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 1bca52106fa..ccd54b369fa 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -160,20 +160,22 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_dashboard
- return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, project)
-
- if Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
+ if Feature.enabled?(:gfm_embedded_metrics, project) && params[:embedded]
result = dashboard_finder.find(
project,
current_user,
environment,
- dashboard_path: params[:dashboard],
embedded: params[:embedded]
)
+ elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
+ result = dashboard_finder.find(
+ project,
+ current_user,
+ environment,
+ dashboard_path: params[:dashboard]
+ )
- unless params[:embedded]
- result[:all_dashboards] = dashboard_finder.find_all_paths(project)
- end
+ result[:all_dashboards] = dashboard_finder.find_all_paths(project)
else
result = dashboard_finder.find(project, current_user, environment)
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index fd94f07cc2c..64c5fae7d96 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -46,7 +46,7 @@ module DropdownsHelper
def dropdown_toggle(toggle_text, data_attr, options = {})
default_label = data_attr[:default_label]
- content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle btn #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
+ content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down')
output.html_safe
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index a3575462de0..bb1cdcb1b31 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -154,4 +154,36 @@ module TreeHelper
"logs-path" => logs_path
}
end
+
+ def breadcrumb_data_attributes
+ attrs = {
+ can_collaborate: can_collaborate_with_project?(@project).to_s,
+ new_blob_path: project_new_blob_path(@project, @id),
+ new_branch_path: new_project_branch_path(@project),
+ new_tag_path: new_project_tag_path(@project),
+ can_edit_tree: can_edit_tree?.to_s
+ }
+
+ if can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+ continue_param = {
+ to: project_new_blob_path(@project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now
+ }
+
+ attrs.merge!(
+ fork_new_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param),
+ fork_new_directory_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
+ to: request.fullpath,
+ notice: _("%{edit_in_new_fork_notice} Try to create a new directory again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
+ })),
+ fork_upload_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
+ to: request.fullpath,
+ notice: _("%{edit_in_new_fork_notice} Try to upload a file again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
+ }))
+ )
+ end
+
+ attrs
+ end
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index ba8cea0cea9..42d4e86fe8d 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -4,11 +4,8 @@ module Ci
class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
- include IgnorableColumn
include StripAttribute
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 8927bb9bc18..8792c5cf98b 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -3,17 +3,15 @@
module Ci
class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
- include IgnorableColumn
include Presentable
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: "User"
has_many :trigger_requests
validates :token, presence: true, uniqueness: true
+ validates :owner, presence: true, unless: :supports_legacy_tokens?
before_validation :set_default_values
@@ -37,8 +35,13 @@ module Ci
self.owner_id.blank?
end
+ def supports_legacy_tokens?
+ Feature.enabled?(:use_legacy_pipeline_triggers, project)
+ end
+
def can_access_project?
- self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project)
+ supports_legacy_tokens? && legacy? ||
+ Ability.allowed?(self.owner, :create_build, project)
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f0256ff4d41..6ae8c3bd7f3 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -29,13 +29,6 @@ module Clusters
content_values.to_yaml
end
- # Need to investigate if pipelines run by this runner will stop upon the
- # executor pod stopping
- # I.e.run a pipeline, and uninstall runner while pipeline is running
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -47,6 +40,14 @@ module Clusters
)
end
+ def prepare_uninstall
+ runner&.update!(active: false)
+ end
+
+ def post_uninstall
+ runner.destroy!
+ end
+
private
def ensure_runner
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index 4514498b84b..803a65726d3 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -46,6 +46,16 @@ module Clusters
command.version = version
end
end
+
+ def prepare_uninstall
+ # Override if your application needs any action before
+ # being uninstalled by Helm
+ end
+
+ def post_uninstall
+ # Override if your application needs any action after
+ # being uninstalled by Helm
+ end
end
end
end
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 54a3dda6d75..342d766f723 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -59,29 +59,33 @@ module Clusters
transition [:scheduled] => :uninstalling
end
- before_transition any => [:scheduled] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:scheduled] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:errored] do |app_status, transition|
+ before_transition any => [:errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:updating] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:updating] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition|
+ before_transition any => [:update_errored, :uninstall_errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:installed, :updated] do |app_status, _|
+ before_transition any => [:installed, :updated] do |application, _|
# When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database.
- app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ end
+
+ after_transition any => [:uninstalling], :use_transactions => false do |application, _|
+ application.prepare_uninstall
end
end
end
diff --git a/app/models/concerns/stepable.rb b/app/models/concerns/stepable.rb
new file mode 100644
index 00000000000..d00a049a004
--- /dev/null
+++ b/app/models/concerns/stepable.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Stepable
+ extend ActiveSupport::Concern
+
+ def steps
+ self.class._all_steps
+ end
+
+ def execute_steps
+ initial_result = {}
+
+ steps.inject(initial_result) do |previous_result, callback|
+ result = method(callback).call
+
+ if result[:status] == :error
+ result[:failed_step] = callback
+
+ break result
+ end
+
+ previous_result.merge(result)
+ end
+ end
+
+ class_methods do
+ def _all_steps
+ @_all_steps ||= []
+ end
+
+ def steps(*methods)
+ _all_steps.concat methods
+ end
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 982a94315bd..12d30389910 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -13,11 +13,8 @@ class Issue < ApplicationRecord
include RelativePositioning
include TimeTrackable
include ThrottledTouch
- include IgnorableColumn
include LabelEventable
- ignore_column :assignee_id, :branch_name, :deleted_at
-
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ba57fefd8f1..68e6e48fb7d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord
include Noteable
include Referable
include Presentable
- include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
include EachBatch
@@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord
SORTING_PREFERENCE_FIELD = :merge_requests_sort
- ignore_column :locked_at,
- :ref_fetched,
- :deleted_at
-
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b3021fab7f1..b8d7348268a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -8,13 +8,10 @@ class Namespace < ApplicationRecord
include AfterCommitQueue
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
- include IgnorableColumn
include FeatureGate
include FromUnion
include Gitlab::Utils::StrongMemoize
- ignore_column :deleted_at
-
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
diff --git a/app/models/project.rb b/app/models/project.rb
index f6f7d373f91..2906aca75fc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -31,7 +31,6 @@ class Project < ApplicationRecord
include FeatureGate
include OptionallySearch
include FromUnion
- include IgnorableColumn
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -56,8 +55,6 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
- ignore_column :import_status, :import_jid, :import_error
-
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index a3b89b2543a..e571700fd02 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -250,7 +250,7 @@ class JiraService < IssueTrackerService
end
log_info("Successfully posted", client_url: client_url)
- "SUCCESS: Successfully posted to http://jira.example.net."
+ "SUCCESS: Successfully posted to #{client_url}."
end
end
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index 209db44539c..578301d7f7e 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -5,7 +5,7 @@ module Ci
delegate { @subject.project }
with_options scope: :subject, score: 0
- condition(:legacy) { @subject.legacy? }
+ condition(:legacy) { @subject.supports_legacy_tokens? && @subject.legacy? }
with_score 0
condition(:is_owner) { @user && @subject.owner_id == @user.id }
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 755d723b9a0..00ce27db7c8 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -11,26 +11,51 @@ module Boards
end
def execute_multiple(issues)
- return false if issues.empty?
+ return execute_multiple_empty_result if issues.empty?
+ handled_issues = []
last_inserted_issue_id = nil
- issues.map do |issue|
+ count = issues.each.inject(0) do |moved_count, issue|
issue_modification_params = issue_params(issue)
- next if issue_modification_params.empty?
+ next moved_count if issue_modification_params.empty?
if last_inserted_issue_id
- issue_modification_params[:move_between_ids] = move_between_ids({ move_after_id: nil, move_before_id: last_inserted_issue_id })
+ issue_modification_params[:move_between_ids] = move_below(last_inserted_issue_id)
end
last_inserted_issue_id = issue.id
- move_single_issue(issue, issue_modification_params)
- end.all?
+ handled_issue = move_single_issue(issue, issue_modification_params)
+ handled_issues << present_issue_entity(handled_issue) if handled_issue
+ handled_issue && handled_issue.valid? ? moved_count + 1 : moved_count
+ end
+
+ {
+ count: count,
+ success: count == issues.size,
+ issues: handled_issues
+ }
end
private
+ def present_issue_entity(issue)
+ ::API::Entities::Issue.represent(issue)
+ end
+
+ def execute_multiple_empty_result
+ @execute_multiple_empty_result ||= {
+ count: 0,
+ success: false,
+ issues: []
+ }
+ end
+
+ def move_below(id)
+ move_between_ids({ move_after_id: nil, move_before_id: id })
+ end
+
def move_single_issue(issue, issue_modification_params)
- return false unless can?(current_user, :update_issue, issue)
+ return unless can?(current_user, :update_issue, issue)
update(issue, issue_modification_params)
end
diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb
index 8786d295d6a..e51d84ef052 100644
--- a/app/services/clusters/applications/check_uninstall_progress_service.rb
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -23,6 +23,7 @@ module Clusters
private
def on_success
+ app.post_uninstall
app.destroy!
rescue StandardError => e
app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message })
diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
new file mode 100644
index 00000000000..e5ef8c15456
--- /dev/null
+++ b/app/services/self_monitoring/project/create_service.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+module SelfMonitoring
+ module Project
+ class CreateService < ::BaseService
+ include Stepable
+
+ DEFAULT_VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
+ DEFAULT_NAME = 'GitLab Instance Administration'
+ DEFAULT_DESCRIPTION = <<~HEREDOC
+ This project is automatically generated and will be used to help monitor this GitLab instance.
+ HEREDOC
+
+ steps :validate_admins,
+ :create_project,
+ :add_project_members,
+ :add_prometheus_manual_configuration
+
+ def initialize
+ super(nil)
+ end
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def validate_admins
+ unless instance_admins.any?
+ log_error('No active admin user found')
+ return error('No active admin user found')
+ end
+
+ success
+ end
+
+ def create_project
+ admin_user = project_owner
+ @project = ::Projects::CreateService.new(admin_user, create_project_params).execute
+
+ if project.persisted?
+ success(project: project)
+ else
+ log_error("Could not create self-monitoring project. Errors: #{project.errors.full_messages}")
+ error('Could not create project')
+ end
+ end
+
+ def add_project_members
+ members = project.add_users(project_maintainers, Gitlab::Access::MAINTAINER)
+ errors = members.flat_map { |member| member.errors.full_messages }
+
+ if errors.any?
+ log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}")
+ error('Could not add admins as members')
+ else
+ success
+ end
+ end
+
+ def add_prometheus_manual_configuration
+ return success unless prometheus_enabled?
+ return success unless prometheus_listen_address.present?
+
+ # TODO: Currently, adding the internal prometheus server as a manual configuration
+ # is only possible if the setting to allow webhooks and services to connect
+ # to local network is on.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 will add
+ # a whitelist that will allow connections to certain ips on the local network.
+
+ service = project.find_or_initialize_service('prometheus')
+
+ unless service.update(prometheus_service_attributes)
+ log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}")
+ return error('Could not save prometheus manual configuration')
+ end
+
+ success
+ end
+
+ def prometheus_enabled?
+ Gitlab.config.prometheus.enable
+ rescue Settingslogic::MissingSetting
+ false
+ end
+
+ def prometheus_listen_address
+ Gitlab.config.prometheus.listen_address
+ rescue Settingslogic::MissingSetting
+ end
+
+ def instance_admins
+ @instance_admins ||= User.admins.active
+ end
+
+ def project_owner
+ instance_admins.first
+ end
+
+ def project_maintainers
+ # Exclude the first so that the project_owner is not added again as a member.
+ instance_admins - [project_owner]
+ end
+
+ def create_project_params
+ {
+ initialize_with_readme: true,
+ visibility_level: DEFAULT_VISIBILITY_LEVEL,
+ name: DEFAULT_NAME,
+ description: DEFAULT_DESCRIPTION
+ }
+ end
+
+ def internal_prometheus_listen_address_uri
+ if prometheus_listen_address.starts_with?('http')
+ prometheus_listen_address
+ else
+ 'http://' + prometheus_listen_address
+ end
+ end
+
+ def prometheus_service_attributes
+ {
+ api_url: internal_prometheus_listen_address_uri,
+ manual_configuration: true,
+ active: true
+ }
+ end
+ end
+ end
+end
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 034273558bb..074edf645ba 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -7,26 +7,26 @@
= render "devise/shared/error_messages", resource: resource
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.")
+ = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
+ = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.")
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.")
+ = f.email_field :email_confirmation, class: "form-control middle", data: { qa_selector: 'new_user_email_confirmation_field' }, required: true, title: _("Please retype the email address.")
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
+ = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
%p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
- = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
+ = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' }
= label_tag :terms_opt_in do
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
@@ -36,4 +36,4 @@
- if show_recaptcha_sign_up?
= recaptcha_tags
.submit-container
- = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button"
+ = f.submit _("Register"), class: "btn-register btn", data: { qa_selector: 'new_user_register_button' }
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index 4cd03be572f..ab8c22532fd 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -3,4 +3,4 @@
%a.nav-link.qa-sign-in-tab.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if allow_signup?
%li.nav-item{ role: 'presentation' }
- %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: 'sign_in', track_event: 'click_button', track_value: 'register', toggle: 'tab' }, role: 'tab' } Register
+ %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab' }, role: 'tab' } Register
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index dae9a7acf6b..5d57337a568 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -46,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10"
+ = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' }
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index a5f57f5893c..c62dce880c0 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -2,7 +2,7 @@
- group_data_attrs = { group_path: j(@group.path), name: j(@group.name), issues_path: issues_group_path(@group), mr_path: merge_requests_group_path(@group) }
- if @project && @project.persisted?
- project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? }
-.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } }
+.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } }
= form_tag search_path, method: :get, class: 'form-inline' do |f|
.search-input-container
.search-input-wrap
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index fc2dea25c77..89f99472270 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -64,7 +64,7 @@
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown'
- if header_link?(:user_dropdown)
- %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", qa_selector: 'user_menu' } }
+ %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", track_value: "", qa_selector: 'user_menu' } }
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 1d7a501e5c2..e28efb09be5 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,4 +1,4 @@
-%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
+%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown", track_value: "" } }
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 54028dc8554..cbe713b7468 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -2,7 +2,7 @@
-# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information.
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_value: "" } }) do
%button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -10,7 +10,7 @@
= render "layouts/nav/projects_dropdown/show"
- if dashboard_nav_link?(:groups)
- = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown", track_value: "" } }) do
%button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 6763513f9ae..95fdad125a7 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -20,6 +20,9 @@
- if vue_file_list_enabled?
#js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
+ - if can_edit_tree?
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
+ = render 'projects/blob/new_dir'
- if @tree.readme
= render "projects/tree/readme", readme: @tree.readme
- else
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 7541737f79c..5d88be0925e 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -54,7 +54,7 @@
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
- = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
+ = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong= s_('ProjectsNew|Initialize repository with a README')
@@ -62,4 +62,4 @@
= s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.')
= f.submit _('Create project'), class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" }
-= link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" }
+= link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel", track_value: "" }
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
index 8442a53ed61..acc2c50294f 100644
--- a/app/views/projects/issues/import_csv/_button.html.haml
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -1,6 +1,6 @@
- type = local_assigns.fetch(:type, :icon)
-%button.csv-import-button.btn.btn-svg{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
+%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
data: { toggle: 'modal', target: '.issues-import-modal' } }
- if type == :icon
= sprite_icon('upload')
diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml
index 86bc54786ad..fe4a4236896 100644
--- a/app/views/projects/issues/import_csv/_modal.html.haml
+++ b/app/views/projects/issues/import_csv/_modal.html.haml
@@ -20,5 +20,5 @@
= _('It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected.')
= _('The maximum file size allowed is %{size}.') % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
.modal-footer
- %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button"} }
+ %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button", track_value: ""} }
= _('Import issues')
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 33de0aa153b..fabe636b05c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -32,15 +32,15 @@
.col-lg-9.js-toggle-container
%ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab" }, role: 'tab' }
+ %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab", track_value: "" }, role: 'tab' }
%span.d-none.d-sm-block= s_('ProjectsNew|Blank project')
%span.d-block.d-sm-none= s_('ProjectsNew|Blank')
%li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' }
+ %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab", track_value: "" }, role: 'tab' }
%span.d-none.d-sm-block.qa-project-create-from-template-tab= s_('ProjectsNew|Create from template')
%span.d-block.d-sm-none= s_('ProjectsNew|Template')
%li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
+ %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab", track_value: "" }, role: 'tab' }
%span.d-none.d-sm-block= s_('ProjectsNew|Import project')
%span.d-block.d-sm-none= s_('ProjectsNew|Import')
= render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
@@ -51,7 +51,7 @@
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
#create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' }
- .card-slim.m-4.p-4
+ .card.card-slim.m-4.p-4
%div
- contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing'
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url }
diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index 6159f1c3542..d1c09e83fd3 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -9,9 +9,9 @@
.text-muted
= template.description
.controls.d-flex.align-items-center
- %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } }
+ %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_event: "click_button", track_value: "" } }
= _("Preview")
%label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name }
- %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } }
+ %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } }
%span
= _("Use template")
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 1d0bc588c9c..41cd044a5b0 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -11,7 +11,7 @@
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
- if vue_file_list_enabled?
- #js-repo-breadcrumb
+ #js-repo-breadcrumb{ data: breadcrumb_data_attributes }
- else
%ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item
diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml
index 96a41aa066c..e686068657c 100644
--- a/app/views/projects/triggers/_content.html.haml
+++ b/app/views/projects/triggers/_content.html.haml
@@ -1,8 +1,9 @@
-%p.append-bottom-default
- Triggers with the
- %span.badge.badge-primary legacy
- label do not have an associated user and only have access to the current project.
- %br
- = succeed '.' do
- Learn more in the
- = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
+- if Feature.enabled?(:use_legacy_pipeline_triggers, @project)
+ %p.append-bottom-default
+ Triggers with the
+ %span.badge.badge-primary legacy
+ label do not have an associated user and only have access to the current project.
+ %br
+ = succeed '.' do
+ Learn more in the
+ = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 6f6f1e5e0c5..31a598ccd5e 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -8,8 +8,11 @@
.label-container
- if trigger.legacy?
- %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy
- - if !trigger.can_access_project?
+ - if trigger.supports_legacy_tokens?
+ %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy
+ - else
+ %span.badge.badge-danger.has-tooltip{ title: "Trigger is invalid due to being a legacy trigger. We recommend replacing it with a new trigger" } invalid
+ - elsif !trigger.can_access_project?
%span.badge.badge-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid
%td
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 342fdb20d41..82ffdc9cd13 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -4,7 +4,7 @@
- next if disallowed || restricted
.form-check
- = form.radio_button model_method, level, checked: (selected_level == level), class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}", track_value: "#{level}" }
+ = form.radio_button model_method, level, checked: (selected_level == level), class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "" }
= form.label "#{model_method}_#{level}", class: 'form-check-label' do
= visibility_level_icon(level)
.option-title
diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
index c9506a3295c..83f60fa6fe2 100644
--- a/app/views/shared/issuable/_feed_buttons.html.haml
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -1,4 +1,4 @@
-= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip js-rss-button', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
- = sprite_icon('rss')
-= link_to safe_params.merge(calendar_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
- = sprite_icon('calendar')
+= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
+ = icon('rss')
+= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
+ = custom_icon('icon_calendar')
diff --git a/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml b/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml
new file mode 100644
index 00000000000..3632c8eec20
--- /dev/null
+++ b/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml
@@ -0,0 +1,5 @@
+---
+title: Allow GitLab Runner to be uninstalled from the UI
+merge_request: 30176
+author:
+type: added
diff --git a/changelogs/unreleased/61145-fix-button-dimensions.yml b/changelogs/unreleased/61145-fix-button-dimensions.yml
deleted file mode 100644
index 8f209ceaa8e..00000000000
--- a/changelogs/unreleased/61145-fix-button-dimensions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updating button dimensions according to design spec
-merge_request: 28545
-author:
-type: fixed
diff --git a/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml b/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml
new file mode 100644
index 00000000000..f4ed4551aab
--- /dev/null
+++ b/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml
@@ -0,0 +1,5 @@
+---
+title: Change color for namespace in commit search
+merge_request: 30312
+author:
+type: other
diff --git a/changelogs/unreleased/61613-spacing-mr-widgets.yml b/changelogs/unreleased/61613-spacing-mr-widgets.yml
new file mode 100644
index 00000000000..7d37ef8da2e
--- /dev/null
+++ b/changelogs/unreleased/61613-spacing-mr-widgets.yml
@@ -0,0 +1,5 @@
+---
+title: Left align mr widget icons and text
+merge_request: 28561
+author:
+type: fixed
diff --git a/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml
new file mode 100644
index 00000000000..51c1537a159
--- /dev/null
+++ b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml
@@ -0,0 +1,5 @@
+---
+title: "Enable section anchors in Asciidoctor"
+merge_request: 30666
+author: Guillaume Grossetie
+type: added \ No newline at end of file
diff --git a/changelogs/unreleased/bjk-fix_prom_example.yml b/changelogs/unreleased/bjk-fix_prom_example.yml
new file mode 100644
index 00000000000..2f81bc6196b
--- /dev/null
+++ b/changelogs/unreleased/bjk-fix_prom_example.yml
@@ -0,0 +1,5 @@
+---
+title: Update example Prometheus scrape config
+merge_request: 30739
+author:
+type: other
diff --git a/changelogs/unreleased/button-bug-fixes.yml b/changelogs/unreleased/button-bug-fixes.yml
deleted file mode 100644
index b63bfdf24ad..00000000000
--- a/changelogs/unreleased/button-bug-fixes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Project Badge Button Styles
-merge_request: 30678
-author:
-type: fixed
diff --git a/changelogs/unreleased/jc-remove-catfile-flag.yml b/changelogs/unreleased/jc-remove-catfile-flag.yml
new file mode 100644
index 00000000000..6b72de7bc45
--- /dev/null
+++ b/changelogs/unreleased/jc-remove-catfile-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove catfile cache feature flag
+merge_request: 30750
+author:
+type: performance
diff --git a/changelogs/unreleased/mh-mermaid-linebreaks.yml b/changelogs/unreleased/mh-mermaid-linebreaks.yml
new file mode 100644
index 00000000000..e38820d8ce3
--- /dev/null
+++ b/changelogs/unreleased/mh-mermaid-linebreaks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix linebreak rendering in Mermaid flowcharts
+merge_request: 30730
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml
new file mode 100644
index 00000000000..3f4d4bbd432
--- /dev/null
+++ b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml
@@ -0,0 +1,5 @@
+---
+title: Remove support for legacy pipeline triggers
+merge_request: 30133
+author:
+type: removed
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 334c241bcaa..0e78980350f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -952,6 +952,16 @@ production: &base
# address: localhost
# port: 3807
+ ## Prometheus settings
+ # Do not modify these settings here. They should be modified in /etc/gitlab/gitlab.rb
+ # if you installed GitLab via Omnibus.
+ # If you installed from source, you need to install and configure Prometheus
+ # yourself, and then update the values here.
+ # https://docs.gitlab.com/ee/administration/monitoring/prometheus/
+ prometheus:
+ # enable: true
+ # listen_address: 'localhost:9090'
+
#
# 5. Extra customization
# ==========================
@@ -1158,6 +1168,9 @@ test:
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
+ prometheus:
+ enable: true
+ listen_address: 'localhost:9090'
staging:
<<: *base
diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index 5aa6f73c5c5..4f309094447 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -53,7 +53,7 @@ def instrument_classes(instrumentation)
instrumentation.instrument_methods(Banzai::Querying)
instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
- instrumentation.instrument_instance_methods(Banzai::Redactor)
+ instrumentation.instrument_instance_methods(Banzai::ReferenceRedactor)
[Issuable, Mentionable, Participable].each do |klass|
instrumentation.instrument_instance_methods(klass)
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index ec494635f02..0c675cc4c9c 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -88,6 +88,19 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# We ignore revert commits as they are well structured by Git already
return false if commit.message.start_with?('Revert "')
+ # Fail if a suggestion commit is used and squash is not enabled
+ if commit.message.start_with?('Apply suggestion to')
+ if gitlab.mr_json['squash']
+ return false
+ else
+ fail_commit(
+ commit,
+ 'If you are applying suggestions, enable squash in the merge request and re-run the failed job'
+ )
+ return true
+ end
+ end
+
failures = false
subject, separator, details = commit.message.split("\n", 3)
@@ -114,16 +127,6 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
)
end
- # Fail if a suggestion commit is used and squash is not enabled
- if commit.message.start_with?('Apply suggestion to') && !gitlab.mr_json['squash']
- fail_commit(
- commit,
- 'If you are applying suggestions, squash needs to be enabled in the merge request'
- )
-
- failures = true
- end
-
unless subject_starts_with_capital?(subject)
fail_commit(commit, 'The commit subject must start with a capital letter')
failures = true
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index d8094587d14..2fc9db0632e 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -1,19 +1,34 @@
---
comments: false
+type: index
---
-# Authentication and Authorization
+# GitLab authentication and authorization
GitLab integrates with the following external authentication and authorization
-providers.
+providers:
-- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
- and 389 Server
+- [Auth0](../../integration/auth0.md)
+- [Authentiq](authentiq.md)
+- [Azure](../../integration/azure.md)
+- [Bitbucket Cloud](../../integration/bitbucket.md)
+- [CAS](../../integration/cas.md)
+- [Crowd](../../integration/crowd.md)
+- [Facebook](../../integration/facebook.md)
+- [GitHub](../../integration/github.md)
+- [GitLab.com](../../integration/gitlab.md)
+- [Google](../../integration/google.md)
+- [JWT](jwt.md)
+- [Kerberos](../../integration/kerberos.md)
+- [LDAP](ldap.md): Includes Active Directory, Apple Open Directory, Open LDAP,
+ and 389 Server.
- [LDAP for GitLab EE](ldap-ee.md): LDAP additions to GitLab Enterprise Editions **(STARTER ONLY)**
-- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
- Bitbucket, Facebook, Shibboleth, Crowd, Azure, Authentiq ID, and JWT
-- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
-- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
-- [Okta](okta.md) Configure GitLab to sign in using Okta
-- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication
-- [Smartcard](smartcard.md) Smartcard authentication **(PREMIUM ONLY)**
+ - [Google Secure LDAP](google_secure_ldap.md)
+- [Okta](okta.md)
+- [Salesforce](../../integration/salesforce.md)
+- [SAML](../../integration/saml.md)
+- [SAML for GitLab.com groups](../../user/group/saml_sso/index.md) **(SILVER ONLY)**
+- [Shibboleth](../../integration/shibboleth.md)
+- [Smartcard](smartcard.md) **(PREMIUM ONLY)**
+- [Twitter](../../integration/twitter.md)
+- [UltraAuth](../../integration/ultra_auth.md)
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index 835c97c0288..b84eca4ef0d 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Authentiq OmniAuth Provider
To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq.
@@ -66,3 +70,15 @@ On the sign in page there should now be an Authentiq icon below the regular sign
- If not they will be prompted to download the app and then follow the procedure above.
If everything goes right, the user will be returned to GitLab and will be signed in.
+
+<!-- ## 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/administration/auth/crowd.md b/doc/administration/auth/crowd.md
index 86c7bad2ebf..ac63b4f2b97 100644
--- a/doc/administration/auth/crowd.md
+++ b/doc/administration/auth/crowd.md
@@ -1,5 +1,11 @@
+---
+type: reference
+---
+
# Atlassian Crowd OmniAuth Provider
+Authenticate to GitLab using the Atlassian Crowd OmniAuth provider.
+
## Configure a new Crowd application
1. Choose 'Applications' in the top menu, then 'Add application'.
diff --git a/doc/administration/auth/google_secure_ldap.md b/doc/administration/auth/google_secure_ldap.md
index 0e6d7ff1df1..55e6f53622c 100644
--- a/doc/administration/auth/google_secure_ldap.md
+++ b/doc/administration/auth/google_secure_ldap.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Google Secure LDAP **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46391) in GitLab 11.9.
@@ -204,3 +208,15 @@ values obtained during the LDAP client configuration earlier:
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
+
+<!-- ## 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/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 320a65b665d..86dd398343b 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -1,15 +1,9 @@
---
-author: Chris Wilson
-author_gitlab: MrChrisW
-level: intermediary
-article_type: admin guide
-date: 2017-05-03
+type: howto
---
# How to configure LDAP with GitLab CE
-## Introduction
-
Managing a large number of users in GitLab can become a burden for system administrators. As an organization grows so do user accounts. Keeping these user accounts in sync across multiple enterprise applications often becomes a time consuming task.
In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems.
@@ -268,3 +262,15 @@ have extended functionalities with LDAP, such as:
- Multiple LDAP servers
Read through the article on [LDAP for GitLab EE](../how_to_configure_ldap_gitlab_ee/index.md) **(STARTER ONLY)** for an overview.
+
+<!-- ## 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/administration/auth/how_to_configure_ldap_gitlab_ee/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
index 2683950f143..366acb9ed3e 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
@@ -1,16 +1,10 @@
---
-author: Chris Wilson
-author_gitlab: MrChrisW
-level: intermediary
-article_type: admin guide
-date: 2017-05-03
+type: howto
---
# How to configure LDAP with GitLab EE **(STARTER ONLY)**
-## Introduction
-
-The present article follows [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward.
+This article expands on [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward.
## GitLab Enterprise Edition - LDAP features
@@ -117,3 +111,15 @@ Integration of GitLab with Active Directory (LDAP) reduces the complexity of use
It has the advantage of improving user permission controls, whilst easing the deployment of GitLab into an existing [IT environment](https://www.techopedia.com/definition/29199/it-infrastructure). GitLab EE offers advanced group management and multiple LDAP servers.
With the assistance of the [GitLab Support](https://about.gitlab.com/support) team, setting up GitLab with an existing AD/LDAP solution will be a smooth and painless process.
+
+<!-- ## 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/administration/auth/jwt.md b/doc/administration/auth/jwt.md
index 7db22bdd5df..e6b3287ce60 100644
--- a/doc/administration/auth/jwt.md
+++ b/doc/administration/auth/jwt.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# JWT OmniAuth provider
To enable the JWT OmniAuth provider, you must register your application with JWT.
@@ -70,3 +74,15 @@ will be redirected to GitLab and will be signed in.
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../restart_gitlab.md#installations-from-source
+
+<!-- ## 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/administration/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md
index 2afac23c20c..2f2ee8a27d3 100644
--- a/doc/administration/auth/ldap-ee.md
+++ b/doc/administration/auth/ldap-ee.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# LDAP Additions in GitLab EE **(STARTER ONLY)**
This is a continuation of the main [LDAP documentation](ldap.md), detailing LDAP
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 86e6be5f4fa..be05a4d63a7 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
<!-- If the change is EE-specific, put it in `ldap-ee.md`, NOT here. -->
# LDAP
@@ -494,6 +498,13 @@ be mandatory and clients cannot be authenticated with the TLS protocol.
## Troubleshooting
+If a user account is blocked or unblocked due to the LDAP configuration, a
+message will be logged to `application.log`.
+
+If there is an unexpected error during an LDAP lookup (configuration error,
+timeout), the login is rejected and a message will be logged to
+`production.log`.
+
### Debug LDAP user filter with ldapsearch
This example uses ldapsearch and assumes you are using ActiveDirectory. The
@@ -527,18 +538,9 @@ ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$ba
sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
```
-### Connection Refused
+### Connection refused
If you are getting 'Connection Refused' errors when trying to connect to the
LDAP server please double-check the LDAP `port` and `encryption` settings used by
GitLab. Common combinations are `encryption: 'plain'` and `port: 389`, OR
`encryption: 'simple_tls'` and `port: 636`.
-
-### Troubleshooting
-
-If a user account is blocked or unblocked due to the LDAP configuration, a
-message will be logged to `application.log`.
-
-If there is an unexpected error during an LDAP lookup (configuration error,
-timeout), the login is rejected and a message will be logged to
-`production.log`.
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index 758501629af..78d040cda99 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# OpenID Connect OmniAuth provider
GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider.
@@ -146,7 +150,7 @@ for more details:
}
```
-### Troubleshooting
+## Troubleshooting
If you're having trouble, here are some tips:
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index 566003ba708..5524c3ba092 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Okta SSO provider
Okta is a [Single Sign-on provider](https://www.okta.com/products/single-sign-on/) that can be used to authenticate
@@ -157,3 +161,15 @@ Make sure the groups exist and are assigned to the Okta app.
You can take a look of the [SAML documentation](../../integration/saml.md#marking-users-as-external-based-on-saml-groups) on external groups since
it works the same.
+
+<!-- ## 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/administration/auth/smartcard.md b/doc/administration/auth/smartcard.md
index e47751e0cc5..4f236d1afb8 100644
--- a/doc/administration/auth/smartcard.md
+++ b/doc/administration/auth/smartcard.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Smartcard authentication **(PREMIUM ONLY)**
GitLab supports authentication using smartcards.
@@ -22,7 +26,7 @@ To use a smartcard with an X.509 certificate to authenticate against a local
database with GitLab, `CN` and `emailAddress` must be defined in the
certificate. For example:
-```
+```text
Certificate:
Data:
Version: 1 (0x0)
@@ -212,3 +216,15 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Save the file and [restart](../restart_gitlab.md#installations-from-source)
GitLab for the changes to take effect.
+
+<!-- ## 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/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
index 8eee9427b56..27866b7536e 100644
--- a/doc/administration/geo/disaster_recovery/background_verification.md
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -171,14 +171,21 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch
## Current limitations
-Until [issue #5064][ee-5064] is completed, background verification doesn't cover
-CI job artifacts and traces, LFS objects, or user uploads in file storage.
-Verify their integrity manually by following [these instructions][foreground-verification]
-on both nodes, and comparing the output between them.
+Automatic background verification doesn't cover attachments, LFS objects,
+job artifacts, and user uploads in file storage. You can keep track of the
+progress to include them in [ee-1430]. For now, you can verify their integrity
+manually by following [these instructions][foreground-verification] on both
+nodes, and comparing the output between them.
+
+In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects and
+archived traces on secondary nodes after the transfer, compares it with the
+stored checksums, and rejects transfers if mismatched. Please note that Geo
+currently does not support an automatic way to verify these data if they have
+been synced before GitLab EE 12.1.
Data in object storage is **not verified**, as the object store is responsible
for ensuring the integrity of the data.
[reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed
[foreground-verification]: ../../raketasks/check.md
-[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064
+[ee-1430]: https://gitlab.com/groups/gitlab-org/-/epics/1430
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 4317d14ba68..41ef68f5b57 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Scaling and High Availability
GitLab supports several different types of clustering and high-availability.
diff --git a/doc/administration/high_availability/alpha_database.md b/doc/administration/high_availability/alpha_database.md
index 7bf20be60e6..7afd739f44c 100644
--- a/doc/administration/high_availability/alpha_database.md
+++ b/doc/administration/high_availability/alpha_database.md
@@ -2,5 +2,4 @@
redirect_to: 'database.md'
---
-This documentation has been moved to the main
-[database documentation](database.md#configure_using_omnibus_for_high_availability).
+This document was moved to [another location](database.md).
diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md
index 1f93c8130d3..b02a61b9256 100644
--- a/doc/administration/high_availability/consul.md
+++ b/doc/administration/high_availability/consul.md
@@ -1,6 +1,8 @@
-# Working with the bundled Consul service **(PREMIUM ONLY)**
+---
+type: reference
+---
-## Overview
+# Working with the bundled Consul service **(PREMIUM ONLY)**
As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](https://www.consul.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index 1702a731647..f7a1f425b40 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -1,5 +1,12 @@
+---
+type: reference
+---
+
# Configuring PostgreSQL for Scaling and High Availability
+In this section, you'll be guided through configuring a PostgreSQL database
+to be used with GitLab in a highly available environment.
+
## Provide your own PostgreSQL instance **(CORE ONLY)**
If you're hosting GitLab on a cloud provider, you can optionally use a
diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md
index b7eaa4ce105..739d1ae35fb 100644
--- a/doc/administration/high_availability/gitaly.md
+++ b/doc/administration/high_availability/gitaly.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring Gitaly for Scaled and High Availability
Gitaly does not yet support full high availability. However, Gitaly is quite
@@ -46,3 +50,15 @@ Continue configuration of other components by going back to:
```
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+
+<!-- ## 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/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 83838928519..8818a9606de 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -1,4 +1,8 @@
-# Configuring GitLab Scaling and High Availability
+---
+type: reference
+---
+
+# Configuring GitLab for Scaling and High Availability
> **Note:** There is some additional configuration near the bottom for
additional GitLab application servers. It's important to read and understand
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index 28b226cacd5..9e9f604317a 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Load Balancer for GitLab HA
In an active/active GitLab configuration, you will need a load balancer to route
@@ -114,3 +118,15 @@ Read more on high-availability configuration:
if SSL was terminated at the load balancer.
[gitlab-pages]: ../pages/index.md
+
+<!-- ## 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/administration/high_availability/monitoring_node.md b/doc/administration/high_availability/monitoring_node.md
index cbc1d4bcd52..b91a994d01e 100644
--- a/doc/administration/high_availability/monitoring_node.md
+++ b/doc/administration/high_availability/monitoring_node.md
@@ -1,7 +1,13 @@
+---
+type: reference
+---
+
# Configuring a Monitoring node for Scaling and High Availability
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3786) in GitLab 12.0.
+You can configure a Prometheus node to monitor GitLab.
+
## Standalone Monitoring node using GitLab Omnibus
The GitLab Omnibus package can be used to configure a standalone Monitoring node running [Prometheus](../monitoring/prometheus/index.md) and [Grafana](../monitoring/performance/grafana_configuration.md).
@@ -67,3 +73,15 @@ Once monitoring using Service Discovery is enabled with `consul['monitoring_serv
ensure that `prometheus['scrape_configs']` is not set in `/etc/gitlab/gitlab.rb`. Setting both
`consul['monitoring_service_discovery'] = true` and `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb`
will result in errors.
+
+<!-- ## 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/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 6ab6b8bed30..294f0e969d5 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# NFS
You can view information and options set for each of the mounted NFS file
@@ -47,29 +51,15 @@ management between systems:
### Improving NFS performance with GitLab
-NOTE: **Note:** This is only available starting in certain versions of GitLab: 11.5.11,
-11.6.11, 11.7.12, 11.8.8, 11.9.0 and up (e.g. 11.10, 11.11, etc.)
+NOTE: **Note:** From GitLab 12.1, it will automatically be detected if Rugged can and should be used per storage.
-If you are using NFS to share Git data, we recommend that you enable a
-number of feature flags that will allow GitLab application processes to
-access Git data directly instead of going through the [Gitaly
-service](../gitaly/index.md). Depending on your workload and disk
-performance, these flags may help improve performance. See [the
-issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/57317) for more
-details.
-
-To do this, run the Rake task:
+If you previously enabled Rugged using the feature flag, you will need to unset the feature flag by using:
```sh
-sudo gitlab-rake gitlab:features:enable_rugged
+sudo gitlab-rake gitlab:features:unset_rugged
```
-If you need to undo this setting for some reason such as switching to [Gitaly without NFS](gitaly.md)
-(recommended), run:
-
-```sh
-sudo gitlab-rake gitlab:features:disable_rugged
-```
+If the Rugged feature flag is explicitly set to either true or false, GitLab will use the value explicitly set.
### Known issues
@@ -236,3 +226,15 @@ Read more on high-availability configuration:
1. [Configure the load balancers](load_balancer.md)
[udp-log-shipping]: https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
+
+<!-- ## 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/administration/high_availability/nfs_host_client_setup.md b/doc/administration/high_availability/nfs_host_client_setup.md
index a8d69b9ab0a..9b0e085fe25 100644
--- a/doc/administration/high_availability/nfs_host_client_setup.md
+++ b/doc/administration/high_availability/nfs_host_client_setup.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring NFS for GitLab HA
Setting up NFS for a GitLab HA setup allows all applications nodes in a cluster
@@ -133,3 +137,15 @@ client with the command below.
```sh
sudo ufw allow from <client-ip-address> to any port nfs
```
+
+<!-- ## 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/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md
index 6890b0f7db7..0b945bc6244 100644
--- a/doc/administration/high_availability/pgbouncer.md
+++ b/doc/administration/high_availability/pgbouncer.md
@@ -1,6 +1,8 @@
-# Working with the bundle Pgbouncer service
+---
+type: reference
+---
-## Overview
+# Working with the bundle Pgbouncer service
As part of its High Availability stack, GitLab Premium includes a bundled version of [Pgbouncer](https://pgbouncer.github.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index c29514ed9f6..1b79dde9476 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring Redis for Scaling and High Availability
## Provide your own Redis instance **(CORE ONLY)**
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index a5463e5128c..63915e5d96c 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring non-Omnibus Redis for GitLab HA
This is the documentation for configuring a Highly Available Redis setup when
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 341ea3330d7..c8968c51393 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -134,17 +134,57 @@ To use an external Prometheus server:
```yaml
scrape_configs:
- - job_name: 'gitlab_exporters'
+ - job_name: nginx
static_configs:
- - targets: ['1.1.1.1:9168', '1.1.1.1:9236', '1.1.1.1:9236', '1.1.1.1:9100', '1.1.1.1:9121', '1.1.1.1:9187']
-
- - job_name: 'gitlab_metrics'
- metrics_path: /-/metrics
+ - targets:
+ - 1.1.1.1:8060
+ - job_name: redis
+ static_configs:
+ - targets:
+ - 1.1.1.1:9121
+ - job_name: postgres
+ static_configs:
+ - targets:
+ - 1.1.1.1:9187
+ - job_name: node
+ static_configs:
+ - targets:
+ - 1.1.1.1:9100
+ - job_name: gitlab-workhorse
+ static_configs:
+ - targets:
+ - 1.1.1.1:9229
+ - job_name: gitlab-rails
+ metrics_path: "/-/metrics"
+ static_configs:
+ - targets:
+ - 1.1.1.1:8080
+ - job_name: gitlab-sidekiq
+ static_configs:
+ - targets:
+ - 1.1.1.1:8082
+ - job_name: gitlab_monitor_database
+ metrics_path: "/database"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_monitor_sidekiq
+ metrics_path: "/sidekiq"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_monitor_process
+ metrics_path: "/process"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitaly
static_configs:
- - targets: ['1.1.1.1:443']
+ - targets:
+ - 1.1.1.1:9236
```
-1. Restart the Prometheus server.
+1. Reload the Prometheus server.
## Viewing performance metrics
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 2b31233d429..8d0b5b42515 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -251,3 +251,22 @@ sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:*]
# to clear a lease for repository garbage collection in a specific project: (id=4)
sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:4]
```
+
+## Display status of database migrations
+
+To check the status of migrations, you can use the following rake task:
+
+```bash
+sudo gitlab-rake db:migrate:status
+```
+
+This will output a table with a `Status` of `up` or `down` for
+each Migration ID.
+
+```bash
+database: gitlabhq_production
+
+ Status Migration ID Migration Name
+--------------------------------------------------
+ up migration_id migration_name
+``` \ No newline at end of file
diff --git a/doc/api/README.md b/doc/api/README.md
index 9d90677e2bb..8e60d1c61df 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,6 +29,7 @@ The following API resources are available in the project context:
| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Dependencies](dependencies.md) **[ULTIMATE]** | `/projects/:id/dependencies`
| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
| [Deployments](deployments.md) | `/projects/:id/deployments` |
| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
new file mode 100644
index 00000000000..ed5ebdade19
--- /dev/null
+++ b/doc/api/dependencies.md
@@ -0,0 +1,50 @@
+# Dependencies API **(ULTIMATE)**
+
+CAUTION: **Caution:**
+This API is in an alpha stage and considered unstable.
+The response payload may be subject to change or breakage
+across GitLab releases.
+
+Every call to this endpoint requires authentication. To perform this call, user should be authorized to read
+[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard).
+
+## List project dependencies
+
+Get a list of project dependencies. This API partially mirroring
+[Dependency List](../user/application_security/dependency_scanning/index.md#dependency-list) feature.
+This list can be generated only for [languages and package managers](../user/application_security/dependency_scanning/index.md#supported-languages-and-package-managers)
+supported by Gemnasium.
+
+```
+GET /projects/:id/dependencies
+GET /projects/:id/vulnerabilities?package_manger=maven
+GET /projects/:id/vulnerabilities?package_manger=yarn,bundler
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `maven`, `npm`, `pip` or `yarn`. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/dependencies
+```
+
+Example response:
+
+```json
+[
+ {
+ "name": "rails",
+ "version": "5.0.1",
+ "package_manager": "bundler",
+ "dependency_file_path": "Gemfile.lock"
+ },
+ {
+ "name": "hanami",
+ "version": "1.3.1",
+ "package_manager": "bundler",
+ "dependency_file_path": "Gemfile.lock"
+ }
+]
+```
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 1add5f432ac..0e45ee1a583 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -409,10 +409,10 @@ Possible response status codes:
> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
> in [GitLab Premium][ee] 9.5.
-Download the artifacts zipped archive from the given reference name and job,
-provided the job finished successfully. This is the same as
-[getting the job's artifacts](#get-job-artifacts), but by defining the job's
-name instead of its ID.
+Download the artifacts zipped archive from the latest successful pipeline for
+the given reference name and job, provided the job finished successfully. This
+is the same as [getting the job's artifacts](#get-job-artifacts), but by
+defining the job's name instead of its ID.
```
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
@@ -506,9 +506,9 @@ Possible response status codes:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23538) in GitLab 11.5.
-Download a single artifact file from a specific tag or branch from within the
-job's artifacts archive. The file is extracted from the archive and streamed to
-the client.
+Download a single artifact file for a specific job of the latest successful
+pipeline for the given reference name from within the job's artifacts archive.
+The file is extracted from the archive and streamed to the client.
```
GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 662a4b3e424..1ade46efb1c 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -44,7 +44,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. |
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
@@ -206,7 +206,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
@@ -358,7 +358,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
diff --git a/doc/api/releases/img/upcoming_release_v12_1.png b/doc/api/releases/img/upcoming_release_v12_1.png
new file mode 100644
index 00000000000..8bd8573ce84
--- /dev/null
+++ b/doc/api/releases/img/upcoming_release_v12_1.png
Binary files differ
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index e7f79a0d359..e74b35fd959 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -6,7 +6,7 @@
## List Releases
-Paginated list of Releases, sorted by `created_at`.
+Paginated list of Releases, sorted by `released_at`.
```
GET /projects/:id/releases
@@ -32,6 +32,7 @@ Example response:
"name":"Awesome app v0.2 beta",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eEscape label and milestone titles to prevent XSS in GFM autocomplete. !2740\u003c/li\u003e\n\u003cli\u003ePrevent private snippets from being embeddable.\u003c/li\u003e\n\u003cli\u003eAdd subresources removal to member destroy service.\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:56:19.539Z",
+ "released_at":"2019-01-03T01:56:19.539Z",
"author":{
"id":1,
"name":"Administrator",
@@ -98,6 +99,7 @@ Example response:
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -178,6 +180,7 @@ Example response:
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -247,6 +250,7 @@ POST /projects/:id/releases
| `assets:links`| array of hash | no | An array of assets links. |
| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
| `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. |
+| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
@@ -265,6 +269,7 @@ Example response:
"name":"New release",
"description_html":"\u003cp dir=\"auto\"\u003eSuper nice release\u003c/p\u003e",
"created_at":"2019-01-03T02:22:45.118Z",
+ "released_at":"2019-01-03T02:22:45.118Z",
"author":{
"id":1,
"name":"Administrator",
@@ -335,6 +340,7 @@ PUT /projects/:id/releases/:tag_name
| `tag_name` | string | yes | The tag where the release will be created from. |
| `name` | string | no | The release name. |
| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
+| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
@@ -351,6 +357,7 @@ Example response:
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -430,6 +437,7 @@ Example response:
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -480,3 +488,11 @@ Example response:
}
}
```
+
+## Upcoming Releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38105) in GitLab 12.1.
+
+A release with a `released_at` attribute set to a future date will be labeled an **Upcoming Release** in the UI:
+
+![Upcoming release](img/upcoming_release_v12_1.png)
diff --git a/doc/api/users.md b/doc/api/users.md
index 54641f4c862..fdc84826680 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -147,6 +147,21 @@ GET /users
]
```
+Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
+
+```json
+[
+ {
+ "id": 1,
+ ...
+ "shared_runners_minutes_limit": 133,
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
+ ...
+ }
+]
+```
+
Users on GitLab [Silver or higher](https://about.gitlab.com/pricing/) will also see
the `group_saml` provider option:
@@ -284,14 +299,15 @@ Example Responses:
```
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
-the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters.
+the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
```json
{
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
...
}
```
@@ -304,7 +320,8 @@ see the `group_saml` option:
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
"identities": [
{"provider": "github", "extern_uid": "2435223452345"},
{"provider": "bitbucket", "extern_uid": "john.smith"},
@@ -399,6 +416,7 @@ Parameters:
- `private_profile` (optional) - User's profile is private - true or false (default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
+- `note` (optional) - Admin notes for this user **(STARTER)**
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index f9dcd6417e0..f3896c5232c 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -193,13 +193,14 @@ You can simply define an image that will be used for all jobs and a list of
services that you want to use during build time:
```yaml
-image: ruby:2.2
+default:
+ image: ruby:2.2
-services:
- - postgres:9.3
+ services:
+ - postgres:9.3
-before_script:
- - bundle install
+ before_script:
+ - bundle install
test:
script:
@@ -209,8 +210,9 @@ test:
It is also possible to define different images and services per job:
```yaml
-before_script:
- - bundle install
+default:
+ before_script:
+ - bundle install
test:2.1:
image: ruby:2.1
@@ -231,18 +233,19 @@ Or you can pass some [extended configuration options](#extended-docker-configura
for `image` and `services`:
```yaml
-image:
- name: ruby:2.2
- entrypoint: ["/bin/bash"]
+default:
+ image:
+ name: ruby:2.2
+ entrypoint: ["/bin/bash"]
-services:
-- name: my-postgres:9.4
- alias: db-postgres
- entrypoint: ["/usr/local/bin/db-postgres"]
- command: ["start"]
+ services:
+ - name: my-postgres:9.4
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
-before_script:
-- bundle install
+ before_script:
+ - bundle install
test:
script:
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 5a302392c54..9295dcfd4e0 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -37,6 +37,7 @@ The following table lists examples with step-by-step tutorials that are containe
| 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). |
+| Parallel testing Ruby & JS | [GitLab CI parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
### Contributing examples
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index c2ef58acf15..001f951ebb8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -119,6 +119,35 @@ The following table lists available parameters for jobs:
NOTE: **Note:**
Parameters `types` and `type` are [deprecated](#deprecated-parameters).
+## Setting default parameters
+
+Some parameters can be set globally as the default for all jobs using the
+`default:` keyword. Default parameters can then be overridden by job-specific
+configuration.
+
+The following job parameters can be defined inside a `default:` block:
+
+- [`image`](#image)
+- [`services`](#services)
+- [`before_script`](#before_script-and-after_script)
+- [`after_script`](#before_script-and-after_script)
+- [`cache`](#cache)
+
+In the following example, the `ruby:2.5` image is set as the default for all
+jobs except the `rspec 2.6` job, which uses the `ruby:2.6` image:
+
+```yaml
+default:
+ image: ruby:2.5
+
+rspec:
+ script: bundle exec rspec
+
+rspec 2.6:
+ image: ruby:2.6
+ script: bundle exec rspec
+```
+
## Parameter details
The following are detailed explanations for parameters used to configure CI/CD pipelines.
@@ -239,8 +268,9 @@ It's possible to overwrite the globally defined `before_script` and `after_scrip
if you set it per-job:
```yaml
-before_script:
- - global before script
+default:
+ before_script:
+ - global before script
job:
before_script:
@@ -974,7 +1004,7 @@ review_app:
stop_review_app:
stage: deploy
variables:
- GIT_STRATEGY: none
+ GIT_STRATEGY: none
script: make delete-app
when: manual
environment:
@@ -1749,6 +1779,10 @@ test:
parallel: 5
```
+TIP: **Tip:**
+Parallelize tests suites across parallel jobs.
+Different languages have different tools to facilitate this.
+
### `trigger` **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
@@ -2221,10 +2255,10 @@ spinach:
script: rake spinach
```
-It's also possible to use multiple parents for `extends`.
-The algorithm used for merge is "closest scope wins", so keys
-from the last member will always shadow anything defined on other levels.
-For example:
+In GitLab 12.0 and later, it's also possible to use multiple parents for
+`extends`. The algorithm used for merge is "closest scope wins", so
+keys from the last member will always shadow anything defined on other
+levels. For example:
```yaml
.only-important:
@@ -2550,18 +2584,39 @@ You can set it globally or per-job in the [`variables`](#variables) section.
The following parameters are deprecated.
-### `types`
+### Globally-defined `types`
CAUTION: **Deprecated:**
`types` is deprecated, and could be removed in a future release.
Use [`stages`](#stages) instead.
-### `type`
+### Job-defined `type`
CAUTION: **Deprecated:**
`type` is deprecated, and could be removed in one of the future releases.
Use [`stage`](#stage) instead.
+### Globally-defined `image`, `services`, `cache`, `before_script`, `after_script`
+
+Defining `image`, `services`, `cache`, `before_script`, and
+`after_script` globally is deprecated. Support could be removed
+from a future release.
+
+Use [`default:`](#setting-default-parameters) instead. For example:
+
+```yaml
+default:
+ image: ruby:2.5
+ services:
+ - docker:dind
+ cache:
+ paths: [vendor/]
+ before_script:
+ - bundle install --path vendor/
+ after_script:
+ - rm -rf tmp/
+```
+
## Custom build directories
> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1267) in Gitlab Runner 11.10
@@ -2644,7 +2699,7 @@ variables:
The value of `GIT_CLONE_PATH` is expanded once into
`$CI_BUILDS_DIR/go/src/namespace/project`, and results in failure
-because `$CI_BUILDS_DIR` is not expanded.
+because `$CI_BUILDS_DIR` is not expanded.
## Special YAML features
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index 6ab3fa4acf3..716f6ad7f92 100644
--- a/doc/development/fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
@@ -47,7 +47,7 @@ There's a more convenient solution to this problem. When working with HAML templ
Below is an example of `data-track-*` attributes assigned to a button in HAML:
```ruby
-%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
+%button.btn{ data: { track_label: "template_preview", track_property: "my-template", track_event: "click_button", track_value: "" } }
```
By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index fff06254da7..20caac4cb74 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -42,7 +42,11 @@ use the packages that are available for your OS.
In order to improve elasticsearch indexing performance, GitLab has made available a [new indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
This will replace the included Ruby indexer in the future but should be considered beta software for now, so there may be some bugs.
-If you would like to use it, please follow the instructions below.
+The Elasticsearch Go indexer is included in Omnibus for GitLab 11.8 and newer.
+
+To use the new Elasticsearch indexer included in Omnibus, check the box "Use the new repository indexer (beta)" when [enabling the Elasticsearch integration](#enabling-elasticsearch).
+
+If you would like to use the Elasticsearch Go indexer with a source installation or an older version of GitLab, please follow the instructions below.
### Installation
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 62a20d3f257..749ccf924b5 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -1,6 +1,7 @@
---
type: concepts
---
+
# Information exclusivity
Git is a distributed version control system (DVCS). This means that everyone
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index d78293c75c6..9909ef4a8e4 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -1,19 +1,31 @@
---
type: reference, howto
---
+
# Custom password length limits
-If you want to enforce longer user passwords you can create an extra Devise
-initializer with the steps below.
+The user password length is set to a minimum of 8 characters by default.
+To change that for installations from source:
+
+1. Edit `devise_password_length.rb`:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
+ sudo -u git -H editor config/initializers/devise_password_length.rb
+ ```
+
+1. Change the new password length limits:
+
+ ```ruby
+ config.password_length = 12..128
+ ```
-If you do not use the `devise_password_length.rb` initializer the password
-length is set to a minimum of 8 characters in `config/initializers/devise.rb`.
+ In this example, the minimum length is 12 characters, and the maximum length
+ is 128 characters.
-```bash
-cd /home/git/gitlab
-sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
-sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits
-```
+1. [Restart GitLab](../administration/restart_gitlab.md#installations-from-source)
+ for the changes to take effect.
<!-- ## Troubleshooting
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 1b75798013d..1e5678ec47c 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -1,6 +1,7 @@
---
type: reference, howto
---
+
# Rack Attack
[Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index a58d70f0ff2..6a6c5262179 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# How to reset your root password
To reset your root password, first log into your server with root privileges.
diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index ae4cc44519e..4c60daf77f4 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -1,6 +1,7 @@
---
type: reference, howto
---
+
# Restrict allowed SSH key technologies and minimum length
`ssh-keygen` allows users to create RSA keys with as few as 768 bits, which
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 6251f8e2f66..b08d9ffa26e 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# Enforce Two-factor Authentication (2FA)
Two-factor Authentication (2FA) provides an additional level of security to your
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index 2e14e631d68..d34826c853c 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -2,37 +2,44 @@
type: howto
---
-# How to unlock a locked user
+# How to unlock a locked user from the command line
-To unlock a locked user, first log into your server with root privileges.
+After six failed login attempts a user gets in a locked state.
-Start a Ruby on Rails console with this command:
+To unlock a locked user:
-```bash
-gitlab-rails console production
-```
+1. SSH into your GitLab server.
+1. Start a Ruby on Rails console:
-Wait until the console has loaded.
+ ```sh
+ ## For Omnibus GitLab
+ sudo gitlab-rails console production
-There are multiple ways to find your user. You can search for email or username.
+ ## For installations from source
+ sudo -u git -H bundle exec rails console RAILS_ENV=production
+ ```
-```bash
-user = User.where(id: 1).first
-```
+1. Find the user to unlock. You can search by email or ID.
-or
+ ```ruby
+ user = User.find_by(email: 'admin@local.host')
+ ```
-```bash
-user = User.find_by(email: 'admin@local.host')
-```
+ or
-Unlock the user:
+ ```ruby
+ user = User.where(id: 1).first
+ ```
-```bash
-user.unlock_access!
-```
+1. Unlock the user:
-Exit the console, the user should now be able to log in again.
+ ```ruby
+ user.unlock_access!
+ ```
+
+1. Exit the console with <kbd>Ctrl</kbd>+<kbd>d</kbd>
+
+The user should now be able to log in.
<!-- ## Troubleshooting
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index f0af0a7ac6a..7ba50acbb06 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# User email confirmation at sign-up
GitLab can be configured to require confirmation of a user's email address when
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
index f34528a6e05..9fc8f7ec985 100644
--- a/doc/security/user_file_uploads.md
+++ b/doc/security/user_file_uploads.md
@@ -1,6 +1,7 @@
---
type: reference
---
+
# User File Uploads
Images that are attached to issues, merge requests, or comments
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index d4fa088cb15..1194234a295 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -1,6 +1,7 @@
---
type: concepts, reference, howto
---
+
# Webhooks and insecure internal web services
If you have non-GitLab web services running on your GitLab server or within its
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 6dfe42f68cf..f9ad952aaad 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -222,7 +222,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y
[quick start guide](quick_start_guide.md).
GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users
-can enable/disable Auto DevOps at either the project-level or instance-level.
+can enable/disable Auto DevOps at the project-level, group-level or instance-level.
### Enabling/disabling Auto DevOps at the instance-level (Administrators only)
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 0fb7338a688..e56acac527f 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -164,3 +164,23 @@ 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. -->
+
+## Required pipeline configuration **(PREMIUM ONLY)**
+
+GitLab administrators can force a pipeline configuration to run on every
+pipeline.
+
+The configuration applies to all pipelines for a GitLab instance and is
+sourced from:
+
+- The [instance template repository](instance_template_repository.md).
+- GitLab-supplied configuration.
+
+To set required pipeline configuration:
+
+1. Go to **Admin area > Settings > CI/CD**.
+1. Expand the **Required pipeline configuration** section.
+1. Select the required configuration from the provided dropdown.
+1. Click **Save changes**.
+
+![Required pipeline](img/admin_required_pipeline.png)
diff --git a/doc/user/admin_area/settings/img/admin_required_pipeline.png b/doc/user/admin_area/settings/img/admin_required_pipeline.png
new file mode 100644
index 00000000000..58488674d51
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_required_pipeline.png
Binary files differ
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 0dd0fd3f136..09bd306363c 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -149,6 +149,8 @@ using environment variables.
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
+| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). |
+| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
## Reports JSON format
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 2246ea8ed5a..6956086c382 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -251,6 +251,7 @@ The applications below can be uninstalled.
| Application | GitLab version | Notes |
| ----------- | -------------- | ----- |
+| GitLab Runner | 12.2+ | Any running pipelines will be canceled. |
| Ingress | 12.1+ | The associated load balancer and IP will be deleted and cannot be restored. Furthermore, it can only be uninstalled if JupyterHub is not installed. |
| JupyterHub | 12.1+ | All data not committed to GitLab will be deleted and cannot be restored. |
| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
diff --git a/doc/user/project/autocomplete_characters.md b/doc/user/project/autocomplete_characters.md
new file mode 100644
index 00000000000..9ebf7f821a1
--- /dev/null
+++ b/doc/user/project/autocomplete_characters.md
@@ -0,0 +1,48 @@
+# Autocomplete characters
+
+The autocomplete characters provide a quick way of entering field values into
+Markdown fields. When you start typing a word in a Markdown field with one of
+the following characters, GitLab progressively autocompletes against a set of
+matching values. The string matching is not case sensitive.
+
+| Character | Autocompletes |
+| :-------- | :------------ |
+| `~` | Labels |
+| `%` | Milestones |
+| `@` | Users and groups |
+| `#` | Issues |
+| `!` | Merge requests |
+| `&` | Epics |
+| `$` | Snippets |
+| `:` | Emoji |
+| `/` | Quick Actions |
+
+Up to 5 of the most relevant matches are displayed in a popup list. When you
+select an item from the list, the value is entered in the field. The more
+characters you enter, the more precise the matches are.
+
+Autocomplete characters are useful when combined with [Quick Actions](quick_actions.md).
+
+## Example
+
+Assume your GitLab instance includes the following users:
+
+| Username | Name |
+| :-------------- | :--- |
+| alessandra | Rosy Grant |
+| lawrence.white | Kelsey Kerluke |
+| leanna | Rosemarie Rogahn |
+| logan_gutkowski | Lee Wuckert |
+| shelba | Josefine Haley |
+
+In an Issue comment, entering `@l` results in the following popup list
+appearing. Note that user `shelba` is not included, because the list includes
+only the 5 users most relevant to the Issue.
+
+![Popup list which includes users whose username or name contains the letter `l`](img/autocomplete_characters_example1_v12_0.png)
+
+If you continue to type, `@le`, the popup list changes to the following. The
+popup now only includes users where `le` appears in their username, or a word in
+their name.
+
+![Popup list which includes users whose username or name contains the string `le`](img/autocomplete_characters_example2_v12_0.png)
diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
new file mode 100755
index 00000000000..9c6fa923b80
--- /dev/null
+++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
Binary files differ
diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
new file mode 100755
index 00000000000..b2e8a782a0b
--- /dev/null
+++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
Binary files differ
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 0ffa69b6b78..7307c5b8991 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -52,6 +52,9 @@ When you create a project in GitLab, you'll have access to a large number of
templates for issue and merge request description fields for your project
- [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
common actions on issues or merge requests
+- [Autocomplete characters](autocomplete_characters.md): Autocomplete
+ references to users, groups, issues, merge requests, and other GitLab
+ elements.
- [Web IDE](web_ide/index.md)
**GitLab CI/CD:**
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 69fefbf3891..72f12972596 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -121,68 +121,91 @@ GitLab supports a limited set of [CI variables](../../../ci/variables/README.htm
To specify a variable in a query, enclose it in curly braces with a leading percent. For example: `%{ci_environment_slug}`.
-### Defining Dashboards for Prometheus Metrics per Project
+### Defining custom dashboards per project
-All projects include a GitLab-defined system dashboard, which includes a few key metrics. Optionally, additional dashboards can also be defined by including configuration files in the project repository under `.gitlab/dashboards`. Configuration files nested under subdirectories will not be available in the UI. Each file should define the layout of the dashboard and the prometheus queries used to populate data. Dashboards can be selected from the dropdown in the UI.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59974) in GitLab 12.1.
-#### Relationship to Custom Metrics
+By default, all projects include a GitLab-defined Prometheus dashboard, which
+includes a few key metrics, but you can also define your own custom dashboards.
-[Custom Metrics](#adding-additional-metrics-premium) are defined through the UI and, at this point, are unique from metrics defined in dashboard configuration files. Custom Metrics will appear on the system dashboard, as well as support alerting, whereas metrics defined in configuration files do not yet support alerts.
+NOTE: **Note:**
+The custom metrics as defined below do not support alerts, unlike
+[additional metrics](#adding-additional-metrics-premium).
-#### Dashboard Configuration
+Dashboards have several components:
-Dashboards have several components. A dashboard has many panel groups, which are comprised of panels, which support one or more metrics. The dashboard should be saved with the `.yml` extension.
+- Panel groups, which comprise panels.
+- Panels, which support one or more metrics.
-Sample YML Configuration
-```
-dashboard: 'Dashboard Title'
-priority: 2
-panel_groups:
- - group: 'Group Title'
- panels:
- - type: area-chart
- title: "Chart Title"
- y_label: "Y-Axis"
- metrics:
- - id: metric_of_ages
- query_range: 'http_requests_total'
- label: "Metric of Ages"
- unit: "count"
-```
+To configure a custom dashboard:
+
+1. Create a YAML file with the `.yml` extension under your repository's root
+ directory inside `.gitlab/dashboards/`. For example, create
+ `.gitlab/dashboards/prom_alerts.yml` with the following contents:
+
+ ```yaml
+ dashboard: 'Dashboard Title'
+ panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ metrics:
+ - id: metric_of_ages
+ query_range: 'http_requests_total'
+ label: "Metric of Ages"
+ unit: "count"
+ ```
+
+ The above sample dashboard would display a single area chart. Each file should
+ define the layout of the dashboard and the Prometheus queries used to populate
+ data.
+
+1. Save the file, commit, and push to your repository.
+1. Navigate to your project's **Operations > Metrics** and choose the custom
+ dashboard from the dropdown.
-The above sample dashboard would display a single area chart. The following sections outline the details of expected properties.
+NOTE: **Note:**
+Configuration files nested under subdirectories of `.gitlab/dashboards` are not
+supported and will not be available in the UI.
-##### Dashboard Properties
-| Property | Type | Required? | Meaning |
+The following tables outline the details of expected properties.
+
+**Dashboard properties:**
+
+| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
-| `dashboard` | string | required | Heading for the dashboard. Only one dashboard should be defined per file. |
-| `priority` | number | optional, default to definition order | Order to appear in dashboard dropdown, higher priority should be higher in the dropdown. Numbers do not need to be consecutive. |
-| `panel_groups` | array | required | The panel groups which should be on the dashboard. |
+| `dashboard` | string | yes | Heading for the dashboard. Only one dashboard should be defined per file. |
+| `panel_groups` | array | yes | The panel groups which should be on the dashboard. |
+
+**Panel group (`panel_groups`) properties:**
-##### Panel Group Properties
-| Property | Type | Required? | Meaning |
+| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
| `group` | string | required | Heading for the panel group. |
-| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard, higher priority will be higher on the page. Numbers do not need to be consecutive. |
+| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard. Higher number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
| `panels` | array | required | The panels which should be in the panel group. |
-##### Panel Properties
-| Property | Type | Required? | Meaning |
+**Panel (`panels`) properties:**
+
+| Property | Type | Required | Description |
| ------ | ------ | ------ | ------- |
-| `type` | enum | optional, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. |
-| `title` | string | required | Heading for the panel. |
-| `y_label` | string | optional, but highly encouraged | Y-Axis label for the panel. |
-| `weight` | number | optional, defaults to order in file | Order to appear within the grouping, higher priority will be higher on the page. Numbers do not need to be consecutive. |
-| `metrics` | array | required | The metrics which should be displayed in the panel. |
-
-##### Metric Properties
-| Property | Type | Required? | Meaning |
+| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. |
+| `title` | string | yes | Heading for the panel. |
+| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
+| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
+| `metrics` | array | yes | The metrics which should be displayed in the panel. |
+
+**Metrics (`metrics`) properties:**
+
+| Property | Type | Required | Description |
| ------ | ------ | ------ | ------ |
-| `id` | string | optional | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). |
-| `unit` | string | required | Defines the unit of the query's return data. |
-| `label` | string | optional, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. |
-| `query` | string | required unless `query_range` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
-| `query_range` | string | required unless `query` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `id` | string | no | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). |
+| `unit` | string | yes | Defines the unit of the query's return data. |
+| `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. |
+| `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
### Setting up alerts for Prometheus metrics **(ULTIMATE)**
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index 6c0d3e9e9d3..219e141d72e 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -179,15 +179,26 @@ From that page, you can view, add, and remove them.
### Redirecting `www.domain.com` to `domain.com` with Cloudflare
-If you use Cloudflare, you can redirect `www` to `domain.com` without adding both
-`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates
-a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case,
-you can use the following setup:
+If you use Cloudflare, you can redirect `www` to `domain.com`
+without adding both `www.domain.com` and `domain.com` to GitLab.
+
+To do so, you can use Cloudflare's page rules associated to a
+CNAME record to redirect `www.domain.com` to `domain.com`. You
+can use the following setup:
1. In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`.
-1. In GitLab, add the domain to GitLab Pages.
+1. In GitLab, add the domain to GitLab Pages and get the verification code.
1. In Cloudflare, create a DNS `TXT` record to verify your domain.
+1. In GitLab, verify your domain.
1. In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`.
+1. In Cloudflare, add a Page Rule pointing `www.domain,com` to `domain.com`:
+ - Navigate to your domain's dashboard and click **Page Rules**
+ on the top nav.
+ - Click **Create Page Rule**.
+ - Enter the domain `www.domain.com` and click **+ Add a Setting**.
+ - From the dropdown menu, choose **Forwarding URL**, then select the
+ status code **301 - Permanent Redirect**.
+ - Enter the destination URL `https://domain.com`.
## Adding an SSL/TLS certificate to Pages
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index e60da6a3e59..df82daa3da3 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -89,6 +89,22 @@ in the jobs table.
A few examples of known coverage tools for a variety of languages can be found
in the pipelines settings page.
+### Removing color codes
+
+Some test coverage tools output with ANSI color codes that won't be
+parsed correctly by the regular expression and will cause coverage
+parsing to fail.
+
+If your coverage tool doesn't provide an option to disable color
+codes in the output, you can pipe the output of the coverage tool through a
+small one line script that will strip the color codes off.
+
+For example:
+
+```bash
+lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g'
+```
+
## Visibility of pipelines
Access to pipelines and job details (including output of logs and artifacts)
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index c6e945ef113..948ac91a886 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -35,18 +35,18 @@ discussions, and descriptions:
| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified | ✓ | ✓ |
-| <code>/copy_metadata &lt;#issue &#124; !merge_request&gt;</code> | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
-| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | ✓ | ✓ |
+| `/copy_metadata <#issue | !merge_request>` | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
+| `/estimate <1w 3d 2h 14m>` | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
-| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
+| `/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)>` | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
| `/lock` | Lock the thread | ✓ | ✓ |
| `/unlock` | Unlock the thread | ✓ | ✓ |
-| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code>| Set due date | ✓ | |
+| `/due <in 2 days | this Friday | December 31st>`| Set due date | ✓ | |
| `/remove_due_date` | Remove due date | ✓ | |
-| <code>/weight &lt;0 &#124; 1 &#124; 2 &#124; ...&gt;</code> | Set weight **(STARTER)** | ✓ | |
+| `/weight <0 | 1 | 2 | ...>` | Set weight **(STARTER)** | ✓ | |
| `/clear_weight` | Clears weight **(STARTER)** | ✓ | |
-| <code>/epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Add to epic **(ULTIMATE)** | ✓ | |
+| `/epic <&epic | group&epic | Epic URL>` | Add to epic **(ULTIMATE)** | ✓ | |
| `/remove_epic` | Removes from epic **(ULTIMATE)** | ✓ | |
| `/promote` | Promote issue to epic **(ULTIMATE)** | ✓ | |
| `/confidential` | Make confidential | ✓ | |
@@ -86,7 +86,7 @@ The following quick actions are applicable for epics threads and description:
| `/label ~label1 ~label2` | Add label(s) |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified |
-| <code>/child_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| <code>/remove_child_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| <code>/parent_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
-| <code>/remove_parent_epic | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
+| `/child_epic <&epic | group&epic | Epic URL>` | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
+| `/remove_child_epic <&epic | group&epic | Epic URL>` | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
+| `/parent_epic <&epic | group&epic | Epic URL>` | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
+| `/remove_parent_epic` | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 4286a3625a2..b55c6b2e3df 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -75,7 +75,7 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
### Limit displayed units to hours
-> Introduced in GitLab 12.0.
+> Introduced in GitLab 12.1.
The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'.
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index eebded87ebc..c414ad75d9d 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -126,7 +126,7 @@ module API
if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result])
- Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden
+ Gitlab::UsageDataCounters::WebIdeCommitsCounter.increment if find_user_from_warden
present commit_detail, with: Entities::CommitDetail, stats: params[:stats]
else
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index fdd8406388e..7a3d804c30c 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -78,7 +78,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
optional :name, type: String, desc: 'The name of the release'
optional :description, type: String, desc: 'Release notes with markdown support'
- optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
+ optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.'
end
put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
authorize_update_release!
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 30a278fdff1..a4ac5b629b8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -148,7 +148,7 @@ module API
end
desc 'Create a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :email, type: String, desc: 'The email of the user'
@@ -168,7 +168,7 @@ module API
user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
if user.persisted?
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
conflict!('Email has already been taken') if User
.by_any_email(user.email.downcase)
@@ -183,7 +183,7 @@ module API
end
desc 'Update a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
@@ -215,7 +215,7 @@ module API
result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute
if result[:status] == :success
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
render_validation_error!(user)
end
diff --git a/lib/banzai/filter/ascii_doc_sanitization_filter.rb b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
index a78bb60103c..d8d63075752 100644
--- a/lib/banzai/filter/ascii_doc_sanitization_filter.rb
+++ b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
@@ -6,6 +6,9 @@ module Banzai
#
# Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
+ # Section anchor link pattern
+ SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
+
# Classes used by Asciidoctor to style components
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
CALLOUT_CLASSES = ['conum'].freeze
@@ -19,14 +22,17 @@ module Banzai
td: ['icon'].freeze,
i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES,
ul: LIST_CLASSES,
- ol: LIST_CLASSES
+ ol: LIST_CLASSES,
+ a: ['anchor'].freeze
}.freeze
+ ALLOWED_HEADERS = %w(h2 h3 h4 h5 h6).freeze
+
def customize_whitelist(whitelist)
# Allow marks
whitelist[:elements].push('mark')
- # Allow any classes in `span`, `i`, `div`, `td`, `ul` and `ol` elements
+ # Allow any classes in `span`, `i`, `div`, `td`, `ul`, `ol` and `a` elements
# but then remove any unknown classes
whitelist[:attributes]['span'] = %w(class)
whitelist[:attributes]['div'].push('class')
@@ -34,12 +40,32 @@ module Banzai
whitelist[:attributes]['i'] = %w(class)
whitelist[:attributes]['ul'] = %w(class)
whitelist[:attributes]['ol'] = %w(class)
+ whitelist[:attributes]['a'].push('class')
whitelist[:transformers].push(self.class.remove_element_classes)
+ # Allow `id` in heading elements for section anchors
+ ALLOWED_HEADERS.each do |header|
+ whitelist[:attributes][header] = %w(id)
+ end
+ whitelist[:transformers].push(self.class.remove_non_heading_ids)
+
whitelist
end
class << self
+ def remove_non_heading_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless ALLOWED_HEADERS.any?(node.name)
+ return unless node.has_attribute?('id')
+
+ return if node['id'] =~ SECTION_LINK_REF_PATTERN
+
+ node.remove_attribute('id')
+ end
+ end
+
def remove_element_classes
lambda do |env|
node = env[:node]
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/reference_redactor_filter.rb
index 1f091f594f8..485d3fd5fc7 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/reference_redactor_filter.rb
@@ -7,12 +7,12 @@ module Banzai
#
# Expected to be run in its own post-processing pipeline.
#
- class RedactorFilter < HTML::Pipeline::Filter
+ class ReferenceRedactorFilter < HTML::Pipeline::Filter
def call
unless context[:skip_redaction]
context = RenderContext.new(project, current_user)
- Redactor.new(context).redact([doc])
+ ReferenceRedactor.new(context).redact([doc])
end
doc
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 75661ffa233..d6d29f4bfab 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -72,7 +72,7 @@ module Banzai
#
# Returns an Array containing the redacted documents.
def redact_documents(documents)
- redactor = Redactor.new(context)
+ redactor = ReferenceRedactor.new(context)
redactor.redact(documents)
end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 5c199453638..54af26b41be 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -12,7 +12,7 @@ module Banzai
def self.internal_link_filters
[
- Filter::RedactorFilter,
+ Filter::ReferenceRedactorFilter,
Filter::InlineMetricsRedactorFilter,
Filter::RelativeLinkFilter,
Filter::IssuableStateFilter,
diff --git a/lib/banzai/redactor.rb b/lib/banzai/reference_redactor.rb
index c2da7fec7cc..eb5c35da375 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/reference_redactor.rb
@@ -3,7 +3,7 @@
module Banzai
# Class for removing Markdown references a certain user is not allowed to
# view.
- class Redactor
+ class ReferenceRedactor
attr_reader :context
# context - An instance of `Banzai::RenderContext`.
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 81f32ef5bcf..3cb9ec21e8f 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -134,7 +134,7 @@ module Banzai
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
- # permission to make (`RedactorFilter`).
+ # permission to make (`ReferenceRedactorFilter`).
#
# html - String to process
# context - Hash of options to customize output
diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb
index 67c0b902c0c..edfd2fb17f3 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -5,16 +5,12 @@ require 'set'
class Feature
class Gitaly
# Server feature flags should use '_' to separate words.
- # CATFILE_CACHE sets an incorrect example
- CATFILE_CACHE = 'catfile-cache'.freeze
-
SERVER_FEATURE_FLAGS =
[
- CATFILE_CACHE,
'get_commit_signatures'.freeze
].freeze
- DEFAULT_ON_FLAGS = Set.new([CATFILE_CACHE]).freeze
+ DEFAULT_ON_FLAGS = Set.new([]).freeze
class << self
def enabled?(feature_flag)
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 00c87cce7b6..da65caa6c9c 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -13,6 +13,7 @@ module Gitlab
MAX_INCLUDE_DEPTH = 5
DEFAULT_ADOC_ATTRS = {
'showtitle' => true,
+ 'sectanchors' => true,
'idprefix' => 'user-content-',
'idseparator' => '-',
'env' => 'gitlab',
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index c911bfa7ff6..afad391e8e0 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -20,6 +20,12 @@ module Gitlab
end
end
+ def uses_unsupported_legacy_trigger?
+ trigger_request.present? &&
+ trigger_request.trigger.legacy? &&
+ !trigger_request.trigger.supports_legacy_tokens?
+ end
+
def branch_exists?
strong_memoize(:is_branch) do
project.repository.branch_exists?(ref)
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
index aaa3daddcc5..357a1d55b3b 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -14,6 +14,10 @@ module Gitlab
return error('Pipelines are disabled!')
end
+ if @command.uses_unsupported_legacy_trigger?
+ return error('Trigger token is invalid because is not owned by any user')
+ end
+
unless allowed_to_trigger_pipeline?
if can?(current_user, :create_pipeline, project)
return error("Insufficient permissions for protected ref '#{command.ref}'")
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
index e4cf360a1c1..0212fa9d661 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
@@ -8,11 +8,10 @@ module Gitlab
require_dependency 're2'
class Pattern < Lexeme::Value
- PATTERN = %r{^/.+/[ismU]*$}.freeze
- NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
+ PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
- @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp
+ @value = regexp.gsub(/\\\//, '/')
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
@@ -26,16 +25,12 @@ module Gitlab
end
def self.pattern
- eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN
+ PATTERN
end
def self.build(string)
new(string)
end
-
- def self.eager_matching_with_escape_characters?
- Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
- end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index 22c210ae26b..7d7582612f9 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -17,17 +17,6 @@ module Gitlab
Expression::Lexeme::Equals,
Expression::Lexeme::Matches,
Expression::Lexeme::NotEquals,
- Expression::Lexeme::NotMatches
- ].freeze
-
- NEW_LEXEMES = [
- Expression::Lexeme::Variable,
- Expression::Lexeme::String,
- Expression::Lexeme::Pattern,
- Expression::Lexeme::Null,
- Expression::Lexeme::Equals,
- Expression::Lexeme::Matches,
- Expression::Lexeme::NotEquals,
Expression::Lexeme::NotMatches,
Expression::Lexeme::And,
Expression::Lexeme::Or
@@ -58,7 +47,7 @@ module Gitlab
return tokens if @scanner.eos?
- lexeme = available_lexemes.find do |type|
+ lexeme = LEXEMES.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
@@ -71,10 +60,6 @@ module Gitlab
raise Lexer::SyntaxError, 'Too many tokens!'
end
-
- def available_lexemes
- Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES
- end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb
index 589bf32a4d7..edb55edf356 100644
--- a/lib/gitlab/ci/pipeline/expression/parser.rb
+++ b/lib/gitlab/ci/pipeline/expression/parser.rb
@@ -13,39 +13,6 @@ module Gitlab
end
def tree
- if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
- rpn_parse_tree
- else
- reverse_descent_parse_tree
- end
- end
-
- def self.seed(statement)
- new(Expression::Lexer.new(statement).tokens)
- end
-
- private
-
- # This produces a reverse descent parse tree.
- # It does not support precedence of operators.
- def reverse_descent_parse_tree
- while token = @tokens.next
- case token.type
- when :operator
- token.build(@nodes.pop, tree).tap do |node|
- @nodes.push(node)
- end
- when :value
- token.build.tap do |leaf|
- @nodes.push(leaf)
- end
- end
- end
- rescue StopIteration
- @nodes.last || Lexeme::Null.new
- end
-
- def rpn_parse_tree
results = []
tokens_rpn.each do |token|
@@ -70,6 +37,12 @@ module Gitlab
results.pop
end
+ def self.seed(statement)
+ new(Expression::Lexer.new(statement).tokens)
+ end
+
+ private
+
# Parse the expression into Reverse Polish Notation
# (See: Shunting-yard algorithm)
def tokens_rpn
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index f176771775e..89eccce69f6 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -41,6 +41,8 @@ dependency_scanning:
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
DS_RUN_ANALYZER_TIMEOUT \
DS_PYTHON_VERSION \
+ PIP_INDEX_URL \
+ PIP_EXTRA_INDEX_URL \
) \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 091351e5cb2..0b6321c66ab 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -211,8 +211,7 @@ module Gitlab
metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
metadata['x-gitlab-correlation-id'] = Labkit::Correlation::CorrelationId.current_id if Labkit::Correlation::CorrelationId.current_id
- metadata['gitaly-session-id'] = session_id if Feature::Gitaly.enabled?(Feature::Gitaly::CATFILE_CACHE)
-
+ metadata['gitaly-session-id'] = session_id
metadata.merge!(Feature::Gitaly.server_feature_flags)
result = { metadata: metadata }
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index d8e9dccb644..ca3e5b51ecc 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -5,7 +5,7 @@ module Gitlab
class RepositoryService
include Gitlab::EncodingHelper
- MAX_MSG_SIZE = 128.kilobytes.freeze
+ MAX_MSG_SIZE = 128.kilobytes
def initialize(repository)
@repository = repository
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index efd3f550a22..1b545b1d049 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -28,7 +28,7 @@ module Gitlab
links: 'Releases::Link',
metrics_setting: 'ProjectMetricsSetting' }.freeze
- USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
+ USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id owner_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
@@ -78,6 +78,9 @@ module Gitlab
def create
return if unknown_service?
+ # Do not import legacy triggers
+ return if !Feature.enabled?(:use_legacy_pipeline_triggers, @project) && legacy_trigger?
+
setup_models
generate_imported_object
@@ -278,6 +281,10 @@ module Gitlab
!Object.const_defined?(parsed_relation_hash['type'])
end
+ def legacy_trigger?
+ @relation_name == 'Ci::Trigger' && @relation_hash['owner_id'].nil?
+ end
+
def find_or_create_object!
return relation_class.find_or_create_by(project_id: @project.id) if @relation_name == :project_feature
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 0180fe7fa71..055e01a9399 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -130,7 +130,7 @@ module Gitlab
def usage_counters
{
- web_ide_commits: Gitlab::WebIdeCommitsCounter.total_count
+ web_ide_commits: Gitlab::UsageDataCounters::WebIdeCommitsCounter.total_count
}
end
diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb
new file mode 100644
index 00000000000..123b8e1bef1
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/redis_counter.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module RedisCounter
+ def increment
+ Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
+ end
+
+ def total_count
+ Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i }
+ end
+
+ def redis_counter_key
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb b/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb
new file mode 100644
index 00000000000..62236fa07a3
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ class WebIdeCommitsCounter
+ extend RedisCounter
+
+ def self.redis_counter_key
+ 'WEB_IDE_COMMITS_COUNT'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/web_ide_commits_counter.rb b/lib/gitlab/web_ide_commits_counter.rb
deleted file mode 100644
index 1cd9b5295b9..00000000000
--- a/lib/gitlab/web_ide_commits_counter.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module WebIdeCommitsCounter
- WEB_IDE_COMMITS_KEY = "WEB_IDE_COMMITS_COUNT".freeze
-
- class << self
- def increment
- Gitlab::Redis::SharedState.with { |redis| redis.incr(WEB_IDE_COMMITS_KEY) }
- end
-
- def total_count
- Gitlab::Redis::SharedState.with { |redis| redis.get(WEB_IDE_COMMITS_KEY).to_i }
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/features.rake b/lib/tasks/gitlab/features.rake
index d88bcca0819..9cf568c07fe 100644
--- a/lib/tasks/gitlab/features.rake
+++ b/lib/tasks/gitlab/features.rake
@@ -10,14 +10,22 @@ namespace :gitlab do
set_rugged_feature_flags(false)
puts 'All Rugged feature flags were disabled.'
end
+
+ task unset_rugged: :environment do
+ set_rugged_feature_flags(nil)
+ puts 'All Rugged feature flags were unset.'
+ end
end
def set_rugged_feature_flags(status)
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
- if status
- Feature.enable(flag)
- else
+ case status
+ when nil
Feature.get(flag).remove
+ when true
+ Feature.enable(flag)
+ when false
+ Feature.disable(flag)
end
end
end
diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake
index 155ba979b36..d76e38b73b5 100644
--- a/lib/tasks/gitlab/seed.rake
+++ b/lib/tasks/gitlab/seed.rake
@@ -1,7 +1,9 @@
namespace :gitlab do
namespace :seed do
desc "GitLab | Seed | Seeds issues"
- task :issues, [:project_full_path] => :environment do |t, args|
+ task :issues, [:project_full_path, :backfill_weeks, :average_issues_per_week] => :environment do |t, args|
+ args.with_defaults(backfill_weeks: 5, average_issues_per_week: 2)
+
projects =
if args.project_full_path
project = Project.find_by_full_path(args.project_full_path)
@@ -26,7 +28,8 @@ namespace :gitlab do
projects.each do |project|
puts "\nSeeding issues for the '#{project.full_path}' project"
seeder = Quality::Seeders::Issues.new(project: project)
- issues_created = seeder.seed(backfill_weeks: 5, average_issues_per_week: 2)
+ issues_created = seeder.seed(backfill_weeks: args.backfill_weeks.to_i,
+ average_issues_per_week: args.average_issues_per_week.to_i)
puts "\n#{issues_created} issues created!"
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f498961c8d1..5e8d1ac206a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -138,9 +138,15 @@ msgstr[1] ""
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
msgstr ""
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
msgstr ""
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
msgid "%{filePath} deleted"
msgstr ""
@@ -704,6 +710,9 @@ msgstr ""
msgid "Add to review"
msgstr ""
+msgid "Add to tree"
+msgstr ""
+
msgid "Add user(s) to the group:"
msgstr ""
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index 5f6ddb9a114..2b1a9ab2b6a 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -5,7 +5,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
- element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern
+ element :authorization_button
end
def needs_authorization?
@@ -13,7 +13,7 @@ module QA
end
def authorize!
- click_button 'Authorize'
+ click_element :authorization_button
end
end
end
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index 46a105003d0..c47d2ce9c74 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -5,28 +5,28 @@ module QA
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
- element :new_user_name
- element :new_user_username
- element :new_user_email
- element :new_user_email_confirmation
- element :new_user_password
+ element :new_user_name_field
+ element :new_user_username_field
+ element :new_user_email_field
+ element :new_user_email_confirmation_field
+ element :new_user_password_field
element :new_user_register_button
- element :new_user_accept_terms
+ element :new_user_accept_terms_checkbox
end
def sign_up!(user)
- fill_element :new_user_name, user.name
- fill_element :new_user_username, user.username
- fill_element :new_user_email, user.email
- fill_element :new_user_email_confirmation, user.email
- fill_element :new_user_password, user.password
+ fill_element :new_user_name_field, user.name
+ fill_element :new_user_username_field, user.username
+ fill_element :new_user_email_field, user.email
+ fill_element :new_user_email_confirmation_field, user.email
+ fill_element :new_user_password_field, user.password
- check_element :new_user_accept_terms if has_element?(:new_user_accept_terms)
+ check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox)
signed_in = retry_until do
click_element :new_user_register_button
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
raise "Failed to register and sign in" unless signed_in
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 4f625c5f0f0..eb30e0ea02a 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -28,16 +28,12 @@ module QA
end
end
- def await_installed(application_name, button_text: 'Installed')
+ def await_installed(application_name)
within(".js-cluster-application-row-#{application_name}") do
- page.has_text?(button_text, wait: 300)
+ page.has_text?(/Installed|Uninstall/, wait: 300)
end
end
- def await_uninstallable(application_name)
- await_installed(application_name, button_text: 'Uninstall')
- end
-
def ingress_ip
# We need to wait longer since it can take some time before the
# ip address is assigned for the ingress controller
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
index c94e1e85256..3c9e8085748 100644
--- a/qa/qa/page/project/sub_menus/common.rb
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -12,7 +12,11 @@ module QA
end
def within_submenu
- within('.fly-out-list') do
+ if has_css?('.fly-out-list')
+ within('.fly-out-list') do
+ yield
+ end
+ else
yield
end
end
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 1dd93dd5b88..27ab7b60211 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -47,7 +47,7 @@ module QA
page.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
- page.await_uninstallable(:prometheus) if @install_prometheus
+ page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
if @install_ingress
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 45cb317e0eb..7969de726e4 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -9,6 +9,7 @@ module QA
:description,
:source_branch,
:target_branch,
+ :target_new_branch,
:assignee,
:milestone,
:labels,
@@ -27,6 +28,7 @@ module QA
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = 'master'
+ resource.new_branch = @target_new_branch
resource.remote_branch = target_branch
end
end
@@ -52,6 +54,7 @@ module QA
@labels = []
@file_name = "added_file.txt"
@file_content = "File Added"
+ @target_new_branch = true
end
def fabricate!
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 2055ce7f09d..8c9b8b9fb02 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -45,7 +45,7 @@ then
then
echo
echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
- echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+ echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files'
echo
exit 1
fi
@@ -55,7 +55,7 @@ then
then
echo
echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
- echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+ echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files'
echo
exit 1
fi
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 5ad5f9cdeea..4eb0545eb6c 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -41,7 +41,7 @@ describe Admin::ApplicationSettingsController do
it 'returns JSON data' do
get :usage_data, format: :json
- body = JSON.parse(response.body)
+ body = json_response
expect(body["version"]).to eq(Gitlab::VERSION)
expect(body).to include('counts')
expect(response.status).to eq(200)
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 447a12b2fac..84bbbac39b0 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -63,8 +63,6 @@ describe ApplicationController do
sign_in user
end
- let(:json_response) { JSON.parse(response.body) }
-
controller(described_class) do
def index
render json: Gon.all_variables
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 6cad060d888..0db58fbefc1 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -52,10 +52,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board, list: list2
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
expect(development.issues.map(&:relative_position)).not_to include(nil)
end
@@ -123,10 +121,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
end
end
@@ -164,7 +160,7 @@ describe Boards::IssuesController do
end
end
- describe 'PUT move_multiple' do
+ describe 'PUT bulk_move' do
let(:todo) { create(:group_label, group: group, name: 'Todo') }
let(:development) { create(:group_label, group: group, name: 'Development') }
let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
@@ -200,6 +196,20 @@ describe Boards::IssuesController do
sign_in(signed_in_user)
end
+ it 'responds as expected' do
+ put :bulk_move, params: move_issues_params
+ expect(response).to have_gitlab_http_status(expected_status)
+
+ if expected_status == 200
+ expect(json_response).to include(
+ 'count' => move_issues_params[:ids].size,
+ 'success' => true
+ )
+
+ expect(json_response['issues'].pluck('id')).to match_array(move_issues_params[:ids])
+ end
+ end
+
it 'moves issues as expected' do
put :bulk_move, params: move_issues_params
expect(response).to have_gitlab_http_status(expected_status)
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index e1f75fa3395..418ca6f3210 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -26,10 +26,8 @@ describe Boards::ListsController do
read_board_list user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('lists')
- expect(parsed_response.length).to eq 3
+ expect(json_response.length).to eq 3
end
context 'with unauthorized user' do
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 5e0f64ccca4..e4232c2c1ab 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -63,10 +63,8 @@ describe Groups::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 1
+ expect(json_response.length).to eq 1
end
context 'with unauthorized user' do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 19b18091aef..bf164aeed38 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -73,7 +73,7 @@ describe Groups::MilestonesController do
it 'lists legacy group milestones and group milestones' do
get :index, params: { group_id: group.to_param }, format: :json
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones.count).to eq(2)
expect(milestones.first["title"]).to eq("group milestone")
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 19d739fcf4f..92f005faf4a 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthCheckController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index fc62a8310aa..e82dcfcdb64 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
let(:not_whitelisted_ip) { '127.0.0.2' }
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index ee454a7818c..84027119491 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe MetricsController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:metrics_multiproc_dir) { Dir.mktmpdir }
let(:whitelisted_ip) { '127.0.0.1' }
let(:whitelisted_ip_range) { '10.0.0.0/24' }
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 44500d3cde3..45aebd1554c 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -160,7 +160,7 @@ describe Projects::BlobController do
it 'renders diff context lines Gitlab::Diff::Line array' do
do_get(since: 1, to: 2, offset: 0, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
lines.each do |line|
@@ -173,7 +173,7 @@ describe Projects::BlobController do
it 'handles full being true' do
do_get(full: true, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index c07afc57aea..543479d8dd5 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -69,10 +69,8 @@ describe Projects::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 2
+ expect(json_response.length).to eq 2
end
context 'with unauthorized user' do
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index b30966e70a7..f5bcea4a097 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -495,10 +495,8 @@ describe Projects::BranchesController do
search: 'master'
}
- parsed_response = JSON.parse(response.body)
-
- expect(parsed_response.length).to eq 1
- expect(parsed_response.first).to eq 'master'
+ expect(json_response.length).to eq 1
+ expect(json_response.first).to eq 'master'
end
end
@@ -591,8 +589,7 @@ describe Projects::BranchesController do
end
it 'returns the commit counts behind and ahead of default branch' do
- parsed_response = JSON.parse(response.body)
- expect(parsed_response).to eq(
+ expect(json_response).to eq(
"fix" => { "behind" => 29, "ahead" => 2 },
"branch-merged" => { "behind" => 1, "ahead" => 0 },
"add-pdf-file" => { "behind" => 0, "ahead" => 3 }
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index b5c6382a26d..58a1d96d010 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -378,8 +378,8 @@ describe Projects::CommitController do
get_pipelines(id: commit.id, format: :json)
expect(response).to be_ok
- expect(JSON.parse(response.body)['pipelines']).not_to be_empty
- expect(JSON.parse(response.body)['count']['all']).to eq 1
+ expect(json_response['pipelines']).not_to be_empty
+ expect(json_response['count']['all']).to eq 1
expect(response).to include_pagination_headers
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 92380a2bf09..48a92a772dc 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -302,8 +302,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- signatures = parsed_body['signatures']
+ signatures = json_response['signatures']
expect(signatures.size).to eq(1)
expect(signatures.first['commit_sha']).to eq(signature_commit.sha)
@@ -332,8 +331,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
@@ -345,8 +343,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
end
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index fcd14f13863..ccad76eaddd 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -52,12 +52,10 @@ describe Projects::DeployKeysController do
it 'returns json in a correct format' do
get :index, params: params.merge(format: :json)
- json = JSON.parse(response.body)
-
- expect(json.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
- expect(json['enabled_keys'].count).to eq(1)
- expect(json['available_project_keys'].count).to eq(1)
- expect(json['public_keys'].count).to eq(1)
+ expect(json_response.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
+ expect(json_response['enabled_keys'].count).to eq(1)
+ expect(json_response['available_project_keys'].count).to eq(1)
+ expect(json_response['public_keys'].count).to eq(1)
end
end
end
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 4c29162cd0f..e30b28a4bd5 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -112,7 +112,7 @@ describe Projects::DiscussionsController do
it "returns the name of the resolving user" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name)
+ expect(json_response['resolved_by']['name']).to eq(user.name)
end
it "returns status 200" do
@@ -135,7 +135,7 @@ describe Projects::DiscussionsController do
it "returns truncated diff lines" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present
+ expect(json_response['truncated_diff_lines']).to be_present
end
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 4c2c6160c62..ebbbebf1bc0 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::EnvironmentsController do
+ include MetricsDashboardHelpers
+
set(:user) { create(:user) }
set(:project) { create(:project) }
@@ -445,131 +447,186 @@ describe Projects::EnvironmentsController do
end
end
- describe 'metrics_dashboard' do
- context 'when prometheus endpoint is disabled' do
- before do
- stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
- end
+ describe 'GET #metrics_dashboard' do
+ shared_examples_for 'correctly formatted response' do |status_code|
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it 'responds with status code 403' do
- get :metrics_dashboard, params: environment_params(format: :json)
+ # Exlcude `all_dashboards` to handle separately.
+ found_keys = json_response.keys - ['all_dashboards']
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
end
end
- shared_examples_for '200 response' do |contains_all_dashboards: false|
+ shared_examples_for '200 response' do
let(:expected_keys) { %w(dashboard status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
-
- it 'returns a json representation of the environment dashboard' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.keys).to contain_exactly(*expected_keys)
- expect(json_response['dashboard']).to be_an_instance_of(Hash)
- end
+ it_behaves_like 'correctly formatted response', :ok
end
- shared_examples_for 'error response' do |status_code, contains_all_dashboards: false|
+ shared_examples_for 'error response' do |status_code|
let(:expected_keys) { %w(message status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
+ it_behaves_like 'correctly formatted response', status_code
+ end
- it 'returns an error response' do
+ shared_examples_for 'includes all dashboards' do
+ it 'includes info for all findable dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(response).to have_gitlab_http_status(status_code)
- expect(json_response.keys).to contain_exactly(*expected_keys)
+ expect(json_response).to have_key('all_dashboards')
+ expect(json_response['all_dashboards']).to be_an_instance_of(Array)
+ expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') )
end
end
- shared_examples_for 'has all dashboards' do
- it 'includes an index of all available dashboards' do
+ shared_examples_for 'the default dashboard' do
+ all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards)
+
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards' if all_dashboards
+
+ it 'is the default dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(json_response.keys).to include('all_dashboards')
- expect(json_response['all_dashboards']).to be_an_instance_of(Array)
- expect(json_response['all_dashboards']).to all( include('path', 'default') )
+ expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
end
end
- context 'when multiple dashboards is disabled' do
- before do
- stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
- end
+ shared_examples_for 'the specified dashboard' do |expected_dashboard|
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards'
- let(:dashboard_params) { { format: :json } }
+ it 'has the correct name' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it_behaves_like '200 response'
+ dashboard_name = json_response['dashboard']['dashboard']
- context 'when the dashboard could not be provided' do
+ # 'Environment metrics' is the default dashboard.
+ expect(dashboard_name).not_to eq('Environment metrics')
+ expect(dashboard_name).to eq(expected_dashboard)
+ end
+
+ context 'when the dashboard cannot not be processed' do
before do
allow(YAML).to receive(:safe_load).and_return({})
end
it_behaves_like 'error response', :unprocessable_entity
end
-
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } }
-
- it_behaves_like '200 response'
- end
end
- context 'when multiple dashboards is enabled' do
- let(:dashboard_params) { { format: :json } }
+ shared_examples_for 'the default dynamic dashboard' do
+ it_behaves_like '200 response'
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it 'contains only the Memory and CPU charts' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- context 'when a dashboard could not be provided' do
- before do
- allow(YAML).to receive(:safe_load).and_return({})
- end
+ dashboard = json_response['dashboard']
+ panel_group = dashboard['panel_groups'].first
+ titles = panel_group['panels'].map { |panel| panel['title'] }
- it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ expect(dashboard['dashboard']).to be_nil
+ expect(dashboard['panel_groups'].length).to eq 1
+ expect(panel_group['group']).to be_nil
+ expect(titles).to eq ['Memory Usage (Total)', 'Core Usage (Total)']
end
+ end
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+ shared_examples_for 'dashboard can be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:dashboard_params) { { format: :json, dashboard: dashboard_path } }
+
+ it_behaves_like 'error response', :not_found
- context 'when the dashboard is available' do
+ context 'when the project dashboard is available' do
let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
- let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } }
- let(:project) { create(:project, :custom_repo, files: dashboard_file) }
+ let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) }
let(:environment) { create(:environment, name: 'production', project: project) }
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it_behaves_like 'the specified dashboard', 'Test Dashboard'
end
- context 'when the dashboard does not exist' do
- it_behaves_like 'error response', :not_found, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ context 'when the specified dashboard is the default dashboard' do
+ let(:dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH }
+
+ it_behaves_like 'the default dashboard'
end
end
+ end
- context 'when the dashboard is intended for embedding' do
+ shared_examples_for 'dashboard can be embedded' do
+ context 'when the embedded flag is included' do
let(:dashboard_params) { { format: :json, embedded: true } }
- it_behaves_like '200 response'
+ it_behaves_like 'the default dynamic dashboard'
- context 'when a dashboard path is provided' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml', embedded: true } }
+ context 'when the dashboard is specified' do
+ let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } }
- # The dashboard path should simple be ignored.
- it_behaves_like '200 response'
+ # The dashboard param should be ignored.
+ it_behaves_like 'the default dynamic dashboard'
end
end
end
+
+ shared_examples_for 'dashboard cannot be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+
+ it_behaves_like 'the default dashboard'
+ end
+ end
+
+ shared_examples_for 'dashboard cannot be embedded' do
+ context 'when the embedded flag is included' do
+ let(:dashboard_params) { { format: :json, embedded: true } }
+
+ it_behaves_like 'the default dashboard'
+ end
+ end
+
+ let(:dashboard_params) { { format: :json } }
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard can be specified'
+ it_behaves_like 'dashboard can be embedded'
+
+ context 'when multiple dashboards is enabled and embedding metrics is disabled' do
+ before do
+ stub_feature_flags(gfm_embedded_metrics: false)
+ end
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard can be specified'
+ it_behaves_like 'dashboard cannot be embedded'
+ end
+
+ context 'when multiple dashboards is disabled and embedding metrics is enabled' do
+ before do
+ stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
+ end
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard cannot be specified'
+ it_behaves_like 'dashboard can be embedded'
+ end
+
+ context 'when multiple dashboards and embedding metrics are disabled' do
+ before do
+ stub_feature_flags(
+ environment_metrics_show_multiple_dashboards: false,
+ gfm_embedded_metrics: false
+ )
+ end
+
+ it_behaves_like 'the default dashboard'
+ it_behaves_like 'dashboard cannot be specified'
+ it_behaves_like 'dashboard cannot be embedded'
+ end
end
describe 'GET #search' do
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 538dbb5ad0b..a493985f8a0 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -53,10 +53,9 @@ describe Projects::FindFileController do
it 'returns an array of file path list' do
go
- json = JSON.parse(response.body)
is_expected.to respond_with(:success)
- expect(json).not_to eq(nil)
- expect(json.length).to be >= 0
+ expect(json_response).not_to eq(nil)
+ expect(json_response.length).to be >= 0
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index bc5e0b4671e..32d14dce936 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -444,7 +444,7 @@ describe Projects::IssuesController do
it 'renders json with recaptcha_html' do
subject
- expect(JSON.parse(response.body)).to have_key('recaptcha_html')
+ expect(json_response).to have_key('recaptcha_html')
end
end
end
@@ -484,10 +484,8 @@ describe Projects::IssuesController do
it 'returns last edited time' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('updated_at')
- expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
+ expect(json_response).to include('updated_at')
+ expect(json_response['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
end
end
@@ -520,10 +518,8 @@ describe Projects::IssuesController do
it 'returns the necessary data' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('title_text', 'description', 'description_text')
- expect(data).to include('task_status', 'lock_version')
+ expect(json_response).to include('title_text', 'description', 'description_text')
+ expect(json_response).to include('task_status', 'lock_version')
end
end
end
@@ -692,9 +688,7 @@ describe Projects::IssuesController do
update_issue(issue_params: { assignee_ids: [assignee.id] })
- body = JSON.parse(response.body)
-
- expect(body['assignees'].first.keys)
+ expect(json_response['assignees'].first.keys)
.to match_array(%w(id name username avatar_url state web_url))
end
end
@@ -1314,7 +1308,7 @@ describe Projects::IssuesController do
it 'filters notes that the user should not see' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
- expect(JSON.parse(response.body).count).to eq(1)
+ expect(json_response.count).to eq(1)
end
it 'does not result in N+1 queries' do
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 13a28b738ca..d940d226176 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -112,7 +112,7 @@ describe Projects::MergeRequests::DiffsController do
it 'only renders the diffs for the path given' do
diff_for_path(old_path: existing_path, new_path: existing_path)
- paths = JSON.parse(response.body)["diff_files"].map { |file| file['new_path'] }
+ paths = json_response["diff_files"].map { |file| file['new_path'] }
expect(paths).to include(existing_path)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index cc6adc0a6c6..f11880122b1 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -242,9 +242,7 @@ describe Projects::MergeRequestsController do
update_merge_request({ assignee_ids: [assignee.id] }, format: :json)
- body = JSON.parse(response.body)
-
- expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
+ expect(json_response['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 1db1963476c..98aea9056dc 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -29,7 +29,7 @@ describe Projects::NotesController do
}
end
- let(:parsed_response) { JSON.parse(response.body).with_indifferent_access }
+ let(:parsed_response) { json_response.with_indifferent_access }
let(:note_json) { parsed_response[:notes].first }
before do
@@ -614,7 +614,7 @@ describe Projects::NotesController do
it "returns the name of the resolving user" do
post :resolve, params: request_params.merge(html: true)
- expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name)
+ expect(json_response["resolved_by"]).to eq(user.name)
end
it "returns status 200" do
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 9e7d34b10c0..d5ef2b0e114 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -7,7 +7,6 @@ describe Projects::TemplatesController do
let(:user) { create(:user) }
let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' }
let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' }
- let(:body) { JSON.parse(response.body) }
let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') }
let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') }
@@ -17,8 +16,8 @@ describe Projects::TemplatesController do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
- expect(body['name']).to eq('issue_template')
- expect(body['content']).to eq('issue content')
+ expect(json_response['name']).to eq('issue_template')
+ expect(json_response['content']).to eq('issue content')
end
end
@@ -27,8 +26,8 @@ describe Projects::TemplatesController do
get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
- expect(body['name']).to eq('merge_request_template')
- expect(body['content']).to eq('merge request content')
+ expect(json_response['name']).to eq('merge_request_template')
+ expect(json_response['content']).to eq('merge request content')
end
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index f2e0b5e5c1d..a7e5a79b51d 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -103,7 +103,7 @@ describe Projects::WikisController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4e1cac67d23..083a1c1383a 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -740,20 +740,18 @@ describe ProjectsController do
it 'gets a list of branches and tags' do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['Branches']).to include('master')
- expect(parsed_body['Tags'].first).to eq('v1.1.0')
- expect(parsed_body['Tags'].last).to eq('v1.0.0')
- expect(parsed_body['Commits']).to be_nil
+ expect(json_response['Branches']).to include('master')
+ expect(json_response['Tags'].first).to eq('v1.1.0')
+ expect(json_response['Tags'].last).to eq('v1.0.0')
+ expect(json_response['Commits']).to be_nil
end
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
context "when preferred language is Japanese" do
@@ -765,10 +763,9 @@ describe ProjectsController do
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
end
@@ -797,7 +794,7 @@ describe ProjectsController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
context 'when not authorized' do
@@ -821,8 +818,6 @@ describe ProjectsController do
text: issue.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\##{issue.iid} \(closed\)/)
end
@@ -833,8 +828,6 @@ describe ProjectsController do
text: merge_request.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\!#{merge_request.iid} \(closed\)/)
end
end
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 586d59c2d09..652533ac49f 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -26,7 +26,7 @@ describe Snippets::NotesController do
end
it "returns not empty array of notes" do
- expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
+ expect(json_response["notes"].empty?).to be_falsey
end
end
@@ -97,7 +97,7 @@ describe Snippets::NotesController do
it "returns 1 note" do
get :index, params: { snippet_id: private_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(1)
+ expect(json_response['notes'].count).to eq(1)
end
end
end
@@ -114,7 +114,7 @@ describe Snippets::NotesController do
it "does not return any note" do
get :index, params: { snippet_id: public_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(0)
+ expect(json_response['notes'].count).to eq(0)
end
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 3aba02bf3ff..b0092bc8994 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -622,7 +622,7 @@ describe SnippetsController do
post :preview_markdown, params: { id: snippet, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index c3d6ea9cbcd..8b8d4c57000 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -291,7 +291,7 @@ describe UsersController do
it 'response with snippets json data' do
get :snippets, params: { username: user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 743ec322885..7d7738a30c8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -306,34 +306,18 @@ FactoryBot.define do
factory :redmine_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_redmine_service(
- active: true,
- properties: {
- 'project_url' => 'http://redmine/projects/project_name_in_redmine',
- 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
- 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
- }
- )
- end
+ redmine_service
end
factory :youtrack_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_youtrack_service(
- active: true,
- properties: {
- 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
- 'issues_url' => 'http://youtrack/issues/:id'
- }
- )
- end
+ youtrack_service
end
factory :jira_project, parent: :project do
has_external_issue_tracker true
+
jira_service
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index cd1d2c33373..daf842e3075 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -79,14 +79,12 @@ FactoryBot.define do
trait :issue_tracker do
properties(
project_url: 'http://issue-tracker.example.com',
- issues_url: 'http://issue-tracker.example.com',
+ issues_url: 'http://issue-tracker.example.com/issues/:id',
new_issue_url: 'http://issue-tracker.example.com'
)
end
- factory :jira_cloud_service, class: JiraService do
- project
- active true
+ trait :jira_cloud_service do
properties(
url: 'https://mysite.atlassian.net',
username: 'jira_user',
diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb
index 387e130a743..5a3639895b6 100644
--- a/spec/factories/services_data.rb
+++ b/spec/factories/services_data.rb
@@ -1,18 +1,12 @@
# frozen_string_literal: true
+# these factories should never be called directly, they are used when creating services
FactoryBot.define do
factory :jira_tracker_data do
service
- url 'http://jira.example.com'
- api_url 'http://api-jira.example.com'
- username 'jira_username'
- password 'jira_password'
end
factory :issue_tracker_data do
service
- project_url 'http://issuetracker.example.com'
- issues_url 'http://issues.example.com'
- new_issue_url 'http://new-issue.example.com'
end
end
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 7008b361394..e3bcaca737e 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -21,4 +21,22 @@ describe 'Mermaid rendering', :js do
expect(page).to have_selector('svg text', text: label)
end
end
+
+ it 'renders linebreaks in Mermaid diagrams' do
+ description = <<~MERMAID
+ ```mermaid
+ graph TD;
+ A(Line 1<br>Line 2)-->B(Line 1<br/>Line 2);
+ C(Line 1<br />Line 2)-->D(Line 1<br />Line 2);
+ ```
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ expected = '<text><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
+ expect(page.html.scan(expected).count).to be(4)
+ end
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 919859c145a..7c44680e9f7 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -66,7 +66,7 @@ describe 'Triggers', :js do
it 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
- create(:ci_trigger, owner: nil, project: @project)
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
@@ -127,17 +127,19 @@ describe 'Triggers', :js do
end
describe 'show triggers workflow' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
- it 'show "legacy" badge for legacy trigger' do
- create(:ci_trigger, owner: nil, project: @project)
+ it 'show "invalid" badge for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
- # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
- expect(page.find('.triggers-list')).to have_content 'legacy'
- expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ expect(page.find('.triggers-list')).to have_content 'invalid'
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
@@ -149,6 +151,19 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
+ it 'do not show "Edit" or full token for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ .update_attribute(:owner, nil)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger is non-editable
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
@@ -175,5 +190,56 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ it 'show "legacy" badge for legacy trigger' do
+ create(:ci_trigger, owner: nil, project: @project)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
+ expect(page.find('.triggers-list')).to have_content 'legacy'
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "invalid" badge for trigger with owner having insufficient permissions' do
+ create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
+ expect(page.find('.triggers-list')).to have_content 'invalid'
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'do not show "Edit" or full token for not owned trigger' do
+ # Create trigger with user different from current_user
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name doesn't match with current_user and trigger is non-editable
+ expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "Edit" and full token for owned trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger shows full token and has copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
+ expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name matches with current_user and is editable
+ expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+ end
end
end
diff --git a/spec/frontend/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js
index 6e41fdabdce..dcc6fa96d18 100644
--- a/spec/frontend/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/create_merge_request_dropdown_spec.js
@@ -1,6 +1,7 @@
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
+import confidentialState from '~/confidential_merge_request/state';
import { TEST_HOST } from './helpers/test_constants';
describe('CreateMergeRequestDropdown', () => {
@@ -66,4 +67,37 @@ describe('CreateMergeRequestDropdown', () => {
);
});
});
+
+ describe('enable', () => {
+ beforeEach(() => {
+ dropdown.createMergeRequestButton.classList.add('disabled');
+ });
+
+ afterEach(() => {
+ confidentialState.selectedProject = {};
+ });
+
+ it('enables button when not confidential issue', () => {
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('enables when can create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+ confidentialState.selectedProject = { name: 'test' };
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('does not enable when can not create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).toContain('disabled');
+ });
+ });
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index 068fa317a87..707eae34793 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,12 +1,14 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { GlDropdown } from '@gitlab/ui';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm;
-function factory(currentPath) {
+function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
+ ...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
@@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => {
.attributes('aria-current'),
).toEqual('page');
});
+
+ it('does not render add to tree dropdown when permissions are false', () => {
+ factory('/', { canCollaborate: false });
+
+ vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(false);
+ });
+
+ it('renders add to tree dropdown when permissions are true', () => {
+ factory('/', { canCollaborate: true });
+
+ vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(true);
+ });
});
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 919825a6102..e87440895e0 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::RedactorFilter do
+describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 7b855251a74..e3e6e22568c 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -22,8 +22,8 @@ describe Banzai::ObjectRenderer do
expect(object.user_visible_reference_count).to eq 0
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([object], :note)
end
@@ -82,8 +82,8 @@ describe Banzai::ObjectRenderer do
expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([cacheless_thing], :title)
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 7119c826bca..469692f7b5a 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -32,7 +32,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
@@ -61,7 +61,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'allows to use long external reference syntax for Redmine' do
@@ -70,7 +70,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 718649e0e10..a3b47c4d826 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Redactor do
+describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index ff002acbd35..5293732ead1 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -96,6 +96,75 @@ module Gitlab
end
end
+ context 'with passthrough' do
+ it 'removes non heading ids' do
+ input = <<~ADOC
+ ++++
+ <h2 id="foo">Title</h2>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <h2>Title</h2>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with section anchors' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ = Title
+
+ == First section
+
+ This is the first section.
+
+ == Second section
+
+ This is the second section.
+
+ == Thunder âš¡ !
+
+ This is the third section.
+ ADOC
+
+ output = <<~HTML
+ <h1>Title</h1>
+ <div>
+ <h2 id="user-content-first-section">
+ <a class="anchor" href="#user-content-first-section"></a>First section</h2>
+ <div>
+ <div>
+ <p>This is the first section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-second-section">
+ <a class="anchor" href="#user-content-second-section"></a>Second section</h2>
+ <div>
+ <div>
+ <p>This is the second section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-thunder">
+ <a class="anchor" href="#user-content-thunder"></a>Thunder âš¡ !</h2>
+ <div>
+ <div>
+ <p>This is the third section.</p>
+ </div>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
context 'with checklist' do
it 'preserves classes' do
input = <<~ADOC
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 7d750877d09..b3e58c3dfdb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -10,7 +10,11 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request)
+ project: project,
+ current_user: user,
+ origin_ref: origin_ref,
+ merge_request: merge_request,
+ trigger_request: trigger_request)
end
let(:step) { described_class.new(pipeline, command) }
@@ -18,6 +22,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:ref) { 'master' }
let(:origin_ref) { ref }
let(:merge_request) { nil }
+ let(:trigger_request) { nil }
shared_context 'detached merge request pipeline' do
let(:merge_request) do
@@ -69,6 +74,43 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
end
+ context 'when pipeline triggered by legacy trigger' do
+ let(:user) { nil }
+ let(:trigger_request) do
+ build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil))
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ step.perform!
+ end
+
+ it 'allows legacy triggers to create a pipeline' do
+ expect(pipeline).to be_valid
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to eq false
+ end
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is disabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ step.perform!
+ end
+
+ it 'prevents legacy triggers from creating a pipeline' do
+ expect(pipeline.errors.to_a).to include /Trigger token is invalid/
+ end
+
+ it 'breaks the pipeline builder chain' do
+ expect(step.break?).to eq true
+ end
+ end
+ end
+
describe '#allowed_to_create?' do
subject { step.allowed_to_create? }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 30ea3f3e28e..6ce4b321397 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -107,42 +107,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
expect(token.build.evaluate)
.to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
- it 'does not recognize the \ escape character for /' do
- scanner = StringScanner.new('/some .* \/ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
- end
-
- it 'does not recognize the \ escape character for $' do
- scanner = StringScanner.new('/some numeric \$ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
- end
- end
end
describe '#evaluate' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index d8db9c262a1..7c98e729b0b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -80,34 +80,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
it { is_expected.to eq(tokens) }
end
end
-
- context 'with the ci_variables_complex_expressions feature flag turned off' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'incorrectly tokenizes conjunctive match statements as one match statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'incorrectly tokenizes disjunctive match statements as one statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'raises an error about && operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
-
- it 'raises an error about || operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
- end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index a2c2e3653d5..b259ef711aa 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,6 +1,4 @@
-# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
-# require 'fast_spec_helper'
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -118,54 +116,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
expect(subject.evaluate).to eq(value)
end
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- '$PATH_VARIABLE =~ /path/variable/' | true
- '$PATH_VARIABLE =~ /path\/variable/' | true
- '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
- '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
- '$PRESENT_VARIABLE != "my variable"' | false
- '"my variable" != $PRESENT_VARIABLE' | false
- '$PRESENT_VARIABLE != null' | true
- '$EMPTY_VARIABLE != null' | true
- '"" != $EMPTY_VARIABLE' | false
- '$UNDEFINED_VARIABLE != null' | false
- 'null != $UNDEFINED_VARIABLE' | false
- "$PRESENT_VARIABLE !~ /var.*e$/" | false
- "$PRESENT_VARIABLE !~ /^var.*/" | true
- "$EMPTY_VARIABLE !~ /var.*/" | true
- "$UNDEFINED_VARIABLE !~ /var.*/" | true
- "$PRESENT_VARIABLE !~ /VAR.*/i" | false
- end
-
- with_them do
- let(:text) { expression }
-
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
- end
- end
- end
end
describe '#truthful?' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7991e2f48b5..46ea0d7554b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -1,32 +1,30 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:attributes) { { name: 'rspec', ref: 'master' } }
- let(:attributes) do
- { name: 'rspec', ref: 'master' }
- end
-
- subject do
- described_class.new(pipeline, attributes)
- end
+ let(:seed_build) { described_class.new(pipeline, attributes) }
describe '#attributes' do
- it 'returns hash attributes of a build' do
- expect(subject.attributes).to be_a Hash
- expect(subject.attributes)
- .to include(:name, :project, :ref)
- end
+ subject { seed_build.attributes }
+
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
end
describe '#bridge?' do
+ subject { seed_build.bridge? }
+
context 'when job is a bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it { is_expected.to be_bridge }
+ it { is_expected.to be_truthy }
end
context 'when trigger definition is empty' do
@@ -34,20 +32,20 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: '' } }
end
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
context 'when job is not a bridge' do
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
end
describe '#to_resource' do
+ subject { seed_build.to_resource }
+
context 'when job is not a bridge' do
- it 'returns a valid build resource' do
- expect(subject.to_resource).to be_a(::Ci::Build)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Build) }
+ it { is_expected.to be_valid }
end
context 'when job is a bridge' do
@@ -55,71 +53,117 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it 'returns a valid bridge resource' do
- expect(subject.to_resource).to be_a(::Ci::Bridge)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Bridge) }
+ it { is_expected.to be_valid }
end
it 'memoizes a resource object' do
- build = subject.to_resource
-
- expect(build.object_id).to eq subject.to_resource.object_id
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
end
it 'can not be persisted without explicit assignment' do
- build = subject.to_resource
-
pipeline.save!
- expect(build).not_to be_persisted
+ expect(subject).not_to be_persisted
end
end
- describe 'applying only/except policies' do
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
+
context 'when no branch policy is specified' do
- let(:attributes) { { name: 'rspec' } }
+ let(:attributes) do
+ { name: 'rspec' }
+ end
it { is_expected.to be_included }
end
context 'when branch policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ['deploy'] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch regexp policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -127,13 +171,29 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -141,50 +201,78 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'with source-keyword policy' do
using RSpec::Parameterized
- let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+ end
context 'matches' do
where(:keyword, :source) do
[
- %w(pushes push),
- %w(web web),
- %w(triggers trigger),
- %w(schedules schedule),
- %w(api api),
- %w(external external)
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
]
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -193,29 +281,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'non-matches' do
where(:keyword, :source) do
- %w(web trigger schedule api external).map { |source| ['pushes', source] } +
- %w(push trigger schedule api external).map { |source| ['web', source] } +
- %w(push web schedule api external).map { |source| ['triggers', source] } +
- %w(push web trigger api external).map { |source| ['schedules', source] } +
- %w(push web trigger schedule external).map { |source| ['api', source] } +
- %w(push web trigger schedule api).map { |source| ['external', source] }
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -239,12 +337,24 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.not_to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: ["branches@#{pipeline.project_full_path}"] },
+ except: { refs: ["branches@#{pipeline.project_full_path}"] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when repository path does not matches' do
context 'when using only' do
let(:attributes) do
- { name: 'rspec', only: { refs: ['branches@fork'] } }
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
end
it { is_expected.not_to be_included }
@@ -252,11 +362,23 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when using except' do
let(:attributes) do
- { name: 'rspec', except: { refs: ['branches@fork'] } }
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
end
end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index eed233f1f3e..b8debee3b58 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -171,17 +171,6 @@ describe Gitlab::GitalyClient do
end
end
end
-
- context 'when catfile-cache feature is disabled' do
- before do
- stub_feature_flags({ 'gitaly_catfile-cache': false })
- end
-
- it 'does not set the gitaly-session-id in the metadata' do
- results = described_class.request_kwargs('default', nil)
- expect(results[:metadata]).not_to include('gitaly-session-id')
- end
- end
end
describe 'enforce_gitaly_request_limits?' do
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 8be074f4b9b..c0b97486eeb 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6630,8 +6630,16 @@
"id": 123,
"token": "cdbfasdf44a5958c83654733449e585",
"project_id": 5,
+ "owner_id": 1,
"created_at": "2017-01-16T15:25:28.637Z",
"updated_at": "2017-01-16T15:25:28.637Z"
+ },
+ {
+ "id": 456,
+ "token": "33a66349b5ad01fc00174af87804e40",
+ "project_id": 5,
+ "created_at": "2017-01-16T15:25:29.637Z",
+ "updated_at": "2017-01-16T15:25:29.637Z"
}
],
"deploy_keys": [],
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index ca46006ea58..e6ce3f1bcea 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -32,6 +32,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'JSON' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'restores models based on JSON' do
expect(@restored_project_json).to be_truthy
end
@@ -198,8 +202,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'tokens are regenerated' do
- it 'has a new CI trigger token' do
- expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ it 'has new CI trigger tokens' do
+ expect(Ci::Trigger.where(token: %w[cdbfasdf44a5958c83654733449e585 33a66349b5ad01fc00174af87804e40]))
+ .to be_empty
end
it 'has a new CI build token' do
@@ -212,7 +217,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(@project.merge_requests.size).to eq(9)
end
- it 'has the correct number of triggers' do
+ it 'only restores valid triggers' do
expect(@project.triggers.size).to eq(1)
end
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
new file mode 100644
index 00000000000..38b4c22e186
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
+ context 'when redis_key is not defined' do
+ subject do
+ Class.new.extend(described_class)
+ end
+
+ describe '.increment' do
+ it 'raises a NotImplementedError exception' do
+ expect { subject.increment}.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '.total_count' do
+ it 'raises a NotImplementedError exception' do
+ expect { subject.total_count}.to raise_error(NotImplementedError)
+ end
+ end
+ end
+
+ context 'when redis_key is defined' do
+ subject do
+ counter_module = described_class
+
+ Class.new do
+ extend counter_module
+
+ def self.redis_counter_key
+ 'foo_redis_key'
+ end
+ end
+ end
+
+ describe '.increment' do
+ it 'increments the web ide commits counter by 1' do
+ expect do
+ subject.increment
+ end.to change { subject.total_count }.from(0).to(1)
+ end
+ end
+
+ describe '.total_count' do
+ it 'returns the total amount of web ide commits' do
+ subject.increment
+ subject.increment
+
+ expect(subject.total_count).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 67d49a30825..90a534de202 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::UsageData do
before do
create(:jira_service, project: projects[0])
create(:jira_service, project: projects[1])
- create(:jira_cloud_service, project: projects[2])
+ create(:jira_service, :jira_cloud_service, project: projects[2])
create(:prometheus_service, project: projects[1])
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
deleted file mode 100644
index c51889a1c63..00000000000
--- a/spec/lib/gitlab/web_ide_commits_counter_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
- describe '.increment' do
- it 'increments the web ide commits counter by 1' do
- expect do
- described_class.increment
- end.to change { described_class.total_count }.from(0).to(1)
- end
- end
-
- describe '.total_count' do
- it 'returns the total amount of web ide commits' do
- expect(described_class.total_count).to eq(0)
- end
- end
-end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index fde8375f2a5..5b5d6f51b33 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -54,19 +54,31 @@ describe Ci::Trigger do
end
describe '#can_access_project?' do
+ let(:owner) { create(:user) }
let(:trigger) { create(:ci_trigger, owner: owner, project: project) }
context 'when owner is blank' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
subject { trigger.can_access_project? }
- it { is_expected.to eq(true) }
+ it { is_expected.to eq(false) }
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ subject { trigger.can_access_project? }
+
+ it { is_expected.to eq(true) }
+ end
end
context 'when owner is set' do
- let(:owner) { create(:user) }
-
subject { trigger.can_access_project? }
context 'and is member of the project' do
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 4f0cd0efe9c..4abe45a2152 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
subject { gitlab_runner.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#install_command' do
@@ -156,4 +156,35 @@ describe Clusters::Applications::Runner do
end
end
end
+
+ describe '#prepare_uninstall' do
+ it 'pauses associated runner' do
+ active_runner = create(:ci_runner, contacted_at: 1.second.ago)
+
+ expect(active_runner.status).to eq(:online)
+
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner)
+ application_runner.prepare_uninstall
+
+ expect(active_runner.status).to eq(:paused)
+ end
+ end
+
+ describe '#make_uninstalling!' do
+ subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) }
+
+ it 'calls prepare_uninstall' do
+ expect_any_instance_of(described_class).to receive(:prepare_uninstall).and_call_original
+
+ subject.make_uninstalling!
+ end
+ end
+
+ describe '#post_uninstall' do
+ it 'destroys its runner' do
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: ci_runner)
+
+ expect { application_runner.post_uninstall }.to change { Ci::Runner.count }.by(-1)
+ end
+ end
end
diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb
new file mode 100644
index 00000000000..5685de6a9bf
--- /dev/null
+++ b/spec/models/concerns/stepable_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Stepable do
+ let(:described_class) do
+ Class.new do
+ include Stepable
+
+ steps :method1, :method2, :method3
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def method1
+ { status: :success }
+ end
+
+ def method2
+ return { status: :error } unless @pass
+
+ { status: :success, variable1: 'var1' }
+ end
+
+ def method3
+ { status: :success, variable2: 'var2' }
+ end
+ end
+ end
+
+ let(:prepended_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ prepended do
+ steps :appended_method1
+ end
+
+ private
+
+ def appended_method1
+ { status: :success }
+ end
+ end
+ end
+
+ before do
+ described_class.prepend(prepended_module)
+ end
+
+ it 'stops after the first error' do
+ expect(subject).not_to receive(:method3)
+ expect(subject).not_to receive(:appended_method1)
+
+ expect(subject.execute).to eq(
+ status: :error,
+ failed_step: :method2
+ )
+ end
+
+ context 'when all methods return success' do
+ before do
+ subject.instance_variable_set(:@pass, true)
+ end
+
+ it 'calls all methods in order' do
+ expect(subject).to receive(:method1).and_call_original.ordered
+ expect(subject).to receive(:method2).and_call_original.ordered
+ expect(subject).to receive(:method3).and_call_original.ordered
+ expect(subject).to receive(:appended_method1).and_call_original.ordered
+
+ subject.execute
+ end
+
+ it 'merges variables returned by all steps' do
+ expect(subject.execute).to eq(
+ status: :success,
+ variable1: 'var1',
+ variable2: 'var2'
+ )
+ end
+ end
+
+ context 'with multiple stepable classes' do
+ let(:other_class) do
+ Class.new do
+ include Stepable
+
+ steps :other_method1, :other_method2
+
+ private
+
+ def other_method1
+ { status: :success }
+ end
+
+ def other_method2
+ { status: :success }
+ end
+ end
+ end
+
+ it 'does not leak steps' do
+ expect(other_class.new.steps).to contain_exactly(:other_method1, :other_method2)
+ expect(subject.steps).to contain_exactly(:method1, :method2, :method3, :appended_method1)
+ end
+ end
+end
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index d8a63066265..e9a85890082 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -3,52 +3,24 @@ require 'spec_helper'
describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
- let(:policies) do
- described_class.new(user, trigger)
- end
-
- shared_examples 'allows to admin and manage trigger' do
- it 'does include ability to admin trigger' do
- expect(policies).to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'allows to manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'disallows to admin and manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does not include ability to manage trigger' do
- expect(policies).not_to be_allowed :manage_trigger
- end
- end
+ subject { described_class.new(user, trigger) }
describe '#rules' do
context 'when owner is undefined' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -56,35 +28,63 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
- context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ context 'when user is maintainer of the project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is developer of the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is not member of the project' do
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
end
end
context 'when owner is an user' do
- let(:owner) { user }
+ before do
+ trigger.update!(owner: user)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
end
end
context 'when owner is another user' do
- let(:owner) { create(:user) }
-
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -92,11 +92,13 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 3df5d9412f8..204e378f7be 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -281,7 +281,7 @@ describe API::Commits do
end
it 'does not increment the usage counters using access token authentication' do
- expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment)
+ expect(::Gitlab::UsageDataCounters::WebIdeCommitsCounter).not_to receive(:increment)
post api(url, user), params: valid_c_params
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 3e0b478abb3..8abdcaa2e0e 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -89,7 +89,7 @@ describe API::Search do
it 'returns empty array' do
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
@@ -356,7 +356,7 @@ describe API::Search do
it 'returns empty array' do
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 3f79e332b90..91cb8760a04 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -85,9 +85,7 @@ describe API::Services do
include_context service
# inject some properties into the service
- before do
- initialize_service(service)
- end
+ let!(:initialized_service) { initialize_service(service) }
it 'returns authentication error when unauthenticated' do
get api("/projects/#{project.id}/services/#{dashed_service}")
@@ -108,6 +106,15 @@ describe API::Services do
expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
+ it "returns empty hash if properties are empty" do
+ # deprecated services are not valid for update
+ initialized_service.update_attribute(:properties, {})
+ get api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['properties'].keys).to be_empty
+ end
+
it "returns error when authenticated but not a project owner" do
project.add_developer(user2)
get api("/projects/#{project.id}/services/#{dashed_service}", user2)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 0ad50e5347a..af2bee4563a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -448,6 +448,7 @@ describe API::Users do
it "returns 201 Created on success" do
post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(201)
end
@@ -643,6 +644,13 @@ describe API::Users do
describe "PUT /users/:id" do
let!(:admin_user) { create(:admin) }
+ it "returns 200 OK on success" do
+ put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it "updates user with new bio" do
put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 1781759c54b..dc25e4d808e 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1439,8 +1439,4 @@ describe 'Git LFS API and storage' do
post(url, params: params, headers: headers)
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 5b7b3d2fdd6..11436e5cd0c 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -163,8 +163,4 @@ describe 'Git LFS File Locking API' do
def do_get(url, params = nil, headers = nil)
get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 1bfb5602df2..cf84ec8fd4c 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -68,8 +68,8 @@ describe Boards::Issues::MoveService do
project.add_developer(user)
end
- it 'returns false if list of issues is empty' do
- expect(described_class.new(group, user, params).execute_multiple([])).to eq(false)
+ it 'returns the expected result if list of issues is empty' do
+ expect(described_class.new(group, user, params).execute_multiple([])).to eq({ count: 0, success: false, issues: [] })
end
context 'moving multiple issues' do
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index 9ab83d913f5..a948b442441 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -41,7 +41,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
end
end
- context 'when application is installing' do
+ context 'when application is uninstalling' do
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
context 'when installation POD succeeded' do
@@ -56,6 +56,12 @@ describe Clusters::Applications::CheckUninstallProgressService do
service.execute
end
+ it 'runs application post_uninstall' do
+ expect(application).to receive(:post_uninstall).and_call_original
+
+ service.execute
+ end
+
it 'destroys the application' do
expect(worker_class).not_to receive(:perform_in)
diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb
new file mode 100644
index 00000000000..d11e27c6d52
--- /dev/null
+++ b/spec/services/self_monitoring/project/create_service_spec.rb
@@ -0,0 +1,201 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SelfMonitoring::Project::CreateService do
+ describe '#execute' do
+ let(:result) { subject.execute }
+
+ let(:prometheus_settings) do
+ OpenStruct.new(
+ enable: true,
+ listen_address: 'localhost:9090'
+ )
+ end
+
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings)
+ end
+
+ context 'without admin users' do
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :error,
+ message: 'No active admin user found',
+ failed_step: :validate_admins
+ )
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { result[:project] }
+
+ let!(:user) { create(:user, :admin) }
+
+ before do
+ allow(ApplicationSetting)
+ .to receive(:current)
+ .and_return(
+ ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)
+ )
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ expect(result[:status]).to eq(:success)
+
+ prometheus = project.prometheus_service
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates project with internal visibility' do
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with internal visibility even when internal visibility is restricted' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with correct name and description' do
+ expect(result[:status]).to eq(:success)
+ expect(project.name).to eq(described_class::DEFAULT_NAME)
+ expect(project.description).to eq(described_class::DEFAULT_DESCRIPTION)
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
+ create(:user)
+
+ expect(result[:status]).to eq(:success)
+ expect(project.owner).to eq(user)
+ expect(project.members.collect(&:user)).to contain_exactly(user, admin1, admin2)
+ expect(project.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER
+ )
+ end
+
+ # This should pass when https://gitlab.com/gitlab-org/gitlab-ce/issues/44496
+ # is complete and the prometheus listen address is added to the whitelist.
+ # context 'when local requests from hooks and services are not allowed' do
+ # before do
+ # allow(ApplicationSetting)
+ # .to receive(:current)
+ # .and_return(
+ # ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: false)
+ # )
+ # end
+
+ # it_behaves_like 'has prometheus service', 'http://localhost:9090'
+ # end
+
+ context 'with non default prometheus address' do
+ before do
+ prometheus_settings.listen_address = 'https://localhost:9090'
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ before do
+ prometheus_settings.enable = false
+ end
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ before do
+ prometheus_settings.listen_address = ''
+ end
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when project cannot be created' do
+ let(:project) { build(:project) }
+
+ before do
+ project.errors.add(:base, "Test error")
+
+ expect_next_instance_of(::Projects::CreateService) do |project_create_service|
+ expect(project_create_service).to receive(:execute)
+ .and_return(project)
+ end
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq({
+ status: :error,
+ message: 'Could not create project',
+ failed_step: :create_project
+ })
+ end
+ end
+
+ context 'when user cannot be added to project' do
+ before do
+ subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)])
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq({
+ status: :error,
+ message: 'Could not add admins as members',
+ failed_step: :add_project_members
+ })
+ end
+ end
+
+ context 'when prometheus manual configuration cannot be saved' do
+ before do
+ prometheus_settings.listen_address = 'httpinvalid://localhost:9090'
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :error,
+ message: 'Could not save prometheus manual configuration',
+ failed_step: :add_prometheus_manual_configuration
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 02d310a9afa..0de92aedba5 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -6,7 +6,7 @@ end
shared_examples "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's feed token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}'], .js-rss-button[href*='feed_token=#{user.feed_token}']")
+ expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
end
end
@@ -18,6 +18,6 @@ end
shared_examples "it has an RSS button without a feed token" do
it "shows the RSS button without a feed token" do
- expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token']), .js-rss-button:not([href*='feed_token'])")
+ expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
end
end
diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb
index 210b0e6d867..43d8ab73dde 100644
--- a/spec/support/json_response.rb
+++ b/spec/support/json_response.rb
@@ -1,5 +1,5 @@
RSpec.configure do |config|
- config.include_context 'JSON response'
+ config.include_context 'JSON response', type: :controller
config.include_context 'JSON response', type: :request
config.include_context 'JSON response', :api
end
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 0acc9e2a836..f4b02dc5350 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -46,7 +46,7 @@ shared_examples 'issuable notes filter' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(false)
@@ -56,7 +56,7 @@ shared_examples 'issuable notes filter' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(true)
diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 64568de424e..4cb6d001b9b 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -38,7 +38,7 @@ shared_examples 'update invalid issuable' do |klass|
put :update, params: params
expect(response.status).to eq(409)
- expect(JSON.parse(response.body)).to have_key('errors')
+ expect(json_response).to have_key('errors')
end
end