summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flayignore2
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/behaviors/secret_values.js8
-rw-r--r--app/assets/javascripts/blob/file_template_mediator.js2
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js205
-rw-r--r--app/assets/javascripts/ci_variable_list/native_form_variable_list.js26
-rw-r--r--app/assets/javascripts/commons/polyfills.js2
-rw-r--r--app/assets/javascripts/dispatcher.js52
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js2
-rw-r--r--app/assets/javascripts/issue.js40
-rw-r--r--app/assets/javascripts/job.js25
-rw-r--r--app/assets/javascripts/labels_select.js162
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js32
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js18
-rw-r--r--app/assets/javascripts/main.js4
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_service.js14
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js19
-rw-r--r--app/assets/javascripts/merge_request_tabs.js47
-rw-r--r--app/assets/javascripts/milestone.js18
-rw-r--r--app/assets/javascripts/milestone_select.js119
-rw-r--r--app/assets/javascripts/notes.js22
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js17
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/index/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/boards/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/project.js19
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js4
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js4
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js12
-rw-r--r--app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js7
-rw-r--r--app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js73
-rw-r--r--app/assets/javascripts/preview_markdown.js30
-rw-r--r--app/assets/javascripts/toggle_buttons.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/buttons.scss5
-rw-r--r--app/assets/stylesheets/framework/ci_variable_list.scss88
-rw-r--r--app/assets/stylesheets/framework/gfm.scss28
-rw-r--r--app/assets/stylesheets/framework/secondary-navigation-elements.scss7
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss81
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb5
-rw-r--r--app/controllers/admin/cohorts_controller.rb2
-rw-r--r--app/controllers/admin/services_controller.rb5
-rw-r--r--app/controllers/application_controller.rb23
-rw-r--r--app/controllers/boards/issues_controller.rb6
-rw-r--r--app/controllers/concerns/enforces_two_factor_authentication.rb6
-rw-r--r--app/controllers/concerns/requires_whitelisted_monitoring_client.rb4
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb5
-rw-r--r--app/controllers/invites_controller.rb2
-rw-r--r--app/controllers/koding_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb3
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/passwords_controller.rb4
-rw-r--r--app/controllers/projects/commits_controller.rb5
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb5
-rw-r--r--app/controllers/projects/forks_controller.rb5
-rw-r--r--app/controllers/projects/issues_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/projects/network_controller.rb5
-rw-r--r--app/controllers/projects/notes_controller.rb5
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb7
-rw-r--r--app/controllers/registrations_controller.rb6
-rw-r--r--app/controllers/root_controller.rb6
-rw-r--r--app/controllers/user_callouts_controller.rb23
-rw-r--r--app/helpers/application_settings_helper.rb14
-rw-r--r--app/helpers/auth_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb10
-rw-r--r--app/helpers/user_callouts_helper.rb14
-rw-r--r--app/helpers/version_check_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb4
-rw-r--r--app/helpers/webpack_helper.rb18
-rw-r--r--app/mailers/abuse_report_mailer.rb6
-rw-r--r--app/mailers/base_mailer.rb4
-rw-r--r--app/models/clusters/applications/ingress.rb6
-rw-r--r--app/models/clusters/platforms/kubernetes.rb3
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb16
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/issue_assignee.rb2
-rw-r--r--app/models/key.rb12
-rw-r--r--app/models/namespace.rb19
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/project.rb26
-rw-r--r--app/models/project_services/kubernetes_service.rb3
-rw-r--r--app/models/protected_branch.rb6
-rw-r--r--app/models/repository.rb14
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb19
-rw-r--r--app/models/user_callout.rb13
-rw-r--r--app/services/akismet_service.rb6
-rw-r--r--app/services/auth/container_registry_authentication_service.rb4
-rw-r--r--app/services/base_service.rb1
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/services/git_push_service.rb1
-rw-r--r--app/services/gravatar_service.rb4
-rw-r--r--app/services/merge_requests/build_service.rb30
-rw-r--r--app/services/merge_requests/refresh_service.rb7
-rw-r--r--app/services/projects/housekeeping_service.rb10
-rw-r--r--app/services/projects/update_pages_service.rb4
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/submit_usage_ping_service.rb4
-rw-r--r--app/services/system_note_service.rb26
-rw-r--r--app/services/upload_service.rb4
-rw-r--r--app/services/users/build_service.rb6
-rw-r--r--app/views/admin/broadcast_messages/preview.js.haml1
-rw-r--r--app/views/admin/conversational_development_index/show.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/health_check/show.html.haml8
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/ci/variables/_variable_row.html.haml49
-rw-r--r--app/views/dashboard/_groups_head.html.haml2
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/projects/_nav.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/devise/confirmations/almost_there.haml4
-rw-r--r--app/views/help/index.html.haml8
-rw-r--r--app/views/koding/index.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/devise.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/notify/_note_email.html.haml2
-rw-r--r--app/views/notify/_note_email.text.erb2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/notify/new_user_email.html.haml2
-rw-r--r--app/views/projects/_export.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml16
-rw-r--r--app/views/projects/pipeline_schedules/_tabs.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_variable_row.html.haml17
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml4
-rw-r--r--app/views/shared/_event_filter.html.haml25
-rw-r--r--app/views/shared/_milestones_filter.html.haml2
-rw-r--r--app/views/shared/builds/_tabs.html.haml2
-rw-r--r--app/views/shared/issuable/_nav.html.haml2
-rw-r--r--app/views/snippets/_snippets_scope_menu.html.haml2
-rw-r--r--app/workers/git_garbage_collect_worker.rb3
-rw-r--r--changelogs/unreleased/24167__color_label.yml5
-rw-r--r--changelogs/unreleased/25327-coverage-badge-rounding.yml5
-rw-r--r--changelogs/unreleased/32283-trending-projects-unique-constraint2.yml5
-rw-r--r--changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml5
-rw-r--r--changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml5
-rw-r--r--changelogs/unreleased/40793-fix-mr-title-for-jira.yml5
-rw-r--r--changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml6
-rw-r--r--changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml5
-rw-r--r--changelogs/unreleased/fix-adjust-button-group-width-on-mobile.yml5
-rw-r--r--changelogs/unreleased/issue-42689-new-file-template.yml5
-rw-r--r--changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml5
-rw-r--r--changelogs/unreleased/osw-system-notes-for-commits-regression.yml5
-rw-r--r--changelogs/unreleased/persistent-callouts.yml5
-rw-r--r--changelogs/unreleased/query-counts.yml5
-rw-r--r--changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml5
-rw-r--r--config/initializers/query_limiting.rb9
-rw-r--r--config/routes.rb3
-rw-r--r--config/webpack.config.js23
-rw-r--r--db/migrate/20180125214301_create_user_callouts.rb16
-rw-r--r--db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb19
-rw-r--r--db/post_migrate/20171207150300_remove_project_labels_group_id_copy.rb21
-rw-r--r--db/post_migrate/20180202111106_remove_project_labels_group_id.rb19
-rw-r--r--db/schema.rb13
-rw-r--r--doc/administration/environment_variables.md2
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/query_count_limits.md65
-rw-r--r--doc/user/markdown.md41
-rw-r--r--doc/user/project/import/github.md2
-rw-r--r--lib/api/api_guard.rb6
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/helpers/runner.rb6
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/api/pipelines.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/api/v3/branches.rb2
-rw-r--r--lib/api/v3/issues.rb6
-rw-r--r--lib/api/v3/merge_requests.rb4
-rw-r--r--lib/api/v3/pipelines.rb2
-rw-r--r--lib/api/v3/triggers.rb2
-rw-r--r--lib/banzai/color_parser.rb44
-rw-r--r--lib/banzai/filter/color_filter.rb31
-rw-r--r--lib/banzai/pipeline/broadcast_message_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/email_template_interceptor.rb4
-rw-r--r--lib/gitlab/asciidoc.rb8
-rw-r--r--lib/gitlab/auth.rb6
-rw-r--r--lib/gitlab/badge/coverage/report.rb2
-rw-r--r--lib/gitlab/badge/coverage/template.rb4
-rw-r--r--lib/gitlab/current_settings.rb108
-rw-r--r--lib/gitlab/git/branch.rb2
-rw-r--r--lib/gitlab/git/repository.rb105
-rw-r--r--lib/gitlab/git/tag.rb2
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb26
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb33
-rw-r--r--lib/gitlab/gon_helper.rb5
-rw-r--r--lib/gitlab/import_export/relation_factory.rb5
-rw-r--r--lib/gitlab/kubernetes/helm/pod.rb8
-rw-r--r--lib/gitlab/legacy_github_import/project_creator.rb4
-rw-r--r--lib/gitlab/metrics/prometheus.rb3
-rw-r--r--lib/gitlab/middleware/go.rb3
-rw-r--r--lib/gitlab/performance_bar.rb4
-rw-r--r--lib/gitlab/polling_interval.rb6
-rw-r--r--lib/gitlab/protocol_access.rb6
-rw-r--r--lib/gitlab/query_limiting.rb36
-rw-r--r--lib/gitlab/query_limiting/active_support_subscriber.rb11
-rw-r--r--lib/gitlab/query_limiting/middleware.rb55
-rw-r--r--lib/gitlab/query_limiting/transaction.rb83
-rw-r--r--lib/gitlab/recaptcha.rb10
-rw-r--r--lib/gitlab/sentry.rb4
-rw-r--r--lib/gitlab/ssh_public_key.rb28
-rw-r--r--lib/gitlab/usage_data.rb8
-rw-r--r--lib/gitlab/visibility_level.rb7
-rw-r--r--lib/tasks/flay.rake2
-rw-r--r--package.json3
-rw-r--r--spec/controllers/health_check_controller_spec.rb2
-rw-r--r--spec/controllers/health_controller_spec.rb2
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb3
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb49
-rw-r--r--spec/factories/keys.rb4
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/factories/user_callouts.rb7
-rw-r--r--spec/features/admin/admin_health_check_spec.rb4
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb12
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/markdown_spec.rb8
-rw-r--r--spec/features/projects/badges/coverage_spec.rb4
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb55
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb30
-rw-r--r--spec/fixtures/markdown.md.erb12
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb47
-rw-r--r--spec/helpers/version_check_helper_spec.rb4
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js163
-rw-r--r--spec/javascripts/ci_variable_list/native_form_variable_list_spec.js30
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb43
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js13
-rw-r--r--spec/javascripts/issue_spec.js127
-rw-r--r--spec/javascripts/job_spec.js252
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js43
-rw-r--r--spec/javascripts/lib/utils/ajax_cache_spec.js68
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js14
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js96
-rw-r--r--spec/javascripts/notes_spec.js186
-rw-r--r--spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js145
-rw-r--r--spec/lib/banzai/color_parser_spec.rb90
-rw-r--r--spec/lib/banzai/filter/color_filter_spec.rb61
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb18
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb40
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb27
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb49
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb4
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb19
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb72
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb144
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb65
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb35
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb6
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb9
-rw-r--r--spec/migrations/remove_project_labels_group_id_spec.rb21
-rw-r--r--spec/models/key_spec.rb58
-rw-r--r--spec/models/namespace_spec.rb20
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/project_spec.rb32
-rw-r--r--spec/models/repository_spec.rb61
-rw-r--r--spec/models/user_callout_spec.rb16
-rw-r--r--spec/models/user_spec.rb6
-rw-r--r--spec/requests/api/groups_spec.rb15
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb58
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb54
-rw-r--r--spec/services/system_note_service_spec.rb33
-rw-r--r--spec/support/matchers/markdown_matchers.rb21
-rw-r--r--spec/support/stored_repositories.rb4
-rw-r--r--spec/support/stub_env.rb2
-rw-r--r--spec/support/unique_ip_check_shared_examples.rb6
-rw-r--r--vendor/ingress/values.yaml8
-rw-r--r--vendor/prometheus/values.yaml174
-rw-r--r--yarn.lock2
295 files changed, 3964 insertions, 1709 deletions
diff --git a/.flayignore b/.flayignore
index acac0ce14c9..87cb3507b05 100644
--- a/.flayignore
+++ b/.flayignore
@@ -6,3 +6,5 @@ app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb
+lib/gitlab/background_migration/*
+app/models/project_services/kubernetes_service.rb
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b4afa953175..9c3556f5cce 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -627,6 +627,8 @@ codequality:
sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
+ variables:
+ CONFIDENCE_LEVEL: 2
before_script: []
script:
- /app/bin/run .
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b366ae6f069..ed56da0353d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,6 +50,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
## Contribute to GitLab
+For a first-time step-by-step guide to the contribution process, see
+["Contributing to GitLab"](https://about.gitlab.com/contributing/).
+
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4a7076db09a..bd14e8533ef 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.77.0
+0.78.0
diff --git a/Gemfile b/Gemfile
index 7bffae06b51..c8e7a970769 100644
--- a/Gemfile
+++ b/Gemfile
@@ -349,7 +349,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
- gem 'flay', '~> 2.8.0', require: false
+ gem 'flay', '~> 2.10.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 2ddf8221a06..cf9f160499d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -211,7 +211,7 @@ GEM
fast_gettext (1.4.0)
ffaker (2.4.0)
ffi (1.9.18)
- flay (2.8.1)
+ flay (2.10.0)
erubis (~> 2.7.0)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
@@ -588,7 +588,7 @@ GEM
ast (~> 2.3)
parslet (1.5.0)
blankslate (~> 2.0)
- path_expander (1.0.1)
+ path_expander (1.0.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
@@ -1037,7 +1037,7 @@ DEPENDENCIES
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
- flay (~> 2.8.0)
+ flay (~> 2.10.0)
flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0)
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
index 7f70fce913a..0d6e0dbefcc 100644
--- a/app/assets/javascripts/behaviors/secret_values.js
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -15,10 +15,12 @@ export default class SecretValues {
init() {
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
- const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
- this.updateDom(isRevealed);
+ if (this.revealButton) {
+ const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
+ this.updateDom(isRevealed);
- this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
+ }
}
onRevealButtonClicked() {
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index 583e5faa506..37074301b51 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -235,7 +235,7 @@ export default class FileTemplateMediator {
}
setFilename(name) {
- this.$filenameInput.val(name);
+ this.$filenameInput.val(name).trigger('change');
}
getSelected() {
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
new file mode 100644
index 00000000000..e46478ddb98
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -0,0 +1,205 @@
+import $ from 'jquery';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
+import { s__ } from '../locale';
+import setupToggleButtons from '../toggle_buttons';
+import CreateItemDropdown from '../create_item_dropdown';
+import SecretValues from '../behaviors/secret_values';
+
+const ALL_ENVIRONMENTS_STRING = s__('CiVariable|All environments');
+
+function createEnvironmentItem(value) {
+ return {
+ title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
+ id: value,
+ text: value,
+ };
+}
+
+export default class VariableList {
+ constructor({
+ container,
+ formField,
+ }) {
+ this.$container = $(container);
+ this.formField = formField;
+ this.environmentDropdownMap = new WeakMap();
+
+ this.inputMap = {
+ id: {
+ selector: '.js-ci-variable-input-id',
+ default: '',
+ },
+ key: {
+ selector: '.js-ci-variable-input-key',
+ default: '',
+ },
+ value: {
+ selector: '.js-ci-variable-input-value',
+ default: '',
+ },
+ protected: {
+ selector: '.js-ci-variable-input-protected',
+ default: 'true',
+ },
+ environment: {
+ // We can't use a `.js-` class here because
+ // gl_dropdown replaces the <input> and doesn't copy over the class
+ // See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458
+ selector: `input[name="${this.formField}[variables_attributes][][environment]"]`,
+ default: '*',
+ },
+ _destroy: {
+ selector: '.js-ci-variable-input-destroy',
+ default: '',
+ },
+ };
+
+ this.secretValues = new SecretValues({
+ container: this.$container[0],
+ valueSelector: '.js-row:not(:last-child) .js-secret-value',
+ placeholderSelector: '.js-row:not(:last-child) .js-secret-value-placeholder',
+ });
+ }
+
+ init() {
+ this.bindEvents();
+ this.secretValues.init();
+ }
+
+ bindEvents() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ this.initRow(rowEl);
+ });
+
+ this.$container.on('click', '.js-row-remove-button', (e) => {
+ e.preventDefault();
+ this.removeRow($(e.currentTarget).closest('.js-row'));
+ });
+
+ const inputSelector = Object.keys(this.inputMap)
+ .map(name => this.inputMap[name].selector)
+ .join(',');
+
+ // Remove any empty rows except the last row
+ this.$container.on('blur', inputSelector, (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+
+ if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
+ this.removeRow($row);
+ }
+ });
+
+ // Always make sure there is an empty last row
+ this.$container.on('input trigger-change', inputSelector, () => {
+ const $lastRow = this.$container.find('.js-row').last();
+
+ if (this.checkIfRowTouched($lastRow)) {
+ this.insertRow($lastRow);
+ }
+ });
+ }
+
+ initRow(rowEl) {
+ const $row = $(rowEl);
+
+ setupToggleButtons($row[0]);
+
+ const $environmentSelect = $row.find('.js-variable-environment-toggle');
+ if ($environmentSelect.length) {
+ const createItemDropdown = new CreateItemDropdown({
+ $dropdown: $environmentSelect,
+ defaultToggleLabel: ALL_ENVIRONMENTS_STRING,
+ fieldName: `${this.formField}[variables_attributes][][environment]`,
+ getData: (term, callback) => callback(this.getEnvironmentValues()),
+ createNewItemFromValue: createEnvironmentItem,
+ onSelect: () => {
+ // Refresh the other dropdowns in the variable list
+ // so they have the new value we just picked
+ this.refreshDropdownData();
+
+ $row.find(this.inputMap.environment.selector).trigger('trigger-change');
+ },
+ });
+
+ // Clear out any data that might have been left-over from the row clone
+ createItemDropdown.clearDropdown();
+
+ this.environmentDropdownMap.set($row[0], createItemDropdown);
+ }
+ }
+
+ insertRow($row) {
+ const $rowClone = $row.clone();
+ $rowClone.removeAttr('data-is-persisted');
+
+ // Reset the inputs to their defaults
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ $rowClone.find(entry.selector).val(entry.default);
+ });
+
+ this.initRow($rowClone);
+
+ $row.after($rowClone);
+ }
+
+ removeRow($row) {
+ const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
+
+ if (isPersisted) {
+ $row.hide();
+ $row
+ // eslint-disable-next-line no-underscore-dangle
+ .find(this.inputMap._destroy.selector)
+ .val(true);
+ } else {
+ $row.remove();
+ }
+ }
+
+ checkIfRowTouched($row) {
+ return Object.keys(this.inputMap).some((name) => {
+ const entry = this.inputMap[name];
+ const $el = $row.find(entry.selector);
+ return $el.length && $el.val() !== entry.default;
+ });
+ }
+
+ getAllData() {
+ // Ignore the last empty row because we don't want to try persist
+ // a blank variable and run into validation problems.
+ const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
+
+ return validRows.map((rowEl) => {
+ const resultant = {};
+ Object.keys(this.inputMap).forEach((name) => {
+ const entry = this.inputMap[name];
+ const $input = $(rowEl).find(entry.selector);
+ if ($input.length) {
+ resultant[name] = $input.val();
+ }
+ });
+
+ return resultant;
+ });
+ }
+
+ getEnvironmentValues() {
+ const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
+ .reduce((prevValueMap, envInput) => ({
+ ...prevValueMap,
+ [envInput.value]: envInput.value,
+ }), {});
+
+ return Object.keys(valueMap).map(createEnvironmentItem);
+ }
+
+ refreshDropdownData() {
+ this.$container.find('.js-row').each((index, rowEl) => {
+ const environmentDropdown = this.environmentDropdownMap.get(rowEl);
+ if (environmentDropdown) {
+ environmentDropdown.refreshData();
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
new file mode 100644
index 00000000000..d54ea7df1c3
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -0,0 +1,26 @@
+import VariableList from './ci_variable_list';
+
+// Used for the variable list on scheduled pipeline edit page
+export default function setupNativeFormVariableList({
+ container,
+ formField = 'variables',
+}) {
+ const $container = $(container);
+
+ const variableList = new VariableList({
+ container: $container,
+ formField,
+ });
+ variableList.init();
+
+ // Clear out the names in the empty last row so it
+ // doesn't get submitted and throw validation errors
+ $container.closest('form').on('submit trigger-submit', () => {
+ const $lastRow = $container.find('.js-row').last();
+
+ const isTouched = variableList.checkIfRowTouched($lastRow);
+ if (!isTouched) {
+ $lastRow.find('input, textarea').attr('name', '');
+ }
+ });
+}
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index ff9e4485916..46232726510 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,8 @@ import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
import 'core-js/fn/symbol';
+import 'core-js/es6/map';
+import 'core-js/es6/weak-map';
// Browser polyfills
import 'classlist-polyfill';
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 262ed3783fb..ab28b7d8d44 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -12,9 +12,9 @@ import ShortcutsIssuable from './shortcuts_issuable';
import Diff from './diff';
import SearchAutocomplete from './search_autocomplete';
-(function() {
- var Dispatcher;
+var Dispatcher;
+(function() {
Dispatcher = (function() {
function Dispatcher() {
this.initSearch();
@@ -49,46 +49,16 @@ import SearchAutocomplete from './search_autocomplete';
});
switch (page) {
- case 'sessions:new':
- import('./pages/sessions/new')
- .then(callDefault)
- .catch(fail);
- break;
- case 'projects:boards:show':
- case 'projects:boards:index':
- import('./pages/projects/boards/index')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:environments:metrics':
import('./pages/projects/environments/metrics')
.then(callDefault)
.catch(fail);
break;
case 'projects:merge_requests:index':
- import('./pages/projects/merge_requests/index')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:issues:index':
- import('./pages/projects/issues/index')
- .then(callDefault)
- .catch(fail);
- shortcut_handler = true;
- break;
case 'projects:issues:show':
- import('./pages/projects/issues/show')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
- case 'dashboard:milestones:index':
- import('./pages/dashboard/milestones/index')
- .then(callDefault)
- .catch(fail);
- break;
case 'projects:milestones:index':
import('./pages/projects/milestones/index')
.then(callDefault)
@@ -318,9 +288,6 @@ import SearchAutocomplete from './search_autocomplete';
shortcut_handler = true;
break;
case 'projects:show':
- import('./pages/projects/show')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'projects:edit':
@@ -352,9 +319,6 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'groups:show':
- import('./pages/groups/show')
- .then(callDefault)
- .catch(fail);
shortcut_handler = true;
break;
case 'groups:group_members:index':
@@ -363,7 +327,7 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'projects:project_members:index':
- import('./pages/projects/project_members/')
+ import('./pages/projects/project_members')
.then(callDefault)
.catch(fail);
break;
@@ -605,7 +569,7 @@ import SearchAutocomplete from './search_autocomplete';
}
break;
case 'profiles':
- import('./pages/profiles/index/')
+ import('./pages/profiles/index')
.then(callDefault)
.catch(fail);
break;
@@ -662,8 +626,8 @@ import SearchAutocomplete from './search_autocomplete';
return Dispatcher;
})();
+})();
- $(window).on('load', function() {
- new Dispatcher();
- });
-}).call(window);
+export default function initDispatcher() {
+ return new Dispatcher();
+}
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index df20e1e9c88..57a1fa107e5 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -461,7 +461,7 @@ class GfmAutoComplete {
const accentAChar = decodeURI('%C3%80');
const accentYChar = decodeURI('%C3%BF');
- const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
+ const regexp = new RegExp(`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, 'gi');
return regexp.exec(targetSubtext);
}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 411c820cc43..ff65ea99e9a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,7 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
import 'vendor/jquery.waitforimages';
+import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
-import Flash from './flash';
+import flash from './flash';
import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
@@ -42,12 +43,8 @@ export default class Issue {
this.disableCloseReopenButton($button);
url = $button.attr('href');
- return $.ajax({
- type: 'PUT',
- url: url
- })
- .fail(() => new Flash(issueFailMessage))
- .done((data) => {
+ return axios.put(url)
+ .then(({ data }) => {
const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
@@ -74,9 +71,10 @@ export default class Issue {
}
}
} else {
- new Flash(issueFailMessage);
+ flash(issueFailMessage);
}
})
+ .catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
@@ -115,24 +113,22 @@ export default class Issue {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
- return $.getJSON($container.data('url')).fail(function() {
- return new Flash('Failed to load referenced merge requests');
- }).done(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
+ return axios.get($container.data('url'))
+ .then(({ data }) => {
+ if ('html' in data) {
+ $container.html(data.html);
+ }
+ }).catch(() => flash('Failed to load referenced merge requests'));
}
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
- return $.getJSON($container.data('url')).fail(function() {
- return new Flash('Failed to load related branches');
- }).done(function(data) {
- if ('html' in data) {
- return $container.html(data.html);
- }
- });
+ return axios.get($container.data('url'))
+ .then(({ data }) => {
+ if ('html' in data) {
+ $container.html(data.html);
+ }
+ }).catch(() => flash('Failed to load related branches'));
}
}
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 9b5092c5e3f..d0b7ea75082 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { numberToHumanSize } from './lib/utils/number_utils';
@@ -8,6 +9,7 @@ export default class Job {
constructor(options) {
this.timeout = null;
this.state = null;
+ this.fetchingStatusFavicon = false;
this.options = options || $('.js-build-options').data();
this.pagePath = this.options.pagePath;
@@ -171,12 +173,23 @@ export default class Job {
}
getBuildTrace() {
- return $.ajax({
- url: `${this.pagePath}/trace.json`,
- data: { state: this.state },
+ return axios.get(`${this.pagePath}/trace.json`, {
+ params: { state: this.state },
})
- .done((log) => {
- setCiStatusFavicon(`${this.pagePath}/status.json`);
+ .then((res) => {
+ const log = res.data;
+
+ if (!this.fetchingStatusFavicon) {
+ this.fetchingStatusFavicon = true;
+
+ setCiStatusFavicon(`${this.pagePath}/status.json`)
+ .then(() => {
+ this.fetchingStatusFavicon = false;
+ })
+ .catch(() => {
+ this.fetchingStatusFavicon = false;
+ });
+ }
if (log.state) {
this.state = log.state;
@@ -217,7 +230,7 @@ export default class Job {
visitUrl(this.pagePath);
}
})
- .fail(() => {
+ .catch(() => {
this.$buildRefreshAnimation.remove();
})
.then(() => {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 664e793fc8e..5ecf81ad11d 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -2,9 +2,12 @@
/* global Issuable */
/* global ListLabel */
import _ from 'underscore';
+import { __ } from './locale';
+import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
+import flash from './flash';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -82,99 +85,96 @@ export default class LabelsSelect {
}
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
- return $.ajax({
- type: 'PUT',
- url: issueUpdateURL,
- dataType: 'JSON',
- data: data
- }).done(function(data) {
- var labelCount, template, labelTooltipTitle, labelTitles;
- $loading.fadeOut();
- $dropdown.trigger('loaded.gl.dropdown');
- $selectbox.hide();
- data.issueURLSplit = issueURLSplit;
- labelCount = 0;
- if (data.labels.length) {
- template = labelHTMLTemplate(data);
- labelCount = data.labels.length;
- }
- else {
- template = labelNoneHTMLTemplate;
- }
- $value.removeAttr('style').html(template);
- $sidebarCollapsedValue.text(labelCount);
+ axios.put(issueUpdateURL, data)
+ .then(({ data }) => {
+ var labelCount, template, labelTooltipTitle, labelTitles;
+ $loading.fadeOut();
+ $dropdown.trigger('loaded.gl.dropdown');
+ $selectbox.hide();
+ data.issueURLSplit = issueURLSplit;
+ labelCount = 0;
+ if (data.labels.length) {
+ template = labelHTMLTemplate(data);
+ labelCount = data.labels.length;
+ }
+ else {
+ template = labelNoneHTMLTemplate;
+ }
+ $value.removeAttr('style').html(template);
+ $sidebarCollapsedValue.text(labelCount);
- if (data.labels.length) {
- labelTitles = data.labels.map(function(label) {
- return label.title;
- });
+ if (data.labels.length) {
+ labelTitles = data.labels.map(function(label) {
+ return label.title;
+ });
- if (labelTitles.length > 5) {
- labelTitles = labelTitles.slice(0, 5);
- labelTitles.push('and ' + (data.labels.length - 5) + ' more');
- }
+ if (labelTitles.length > 5) {
+ labelTitles = labelTitles.slice(0, 5);
+ labelTitles.push('and ' + (data.labels.length - 5) + ' more');
+ }
- labelTooltipTitle = labelTitles.join(', ');
- }
- else {
- labelTooltipTitle = '';
- $sidebarLabelTooltip.tooltip('destroy');
- }
+ labelTooltipTitle = labelTitles.join(', ');
+ }
+ else {
+ labelTooltipTitle = '';
+ $sidebarLabelTooltip.tooltip('destroy');
+ }
- $sidebarLabelTooltip
- .attr('title', labelTooltipTitle)
- .tooltip('fixTitle');
+ $sidebarLabelTooltip
+ .attr('title', labelTooltipTitle)
+ .tooltip('fixTitle');
- $('.has-tooltip', $value).tooltip({
- container: 'body'
- });
- });
+ $('.has-tooltip', $value).tooltip({
+ container: 'body'
+ });
+ })
+ .catch(() => flash(__('Error saving label update.')));
};
$dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
- return $.ajax({
- url: labelUrl
- }).done(function(data) {
- data = _.chain(data).groupBy(function(label) {
- return label.title;
- }).map(function(label) {
- var color;
- color = _.map(label, function(dup) {
- return dup.color;
- });
- return {
- id: label[0].id,
- title: label[0].title,
- color: color,
- duplicate: color.length > 1
- };
- }).value();
- if ($dropdown.hasClass('js-extra-options')) {
- var extraData = [];
- if (showNo) {
- extraData.unshift({
- id: 0,
- title: 'No Label'
+ axios.get(labelUrl)
+ .then((res) => {
+ let data = _.chain(res.data).groupBy(function(label) {
+ return label.title;
+ }).map(function(label) {
+ var color;
+ color = _.map(label, function(dup) {
+ return dup.color;
});
+ return {
+ id: label[0].id,
+ title: label[0].title,
+ color: color,
+ duplicate: color.length > 1
+ };
+ }).value();
+ if ($dropdown.hasClass('js-extra-options')) {
+ var extraData = [];
+ if (showNo) {
+ extraData.unshift({
+ id: 0,
+ title: 'No Label'
+ });
+ }
+ if (showAny) {
+ extraData.unshift({
+ isAny: true,
+ title: 'Any Label'
+ });
+ }
+ if (extraData.length) {
+ extraData.push('divider');
+ data = extraData.concat(data);
+ }
}
- if (showAny) {
- extraData.unshift({
- isAny: true,
- title: 'Any Label'
- });
- }
- if (extraData.length) {
- extraData.push('divider');
- data = extraData.concat(data);
- }
- }
- callback(data);
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- });
+ callback(data);
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ })
+ .catch(() => flash(__('Error fetching labels.')));
},
renderRow: function(label, instance) {
var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue;
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 629d8f44e18..616d8952ada 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -1,3 +1,4 @@
+import axios from './axios_utils';
import Cache from './cache';
class AjaxCache extends Cache {
@@ -18,25 +19,18 @@ class AjaxCache extends Cache {
let pendingRequest = this.pendingRequests[endpoint];
if (!pendingRequest) {
- pendingRequest = new Promise((resolve, reject) => {
- // jQuery 2 is not Promises/A+ compatible (missing catch)
- $.ajax(endpoint) // eslint-disable-line promise/catch-or-return
- .then(data => resolve(data),
- (jqXHR, textStatus, errorThrown) => {
- const error = new Error(`${endpoint}: ${errorThrown}`);
- error.textStatus = textStatus;
- reject(error);
- },
- );
- })
- .then((data) => {
- this.internalStorage[endpoint] = data;
- delete this.pendingRequests[endpoint];
- })
- .catch((error) => {
- delete this.pendingRequests[endpoint];
- throw error;
- });
+ pendingRequest = axios.get(endpoint)
+ .then(({ data }) => {
+ this.internalStorage[endpoint] = data;
+ delete this.pendingRequests[endpoint];
+ })
+ .catch((e) => {
+ const error = new Error(`${endpoint}: ${e.message}`);
+ error.textStatus = e.message;
+
+ delete this.pendingRequests[endpoint];
+ throw error;
+ });
this.pendingRequests[endpoint] = pendingRequest;
}
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 585214049c7..792871e2ecf 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -19,6 +19,10 @@ axios.interceptors.response.use((config) => {
window.activeVueResources -= 1;
return config;
+}, (e) => {
+ window.activeVueResources -= 1;
+
+ return Promise.reject(e);
});
export default axios;
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 8018ec411c1..5811d059e0b 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,5 +1,5 @@
-import { getLocationHash } from './url_utility';
import axios from './axios_utils';
+import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
@@ -28,16 +28,11 @@ export const isInIssuePage = () => {
return page === 'issues' && action === 'show';
};
-export const ajaxGet = url => $.ajax({
- type: 'GET',
- url,
- dataType: 'script',
-});
-
-export const ajaxPost = (url, data) => $.ajax({
- type: 'POST',
- url,
- data,
+export const ajaxGet = url => axios.get(url, {
+ params: { format: 'js' },
+ responseType: 'text',
+}).then(({ data }) => {
+ $.globalEval(data);
});
export const rstrip = (val) => {
@@ -412,7 +407,6 @@ window.gl.utils = {
getGroupSlug,
isInIssuePage,
ajaxGet,
- ajaxPost,
rstrip,
updateTooltipTitle,
disableButtonIfEmptyField,
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index d8b881a8fac..39445a85c77 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -33,7 +33,7 @@ import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
-import './dispatcher';
+import initDispatcher from './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
@@ -265,4 +265,6 @@ $(() => {
removeFlashClickListener(flashEl);
});
}
+
+ initDispatcher();
});
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
index c012b77e0bf..c68b47c9348 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
@@ -1,4 +1,5 @@
/* eslint-disable no-param-reassign, comma-dangle */
+import axios from '../lib/utils/axios_utils';
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -10,20 +11,11 @@
}
fetchConflictsData() {
- return $.ajax({
- dataType: 'json',
- url: this.conflictsPath
- });
+ return axios.get(this.conflictsPath);
}
submitResolveConflicts(data) {
- return $.ajax({
- url: this.resolveConflictsPath,
- data: JSON.stringify(data),
- contentType: 'application/json',
- dataType: 'json',
- method: 'POST'
- });
+ return axios.post(this.resolveConflictsPath, data);
}
}
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 792b7523889..b4b3c15108d 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -38,24 +38,23 @@ $(() => {
showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); }
},
created() {
- mergeConflictsService
- .fetchConflictsData()
- .done((data) => {
+ mergeConflictsService.fetchConflictsData()
+ .then(({ data }) => {
if (data.type === 'error') {
mergeConflictsStore.setFailedRequest(data.message);
} else {
mergeConflictsStore.setConflictsData(data);
}
- })
- .error(() => {
- mergeConflictsStore.setFailedRequest();
- })
- .always(() => {
+
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
syntaxHighlight($('.js-syntax-highlight'));
});
+ })
+ .catch(() => {
+ mergeConflictsStore.setLoadingState(false);
+ mergeConflictsStore.setFailedRequest();
});
},
methods: {
@@ -82,10 +81,10 @@ $(() => {
mergeConflictsService
.submitResolveConflicts(mergeConflictsStore.getCommitData())
- .done((data) => {
+ .then(({ data }) => {
window.location.href = data.redirect_to;
})
- .error(() => {
+ .catch(() => {
mergeConflictsStore.setSubmitState(false);
new Flash('Failed to save merge conflicts resolutions. Please try again!');
});
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index acfc62fe5cb..3e97a8c758d 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,7 +1,8 @@
/* eslint-disable no-new, class-methods-use-this */
import Cookies from 'js-cookie';
-import Flash from './flash';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
@@ -244,15 +245,22 @@ export default class MergeRequestTabs {
if (this.commitsLoaded) {
return;
}
- this.ajaxGet({
- url: `${source}.json`,
- success: (data) => {
+
+ this.toggleLoading(true);
+
+ axios.get(`${source}.json`)
+ .then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
- },
- });
+
+ this.toggleLoading(false);
+ })
+ .catch(() => {
+ this.toggleLoading(false);
+ flash('An error occurred while fetching this tab.');
+ });
}
mountPipelinesView() {
@@ -283,9 +291,10 @@ export default class MergeRequestTabs {
// some pages like MergeRequestsController#new has query parameters on that anchor
const urlPathname = parseUrlPathname(source);
- this.ajaxGet({
- url: `${urlPathname}.json${location.search}`,
- success: (data) => {
+ this.toggleLoading(true);
+
+ axios.get(`${urlPathname}.json${location.search}`)
+ .then(({ data }) => {
const $container = $('#diffs');
$container.html(data.html);
@@ -335,8 +344,13 @@ export default class MergeRequestTabs {
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');
}
- },
- });
+
+ this.toggleLoading(false);
+ })
+ .catch(() => {
+ this.toggleLoading(false);
+ flash('An error occurred while fetching this tab.');
+ });
}
// Show or hide the loading spinner
@@ -346,17 +360,6 @@ export default class MergeRequestTabs {
$('.mr-loading-status .loading').toggle(status);
}
- ajaxGet(options) {
- const defaults = {
- beforeSend: () => this.toggleLoading(true),
- error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
- complete: () => this.toggleLoading(false),
- dataType: 'json',
- type: 'GET',
- };
- $.ajax($.extend({}, defaults, options));
- }
-
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index dd6c6b854bc..b1d74250dfd 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,4 +1,5 @@
-import Flash from './flash';
+import axios from './lib/utils/axios_utils';
+import flash from './flash';
export default class Milestone {
constructor() {
@@ -33,15 +34,12 @@ export default class Milestone {
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
- $.ajax({
- url: endpoint,
- dataType: 'JSON',
- })
- .fail(() => new Flash('Error loading milestone tab'))
- .done((data) => {
- $(tabElId).html(data.html);
- $target.addClass('is-loaded');
- });
+ axios.get(endpoint)
+ .then(({ data }) => {
+ $(tabElId).html(data.html);
+ $target.addClass('is-loaded');
+ })
+ .catch(() => flash('Error loading milestone tab'));
}
}
}
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 0e854295fe3..6581be606eb 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
+import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
export default class MilestoneSelect {
@@ -52,48 +53,47 @@ export default class MilestoneSelect {
}
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
- data: (term, callback) => $.ajax({
- url: milestonesUrl
- }).done((data) => {
- const extraOptions = [];
- if (showAny) {
- extraOptions.push({
- id: 0,
- name: '',
- title: 'Any Milestone'
- });
- }
- if (showNo) {
- extraOptions.push({
- id: -1,
- name: 'No Milestone',
- title: 'No Milestone'
- });
- }
- if (showUpcoming) {
- extraOptions.push({
- id: -2,
- name: '#upcoming',
- title: 'Upcoming'
- });
- }
- if (showStarted) {
- extraOptions.push({
- id: -3,
- name: '#started',
- title: 'Started'
- });
- }
- if (extraOptions.length) {
- extraOptions.push('divider');
- }
+ data: (term, callback) => axios.get(milestonesUrl)
+ .then(({ data }) => {
+ const extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: 0,
+ name: '',
+ title: 'Any Milestone'
+ });
+ }
+ if (showNo) {
+ extraOptions.push({
+ id: -1,
+ name: 'No Milestone',
+ title: 'No Milestone'
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: 'Upcoming'
+ });
+ }
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: 'Started'
+ });
+ }
+ if (extraOptions.length) {
+ extraOptions.push('divider');
+ }
- callback(extraOptions.concat(data));
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
- }),
+ callback(extraOptions.concat(data));
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ }),
renderRow: milestone => `
<li data-milestone-id="${milestone.name}">
<a href='#' class='dropdown-menu-milestone-link'>
@@ -200,26 +200,23 @@ export default class MilestoneSelect {
data[abilityName].milestone_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
- return $.ajax({
- type: 'PUT',
- url: issueUpdateURL,
- data: data
- }).done((data) => {
- $dropdown.trigger('loaded.gl.dropdown');
- $loading.fadeOut();
- $selectBox.hide();
- $value.css('display', '');
- if (data.milestone != null) {
- data.milestone.full_path = this.currentProject.full_path;
- data.milestone.remaining = timeFor(data.milestone.due_date);
- data.milestone.name = data.milestone.title;
- $value.html(milestoneLinkTemplate(data.milestone));
- return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
- } else {
- $value.html(milestoneLinkNoneTemplate);
- return $sidebarCollapsedValue.find('span').text('No');
- }
- });
+ return axios.put(issueUpdateURL, data)
+ .then(({ data }) => {
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.fadeOut();
+ $selectBox.hide();
+ $value.css('display', '');
+ if (data.milestone != null) {
+ data.milestone.full_path = this.currentProject.full_path;
+ data.milestone.remaining = timeFor(data.milestone.due_date);
+ data.milestone.name = data.milestone.title;
+ $value.html(milestoneLinkTemplate(data.milestone));
+ return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
+ } else {
+ $value.html(milestoneLinkNoneTemplate);
+ return $sidebarCollapsedValue.find('span').text('No');
+ }
+ });
}
}
});
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index bcb342f407f..8efb8ac5320 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -24,7 +24,7 @@ import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
import Autosave from './autosave';
import TaskList from './task_list';
-import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
+import { isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
@@ -1399,7 +1399,7 @@ export default class Notes {
* 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
* 3) Build temporary placeholder element (using `createPlaceholderNote`)
* 4) Show placeholder note on UI
- * 5) Perform network request to submit the note using `ajaxPost`
+ * 5) Perform network request to submit the note using `axios.post`
* a) If request is successfully completed
* 1. Remove placeholder element
* 2. Show submitted Note element
@@ -1481,8 +1481,10 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */
// Make request to submit comment on server
- ajaxPost(formAction, formData)
- .then((note) => {
+ axios.post(formAction, formData)
+ .then((res) => {
+ const note = res.data;
+
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
@@ -1555,7 +1557,7 @@ export default class Notes {
}
$form.trigger('ajax:success', [note]);
- }).fail(() => {
+ }).catch(() => {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
@@ -1594,7 +1596,7 @@ export default class Notes {
*
* 1) Get Form metadata
* 2) Update note element with new content
- * 3) Perform network request to submit the updated note using `ajaxPost`
+ * 3) Perform network request to submit the updated note using `axios.post`
* a) If request is successfully completed
* 1. Show submitted Note element
* b) If request failed
@@ -1625,12 +1627,12 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */
// Make request to update comment on server
- ajaxPost(formAction, formData)
- .then((note) => {
+ axios.post(formAction, formData)
+ .then(({ data }) => {
// Submission successful! render final note element
- this.updateNote(note, $editingNote);
+ this.updateNote(data, $editingNote);
})
- .fail(() => {
+ .catch(() => {
// Submission failed, revert back to original note
$noteBodyText.html(_.escape(cachedNoteBodyText));
$editingNote.removeClass('being-posted fade-in');
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index 857a6793fe3..885acfac6d0 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -1,4 +1,7 @@
import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() {
@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() {
if (message === '') {
$('.js-broadcast-message-preview').text('Your message here');
} else {
- $.ajax({
- url: previewPath,
- type: 'POST',
- data: {
- broadcast_message: { message },
+ axios.post(previewPath, {
+ broadcast_message: {
+ message,
},
- });
+ })
+ .then(({ data }) => {
+ $('.js-broadcast-message-preview').html(data.message);
+ })
+ .catch(() => flash(__('An error occurred while rendering preview broadcast message')));
}
}, 250));
}
diff --git a/app/assets/javascripts/pages/dashboard/milestones/index/index.js b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
index 0f2f1bd4a25..38ddebe30d9 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
@@ -1,3 +1,3 @@
import projectSelect from '~/project_select';
-export default projectSelect;
+document.addEventListener('DOMContentLoaded', projectSelect);
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index 6ed0f010f15..5c763986da3 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -7,7 +7,7 @@ import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation';
import initGroupsList from '../../../groups';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
new ShortcutsNavigation();
new NotificationsForm();
@@ -19,4 +19,4 @@ export default () => {
}
initGroupsList();
-};
+});
diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js
index 42c9bb5ec99..3aeeedbb45d 100644
--- a/app/assets/javascripts/pages/projects/boards/index.js
+++ b/app/assets/javascripts/pages/projects/boards/index.js
@@ -1,7 +1,7 @@
import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index 0d3f35f044d..39c043edc38 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -7,10 +7,10 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.ISSUES);
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation();
new UsersSelect();
-};
+});
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index 48ed8fb2243..da312c1f1b7 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -1,13 +1,13 @@
-
/* eslint-disable no-new */
+
import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue';
import ShortcutsIssuable from '~/shortcuts_issuable';
import ZenMode from '~/zen_mode';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Issue();
new ShortcutsIssuable();
new ZenMode();
initIssuableSidebar();
-};
+});
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index b386e8fb48d..adadbf28e49 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -5,9 +5,9 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
-};
+});
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index e30d558726b..863dac0d20e 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,7 +1,10 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie';
-import { visitUrl } from '../../lib/utils/url_utility';
+import { __ } from '~/locale';
+import { visitUrl } from '~/lib/utils/url_utility';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
import projectSelect from '../../project_select';
export default class Project {
@@ -67,17 +70,15 @@ export default class Project {
$dropdown = $(this);
selected = $dropdown.data('selected');
return $dropdown.glDropdown({
- data: function(term, callback) {
- return $.ajax({
- url: $dropdown.data('refs-url'),
- data: {
+ data(term, callback) {
+ axios.get($dropdown.data('refs-url'), {
+ params: {
ref: $dropdown.data('ref'),
search: term,
},
- dataType: 'json',
- }).done(function(refs) {
- return callback(refs);
- });
+ })
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('An error occurred while getting projects')));
},
selectable: true,
filterable: true,
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 55154cdddcb..9b87f249f09 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -8,7 +8,7 @@ import { ajaxGet } from '~/lib/utils/common_utils';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
@@ -24,4 +24,4 @@ export default () => {
$('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
-};
+});
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index f163557babc..a0aa0499776 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -2,10 +2,10 @@ import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
-export default () => {
+document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new OAuthRememberMe({ // eslint-disable-line no-new
container: $('.omniauth-container'),
}).bindEvents();
-};
+});
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index bb34d5d2008..745543c22da 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -1,6 +1,9 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
@@ -77,12 +80,9 @@ export default class UsernameValidator {
this.state.pending = true;
this.state.available = false;
this.renderState();
- return $.ajax({
- type: 'GET',
- url: `${gon.relative_url_root}/users/${username}/exists`,
- dataType: 'json',
- success: (res) => this.setAvailabilityState(res.exists)
- });
+ axios.get(`${gon.relative_url_root}/users/${username}/exists`)
+ .then(({ data }) => this.setAvailabilityState(data.exists))
+ .catch(() => flash(__('An error occurred while validating username')));
}
}
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index f1cf6e92ef5..0b1a81bae13 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -4,7 +4,7 @@ import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
-import { setupPipelineVariableList } from './setup_pipeline_variable_list';
+import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
Vue.use(Translate);
@@ -42,5 +42,8 @@ document.addEventListener('DOMContentLoaded', () => {
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
- setupPipelineVariableList($('.js-pipeline-variable-list'));
+ setupNativeFormVariableList({
+ container: $('.js-ci-variable-list-section'),
+ formField: 'schedule',
+ });
});
diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
deleted file mode 100644
index 9e0e5cacb11..00000000000
--- a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import { convertPermissionToBoolean } from '../lib/utils/common_utils';
-
-function insertRow($row) {
- const $rowClone = $row.clone();
- $rowClone.removeAttr('data-is-persisted');
- $rowClone.find('input, textarea').val('');
- $row.after($rowClone);
-}
-
-function removeRow($row) {
- const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
-
- if (isPersisted) {
- $row.hide();
- $row
- .find('.js-destroy-input')
- .val(1);
- } else {
- $row.remove();
- }
-}
-
-function checkIfRowTouched($row) {
- return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
-}
-
-function setupPipelineVariableList(parent = document) {
- const $parent = $(parent);
-
- $parent.on('click', '.js-row-remove-button', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
- removeRow($row);
-
- e.preventDefault();
- });
-
- // Remove any empty rows except the last r
- $parent.on('blur', '.js-user-input', (e) => {
- const $row = $(e.currentTarget).closest('.js-row');
-
- const isTouched = checkIfRowTouched($row);
- if ($row.is(':not(:last-child)') && !isTouched) {
- removeRow($row);
- }
- });
-
- // Always make sure there is an empty last row
- $parent.on('input', '.js-user-input', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (isTouched) {
- insertRow($lastRow);
- }
- });
-
- // Clear out the empty last row so it
- // doesn't get submitted and throw validation errors
- $parent.closest('form').on('submit', () => {
- const $lastRow = $parent.find('.js-row').last();
-
- const isTouched = checkIfRowTouched($lastRow);
- if (!isTouched) {
- $lastRow.find('input, textarea').attr('name', '');
- }
- });
-}
-
-export {
- setupPipelineVariableList,
- insertRow,
- removeRow,
-};
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 86c7b56198d..464bfb351e7 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -7,6 +7,10 @@
// more than `x` users are referenced.
//
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
var lastTextareaPreviewed;
var lastTextareaHeight = null;
var markdownPreview;
@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
- $.ajax({
- type: 'POST',
- url: url,
- data: {
- text: text
- },
- dataType: 'json',
- success: (function (response) {
- this.ajaxCache = {
- text: text,
- response: response
- };
- success(response);
- }).bind(this)
- });
+ axios.post(url, {
+ text,
+ })
+ .then(({ data }) => {
+ this.ajaxCache = {
+ text: text,
+ response: data,
+ };
+ success(data);
+ })
+ .catch(() => flash(__('An error occurred while fetching markdown preview')));
};
MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index 974dc3ee052..2d680d0f0dc 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -13,7 +13,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
```
*/
-function updatetoggle(toggle, isOn) {
+function updateToggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn);
}
@@ -21,7 +21,7 @@ function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading
- updatetoggle(toggle, !previousIsOn);
+ updateToggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true);
@@ -32,7 +32,7 @@ function onToggleClicked(toggle, input, clickCallback) {
})
.catch(() => {
// Revert the visuals if something goes wrong
- updatetoggle(toggle, previousIsOn);
+ updateToggle(toggle, previousIsOn);
})
.then(() => {
// Remove the loading indicator in any case
@@ -54,7 +54,7 @@ export default function setupToggleButtons(container, clickCallback = () => {})
const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input
- updatetoggle(toggle, isOn);
+ updateToggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index 7ac9eadcde0..cb6e9858736 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,11 +1,26 @@
<script>
+ import tooltip from '../../vue_shared/directives/tooltip';
+
export default {
name: 'MRWidgetAuthor',
+ directives: {
+ tooltip,
+ },
props: {
author: {
type: Object,
required: true,
},
+ showAuthorName: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ showAuthorTooltip: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
authorUrl() {
@@ -21,12 +36,17 @@
<a
:href="authorUrl"
class="author-link inline"
+ :v-tooltip="showAuthorTooltip"
+ :title="author.name"
>
<img
:src="avatarUrl"
class="avatar avatar-inline s16"
/>
- <span class="author">
+ <span
+ class="author"
+ v-if="showAuthorName"
+ >
{{ author.name }}
</span>
</a>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index cb8e6072a9b..63d8329e495 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -48,7 +48,7 @@
};
</script>
<template>
- <ul class="nav-links scrolling-tabs">
+ <ul class="nav-links scrolling-tabs separator">
<li
v-for="(tab, i) in tabs"
:key="i"
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index cff47ea76ec..c4aad24e9c1 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -60,3 +60,4 @@
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
+@import "framework/ci_variable_list";
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index d0b0c69b18f..c4b046a6d68 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -176,6 +176,11 @@
&.btn-remove {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
}
+
+ &.btn-primary,
+ &.btn-info {
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ }
}
&.btn-gray {
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
new file mode 100644
index 00000000000..8f654ab363c
--- /dev/null
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -0,0 +1,88 @@
+.ci-variable-list {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+ clear: both;
+}
+
+.ci-variable-row {
+ display: flex;
+ align-items: flex-end;
+
+ &:not(:last-child) {
+ margin-bottom: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-bottom: 3 * $gl-btn-padding;
+ }
+ }
+
+ &:last-child {
+ .ci-variable-body-item:last-child {
+ margin-right: $ci-variable-remove-button-width;
+
+ @media (max-width: $screen-xs-max) {
+ margin-right: 0;
+ }
+ }
+
+ .ci-variable-row-remove-button {
+ display: none;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .ci-variable-row-body {
+ margin-right: $ci-variable-remove-button-width;
+ }
+ }
+ }
+}
+
+.ci-variable-row-body {
+ display: flex;
+ width: 100%;
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+}
+
+.ci-variable-body-item {
+ flex: 1;
+
+ &:not(:last-child) {
+ margin-right: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-right: 0;
+ margin-bottom: $gl-btn-padding;
+ }
+ }
+}
+
+.ci-variable-protected-item {
+ flex: 0 1 auto;
+ display: flex;
+ align-items: center;
+}
+
+.ci-variable-row-remove-button {
+ @include transition(color);
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: $ci-variable-remove-button-width;
+ height: $input-height;
+ padding: 0;
+ background: transparent;
+ border: 0;
+ color: $gl-text-color-secondary;
+
+ &:hover,
+ &:focus {
+ outline: none;
+ color: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 5621505996d..e378e84ca1b 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -16,3 +16,31 @@
background-color: $user-mention-bg-hover;
}
}
+
+.gfm-color_chip {
+ display: inline-block;
+ margin: 0 0 2px 4px;
+ vertical-align: middle;
+ border-radius: 3px;
+
+ $chip-size: 0.9em;
+ $bg-size: $chip-size / 0.9;
+ $bg-pos: $bg-size / 2;
+
+ width: $chip-size;
+ height: $chip-size;
+ background: $white-light;
+ background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
+ linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
+ background-size: $bg-size $bg-size;
+ background-position: 0 0, $bg-pos $bg-pos;
+
+ > span {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ margin-bottom: 2px;
+ border-radius: 3px;
+ border: 1px solid $black-transparent;
+ }
+}
diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
index 5f67126bafa..17c31d6b184 100644
--- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss
+++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
@@ -82,6 +82,10 @@
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-xs-max) {
width: 100%;
+
+ &.mobile-separator {
+ border-bottom: 1px solid $border-color;
+ }
}
}
@@ -168,9 +172,9 @@
display: inline-block;
}
- // Applies on /dashboard/issues
.project-item-select-holder {
margin: 0;
+ width: 100%;
}
}
}
@@ -340,7 +344,6 @@
.project-item-select-holder.btn-group {
display: flex;
- max-width: 350px;
overflow: hidden;
float: right;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f76c6866463..1cc22f5658d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -668,9 +668,9 @@ $pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px;
/*
-Pipeline Schedules
+CI variable lists
*/
-$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
+$ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/*
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index b698a4f9afa..bc7fa8a26d9 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -78,84 +78,3 @@
margin-right: 3px;
}
}
-
-.pipeline-variable-list {
- margin-left: 0;
- margin-bottom: 0;
- padding-left: 0;
- list-style: none;
- clear: both;
-}
-
-.pipeline-variable-row {
- display: flex;
- align-items: flex-end;
-
- &:not(:last-child) {
- margin-bottom: $gl-btn-padding;
- }
-
- @media (max-width: $screen-sm-max) {
- padding-right: $gl-col-padding;
- }
-
- &:last-child {
- .pipeline-variable-row-remove-button {
- display: none;
- }
-
- @media (max-width: $screen-sm-max) {
- .pipeline-variable-value-input {
- margin-right: $pipeline-variable-remove-button-width;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- .pipeline-variable-row-body {
- margin-right: $pipeline-variable-remove-button-width;
- }
- }
- }
-}
-
-.pipeline-variable-row-body {
- display: flex;
- width: calc(75% - #{$gl-col-padding});
- padding-left: $gl-col-padding;
-
- @media (max-width: $screen-sm-max) {
- width: 100%;
- }
-
- @media (max-width: $screen-xs-max) {
- display: block;
- }
-}
-
-.pipeline-variable-key-input {
- margin-right: $gl-btn-padding;
-
- @media (max-width: $screen-xs-max) {
- margin-bottom: $gl-btn-padding;
- }
-}
-
-.pipeline-variable-row-remove-button {
- @include transition(color);
- flex-shrink: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- width: $pipeline-variable-remove-button-width;
- height: $input-height;
- padding: 0;
- background: transparent;
- border: 0;
- color: $gl-text-color-secondary;
-
- &:hover,
- &:focus {
- outline: none;
- color: $gl-text-color;
- }
-}
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index c49b6459452..a9109a1d4d0 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -1,4 +1,6 @@
class Admin::BroadcastMessagesController < Admin::ApplicationController
+ include BroadcastMessagesHelper
+
before_action :finder, only: [:edit, :update, :destroy]
def index
@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end
def preview
- @broadcast_message = BroadcastMessage.new(broadcast_message_params)
+ broadcast_message = BroadcastMessage.new(broadcast_message_params)
+ render json: { message: render_broadcast_message(broadcast_message) }
end
protected
diff --git a/app/controllers/admin/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb
index 9b77c554908..10d9d1b5345 100644
--- a/app/controllers/admin/cohorts_controller.rb
+++ b/app/controllers/admin/cohorts_controller.rb
@@ -1,6 +1,6 @@
class Admin::CohortsController < Admin::ApplicationController
def index
- if current_application_settings.usage_ping_enabled
+ if Gitlab::CurrentSettings.usage_ping_enabled
cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do
CohortsService.new.execute
end
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 4c3d336b3af..a7025b62ad7 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -1,6 +1,7 @@
class Admin::ServicesController < Admin::ApplicationController
include ServiceParams
+ before_action :whitelist_query_limiting, only: [:index]
before_action :service, only: [:edit, :update]
def index
@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController
def service
@service ||= Service.where(id: params[:id], template: true).first
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42430')
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 95ad38d9230..b04bfaf3e49 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,7 +2,6 @@ require 'gon'
require 'fogbugz'
class ApplicationController < ActionController::Base
- include Gitlab::CurrentSettings
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
@@ -28,7 +27,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
- helper_method :can?, :current_application_settings
+ helper_method :can?
helper_method :import_sources_enabled?, :github_import_enabled?, :gitea_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
@@ -120,7 +119,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- current_application_settings.after_sign_out_path.presence || new_user_session_path
+ Gitlab::CurrentSettings.after_sign_out_path.presence || new_user_session_path
end
def can?(object, action, subject = :global)
@@ -268,15 +267,15 @@ class ApplicationController < ActionController::Base
end
def import_sources_enabled?
- !current_application_settings.import_sources.empty?
+ !Gitlab::CurrentSettings.import_sources.empty?
end
def github_import_enabled?
- current_application_settings.import_sources.include?('github')
+ Gitlab::CurrentSettings.import_sources.include?('github')
end
def gitea_import_enabled?
- current_application_settings.import_sources.include?('gitea')
+ Gitlab::CurrentSettings.import_sources.include?('gitea')
end
def github_import_configured?
@@ -284,7 +283,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_import_enabled?
- request.host != 'gitlab.com' && current_application_settings.import_sources.include?('gitlab')
+ request.host != 'gitlab.com' && Gitlab::CurrentSettings.import_sources.include?('gitlab')
end
def gitlab_import_configured?
@@ -292,7 +291,7 @@ class ApplicationController < ActionController::Base
end
def bitbucket_import_enabled?
- current_application_settings.import_sources.include?('bitbucket')
+ Gitlab::CurrentSettings.import_sources.include?('bitbucket')
end
def bitbucket_import_configured?
@@ -300,19 +299,19 @@ class ApplicationController < ActionController::Base
end
def google_code_import_enabled?
- current_application_settings.import_sources.include?('google_code')
+ Gitlab::CurrentSettings.import_sources.include?('google_code')
end
def fogbugz_import_enabled?
- current_application_settings.import_sources.include?('fogbugz')
+ Gitlab::CurrentSettings.import_sources.include?('fogbugz')
end
def git_import_enabled?
- current_application_settings.import_sources.include?('git')
+ Gitlab::CurrentSettings.import_sources.include?('git')
end
def gitlab_project_import_enabled?
- current_application_settings.import_sources.include?('gitlab_project')
+ Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
end
# U2F (universal 2nd factor) devices need a unique identifier for the application
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index f8049b20b9f..ee23ee0bcc3 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -2,6 +2,7 @@ module Boards
class IssuesController < Boards::ApplicationController
include BoardsResponses
+ before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update]
@@ -92,5 +93,10 @@ module Boards
}
)
end
+
+ def whitelist_query_limiting
+ # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42439
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42428')
+ end
end
end
diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb
index 688e8bd4a37..997af4ab9e9 100644
--- a/app/controllers/concerns/enforces_two_factor_authentication.rb
+++ b/app/controllers/concerns/enforces_two_factor_authentication.rb
@@ -20,13 +20,13 @@ module EnforcesTwoFactorAuthentication
end
def two_factor_authentication_required?
- current_application_settings.require_two_factor_authentication? ||
+ Gitlab::CurrentSettings.require_two_factor_authentication? ||
current_user.try(:require_two_factor_authentication_from_group?)
end
def two_factor_authentication_reason(global: -> {}, group: -> {})
if two_factor_authentication_required?
- if current_application_settings.require_two_factor_authentication?
+ if Gitlab::CurrentSettings.require_two_factor_authentication?
global.call
else
groups = current_user.expanded_groups_requiring_two_factor_authentication.reorder(name: :asc)
@@ -36,7 +36,7 @@ module EnforcesTwoFactorAuthentication
end
def two_factor_grace_period
- periods = [current_application_settings.two_factor_grace_period]
+ periods = [Gitlab::CurrentSettings.two_factor_grace_period]
periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?)
periods.min
end
diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
index 0218ac83441..88d1b34bb06 100644
--- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
+++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
@@ -1,8 +1,6 @@
module RequiresWhitelistedMonitoringClient
extend ActiveSupport::Concern
- include Gitlab::CurrentSettings
-
included do
before_action :validate_ip_whitelisted_or_valid_token!
end
@@ -26,7 +24,7 @@ module RequiresWhitelistedMonitoringClient
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
- current_application_settings.health_check_access_token
+ Gitlab::CurrentSettings.health_check_access_token
)
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 567957ba2cb..f22df992fe9 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -1,4 +1,5 @@
class Import::GitlabProjectsController < Import::BaseController
+ before_action :whitelist_query_limiting, only: [:create]
before_action :verify_gitlab_project_import_enabled
def new
@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file
)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
+ end
end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 04b29aa2384..52430ea771f 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -51,7 +51,7 @@ class InvitesController < ApplicationController
return if current_user
notice = "To accept this invitation, sign in"
- notice << " or create an account" if current_application_settings.allow_signup?
+ notice << " or create an account" if Gitlab::CurrentSettings.allow_signup?
notice << "."
store_location_for :user, request.fullpath
diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb
index 6b1e64ce819..745abf3c0f5 100644
--- a/app/controllers/koding_controller.rb
+++ b/app/controllers/koding_controller.rb
@@ -10,6 +10,6 @@ class KodingController < ApplicationController
private
def check_integration!
- render_404 unless current_application_settings.koding_enabled?
+ render_404 unless Gitlab::CurrentSettings.koding_enabled?
end
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 2443f529c7b..6a21a3f77ad 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,5 +1,4 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
- include Gitlab::CurrentSettings
include Gitlab::GonHelper
include PageLayoutHelper
include OauthApplications
@@ -31,7 +30,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
private
def verify_user_oauth_applications_enabled
- return if current_application_settings.user_oauth_applications?
+ return if Gitlab::CurrentSettings.user_oauth_applications?
redirect_to profile_path
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index d631d09f1b8..83c9a3f035e 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -145,7 +145,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
- if current_application_settings.allow_signup?
+ if Gitlab::CurrentSettings.allow_signup?
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 57761bfbe26..331583c49e6 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -1,6 +1,4 @@
class PasswordsController < Devise::PasswordsController
- include Gitlab::CurrentSettings
-
skip_before_action :require_no_authentication, only: [:edit, :update]
before_action :resource_from_email, only: [:create]
@@ -46,7 +44,7 @@ class PasswordsController < Devise::PasswordsController
if resource
return if resource.allow_password_authentication?
else
- return if current_application_settings.password_authentication_enabled?
+ return if Gitlab::CurrentSettings.password_authentication_enabled?
end
redirect_to after_sending_reset_password_instructions_path_for(resource_name),
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 0a40c67368f..1d910e461b1 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
include RendersCommits
+ before_action :whitelist_query_limiting
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42330')
+ end
end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 88ac3ad046b..d1b8fd80c4e 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include ActionView::Helpers::TextHelper
include CycleAnalyticsParams
+ before_action :whitelist_query_limiting, only: [:show]
before_action :authorize_read_cycle_analytics!
def show
@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
permissions: @cycle_analytics.permissions(user: current_user)
}
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42671')
+ end
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 68978f8fdd1..f43bba18d81 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController
include ContinueParams
# Authorize
+ before_action :whitelist_query_limiting, only: [:create]
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController
render :error
end
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42335')
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 384f18b316c..515cb08f1fc 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -8,6 +8,7 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new]
+ before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index]
@@ -247,4 +248,13 @@ class Projects::IssuesController < Projects::ApplicationController
@finder_type = IssuesFinder
super
end
+
+ def whitelist_query_limiting
+ # Also see the following issues:
+ #
+ # 1. https://gitlab.com/gitlab-org/gitlab-ce/issues/42423
+ # 2. https://gitlab.com/gitlab-org/gitlab-ce/issues/42424
+ # 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
+ end
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 0df80fa700f..a5a2d54ba82 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -4,6 +4,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
include RendersCommits
skip_before_action :merge_request
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
@@ -125,4 +126,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@project.forked_from_project
end
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2e8a738b6d9..8af4e379f0a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update]
+ before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
@@ -339,4 +340,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check
end
+
+ def whitelist_query_limiting
+ # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
+ end
end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index fb68dd771a1..3b10a93e97f 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath
include ApplicationHelper
+ before_action :whitelist_query_limiting
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController
@options[:extended_sha1] = params[:extended_sha1]
@commit = @repo.commit(@options[:extended_sha1])
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42333')
+ end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5940fae8dd0..4f8978c93c3 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesActions
include ToggleAwardEmoji
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController
access_denied! unless can?(current_user, :create_note, noteable)
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42383')
+ end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index e146d0d3cd5..78d109cf33e 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -1,4 +1,5 @@
class Projects::PipelinesController < Projects::ApplicationController
+ before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :commit, only: [:show, :builds, :failures]
before_action :authorize_read_pipeline!
@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController
def commit
@commit ||= @pipeline.commit
end
+
+ def whitelist_query_limiting
+ # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8158934322d..72573e0765d 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -3,6 +3,7 @@ class ProjectsController < Projects::ApplicationController
include ExtractsPath
include PreviewMarkdown
+ before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create]
@@ -394,7 +395,7 @@ class ProjectsController < Projects::ApplicationController
end
def project_export_enabled
- render_404 unless current_application_settings.project_export_enabled?
+ render_404 unless Gitlab::CurrentSettings.project_export_enabled?
end
def redirect_git_extension
@@ -405,4 +406,8 @@ class ProjectsController < Projects::ApplicationController
#
redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
+ end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index d9142311b6f..1848c806c41 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,6 +1,8 @@
class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
+ before_action :whitelist_query_limiting, only: [:destroy]
+
def new
redirect_to(new_user_session_path)
end
@@ -83,4 +85,8 @@ class RegistrationsController < Devise::RegistrationsController
def devise_mapping
@devise_mapping ||= Devise.mappings[:user]
end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42380')
+ end
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 19e38993038..8acefd58e77 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -23,7 +23,7 @@ class RootController < Dashboard::ProjectsController
def redirect_unlogged_user
if redirect_to_home_page_url?
- redirect_to(current_application_settings.home_page_url)
+ redirect_to(Gitlab::CurrentSettings.home_page_url)
else
redirect_to(new_user_session_path)
end
@@ -48,9 +48,9 @@ class RootController < Dashboard::ProjectsController
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
- return false unless current_application_settings.home_page_url.present?
+ return false unless Gitlab::CurrentSettings.home_page_url.present?
- home_page_url = current_application_settings.home_page_url.chomp('/')
+ home_page_url = Gitlab::CurrentSettings.home_page_url.chomp('/')
root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
root_urls.exclude?(home_page_url)
diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb
new file mode 100644
index 00000000000..18cde4a7b1a
--- /dev/null
+++ b/app/controllers/user_callouts_controller.rb
@@ -0,0 +1,23 @@
+class UserCalloutsController < ApplicationController
+ def create
+ if ensure_callout.persisted?
+ respond_to do |format|
+ format.json { head :ok }
+ end
+ else
+ respond_to do |format|
+ format.json { head :bad_request }
+ end
+ end
+ end
+
+ private
+
+ def ensure_callout
+ current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name])
+ end
+
+ def feature_name
+ params.require(:feature_name)
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index e91e1d29d64..7548bc30247 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,25 +1,23 @@
module ApplicationSettingsHelper
extend self
- include Gitlab::CurrentSettings
-
delegate :allow_signup?,
:gravatar_enabled?,
:password_authentication_enabled_for_web?,
:akismet_enabled?,
:koding_enabled?,
- to: :current_application_settings
+ to: :'Gitlab::CurrentSettings.current_application_settings'
def user_oauth_applications?
- current_application_settings.user_oauth_applications
+ Gitlab::CurrentSettings.user_oauth_applications
end
def allowed_protocols_present?
- current_application_settings.enabled_git_access_protocol.present?
+ Gitlab::CurrentSettings.enabled_git_access_protocol.present?
end
def enabled_protocol
- case current_application_settings.enabled_git_access_protocol
+ case Gitlab::CurrentSettings.enabled_git_access_protocol
when 'http'
gitlab_config.protocol
when 'ssh'
@@ -57,7 +55,7 @@ module ApplicationSettingsHelper
# toggle button effect.
def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source|
- checked = current_application_settings.import_sources.include?(source)
+ checked = Gitlab::CurrentSettings.import_sources.include?(source)
css_class = checked ? 'active' : ''
checkbox_name = 'application_setting[import_sources][]'
@@ -72,7 +70,7 @@ module ApplicationSettingsHelper
def oauth_providers_checkboxes
button_based_providers.map do |source|
- disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
+ disabled = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources.include?(source.to_s)
css_class = 'btn'
css_class << ' active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 66dc0b1e6f7..f909f664034 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,6 +1,4 @@
module AuthHelper
- include Gitlab::CurrentSettings
-
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
@@ -41,7 +39,7 @@ module AuthHelper
end
def enabled_button_based_providers
- disabled_providers = current_application_settings.disabled_oauth_sign_in_sources || []
+ disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || []
button_based_providers.map(&:to_s) - disabled_providers
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f7bdcc6fd9c..6512617a02d 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -1,6 +1,4 @@
module ProjectsHelper
- include Gitlab::CurrentSettings
-
def link_to_project(project)
link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
title = content_tag(:span, project.name, class: 'project-name')
@@ -214,7 +212,7 @@ module ProjectsHelper
project.cache_key,
controller.controller_name,
controller.action_name,
- current_application_settings.cache_key,
+ Gitlab::CurrentSettings.cache_key,
'v2.5'
]
@@ -447,10 +445,10 @@ module ProjectsHelper
path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
- return URI.join(current_application_settings.koding_url, path).to_s
+ return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
end
- current_application_settings.koding_url
+ Gitlab::CurrentSettings.koding_url
end
def contribution_guide_path(project)
@@ -559,7 +557,7 @@ module ProjectsHelper
def restricted_levels
return [] if current_user.admin?
- current_application_settings.restricted_visibility_levels || []
+ Gitlab::CurrentSettings.restricted_visibility_levels || []
end
def project_permissions_settings(project)
diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb
new file mode 100644
index 00000000000..6368e248c6e
--- /dev/null
+++ b/app/helpers/user_callouts_helper.rb
@@ -0,0 +1,14 @@
+module UserCalloutsHelper
+ GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
+
+ def show_gke_cluster_integration_callout?(project)
+ can?(current_user, :create_cluster, project) &&
+ !user_dismissed?(GKE_CLUSTER_INTEGRATION)
+ end
+
+ private
+
+ def user_dismissed?(feature_name)
+ current_user&.callouts&.find_by(feature_name: feature_name)
+ end
+end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 456598b4c28..c20753ece72 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,6 +1,6 @@
module VersionCheckHelper
def version_status_badge
- if Rails.env.production? && current_application_settings.version_check_enabled
+ if Rails.env.production? && Gitlab::CurrentSettings.version_check_enabled
image_url = VersionCheck.new.url
image_tag image_url, class: 'js-version-status-badge'
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index c3d5628f241..e395cda03d3 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -151,12 +151,12 @@ module VisibilityLevelHelper
def restricted_visibility_levels(show_all = false)
return [] if current_user.admin? && !show_all
- current_application_settings.restricted_visibility_levels || []
+ Gitlab::CurrentSettings.restricted_visibility_levels || []
end
delegate :default_project_visibility,
:default_group_visibility,
- to: :current_application_settings
+ to: :'Gitlab::CurrentSettings.current_application_settings'
def disallowed_visibility_level?(form_model, level)
return false unless form_model.respond_to?(:visibility_level_allowed?)
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 77433acb92a..9d071f2d59a 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -5,6 +5,24 @@ module WebpackHelper
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain))
end
+ def webpack_controller_bundle_tags
+ bundles = []
+ segments = [*controller.controller_path.split('/'), controller.action_name].compact
+
+ until segments.empty?
+ begin
+ asset_paths = gitlab_webpack_asset_paths("pages.#{segments.join('.')}", extension: 'js')
+ bundles.unshift(*asset_paths)
+ rescue Webpack::Rails::Manifest::EntryPointMissingError
+ # no bundle exists for this path
+ end
+
+ segments.pop
+ end
+
+ javascript_include_tag(*bundles)
+ end
+
# override webpack-rails gem helper until changes can make it upstream
def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false)
return "" unless source.present?
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index d0ce827a595..fe5f68ba3d5 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,13 +1,11 @@
class AbuseReportMailer < BaseMailer
- include Gitlab::CurrentSettings
-
def notify(abuse_report_id)
return unless deliverable?
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
- to: current_application_settings.admin_notification_email,
+ to: Gitlab::CurrentSettings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
@@ -15,6 +13,6 @@ class AbuseReportMailer < BaseMailer
private
def deliverable?
- current_application_settings.admin_notification_email.present?
+ Gitlab::CurrentSettings.admin_notification_email.present?
end
end
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 8e99db444d6..654468bc7fe 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,13 +1,11 @@
class BaseMailer < ActionMailer::Base
- include Gitlab::CurrentSettings
-
around_action :render_with_default_locale
helper ApplicationHelper
helper MarkupHelper
attr_accessor :current_user
- helper_method :current_user, :can?, :current_application_settings
+ helper_method :current_user, :can?
default from: proc { default_sender_address.format }
default reply_to: proc { default_reply_to_address.format }
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 9024f1df1cd..aa5cf97756f 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -17,8 +17,12 @@ module Clusters
'stable/nginx-ingress'
end
+ def chart_values_file
+ "#{Rails.root}/vendor/#{name}/values.yaml"
+ end
+
def install_command
- Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart)
+ Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 9160a169452..7f38dcc4a9c 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -1,7 +1,6 @@
module Clusters
module Platforms
class Kubernetes < ActiveRecord::Base
- include Gitlab::CurrentSettings
include Gitlab::Kubernetes
include ReactiveCaching
@@ -169,7 +168,7 @@ module Clusters
{
token: token,
ca_pem: ca_pem,
- max_session_time: current_application_settings.terminal_max_session_time
+ max_session_time: Gitlab::CurrentSettings.terminal_max_session_time
}
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 99dbd4fbacf..b12c10a84de 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -87,20 +87,10 @@ module Storage
remove_exports!
end
- def remove_exports!
- Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
- end
-
- def export_path
- File.join(Gitlab::ImportExport.storage_path, full_path_was)
- end
+ def remove_legacy_exports!
+ legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
- def full_path_was
- if parent
- parent.full_path + '/' + path_was
- else
- path_was
- end
+ FileUtils.rm_rf(legacy_export_path)
end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 62b1322ebe6..5b7f1b38612 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -274,12 +274,6 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end
- def full_path_was
- return path_was unless has_parent?
-
- "#{parent.full_path}/#{path_was}"
- end
-
def group_member(user)
if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id }
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
index 06d760b6a89..326b9eb7ad5 100644
--- a/app/models/issue_assignee.rb
+++ b/app/models/issue_assignee.rb
@@ -1,6 +1,4 @@
class IssueAssignee < ActiveRecord::Base
- extend Gitlab::CurrentSettings
-
belongs_to :issue
belongs_to :assignee, class_name: "User", foreign_key: :user_id
end
diff --git a/app/models/key.rb b/app/models/key.rb
index a3f8a5d6dc7..ae5769c0627 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -1,7 +1,6 @@
require 'digest/md5'
class Key < ActiveRecord::Base
- include Gitlab::CurrentSettings
include AfterCommitQueue
include Sortable
@@ -34,9 +33,8 @@ class Key < ActiveRecord::Base
after_destroy :refresh_user_cache
def key=(value)
- value&.delete!("\n\r")
- value.strip! unless value.blank?
- write_attribute(:key, value)
+ write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)
+
@public_key = nil
end
@@ -98,13 +96,13 @@ class Key < ActiveRecord::Base
def generate_fingerprint
self.fingerprint = nil
- return unless self.key.present?
+ return unless public_key.valid?
self.fingerprint = public_key.fingerprint
end
def key_meets_restrictions
- restriction = current_application_settings.key_restriction_for(public_key.type)
+ restriction = Gitlab::CurrentSettings.key_restriction_for(public_key.type)
if restriction == ApplicationSetting::FORBIDDEN_KEY_VALUE
errors.add(:key, forbidden_key_type_message)
@@ -115,7 +113,7 @@ class Key < ActiveRecord::Base
def forbidden_key_type_message
allowed_types =
- current_application_settings
+ Gitlab::CurrentSettings
.allowed_key_types
.map(&:upcase)
.to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 37a7417cafc..7b82d076975 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -2,7 +2,6 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField
include Sortable
include Gitlab::ShellAdapter
- include Gitlab::CurrentSettings
include Gitlab::VisibilityLevel
include Routable
include AfterCommitQueue
@@ -222,6 +221,24 @@ class Namespace < ActiveRecord::Base
has_parent?
end
+ def full_path_was
+ return path_was unless has_parent?
+
+ "#{parent.full_path}/#{path_was}"
+ end
+
+ # Exports belonging to projects with legacy storage are placed in a common
+ # subdirectory of the namespace, so a simple `rm -rf` is sufficient to remove
+ # them.
+ #
+ # Exports of projects using hashed storage are placed in a location defined
+ # only by the project ID, so each must be removed individually.
+ def remove_exports!
+ remove_legacy_exports!
+
+ all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
+ end
+
private
def refresh_access_of_projects_invited_groups
diff --git a/app/models/note.rb b/app/models/note.rb
index a84db8982e5..01a778a7424 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -3,7 +3,6 @@
# A note of this type is never resolvable.
class Note < ActiveRecord::Base
extend ActiveModel::Naming
- include Gitlab::CurrentSettings
include Participable
include Mentionable
include Awardable
@@ -196,7 +195,7 @@ class Note < ActiveRecord::Base
end
def max_attachment_size
- current_application_settings.max_attachment_size.megabytes.to_i
+ Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end
def hook_attrs
diff --git a/app/models/project.rb b/app/models/project.rb
index 90f5df6265d..12d5f28f5ea 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -4,7 +4,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
- include Gitlab::CurrentSettings
include AccessRequestable
include Avatarable
include CacheMarkdownField
@@ -23,7 +22,6 @@ class Project < ActiveRecord::Base
include ::Gitlab::Utils::StrongMemoize
extend Gitlab::ConfigHelper
- extend Gitlab::CurrentSettings
BoardLimitExceeded = Class.new(StandardError)
@@ -51,8 +49,8 @@ class Project < ActiveRecord::Base
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
- default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
- default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
+ default_value_for(:repository_storage) { Gitlab::CurrentSettings.pick_repository_storage }
+ default_value_for(:shared_runners_enabled) { Gitlab::CurrentSettings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
@@ -71,6 +69,7 @@ class Project < ActiveRecord::Base
before_destroy :remove_private_deploy_keys
after_destroy -> { run_after_commit { remove_pages } }
+ after_destroy :remove_exports
after_validation :check_pending_delete
@@ -486,14 +485,14 @@ class Project < ActiveRecord::Base
def auto_devops_enabled?
if auto_devops&.enabled.nil?
- current_application_settings.auto_devops_enabled?
+ Gitlab::CurrentSettings.auto_devops_enabled?
else
auto_devops.enabled?
end
end
def has_auto_devops_implicitly_disabled?
- auto_devops&.enabled.nil? && !current_application_settings.auto_devops_enabled?
+ auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled?
end
def empty_repo?
@@ -1471,14 +1470,14 @@ class Project < ActiveRecord::Base
# Ensure HEAD points to the default branch in case it is not master
change_head(default_branch)
- if current_application_settings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
+ if Gitlab::CurrentSettings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
params = {
name: default_branch,
push_access_levels_attributes: [{
- access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}],
merge_access_levels_attributes: [{
- access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}]
}
@@ -1527,6 +1526,8 @@ class Project < ActiveRecord::Base
end
def export_path
+ return nil unless namespace.present? || hashed_storage?(:repository)
+
File.join(Gitlab::ImportExport.storage_path, disk_path)
end
@@ -1535,8 +1536,9 @@ class Project < ActiveRecord::Base
end
def remove_exports
- _, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
- status.zero?
+ return nil unless export_path.present?
+
+ FileUtils.rm_rf(export_path)
end
def full_path_slug
@@ -1773,7 +1775,7 @@ class Project < ActiveRecord::Base
end
def use_hashed_storage
- if self.new_record? && current_application_settings.hashed_storage_enabled
+ if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
end
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index c72b01b64af..e42fd802b92 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -4,7 +4,6 @@
# After we've migrated data, we'll remove KubernetesService. This would happen in a few months.
# If you're modyfiyng this class, please note that you should update the same change in Clusters::Platforms::Kubernetes.
class KubernetesService < DeploymentService
- include Gitlab::CurrentSettings
include Gitlab::Kubernetes
include ReactiveCaching
@@ -231,7 +230,7 @@ class KubernetesService < DeploymentService
{
token: token,
ca_pem: ca_pem,
- max_session_time: current_application_settings.terminal_max_session_time
+ max_session_time: Gitlab::CurrentSettings.terminal_max_session_time
}
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index d28fed11ca8..609780c5587 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -2,8 +2,6 @@ class ProtectedBranch < ActiveRecord::Base
include Gitlab::ShellAdapter
include ProtectedRef
- extend Gitlab::CurrentSettings
-
protected_ref_access_levels :merge, :push
# Check if branch name is marked as protected in the system
@@ -16,7 +14,7 @@ class ProtectedBranch < ActiveRecord::Base
end
def self.default_branch_protected?
- current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
- current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
+ Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
+ Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index edfb236a91a..f1abe5c3e07 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -173,15 +173,7 @@ class Repository
end
def find_branch(name, fresh_repo: true)
- # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
- # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
- # a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
- # may cause the branch to "disappear" erroneously or have the wrong SHA.
- #
- # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
- raw_repo = fresh_repo ? initialize_raw_repository : raw_repository
-
- raw_repo.find_branch(name)
+ raw_repository.find_branch(name, fresh_repo)
end
def find_tag(name)
@@ -721,11 +713,11 @@ class Repository
end
def branch_names_contains(sha)
- refs_contains_sha('branch', sha)
+ raw_repository.branch_names_contains_sha(sha)
end
def tag_names_contains(sha)
- refs_contains_sha('tag', sha)
+ raw_repository.tag_names_contains_sha(sha)
end
def local_branches
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 05a16f11b59..7c8716f8c18 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -11,8 +11,6 @@ class Snippet < ActiveRecord::Base
include Editable
include Gitlab::SQL::Pattern
- extend Gitlab::CurrentSettings
-
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
cache_markdown_field :content
@@ -28,7 +26,7 @@ class Snippet < ActiveRecord::Base
default_content_html_invalidator || file_name_changed?
end
- default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility }
+ default_value_for(:visibility_level) { Gitlab::CurrentSettings.default_snippet_visibility }
belongs_to :author, class_name: 'User'
belongs_to :project
diff --git a/app/models/user.rb b/app/models/user.rb
index 89e787c3274..a57ba3740c9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,10 +2,8 @@ require 'carrierwave/orm/activerecord'
class User < ActiveRecord::Base
extend Gitlab::ConfigHelper
- extend Gitlab::CurrentSettings
include Gitlab::ConfigHelper
- include Gitlab::CurrentSettings
include Gitlab::SQL::Pattern
include AfterCommitQueue
include Avatarable
@@ -30,7 +28,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :rss_token
default_value_for :admin, false
- default_value_for(:external) { current_application_settings.user_default_external }
+ default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
default_value_for :can_create_group, gitlab_config.default_can_create_group
default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false
@@ -137,6 +135,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute'
+ has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
#
@@ -660,11 +659,11 @@ class User < ActiveRecord::Base
end
def allow_password_authentication_for_web?
- current_application_settings.password_authentication_enabled_for_web? && !ldap_user?
+ Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
end
def allow_password_authentication_for_git?
- current_application_settings.password_authentication_enabled_for_git? && !ldap_user?
+ Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !ldap_user?
end
def can_change_username?
@@ -792,7 +791,7 @@ class User < ActiveRecord::Base
# without this safeguard!
return unless has_attribute?(:projects_limit) && projects_limit.nil?
- self.projects_limit = current_application_settings.default_projects_limit
+ self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
end
def requires_ldap_check?
@@ -1215,7 +1214,7 @@ class User < ActiveRecord::Base
else
# Only revert these back to the default if they weren't specifically changed in this update.
self.can_create_group = gitlab_config.default_can_create_group unless can_create_group_changed?
- self.projects_limit = current_application_settings.default_projects_limit unless projects_limit_changed?
+ self.projects_limit = Gitlab::CurrentSettings.default_projects_limit unless projects_limit_changed?
end
end
@@ -1223,15 +1222,15 @@ class User < ActiveRecord::Base
valid = true
error = nil
- if current_application_settings.domain_blacklist_enabled?
- blocked_domains = current_application_settings.domain_blacklist
+ if Gitlab::CurrentSettings.domain_blacklist_enabled?
+ blocked_domains = Gitlab::CurrentSettings.domain_blacklist
if domain_matches?(blocked_domains, email)
error = 'is not from an allowed domain.'
valid = false
end
end
- allowed_domains = current_application_settings.domain_whitelist
+ allowed_domains = Gitlab::CurrentSettings.domain_whitelist
unless allowed_domains.blank?
if domain_matches?(allowed_domains, email)
valid = true
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
new file mode 100644
index 00000000000..e4b69382626
--- /dev/null
+++ b/app/models/user_callout.rb
@@ -0,0 +1,13 @@
+class UserCallout < ActiveRecord::Base
+ belongs_to :user
+
+ enum feature_name: {
+ gke_cluster_integration: 1
+ }
+
+ validates :user, presence: true
+ validates :feature_name,
+ presence: true,
+ uniqueness: { scope: :user_id },
+ inclusion: { in: UserCallout.feature_names.keys }
+end
diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb
index aa6f0e841c9..0521393dd27 100644
--- a/app/services/akismet_service.rb
+++ b/app/services/akismet_service.rb
@@ -1,6 +1,4 @@
class AkismetService
- include Gitlab::CurrentSettings
-
attr_accessor :owner, :text, :options
def initialize(owner, text, options = {})
@@ -41,12 +39,12 @@ class AkismetService
private
def akismet_client
- @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
+ @akismet_client ||= ::Akismet::Client.new(Gitlab::CurrentSettings.akismet_api_key,
Gitlab.config.gitlab.url)
end
def akismet_enabled?
- current_application_settings.akismet_enabled
+ Gitlab::CurrentSettings.akismet_enabled
end
def submit(type)
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index f40cd2b06c8..2b77f6be72a 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -1,7 +1,5 @@
module Auth
class ContainerRegistryAuthenticationService < BaseService
- extend Gitlab::CurrentSettings
-
AUDIENCE = 'container_registry'.freeze
def execute(authentication_abilities:)
@@ -32,7 +30,7 @@ module Auth
end
def self.token_expire_at
- Time.now + current_application_settings.container_registry_token_expire_delay.minutes
+ Time.now + Gitlab::CurrentSettings.container_registry_token_expire_delay.minutes
end
private
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index a0cb00dba58..6883ba36c71 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -1,6 +1,5 @@
class BaseService
include Gitlab::Allowable
- include Gitlab::CurrentSettings
attr_accessor :project, :current_user, :params
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index f832b79ef21..e09b445636f 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -2,8 +2,6 @@ module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
class RegisterJobService
- include Gitlab::CurrentSettings
-
attr_reader :runner
Result = Struct.new(:build, :valid?)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e6fd193ffb3..c037141fcde 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -1,6 +1,5 @@
class GitPushService < BaseService
attr_accessor :push_data, :push_commits
- include Gitlab::CurrentSettings
include Gitlab::Access
# The N most recent commits to process in a single push payload.
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index e77e08aa380..c6e52c3bb91 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,8 +1,6 @@
class GravatarService
- include Gitlab::CurrentSettings
-
def execute(email, size = nil, scale = 2, username: nil)
- return unless current_application_settings.gravatar_enabled?
+ return unless Gitlab::CurrentSettings.gravatar_enabled?
identifier = email.presence || username.presence
return unless identifier
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 22b9b91a957..2ae855d078b 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -1,7 +1,9 @@
module MergeRequests
class BuildService < MergeRequests::BaseService
+ include Gitlab::Utils::StrongMemoize
+
def execute
- @issue_iid = params.delete(:issue_iid)
+ @params_issue_iid = params.delete(:issue_iid)
self.merge_request = MergeRequest.new(params)
merge_request.compare_commits = []
@@ -123,7 +125,7 @@ module MergeRequests
#
def assign_title_and_description
assign_title_and_description_from_single_commit
- assign_title_from_issue
+ assign_title_from_issue if target_project.issues_enabled? || target_project.external_issue_tracker
merge_request.title ||= source_branch.titleize.humanize
merge_request.title = wip_title if compare_commits.empty?
@@ -132,9 +134,9 @@ module MergeRequests
end
def append_closes_description
- return unless issue_iid
+ return unless issue
- closes_issue = "Closes ##{issue_iid}"
+ closes_issue = "Closes #{issue.to_reference}"
if description.present?
merge_request.description += closes_issue.prepend("\n\n")
@@ -154,13 +156,27 @@ module MergeRequests
end
def assign_title_from_issue
- return unless issue && issue.is_a?(Issue)
+ return unless issue
+
+ merge_request.title = "Resolve \"#{issue.title}\"" if issue.is_a?(Issue)
- merge_request.title = "Resolve \"#{issue.title}\""
+ unless merge_request.title
+ branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize
+ merge_request.title = "Resolve #{issue_iid}"
+ merge_request.title += " \"#{branch_title}\"" unless branch_title.empty?
+ end
end
def issue_iid
- @issue_iid ||= source_branch.match(/\A(\d+)-/).try(:[], 1)
+ strong_memoize(:issue_iid) do
+ @params_issue_iid || begin
+ id = if target_project.external_issue_tracker
+ source_branch.match(target_project.external_issue_reference_pattern).try(:[], 0)
+ end
+
+ id || source_branch.match(/\A(\d+)-/).try(:[], 1)
+ end
+ end
end
def issue
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 262622f8bd0..18c40ce8992 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -90,6 +90,10 @@ module MergeRequests
merge_request.mark_as_unchecked
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
+
+ # Upcoming method calls need the refreshed version of
+ # @source_merge_requests diffs (for MergeRequest#commit_shas for instance).
+ merge_requests_for_source_branch(reload: true)
end
def reset_merge_when_pipeline_succeeds
@@ -195,7 +199,8 @@ module MergeRequests
merge_requests.uniq.select(&:source_project)
end
- def merge_requests_for_source_branch
+ def merge_requests_for_source_branch(reload: false)
+ @source_merge_requests = nil if reload
@source_merge_requests ||= merge_requests_for(@branch_name)
end
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index dcef8b66215..120d57a188d 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -7,8 +7,6 @@
#
module Projects
class HousekeepingService < BaseService
- include Gitlab::CurrentSettings
-
# Timeout set to 24h
LEASE_TIMEOUT = 86400
@@ -83,19 +81,19 @@ module Projects
end
def housekeeping_enabled?
- current_application_settings.housekeeping_enabled
+ Gitlab::CurrentSettings.housekeeping_enabled
end
def gc_period
- current_application_settings.housekeeping_gc_period
+ Gitlab::CurrentSettings.housekeeping_gc_period
end
def full_repack_period
- current_application_settings.housekeeping_full_repack_period
+ Gitlab::CurrentSettings.housekeeping_full_repack_period
end
def repack_period
- current_application_settings.housekeeping_incremental_repack_period
+ Gitlab::CurrentSettings.housekeeping_incremental_repack_period
end
end
end
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index a773222bf17..c760bd3b626 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -1,7 +1,5 @@
module Projects
class UpdatePagesService < BaseService
- include Gitlab::CurrentSettings
-
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'.freeze
@@ -134,7 +132,7 @@ module Projects
end
def max_size
- max_pages_size = current_application_settings.max_pages_size.megabytes
+ max_pages_size = Gitlab::CurrentSettings.max_pages_size.megabytes
return MAX_SIZE if max_pages_size.zero?
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index ff4c73c886e..0e235a6d2a0 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -34,7 +34,7 @@ module Projects
def run_auto_devops_pipeline?
return false if project.repository.gitlab_ci_yml || !project.auto_devops.previous_changes.include?('enabled')
- project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && current_application_settings.auto_devops_enabled?)
+ project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?)
end
private
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 14171bce782..2623f253d98 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -11,10 +11,8 @@ class SubmitUsagePingService
percentage_projects_prometheus_active leader_service_desk_issues instance_service_desk_issues
percentage_service_desk_issues].freeze
- include Gitlab::CurrentSettings
-
def execute
- return false unless current_application_settings.usage_ping_enabled?
+ return false unless Gitlab::CurrentSettings.usage_ping_enabled?
response = HTTParty.post(
URL,
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 06b23cd7076..2253d638e93 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -22,8 +22,7 @@ module SystemNoteService
commits_text = "#{total_count} commit".pluralize(total_count)
body = "added #{commits_text}\n\n"
- body << existing_commit_summary(noteable, existing_commits, oldrev)
- body << new_commit_summary(new_commits).join("\n")
+ body << commits_list(noteable, new_commits, existing_commits, oldrev)
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
@@ -481,7 +480,7 @@ module SystemNoteService
# Returns an Array of Strings
def new_commit_summary(new_commits)
new_commits.collect do |commit|
- "* #{commit.short_id} - #{escape_html(commit.title)}"
+ content_tag('li', "#{commit.short_id} - #{commit.title}")
end
end
@@ -604,6 +603,16 @@ module SystemNoteService
"#{cross_reference_note_prefix}#{gfm_reference}"
end
+ # Builds a list of existing and new commits according to existing_commits and
+ # new_commits methods.
+ # Returns a String wrapped in `ul` and `li` tags.
+ def commits_list(noteable, new_commits, existing_commits, oldrev)
+ existing_commit_summary = existing_commit_summary(noteable, existing_commits, oldrev)
+ new_commit_summary = new_commit_summary(new_commits).join
+
+ content_tag('ul', "#{existing_commit_summary}#{new_commit_summary}".html_safe)
+ end
+
# Build a single line summarizing existing commits being added in a merge
# request
#
@@ -640,11 +649,8 @@ module SystemNoteService
branch = noteable.target_branch
branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
- "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
- end
-
- def escape_html(text)
- Rack::Utils.escape_html(text)
+ branch_name = content_tag('code', branch)
+ content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
end
def url_helpers
@@ -661,4 +667,8 @@ module SystemNoteService
start_sha: oldrev
)
end
+
+ def content_tag(*args)
+ ActionController::Base.helpers.content_tag(*args)
+ end
end
diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb
index 76700dfcdee..d5a9b344905 100644
--- a/app/services/upload_service.rb
+++ b/app/services/upload_service.rb
@@ -1,6 +1,4 @@
class UploadService
- include Gitlab::CurrentSettings
-
def initialize(model, file, uploader_class = FileUploader)
@model, @file, @uploader_class = model, file, uploader_class
end
@@ -17,6 +15,6 @@ class UploadService
private
def max_attachment_size
- current_application_settings.max_attachment_size.megabytes.to_i
+ Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 61f1568f366..4fb6d221909 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -1,7 +1,5 @@
module Users
class BuildService < BaseService
- include Gitlab::CurrentSettings
-
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
@@ -34,7 +32,7 @@ module Users
private
def can_create_user?
- (current_user.nil? && current_application_settings.allow_signup?) || current_user&.admin?
+ (current_user.nil? && Gitlab::CurrentSettings.allow_signup?) || current_user&.admin?
end
# Allowed params for creating a user (admins only)
@@ -102,7 +100,7 @@ module Users
end
def skip_user_confirmation_email_from_setting
- !current_application_settings.send_user_confirmation_email
+ !Gitlab::CurrentSettings.send_user_confirmation_email
end
end
end
diff --git a/app/views/admin/broadcast_messages/preview.js.haml b/app/views/admin/broadcast_messages/preview.js.haml
deleted file mode 100644
index c72e59640d7..00000000000
--- a/app/views/admin/broadcast_messages/preview.js.haml
+++ /dev/null
@@ -1 +0,0 @@
-$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
diff --git a/app/views/admin/conversational_development_index/show.html.haml b/app/views/admin/conversational_development_index/show.html.haml
index 30dd87f0463..ed40e7b4d00 100644
--- a/app/views/admin/conversational_development_index/show.html.haml
+++ b/app/views/admin/conversational_development_index/show.html.haml
@@ -6,7 +6,7 @@
= render 'callout'
.prepend-top-default
- - if !current_application_settings.usage_ping_enabled
+ - if !Gitlab::CurrentSettings.usage_ping_enabled
= render 'disabled'
- elsif @metric.blank?
= render 'no_data'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index d251f75a8fd..e3711421b61 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -119,7 +119,7 @@
.well-segment.admin-well
%h4
Components
- - if current_application_settings.version_check_enabled
+ - if Gitlab::CurrentSettings.version_check_enabled
.pull-right
= version_status_badge
%p
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index 10a3bed0a4f..e31fb58b205 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -8,7 +8,7 @@
.pull-left
%p
#{ s_('HealthCheck|Access token is') }
- %code#health-check-token= current_application_settings.health_check_access_token
+ %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token
.prepend-top-10
= button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
@@ -18,11 +18,11 @@
= link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check')
%ul
%li
- %code= readiness_url(token: current_application_settings.health_check_access_token)
+ %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li
- %code= liveness_url(token: current_application_settings.health_check_access_token)
+ %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li
- %code= metrics_url(token: current_application_settings.health_check_access_token)
+ %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
%hr
.panel.panel-default
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 4f60be698e9..1e52646b1cc 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -36,7 +36,7 @@
data: { confirm: _("Are you sure you want to reset registration token?") }
= render partial: 'ci/runner/how_to_setup_runner',
- locals: { registration_token: current_application_settings.runners_registration_token,
+ locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
type: 'shared' }
.append-bottom-20.clearfix
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
new file mode 100644
index 00000000000..495a55660cb
--- /dev/null
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -0,0 +1,49 @@
+- form_field = local_assigns.fetch(:form_field, nil)
+- variable = local_assigns.fetch(:variable, nil)
+- only_key_value = local_assigns.fetch(:only_key_value, false)
+
+- id = variable&.id
+- key = variable&.key
+- value = variable&.value
+- is_protected = variable && !only_key_value ? variable.protected : true
+
+- id_input_name = "#{form_field}[variables_attributes][][id]"
+- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
+- key_input_name = "#{form_field}[variables_attributes][][key]"
+- value_input_name = "#{form_field}[variables_attributes][][value]"
+- protected_input_name = "#{form_field}[variables_attributes][][protected]"
+
+%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
+ .ci-variable-row-body
+ %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
+ %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
+ %input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text",
+ name: key_input_name,
+ value: key,
+ placeholder: s_('CiVariables|Input variable key') }
+ .ci-variable-body-item
+ .form-control.js-secret-value-placeholder{ class: ('hide' unless id) }
+ = '*' * 20
+ %textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id),
+ rows: 1,
+ name: value_input_name,
+ placeholder: s_('CiVariables|Input variable value') }
+ = value
+ - unless only_key_value
+ .ci-variable-body-item.ci-variable-protected-item
+ .append-right-default
+ = s_("CiVariable|Protected")
+ %button{ type: 'button',
+ class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_protected}",
+ "aria-label": s_("CiVariable|Toggle protected") }
+ %input{ type: "hidden",
+ class: 'js-ci-variable-input-protected js-project-feature-toggle-input',
+ name: protected_input_name,
+ value: is_protected }
+ %span.toggle-icon
+ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
+ = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
+ -# EE-specific start
+ -# EE-specific end
+ %button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
+ = icon('minus-circle')
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index cebdbab4e74..617c20b9635 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,5 +1,5 @@
.top-area
- %ul.nav-links
+ %ul.nav-links.mobile-separator
= nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: _("Your groups") do
Your groups
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 9038c4fbebd..449a2ce625e 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -4,7 +4,7 @@
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
- %ul.nav-links.scrolling-tabs
+ %ul.nav-links.scrolling-tabs.mobile-separator
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects
diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml
index c18077bc66f..97f854cc5f0 100644
--- a/app/views/dashboard/projects/_nav.html.haml
+++ b/app/views/dashboard/projects/_nav.html.haml
@@ -1,5 +1,5 @@
.nav-block
- %ul.nav-links
+ %ul.nav-links.mobile-separator
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to s_('DashboardProjects|All'), dashboard_projects_path
= nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 20ca6ec969a..664966989db 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -4,7 +4,7 @@
- if current_user.todos.any?
.top-area
- %ul.nav-links
+ %ul.nav-links.mobile-separator
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do
%span
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
index fb70d158096..79826a364db 100644
--- a/app/views/devise/confirmations/almost_there.haml
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -4,9 +4,9 @@
%p.lead.append-bottom-20
Please check your email to confirm your account
%hr
-- if current_application_settings.after_sign_up_text.present?
+- if Gitlab::CurrentSettings.after_sign_up_text.present?
.well-confirmation.text-center
- = markdown_field(current_application_settings, :after_sign_up_text)
+ = markdown_field(Gitlab::CurrentSettings, :after_sign_up_text)
%p.text-center
No confirmation email received? Please check your spam folder or
.append-bottom-20.prepend-top-20.text-center
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index fdd72ead2cb..63811ea1c81 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,8 +1,8 @@
= webpack_bundle_tag 'docs'
%div
-- if current_application_settings.help_page_text.present?
- = markdown_field(current_application_settings, :help_page_text)
+- if Gitlab::CurrentSettings.help_page_text.present?
+ = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
%h1
@@ -14,7 +14,7 @@
= version_status_badge
%hr
-- unless current_application_settings.help_page_hide_commercial_content?
+- unless Gitlab::CurrentSettings.help_page_hide_commercial_content?
%p.slead
GitLab is open source software to collaborate on code.
%br
@@ -46,6 +46,6 @@
%li
%button.btn-blank.btn-link.js-trigger-shortcut{ type: 'button' }
Use shortcuts
- - unless current_application_settings.help_page_hide_commercial_content?
+ - unless Gitlab::CurrentSettings.help_page_hide_commercial_content?
%li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/'
%li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare'
diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml
index 04e2d4b63e6..bb7f9ba7ae4 100644
--- a/app/views/koding/index.html.haml
+++ b/app/views/koding/index.html.haml
@@ -3,4 +3,4 @@
= icon('circle', class: 'cgreen')
Integration is active for
= link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do
- #{current_application_settings.koding_url}
+ #{Gitlab::CurrentSettings.koding_url}
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index ea13a5e6d62..0c979109b3f 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -41,12 +41,14 @@
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "main"
- = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
+ = webpack_bundle_tag "raven" if Gitlab::CurrentSettings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
+ = webpack_controller_bundle_tags
+
= yield :project_javascripts
= csrf_meta_tags
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 4e9ea33e675..257f7326409 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -15,7 +15,7 @@
.col-sm-7.brand-holder.pull-left
%h1
= brand_title
- = brand_image
+ = brand_image
- if brand_item&.description?
= brand_text
- else
@@ -26,8 +26,8 @@
Perform code reviews and enhance collaboration with merge requests.
Each project can also have an issue tracker and a wiki.
- - if current_application_settings.sign_in_text.present?
- = markdown_field(current_application_settings, :sign_in_text)
+ - if Gitlab::CurrentSettings.sign_in_text.present?
+ = markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text)
%hr.footer-fixed
.container.footer-container
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index a5a62a0695f..c878fcf2808 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -28,7 +28,7 @@
= link_to profile_account_path do
%strong.fly-out-top-item-name
#{ _('Account') }
- - if current_application_settings.user_oauth_applications?
+ - if Gitlab::CurrentSettings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path do
.nav-icon-container
diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml
index 3e36da31ea3..94bd6f96dbc 100644
--- a/app/views/notify/_note_email.html.haml
+++ b/app/views/notify/_note_email.html.haml
@@ -22,7 +22,7 @@
- else
commented on a #{link_to 'discussion', @target_url}
-- elsif current_application_settings.email_author_in_body
+- elsif Gitlab::CurrentSettings.email_author_in_body
%p.details
#{link_to @note.author_name, user_url(@note.author)} commented:
diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb
index cb2e7fab6d5..c319cb55e87 100644
--- a/app/views/notify/_note_email.text.erb
+++ b/app/views/notify/_note_email.text.erb
@@ -12,7 +12,7 @@
<%= ":" -%>
-<% elsif current_application_settings.email_author_in_body -%>
+<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
<%= "#{@note.author_name} commented:" -%>
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index eb5157ccac9..e6cdaf85c0d 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,4 +1,4 @@
-- if current_application_settings.email_author_in_body
+- if Gitlab::CurrentSettings.email_author_in_body
%p.details
#{link_to @issue.author_name, user_url(@issue.author)} created an issue:
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 951c96bdb9c..0a9adc6f243 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -1,4 +1,4 @@
-- if current_application_settings.email_author_in_body
+- if Gitlab::CurrentSettings.email_author_in_body
%p.details
#{link_to @merge_request.author_name, user_url(@merge_request.author)} created a merge request:
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index 00e1b5faae3..db4424a01f9 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -1,7 +1,7 @@
%p
Hi #{@user['name']}!
%p
- - if current_application_settings.allow_signup?
+ - if Gitlab::CurrentSettings.allow_signup?
Your account has been created successfully.
- else
The Administrator created an account for you. Now you are a member of the company GitLab application.
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index e759c87bda7..5dfe973f33c 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -1,4 +1,4 @@
-- return unless current_application_settings.project_export_enabled?
+- return unless Gitlab::CurrentSettings.project_export_enabled?
- project = local_assigns.fetch(:project)
- expanded = Rails.env.test?
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 90272ad9554..64259669c19 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -6,7 +6,7 @@
- link = commit_path(project, commit, merge_request: merge_request)
- cache_key = [project.full_path,
commit.id,
- current_application_settings,
+ Gitlab::CurrentSettings.current_application_settings,
@path.presence,
current_controller?(:commits),
merge_request&.iid,
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 857ae00d0ab..ff440e99042 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -22,14 +22,20 @@
= f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
- .form-group
+ .form-group.js-ci-variable-list-section
.col-md-9
%label.label-light
#{ s_('PipelineSchedules|Variables') }
- %ul.js-pipeline-variable-list.pipeline-variable-list
- - @schedule.variables.each do |variable|
- = render 'variable_row', id: variable.id, key: variable.key, value: variable.value
- = render 'variable_row'
+ %ul.ci-variable-list
+ - @schedule.variables.each do |variable|
+ = render 'ci/variables/variable_row', form_field: 'schedule', variable: variable, only_key_value: true
+ = render 'ci/variables/variable_row', form_field: 'schedule', only_key_value: true
+ - if @schedule.variables.size > 0
+ %button.btn.btn-info.btn-inverted.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" } }
+ - if @schedule.variables.size == 0
+ = n_('Hide value', 'Hide values', @schedule.variables.size)
+ - else
+ = n_('Reveal value', 'Reveal values', @schedule.variables.size)
.form-group
.col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
diff --git a/app/views/projects/pipeline_schedules/_tabs.html.haml b/app/views/projects/pipeline_schedules/_tabs.html.haml
index 7fcb624a9dd..8996c1b3e38 100644
--- a/app/views/projects/pipeline_schedules/_tabs.html.haml
+++ b/app/views/projects/pipeline_schedules/_tabs.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links
+%ul.nav-links.mobile-separator
%li{ class: active_when(scope.nil?) }>
= link_to schedule_path_proc.call(nil) do
= s_("PipelineSchedules|All")
diff --git a/app/views/projects/pipeline_schedules/_variable_row.html.haml b/app/views/projects/pipeline_schedules/_variable_row.html.haml
deleted file mode 100644
index 564cb5d1ca9..00000000000
--- a/app/views/projects/pipeline_schedules/_variable_row.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- id = local_assigns.fetch(:id, nil)
-- key = local_assigns.fetch(:key, "")
-- value = local_assigns.fetch(:value, "")
-%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
- .pipeline-variable-row-body
- %input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
- %input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
- %input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
- name: "schedule[variables_attributes][][key]",
- value: key,
- placeholder: s_('PipelineSchedules|Input variable key') }
- %textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
- name: "schedule[variables_attributes][][value]",
- placeholder: s_('PipelineSchedules|Input variable value') }
- = value
- %button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
- %i.fa.fa-minus-circle{ 'aria-hidden': "true" }
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 398a1c46746..5de17977d5a 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -1,7 +1,7 @@
- failed_builds = @pipeline.statuses.latest.failed
.tabs-holder
- %ul.pipelines-tabs.nav-links.no-top.no-bottom
+ %ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator
%li.js-pipeline-tab-link
= link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
Pipeline
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index c5f9f5aa15b..646c01c0989 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -31,7 +31,7 @@
.radio
= form.label :enabled_ do
= form.radio_button :enabled, ''
- %strong Instance default (#{current_application_settings.auto_devops_enabled? ? 'enabled' : 'disabled'})
+ %strong Instance default (#{Gitlab::CurrentSettings.auto_devops_enabled? ? 'enabled' : 'disabled'})
%br
%span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific <code>.gitlab-ci.yml</code>.
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 67607e4e9c6..b037b57e78a 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -1,8 +1,8 @@
%h3 Shared Runners
.bs-callout.bs-callout-warning.shared-runners-description
- - if current_application_settings.shared_runners_text.present?
- = markdown_field(current_application_settings, :shared_runners_text)
+ - if Gitlab::CurrentSettings.shared_runners_text.present?
+ = markdown_field(Gitlab::CurrentSettings.current_application_settings, :shared_runners_text)
- else
GitLab Shared Runners execute code of different projects on the same Runner
unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 151aad306a0..e7fa7477e0c 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,11 +1,14 @@
-%ul.nav-links.event-filter.scrolling-tabs
- = event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all')
- - if event_filter_visible(:repository)
- = event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events')
- - if event_filter_visible(:merge_requests)
- = event_filter_link EventFilter.merged, _('Merge events'), s_('EventFilterBy|Filter by merge events')
- - if event_filter_visible(:issues)
- = event_filter_link EventFilter.issue, _('Issue events'), s_('EventFilterBy|Filter by issue events')
- - if comments_visible?
- = event_filter_link EventFilter.comments, _('Comments'), s_('EventFilterBy|Filter by comments')
- = event_filter_link EventFilter.team, _('Team'), s_('EventFilterBy|Filter by team')
+.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ %ul.nav-links.event-filter.scrolling-tabs
+ = event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all')
+ - if event_filter_visible(:repository)
+ = event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events')
+ - if event_filter_visible(:merge_requests)
+ = event_filter_link EventFilter.merged, _('Merge events'), s_('EventFilterBy|Filter by merge events')
+ - if event_filter_visible(:issues)
+ = event_filter_link EventFilter.issue, _('Issue events'), s_('EventFilterBy|Filter by issue events')
+ - if comments_visible?
+ = event_filter_link EventFilter.comments, _('Comments'), s_('EventFilterBy|Filter by comments')
+ = event_filter_link EventFilter.team, _('Team'), s_('EventFilterBy|Filter by team')
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index db2ac1e1d12..034b76b978f 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links
+%ul.nav-links.mobile-separator
%li{ class: milestone_class_for_state(params[:state], 'opened', true) }>
= link_to milestones_filter_path(state: 'opened') do
Open
diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml
index 639f28cc210..0b003125912 100644
--- a/app/views/shared/builds/_tabs.html.haml
+++ b/app/views/shared/builds/_tabs.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links
+%ul.nav-links.mobile-separator
%li{ class: active_when(scope.nil?) }>
= link_to build_path_proc.call(nil) do
All
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 6d8a4668cec..4d8109eb90c 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -1,7 +1,7 @@
- type = local_assigns.fetch(:type, :issues)
- page_context_word = type.to_s.humanize(capitalize: false)
-%ul.nav-links.issues-state-filters
+%ul.nav-links.issues-state-filters.mobile-separator
%li{ class: active_when(params[:state] == 'opened') }>
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
#{issuables_state_counter_text(type, :opened)}
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
index 8b6a98a054a..65aa4fbc757 100644
--- a/app/views/snippets/_snippets_scope_menu.html.haml
+++ b/app/views/snippets/_snippets_scope_menu.html.haml
@@ -1,7 +1,7 @@
- subject = local_assigns.fetch(:subject, current_user)
- include_private = local_assigns.fetch(:include_private, false)
-.nav-links.snippet-scope-menu
+.nav-links.snippet-scope-menu.mobile-separator
%li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do
All
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 8e26275669e..7ba224d74c8 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -1,6 +1,5 @@
class GitGarbageCollectWorker
include ApplicationWorker
- include Gitlab::CurrentSettings
sidekiq_options retry: false
@@ -102,7 +101,7 @@ class GitGarbageCollectWorker
end
def bitmaps_enabled?
- current_application_settings.housekeeping_bitmaps_enabled
+ Gitlab::CurrentSettings.housekeeping_bitmaps_enabled
end
def git(write_bitmaps:)
diff --git a/changelogs/unreleased/24167__color_label.yml b/changelogs/unreleased/24167__color_label.yml
new file mode 100644
index 00000000000..68c6c731163
--- /dev/null
+++ b/changelogs/unreleased/24167__color_label.yml
@@ -0,0 +1,5 @@
+---
+title: Add Colors to GitLab Flavored Markdown
+merge_request: 16095
+author: Tony Rom <thetonyrom@gmail.com>
+type: added
diff --git a/changelogs/unreleased/25327-coverage-badge-rounding.yml b/changelogs/unreleased/25327-coverage-badge-rounding.yml
new file mode 100644
index 00000000000..ea985689484
--- /dev/null
+++ b/changelogs/unreleased/25327-coverage-badge-rounding.yml
@@ -0,0 +1,5 @@
+---
+title: Show coverage to two decimal points in coverage badge
+merge_request: 10083
+author: Jeff Stubler
+type: changed
diff --git a/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml b/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml
new file mode 100644
index 00000000000..4fd6b6fddc4
--- /dev/null
+++ b/changelogs/unreleased/32283-trending-projects-unique-constraint2.yml
@@ -0,0 +1,5 @@
+---
+title: Add unique constraint to trending_projects#project_id.
+merge_request: 16846
+author:
+type: other
diff --git a/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml b/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml
new file mode 100644
index 00000000000..5c45d0db602
--- /dev/null
+++ b/changelogs/unreleased/39985-enable-prometheus-metrics-for-deployed-ingresses.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Prometheus metrics for deployed Ingresses
+merge_request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16866
+author: joshlambert
+type: changed
diff --git a/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
new file mode 100644
index 00000000000..9e4811ca308
--- /dev/null
+++ b/changelogs/unreleased/40552-sanitize-extra-blank-spaces-used-when-uploading-a-ssh-key.yml
@@ -0,0 +1,5 @@
+---
+title: Sanitize extra blank spaces used when uploading a SSH key
+merge_request: 40552
+author:
+type: fixed
diff --git a/changelogs/unreleased/40793-fix-mr-title-for-jira.yml b/changelogs/unreleased/40793-fix-mr-title-for-jira.yml
new file mode 100644
index 00000000000..69461510a87
--- /dev/null
+++ b/changelogs/unreleased/40793-fix-mr-title-for-jira.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent JIRA issue identifier from being humanized.
+merge_request: 16491
+author: Andrew McCallum
+type: fixed
diff --git a/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml b/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml
new file mode 100644
index 00000000000..d7a8b6e6f81
--- /dev/null
+++ b/changelogs/unreleased/42270-fix-namespace-remove-exports-for-hashed-storage.yml
@@ -0,0 +1,6 @@
+---
+title: Fix export removal for hashed-storage projects within a renamed or deleted
+ namespace
+merge_request: 16658
+author:
+type: fixed
diff --git a/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml b/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml
new file mode 100644
index 00000000000..c46cc86c99b
--- /dev/null
+++ b/changelogs/unreleased/42696-gitlab-import-leaves-group_id-on-projectlabel.yml
@@ -0,0 +1,5 @@
+---
+title: Fix GitLab import leaving group_id on ProjectLabel
+merge_request: 16877
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
new file mode 100644
index 00000000000..378f0ef7ce9
--- /dev/null
+++ b/changelogs/unreleased/bvl-fix-500-on-fork-without-restricted-visibility-levels.yml
@@ -0,0 +1,5 @@
+---
+title: Fix forking projects when no restricted visibility levels are defined applicationwide
+merge_request: 16881
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-adjust-button-group-width-on-mobile.yml b/changelogs/unreleased/fix-adjust-button-group-width-on-mobile.yml
new file mode 100644
index 00000000000..b79ec2944fd
--- /dev/null
+++ b/changelogs/unreleased/fix-adjust-button-group-width-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Change button group width on mobile
+merge_request: 16726
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/issue-42689-new-file-template.yml b/changelogs/unreleased/issue-42689-new-file-template.yml
new file mode 100644
index 00000000000..d6b77b87605
--- /dev/null
+++ b/changelogs/unreleased/issue-42689-new-file-template.yml
@@ -0,0 +1,5 @@
+---
+title: Trigger change event on filename input when file template is applied
+merge_request: 16911
+author: Sebastian Klingler
+type: fixed
diff --git a/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml b/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml
new file mode 100644
index 00000000000..b2c1cd9710a
--- /dev/null
+++ b/changelogs/unreleased/osw-markdown-bypass-for-commit-messages.yml
@@ -0,0 +1,5 @@
+---
+title: Bypass commits title markdown on notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-system-notes-for-commits-regression.yml b/changelogs/unreleased/osw-system-notes-for-commits-regression.yml
new file mode 100644
index 00000000000..6d0943c7716
--- /dev/null
+++ b/changelogs/unreleased/osw-system-notes-for-commits-regression.yml
@@ -0,0 +1,5 @@
+---
+title: Reload MRs memoization after diffs creation
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/persistent-callouts.yml b/changelogs/unreleased/persistent-callouts.yml
new file mode 100644
index 00000000000..ca949a3b96c
--- /dev/null
+++ b/changelogs/unreleased/persistent-callouts.yml
@@ -0,0 +1,5 @@
+---
+title: Add backend for persistently dismissably callouts
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/query-counts.yml b/changelogs/unreleased/query-counts.yml
new file mode 100644
index 00000000000..e01ff8a4ad8
--- /dev/null
+++ b/changelogs/unreleased/query-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Track and act upon the number of executed queries
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml b/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml
new file mode 100644
index 00000000000..d43675e175d
--- /dev/null
+++ b/changelogs/unreleased/refactor-ci-variable-list-for-future-usage-in-4110.yml
@@ -0,0 +1,5 @@
+---
+title: Hide variable values on pipeline schedule edit page
+merge_request: 16729
+author:
+type: changed
diff --git a/config/initializers/query_limiting.rb b/config/initializers/query_limiting.rb
new file mode 100644
index 00000000000..66864d1898e
--- /dev/null
+++ b/config/initializers/query_limiting.rb
@@ -0,0 +1,9 @@
+if Gitlab::QueryLimiting.enable?
+ require_dependency 'gitlab/query_limiting/active_support_subscriber'
+ require_dependency 'gitlab/query_limiting/transaction'
+ require_dependency 'gitlab/query_limiting/middleware'
+
+ Gitlab::Application.configure do |config|
+ config.middleware.use(Gitlab::QueryLimiting::Middleware)
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index f162043dd5e..e72ea1881cd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -60,6 +60,9 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update]
end
+
+ # UserCallouts
+ resources :user_callouts, only: [:create]
end
# Koding route
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 783677b5b8d..7f3fe551a03 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -3,6 +3,7 @@
var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
+var glob = require('glob');
var webpack = require('webpack');
var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin');
@@ -20,6 +21,26 @@ var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var NO_COMPRESSION = process.env.NO_COMPRESSION;
+// generate automatic entry points
+var autoEntries = {};
+var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
+
+// filter out entries currently imported dynamically in dispatcher.js
+var dispatcher = fs.readFileSync(path.join(ROOT_PATH, 'app/assets/javascripts/dispatcher.js')).toString();
+var dispatcherChunks = dispatcher.match(/(?!import\('.\/)pages\/[^']+/g);
+
+pageEntries.forEach(( path ) => {
+ let chunkPath = path.replace(/\/index\.js$/, '');
+ if (!dispatcherChunks.includes(chunkPath)) {
+ let chunkName = chunkPath.replace(/\//g, '.');
+ autoEntries[chunkName] = './' + path;
+ }
+});
+
+// report our auto-generated bundle count
+var autoEntriesCount = Object.keys(autoEntries).length;
+console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`);
+
var config = {
// because sqljs requires fs.
node: {
@@ -301,6 +322,8 @@ var config = {
}
}
+config.entry = Object.assign({}, autoEntries, config.entry);
+
if (IS_PRODUCTION) {
config.devtool = 'source-map';
config.plugins.push(
diff --git a/db/migrate/20180125214301_create_user_callouts.rb b/db/migrate/20180125214301_create_user_callouts.rb
new file mode 100644
index 00000000000..856eff36ae0
--- /dev/null
+++ b/db/migrate/20180125214301_create_user_callouts.rb
@@ -0,0 +1,16 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateUserCallouts < ActiveRecord::Migration
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :user_callouts do |t|
+ t.integer :feature_name, null: false
+ t.references :user, index: true, foreign_key: { on_delete: :cascade }, null: false
+ end
+
+ add_index :user_callouts, [:user_id, :feature_name], unique: true
+ end
+end
diff --git a/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb b/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb
new file mode 100644
index 00000000000..02e53b8fa8a
--- /dev/null
+++ b/db/migrate/20180201102129_add_unique_constraint_to_trending_projects_project_id.rb
@@ -0,0 +1,19 @@
+class AddUniqueConstraintToTrendingProjectsProjectId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :trending_projects, :project_id, unique: true, name: 'index_trending_projects_on_project_id_unique'
+ remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id'
+ rename_index :trending_projects, 'index_trending_projects_on_project_id_unique', 'index_trending_projects_on_project_id'
+ end
+
+ def down
+ rename_index :trending_projects, 'index_trending_projects_on_project_id', 'index_trending_projects_on_project_id_old'
+ add_concurrent_index :trending_projects, :project_id
+ remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id_old'
+ end
+end
diff --git a/db/post_migrate/20171207150300_remove_project_labels_group_id_copy.rb b/db/post_migrate/20171207150300_remove_project_labels_group_id_copy.rb
new file mode 100644
index 00000000000..2f339172eeb
--- /dev/null
+++ b/db/post_migrate/20171207150300_remove_project_labels_group_id_copy.rb
@@ -0,0 +1,21 @@
+# Copy of 20180202111106 - this one should run before 20171207150343 to fix issues related to
+# the removal of groups with labels.
+
+class RemoveProjectLabelsGroupIdCopy < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # rubocop:disable Migration/UpdateColumnInBatches
+ update_column_in_batches(:labels, :group_id, nil) do |table, query|
+ query.where(table[:type].eq('ProjectLabel').and(table[:group_id].not_eq(nil)))
+ end
+ # rubocop:enable Migration/UpdateColumnInBatches
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20180202111106_remove_project_labels_group_id.rb b/db/post_migrate/20180202111106_remove_project_labels_group_id.rb
new file mode 100644
index 00000000000..db7fd0d167d
--- /dev/null
+++ b/db/post_migrate/20180202111106_remove_project_labels_group_id.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveProjectLabelsGroupId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ update_column_in_batches(:labels, :group_id, nil) do |table, query|
+ query.where(table[:type].eq('ProjectLabel').and(table[:group_id].not_eq(nil)))
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b09e06726f8..50a2ceaaeee 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180201145907) do
+ActiveRecord::Schema.define(version: 20180202111106) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1727,7 +1727,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do
t.integer "project_id", null: false
end
- add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree
+ add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", unique: true, using: :btree
create_table "u2f_registrations", force: :cascade do |t|
t.text "certificate"
@@ -1771,6 +1771,14 @@ ActiveRecord::Schema.define(version: 20180201145907) do
add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
+ create_table "user_callouts", force: :cascade do |t|
+ t.integer "feature_name", null: false
+ t.integer "user_id", null: false
+ end
+
+ add_index "user_callouts", ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
+ add_index "user_callouts", ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
+
create_table "user_custom_attributes", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -2042,6 +2050,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
+ add_foreign_key "user_callouts", "users", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 9bcd13a52f7..e6c8f59549f 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -13,7 +13,7 @@ override certain values.
Variable | Type | Description
-------- | ---- | -----------
-`GITLAB_CDN_HOST` | string | Sets the hostname for a CDN to serve static assets (e.g. `mycdnsubdomain.fictional-cdn.com`)
+`GITLAB_CDN_HOST` | string | Sets the base URL for a CDN to serve static assets (e.g. `//mycdnsubdomain.fictional-cdn.com`)
`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
diff --git a/doc/development/README.md b/doc/development/README.md
index 12cca9f84b7..45e9565f9a7 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -75,6 +75,7 @@ comments: false
- [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md)
- [Database Debugging and Troubleshooting](database_debugging.md)
+- [Query Count Limits](query_count_limits.md)
## Testing guides
diff --git a/doc/development/query_count_limits.md b/doc/development/query_count_limits.md
new file mode 100644
index 00000000000..ebb6e0c2dac
--- /dev/null
+++ b/doc/development/query_count_limits.md
@@ -0,0 +1,65 @@
+# Query Count Limits
+
+Each controller or API endpoint is allowed to execute up to 100 SQL queries. In
+a production environment we'll only log an error in case this threshold is
+exceeded, but in a test environment we'll raise an error instead.
+
+## Solving Failing Tests
+
+When a test fails because it executes more than 100 SQL queries there are two
+solutions to this problem:
+
+1. Reduce the number of SQL queries that are executed.
+2. Whitelist the controller or API endpoint.
+
+You should only resort to whitelisting when an existing controller or endpoint
+is to blame as in this case reducing the number of SQL queries can take a lot of
+effort. Newly added controllers and endpoints are not allowed to execute more
+than 100 SQL queries and no exceptions will be made for this rule. _If_ a large
+number of SQL queries is necessary to perform certain work it's best to have
+this work performed by Sidekiq instead of doing this directly in a web request.
+
+## Whitelisting
+
+In the event that you _have_ to whitelist a controller you'll first need to
+create an issue. This issue should (preferably in the title) mention the
+controller or endpoint and include the appropriate labels (`database`,
+`performance`, and at least a team specific label such as `Discussion`).
+
+Once the issue has been created you can whitelist the code in question. For
+Rails controllers it's best to create a `before_action` hook that runs as early
+as possible. The called method in turn should call
+`Gitlab::QueryLimiting.whitelist('issue URL here')`. For example:
+
+```ruby
+class MyController < ApplicationController
+ before_action :whitelist_query_limiting, only: [:show]
+
+ def index
+ # ...
+ end
+
+ def show
+ # ...
+ end
+
+ def whitelist_query_limiting
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/...')
+ end
+end
+```
+
+By using a `before_action` you don't have to modify the controller method in
+question, reducing the likelihood of merge conflicts.
+
+For Grape API endpoints there unfortunately is not a reliable way of running a
+hook before a specific endpoint. This means that you have to add the whitelist
+call directly into the endpoint like so:
+
+```ruby
+get '/projects/:id/foo' do
+ Gitlab::QueryLimiting.whitelist('...')
+
+ # ...
+end
+```
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 552abac747b..b590dfa0d40 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -253,7 +253,7 @@ GFM will recognize the following:
| `@user_name` | specific user |
| `@group_name` | specific group |
| `@all` | entire team |
-| `#123` | issue |
+| `#12345` | issue |
| `!123` | merge request |
| `$123` | snippet |
| `~123` | label by ID |
@@ -379,6 +379,45 @@ _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._
>**Note:**
This also works for the asciidoctor `:stem: latexmath`. For details see the [asciidoctor user manual][asciidoctor-manual].
+### Colors
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colors
+
+It is possible to have color written in HEX, RGB or HSL format rendered with a color indicator.
+
+Color written inside backticks will be followed by a color "chip".
+
+Examples:
+
+ `#F00`
+ `#F00A`
+ `#FF0000`
+ `#FF0000AA`
+ `RGB(0,255,0)`
+ `RGB(0%,100%,0%)`
+ `RGBA(0,255,0,0.7)`
+ `HSL(540,70%,50%)`
+ `HSLA(540,70%,50%,0.7)`
+
+Becomes:
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.7)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.7)`
+
+#### Supported formats:
+
+* HEX: `` `#RGB[A]` `` or `` `#RRGGBB[AA]` ``
+* RGB: `` `RGB[A](R, G, B[, A])` ``
+* HSL: `` `HSL[A](H, S, L[, A])` ``
+
### Mermaid
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 72def9d1d1d..8c639bd5343 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -46,7 +46,7 @@ namespace that started the import process.
The importer will also import branches on forks of projects related to open pull
requests. These branches will be imported with a naming scheme similar to
-GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepency
+GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepancy
in branches compared to the GitHub Repository.
For a more technical description and an overview of the architecture you can
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9aeebc34525..c2113551207 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -42,7 +42,7 @@ module API
include Gitlab::Auth::UserAuthFinders
def find_current_user!
- user = find_user_from_access_token || find_user_from_warden
+ user = find_user_from_sources
return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
@@ -50,6 +50,10 @@ module API
user
end
+ def find_user_from_sources
+ find_user_from_access_token || find_user_from_warden
+ end
+
private
# An array of scopes that were registered (using `allow_access_with_scope`)
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 0791a110c39..1794207e29b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -29,6 +29,8 @@ module API
use :pagination
end
get ':id/repository/branches' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
+
repository = user_project.repository
branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 2cae53dba53..3d0d1287407 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -1,15 +1,13 @@
module API
module Helpers
module Runner
- include Gitlab::CurrentSettings
-
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 10 * 60
def runner_registration_token_valid?
ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
- current_application_settings.runners_registration_token)
+ Gitlab::CurrentSettings.runners_registration_token)
end
def get_runner_version_from_params
@@ -70,7 +68,7 @@ module API
end
def max_artifacts_size
- current_application_settings.max_artifacts_size.megabytes.to_i
+ Gitlab::CurrentSettings.max_artifacts_size.megabytes.to_i
end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c99fe3ab5b3..b6c278c89d0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -161,6 +161,8 @@ module API
use :issue_params
end
post ':id/issues' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42320')
+
authorize! :create_issue, user_project
# Setting created_at time only allowed for admins and project owners
@@ -201,6 +203,8 @@ module API
:labels, :created_at, :due_date, :confidential, :state_event
end
put ':id/issues/:issue_iid' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42322')
+
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
@@ -234,6 +238,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
post ':id/issues/:issue_iid/move' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42323')
+
issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 420aaf1c964..719afa09295 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -152,6 +152,8 @@ module API
use :optional_params
end
post ":id/merge_requests" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
+
authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false)
@@ -256,6 +258,8 @@ module API
at_least_one_of(*at_least_one_of_ce)
end
put ':id/merge_requests/:merge_request_iid' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42318')
+
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false)
@@ -283,6 +287,8 @@ module API
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
put ':id/merge_requests/:merge_request_iid/merge' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42317')
+
merge_request = find_project_merge_request(params[:merge_request_iid])
merge_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds])
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 675c963bae2..d2b8b832e4e 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -42,6 +42,8 @@ module API
requires :ref, type: String, desc: 'Reference'
end
post ':id/pipeline' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124')
+
authorize! :create_pipeline, user_project
new_pipeline = Ci::CreatePipelineService.new(user_project,
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 8b5e4f8edcc..5b481121a10 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -210,6 +210,8 @@ module API
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end
post ':id/fork' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
+
fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace]
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index dd6801664b1..b3709455bc3 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -15,6 +15,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42283')
+
# validate variables
params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e5de31ad51b..c7c2aa280d5 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -383,6 +383,8 @@ module API
optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
end
delete ":id" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42279')
+
authenticated_as_admin!
user = User.find_by(id: params[:id])
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
index b201bf77667..25176c5b38e 100644
--- a/lib/api/v3/branches.rb
+++ b/lib/api/v3/branches.rb
@@ -14,6 +14,8 @@ module API
success ::API::Entities::Branch
end
get ":id/repository/branches" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42276')
+
repository = user_project.repository
branches = repository.branches.sort_by(&:name)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb
index cb371fdbab8..b59947d81d9 100644
--- a/lib/api/v3/issues.rb
+++ b/lib/api/v3/issues.rb
@@ -134,6 +134,8 @@ module API
use :issue_params
end
post ':id/issues' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42131')
+
# Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at)
@@ -169,6 +171,8 @@ module API
:labels, :created_at, :due_date, :confidential, :state_event
end
put ':id/issues/:issue_id' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42132')
+
issue = user_project.issues.find(params.delete(:issue_id))
authorize! :update_issue, issue
@@ -201,6 +205,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
post ':id/issues/:issue_id/move' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42133')
+
issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 0a24fea52a3..ce216497996 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -91,6 +91,8 @@ module API
use :optional_params
end
post ":id/merge_requests" do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
+
authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false)
@@ -167,6 +169,8 @@ module API
:remove_source_branch
end
put path do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42127')
+
merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
mr_params = declared_params(include_missing: false)
diff --git a/lib/api/v3/pipelines.rb b/lib/api/v3/pipelines.rb
index c48cbd2b765..6d31c12f572 100644
--- a/lib/api/v3/pipelines.rb
+++ b/lib/api/v3/pipelines.rb
@@ -19,6 +19,8 @@ module API
desc: 'Either running, branches, or tags'
end
get ':id/pipelines' do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42123')
+
authorize! :read_pipeline, user_project
pipelines = PipelinesFinder.new(user_project, scope: params[:scope]).execute
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 534911fde5c..34f07dfb486 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -16,6 +16,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42121')
+
# validate variables
params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
diff --git a/lib/banzai/color_parser.rb b/lib/banzai/color_parser.rb
new file mode 100644
index 00000000000..355c364b07b
--- /dev/null
+++ b/lib/banzai/color_parser.rb
@@ -0,0 +1,44 @@
+module Banzai
+ module ColorParser
+ ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/ # 0.0..1.0
+ PERCENTS = /(?:\d{1,2}|100)%/ # 00%..100%
+ ALPHA_CHANNEL = /(?:,\s*(?:#{ALPHA}|#{PERCENTS}))?/
+ BITS = /\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5])/ # 00..255
+ DEGS = /-?\d+(?:deg)?/i # [-]digits[deg]
+ RADS = /-?(?:\d+(?:\.\d+)?|\.\d+)rad/i # [-](digits[.digits] OR .digits)rad
+ HEX_FORMAT = /\#(?:\h{3}|\h{4}|\h{6}|\h{8})/
+ RGB_FORMAT = %r{
+ (?:rgba?
+ \(
+ (?:
+ (?:(?:#{BITS},\s*){2}#{BITS})
+ |
+ (?:(?:#{PERCENTS},\s*){2}#{PERCENTS})
+ )
+ #{ALPHA_CHANNEL}
+ \)
+ )
+ }xi
+ HSL_FORMAT = %r{
+ (?:hsla?
+ \(
+ (?:#{DEGS}|#{RADS}),\s*#{PERCENTS},\s*#{PERCENTS}
+ #{ALPHA_CHANNEL}
+ \)
+ )
+ }xi
+
+ FORMATS = [HEX_FORMAT, RGB_FORMAT, HSL_FORMAT].freeze
+
+ COLOR_FORMAT = /\A(#{Regexp.union(FORMATS)})\z/ix
+
+ # Public: Analyzes whether the String is a color code.
+ #
+ # text - The String to be parsed.
+ #
+ # Returns the recognized color String or nil if none was found.
+ def self.parse(text)
+ text if COLOR_FORMAT =~ text
+ end
+ end
+end
diff --git a/lib/banzai/filter/color_filter.rb b/lib/banzai/filter/color_filter.rb
new file mode 100644
index 00000000000..6ab29ac281f
--- /dev/null
+++ b/lib/banzai/filter/color_filter.rb
@@ -0,0 +1,31 @@
+module Banzai
+ module Filter
+ # HTML filter that renders `color` followed by a color "chip".
+ #
+ class ColorFilter < HTML::Pipeline::Filter
+ COLOR_CHIP_CLASS = 'gfm-color_chip'.freeze
+
+ def call
+ doc.css('code').each do |node|
+ color = ColorParser.parse(node.content)
+ node << color_chip(color) if color
+ end
+
+ doc
+ end
+
+ private
+
+ def color_chip(color)
+ checkerboard = doc.document.create_element('span', class: COLOR_CHIP_CLASS)
+ chip = doc.document.create_element('span', style: inline_styles(color: color))
+
+ checkerboard << chip
+ end
+
+ def inline_styles(color:)
+ "background-color: #{color};"
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
index adc09c8afbd..5dd572de3a1 100644
--- a/lib/banzai/pipeline/broadcast_message_pipeline.rb
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter,
Filter::EmojiFilter,
+ Filter::ColorFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter
]
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index c746f6f64e9..4001b8a85e3 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -14,6 +14,7 @@ module Banzai
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
+ Filter::ColorFilter,
Filter::MermaidFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
diff --git a/lib/email_template_interceptor.rb b/lib/email_template_interceptor.rb
index f2bf3d0fb2b..3978a6d9fe4 100644
--- a/lib/email_template_interceptor.rb
+++ b/lib/email_template_interceptor.rb
@@ -1,10 +1,8 @@
# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
class EmailTemplateInterceptor
- extend Gitlab::CurrentSettings
-
def self.delivering_email(message)
# Remove HTML part if HTML emails are disabled.
- unless current_application_settings.html_emails_enabled
+ unless Gitlab::CurrentSettings.html_emails_enabled
message.parts.delete_if do |part|
part.content_type.start_with?('text/html')
end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index cead1c7eacd..ee7f4be6b9f 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -6,8 +6,6 @@ module Gitlab
# Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
# the resulting HTML through HTML pipeline filters.
module Asciidoc
- extend Gitlab::CurrentSettings
-
DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font'
@@ -33,9 +31,9 @@ module Gitlab
def self.plantuml_setup
Asciidoctor::PlantUml.configure do |conf|
- conf.url = current_application_settings.plantuml_url
- conf.svg_enable = current_application_settings.plantuml_enabled
- conf.png_enable = current_application_settings.plantuml_enabled
+ conf.url = Gitlab::CurrentSettings.plantuml_url
+ conf.svg_enable = Gitlab::CurrentSettings.plantuml_enabled
+ conf.png_enable = Gitlab::CurrentSettings.plantuml_enabled
conf.txt_enable = false
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 65d7fd3ec70..05932378173 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -14,8 +14,6 @@ module Gitlab
DEFAULT_SCOPES = [:api].freeze
class << self
- include Gitlab::CurrentSettings
-
def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil?
@@ -57,7 +55,7 @@ module Gitlab
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
Gitlab::LDAP::Authentication.login(login, password)
- elsif current_application_settings.password_authentication_enabled_for_git?
+ elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git?
user if user.active? && user.valid_password?(password)
end
end
@@ -87,7 +85,7 @@ module Gitlab
private
def authenticate_using_internal_or_ldap_password?
- current_application_settings.password_authentication_enabled_for_git? || Gitlab::LDAP::Config.enabled?
+ Gitlab::CurrentSettings.password_authentication_enabled_for_git? || Gitlab::LDAP::Config.enabled?
end
def service_request_check(login, password, project)
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 9a0482306b7..778d78185ff 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -23,7 +23,7 @@ module Gitlab
@coverage ||= raw_coverage
return unless @coverage
- @coverage.to_i
+ @coverage.to_f.round(2)
end
def metadata
diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb
index fcecb1d9665..afbf9dd17e3 100644
--- a/lib/gitlab/badge/coverage/template.rb
+++ b/lib/gitlab/badge/coverage/template.rb
@@ -25,7 +25,7 @@ module Gitlab
end
def value_text
- @status ? "#{@status}%" : 'unknown'
+ @status ? ("%.2f%%" % @status) : 'unknown'
end
def key_width
@@ -33,7 +33,7 @@ module Gitlab
end
def value_width
- @status ? 36 : 58
+ @status ? 54 : 58
end
def value_color
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 91fd9cc7631..b7c596a973d 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -1,73 +1,79 @@
module Gitlab
module CurrentSettings
- extend self
+ class << self
+ def current_application_settings
+ if RequestStore.active?
+ RequestStore.fetch(:current_application_settings) { ensure_application_settings! }
+ else
+ ensure_application_settings!
+ end
+ end
- def current_application_settings
- if RequestStore.active?
- RequestStore.fetch(:current_application_settings) { ensure_application_settings! }
- else
- ensure_application_settings!
+ def fake_application_settings(defaults = ::ApplicationSetting.defaults)
+ Gitlab::FakeApplicationSettings.new(defaults)
end
- end
- delegate :sidekiq_throttling_enabled?, to: :current_application_settings
+ def method_missing(name, *args, &block)
+ current_application_settings.send(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
+ end
- def fake_application_settings(defaults = ::ApplicationSetting.defaults)
- FakeApplicationSettings.new(defaults)
- end
+ def respond_to_missing?(name, include_private = false)
+ current_application_settings.respond_to?(name, include_private) || super
+ end
- private
+ private
- def ensure_application_settings!
- return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
+ def ensure_application_settings!
+ return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
- cached_application_settings || uncached_application_settings
- end
+ cached_application_settings || uncached_application_settings
+ end
- def cached_application_settings
- begin
- ::ApplicationSetting.cached
- rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL
- # In case Redis isn't running or the Redis UNIX socket file is not available
+ def cached_application_settings
+ begin
+ ::ApplicationSetting.cached
+ rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL
+ # In case Redis isn't running or the Redis UNIX socket file is not available
+ end
end
- end
- def uncached_application_settings
- return fake_application_settings unless connect_to_db?
+ def uncached_application_settings
+ return fake_application_settings unless connect_to_db?
- db_settings = ::ApplicationSetting.current
+ db_settings = ::ApplicationSetting.current
- # If there are pending migrations, it's possible there are columns that
- # need to be added to the application settings. To prevent Rake tasks
- # and other callers from failing, use any loaded settings and return
- # defaults for missing columns.
- if ActiveRecord::Migrator.needs_migration?
- defaults = ::ApplicationSetting.defaults
- defaults.merge!(db_settings.attributes.symbolize_keys) if db_settings.present?
- return fake_application_settings(defaults)
- end
+ # If there are pending migrations, it's possible there are columns that
+ # need to be added to the application settings. To prevent Rake tasks
+ # and other callers from failing, use any loaded settings and return
+ # defaults for missing columns.
+ if ActiveRecord::Migrator.needs_migration?
+ defaults = ::ApplicationSetting.defaults
+ defaults.merge!(db_settings.attributes.symbolize_keys) if db_settings.present?
+ return fake_application_settings(defaults)
+ end
- return db_settings if db_settings.present?
+ return db_settings if db_settings.present?
- ::ApplicationSetting.create_from_defaults || in_memory_application_settings
- end
+ ::ApplicationSetting.create_from_defaults || in_memory_application_settings
+ end
- def in_memory_application_settings
- @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError
- # In case migrations the application_settings table is not created yet,
- # we fallback to a simple OpenStruct
- fake_application_settings
- end
+ def in_memory_application_settings
+ @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError
+ # In case migrations the application_settings table is not created yet,
+ # we fallback to a simple OpenStruct
+ fake_application_settings
+ end
- def connect_to_db?
- # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
- active_db_connection = ActiveRecord::Base.connection.active? rescue false
+ def connect_to_db?
+ # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
+ active_db_connection = ActiveRecord::Base.connection.active? rescue false
- active_db_connection &&
- ActiveRecord::Base.connection.table_exists?('application_settings')
- rescue ActiveRecord::NoDatabaseError
- false
+ active_db_connection &&
+ ActiveRecord::Base.connection.table_exists?('application_settings')
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
end
end
end
diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index 3487e099381..ae7e88f0503 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,5 +1,3 @@
-# Gitaly note: JV: no RPC's here.
-
module Gitlab
module Git
class Branch < Ref
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index f28624ff37a..ab1362a3bb0 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1222,33 +1222,13 @@ module Gitlab
end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
- squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
- env = git_env_for_user(user).merge(
- 'GIT_AUTHOR_NAME' => author.name,
- 'GIT_AUTHOR_EMAIL' => author.email
- )
- diff_range = "#{start_sha}...#{end_sha}"
- diff_files = run_git!(
- %W(diff --name-only --diff-filter=a --binary #{diff_range})
- ).chomp
-
- with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
- # Apply diff of the `diff_range` to the worktree
- diff = run_git!(%W(diff --binary #{diff_range}))
- run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
- stdin.write(diff)
+ gitaly_migrate(:squash) do |is_enabled|
+ if is_enabled
+ gitaly_operation_client.user_squash(user, squash_id, branch,
+ start_sha, end_sha, author, message)
+ else
+ git_squash(user, squash_id, branch, start_sha, end_sha, author, message)
end
-
- # Commit the `diff_range` diff
- run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
-
- # Return the squash sha. May print a warning for ambiguous refs, but
- # we can ignore that with `--quiet` and just take the SHA, if present.
- # HEAD here always refers to the current HEAD commit, even if there is
- # another ref called HEAD.
- run_git!(
- %w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
- ).chomp
end
end
@@ -1363,20 +1343,23 @@ module Gitlab
raise CommandError.new(e)
end
- def refs_contains_sha(ref_type, sha)
- args = %W(#{ref_type} --contains #{sha})
- names = run_git(args).first
-
- if names.respond_to?(:split)
- names = names.split("\n").map(&:strip)
-
- names.each do |name|
- name.slice! '* '
+ def branch_names_contains_sha(sha)
+ gitaly_migrate(:branch_names_contains_sha) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.branch_names_contains_sha(sha)
+ else
+ refs_contains_sha(:branch, sha)
end
+ end
+ end
- names
- else
- []
+ def tag_names_contains_sha(sha)
+ gitaly_migrate(:tag_names_contains_sha) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.tag_names_contains_sha(sha)
+ else
+ refs_contains_sha(:tag, sha)
+ end
end
end
@@ -1454,6 +1437,21 @@ module Gitlab
end
end
+ def refs_contains_sha(ref_type, sha)
+ args = %W(#{ref_type} --contains #{sha})
+ names = run_git(args).first
+
+ return [] unless names.respond_to?(:split)
+
+ names = names.split("\n").map(&:strip)
+
+ names.each do |name|
+ name.slice! '* '
+ end
+
+ names
+ end
+
def rugged_write_config(full_path:)
rugged.config['gitlab.fullpath'] = full_path
end
@@ -2164,6 +2162,37 @@ module Gitlab
end
end
+ def git_squash(user, squash_id, branch, start_sha, end_sha, author, message)
+ squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
+ env = git_env_for_user(user).merge(
+ 'GIT_AUTHOR_NAME' => author.name,
+ 'GIT_AUTHOR_EMAIL' => author.email
+ )
+ diff_range = "#{start_sha}...#{end_sha}"
+ diff_files = run_git!(
+ %W(diff --name-only --diff-filter=a --binary #{diff_range})
+ ).chomp
+
+ with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
+ # Apply diff of the `diff_range` to the worktree
+ diff = run_git!(%W(diff --binary #{diff_range}))
+ run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
+ stdin.write(diff)
+ end
+
+ # Commit the `diff_range` diff
+ run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
+
+ # Return the squash sha. May print a warning for ambiguous refs, but
+ # we can ignore that with `--quiet` and just take the SHA, if present.
+ # HEAD here always refers to the current HEAD commit, even if there is
+ # another ref called HEAD.
+ run_git!(
+ %w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
+ ).chomp
+ end
+ end
+
def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb
index bc4e160dce9..8a8f7b051ed 100644
--- a/lib/gitlab/git/tag.rb
+++ b/lib/gitlab/git/tag.rb
@@ -1,5 +1,3 @@
-# Gitaly note: JV: no RPC's here.
-#
module Gitlab
module Git
class Tag < Ref
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index cd2734b5a07..831cfd1e014 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -183,6 +183,32 @@ module Gitlab
end
end
+ def user_squash(user, squash_id, branch, start_sha, end_sha, author, message)
+ request = Gitaly::UserSquashRequest.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ squash_id: squash_id.to_s,
+ branch: encode_binary(branch),
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: Gitlab::Git::User.from_gitlab(author).to_gitaly,
+ commit_message: encode_binary(message)
+ )
+
+ response = GitalyClient.call(
+ @repository.storage,
+ :operation_service,
+ :user_squash,
+ request
+ )
+
+ if response.git_error.presence
+ raise Gitlab::Git::Repository::GitError, response.git_error
+ end
+
+ response.squash_sha
+ end
+
def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name,
start_branch_name, start_repository)
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 8b9a224b700..ba6b577fd17 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -145,6 +145,32 @@ module Gitlab
raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
end
+ # Limit: 0 implies no limit, thus all tag names will be returned
+ def tag_names_contains_sha(sha, limit: 0)
+ request = Gitaly::ListTagNamesContainingCommitRequest.new(
+ repository: @gitaly_repo,
+ commit_id: sha,
+ limit: limit
+ )
+
+ stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)
+
+ consume_ref_contains_sha_response(stream, :tag_names)
+ end
+
+ # Limit: 0 implies no limit, thus all tag names will be returned
+ def branch_names_contains_sha(sha, limit: 0)
+ request = Gitaly::ListBranchNamesContainingCommitRequest.new(
+ repository: @gitaly_repo,
+ commit_id: sha,
+ limit: limit
+ )
+
+ stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)
+
+ consume_ref_contains_sha_response(stream, :branch_names)
+ end
+
private
def consume_refs_response(response)
@@ -215,6 +241,13 @@ module Gitlab
Gitlab::Git::Commit.decorate(@repository, hash)
end
+ def consume_ref_contains_sha_response(stream, collection_name)
+ stream.each_with_object([]) do |response, array|
+ encoded_names = response.send(collection_name).map { |b| Gitlab::Git.ref_name(b) } # rubocop:disable GitlabSecurity/PublicSend
+ array.concat(encoded_names)
+ end
+ end
+
def invalid_ref!(message)
raise Gitlab::Git::Repository::InvalidRef.new(message)
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9148d7571f2..86a90d57d9c 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -3,12 +3,11 @@
module Gitlab
module GonHelper
include WebpackHelper
- include Gitlab::CurrentSettings
def add_gon_variables
gon.api_version = 'v4'
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
- gon.max_file_size = current_application_settings.max_attachment_size
+ gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
@@ -16,7 +15,7 @@ module Gitlab
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css')
gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js')
- gon.sentry_dsn = current_application_settings.clientside_sentry_dsn if current_application_settings.clientside_sentry_enabled
+ gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn if Gitlab::CurrentSettings.clientside_sentry_enabled
gon.gitlab_url = Gitlab.config.gitlab.url
gon.revision = Gitlab::REVISION
gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png')
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index cb711a83433..759833a5ee5 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -139,13 +139,12 @@ module Gitlab
end
def setup_label
- return unless @relation_hash['type'] == 'GroupLabel'
-
# If there's no group, move the label to a project label
- if @relation_hash['group_id']
+ if @relation_hash['type'] == 'GroupLabel' && @relation_hash['group_id']
@relation_hash['project_id'] = nil
@relation_name = :group_label
else
+ @relation_hash['group_id'] = nil
@relation_hash['type'] = 'ProjectLabel'
end
end
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
index a3216759cae..ca5e06009fa 100644
--- a/lib/gitlab/kubernetes/helm/pod.rb
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -64,7 +64,7 @@ module Gitlab
{
name: 'configuration-volume',
configMap: {
- name: 'values-content-configuration',
+ name: "values-content-configuration-#{command.name}",
items: [{ key: 'values', path: 'values.yaml' }]
}
}
@@ -81,7 +81,11 @@ module Gitlab
def create_config_map
resource = ::Kubeclient::Resource.new
- resource.metadata = { name: 'values-content-configuration', namespace: namespace_name, labels: { name: 'values-content-configuration' } }
+ resource.metadata = {
+ name: "values-content-configuration-#{command.name}",
+ namespace: namespace_name,
+ labels: { name: "values-content-configuration-#{command.name}" }
+ }
resource.data = { values: File.read(command.chart_values_file) }
kubeclient.create_config_map(resource)
end
diff --git a/lib/gitlab/legacy_github_import/project_creator.rb b/lib/gitlab/legacy_github_import/project_creator.rb
index 41e7eac4d08..cbabe5454ca 100644
--- a/lib/gitlab/legacy_github_import/project_creator.rb
+++ b/lib/gitlab/legacy_github_import/project_creator.rb
@@ -1,8 +1,6 @@
module Gitlab
module LegacyGithubImport
class ProjectCreator
- include Gitlab::CurrentSettings
-
attr_reader :repo, :name, :namespace, :current_user, :session_data, :type
def initialize(repo, name, namespace, current_user, session_data, type: 'github')
@@ -36,7 +34,7 @@ module Gitlab
end
def visibility_level
- repo.private ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility
+ repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::CurrentSettings.default_project_visibility
end
#
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index f07ea3560ff..d12ba0ec176 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -71,8 +71,7 @@ module Gitlab
end
def prometheus_metrics_enabled_unmemoized
- metrics_folder_present? &&
- Gitlab::CurrentSettings.current_application_settings[:prometheus_metrics_enabled] || false
+ metrics_folder_present? && Gitlab::CurrentSettings.prometheus_metrics_enabled || false
end
end
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index afbc2600634..1a570f480c6 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -4,7 +4,6 @@ module Gitlab
module Middleware
class Go
include ActionView::Helpers::TagHelper
- include Gitlab::CurrentSettings
PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze
@@ -42,7 +41,7 @@ module Gitlab
project_url = URI.join(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s)
- repository_url = if current_application_settings.enabled_git_access_protocol == 'ssh'
+ repository_url = if Gitlab::CurrentSettings.enabled_git_access_protocol == 'ssh'
shell = config.gitlab_shell
port = ":#{shell.ssh_port}" unless shell.ssh_port == 22
"ssh://#{shell.ssh_user}@#{shell.ssh_host}#{port}/#{path}.git"
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index e29e168fc5a..6c2b2036074 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -1,7 +1,5 @@
module Gitlab
module PerformanceBar
- extend Gitlab::CurrentSettings
-
ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
EXPIRY_TIME = 5.minutes
@@ -13,7 +11,7 @@ module Gitlab
end
def self.allowed_group_id
- current_application_settings.performance_bar_allowed_group_id
+ Gitlab::CurrentSettings.performance_bar_allowed_group_id
end
def self.allowed_user_ids
diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb
index 4780675a492..fe4bdfe3831 100644
--- a/lib/gitlab/polling_interval.rb
+++ b/lib/gitlab/polling_interval.rb
@@ -1,12 +1,10 @@
module Gitlab
class PollingInterval
- extend Gitlab::CurrentSettings
-
HEADER_NAME = 'Poll-Interval'.freeze
def self.set_header(response, interval:)
if polling_enabled?
- multiplier = current_application_settings.polling_interval_multiplier
+ multiplier = Gitlab::CurrentSettings.polling_interval_multiplier
value = (interval * multiplier).to_i
else
value = -1
@@ -16,7 +14,7 @@ module Gitlab
end
def self.polling_enabled?
- !current_application_settings.polling_interval_multiplier.zero?
+ !Gitlab::CurrentSettings.polling_interval_multiplier.zero?
end
end
end
diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb
index 09fa14764e6..2819c7d062c 100644
--- a/lib/gitlab/protocol_access.rb
+++ b/lib/gitlab/protocol_access.rb
@@ -1,14 +1,12 @@
module Gitlab
module ProtocolAccess
- extend Gitlab::CurrentSettings
-
def self.allowed?(protocol)
if protocol == 'web'
true
- elsif current_application_settings.enabled_git_access_protocol.blank?
+ elsif Gitlab::CurrentSettings.enabled_git_access_protocol.blank?
true
else
- protocol == current_application_settings.enabled_git_access_protocol
+ protocol == Gitlab::CurrentSettings.enabled_git_access_protocol
end
end
end
diff --git a/lib/gitlab/query_limiting.rb b/lib/gitlab/query_limiting.rb
new file mode 100644
index 00000000000..f64f1757144
--- /dev/null
+++ b/lib/gitlab/query_limiting.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module QueryLimiting
+ # Returns true if we should enable tracking of query counts.
+ #
+ # This is only enabled in production/staging if we're running on GitLab.com.
+ # This ensures we don't produce any errors that users can't do anything
+ # about themselves.
+ def self.enable?
+ Gitlab.com? || Rails.env.development? || Rails.env.test?
+ end
+
+ # Allows the current request to execute any number of SQL queries.
+ #
+ # This method should _only_ be used when there's a corresponding issue to
+ # reduce the number of queries.
+ #
+ # The issue URL is only meant to push developers into creating an issue
+ # instead of blindly whitelisting offending blocks of code.
+ def self.whitelist(issue_url)
+ return unless enable_whitelist?
+
+ unless issue_url.start_with?('https://')
+ raise(
+ ArgumentError,
+ 'You must provide a valid issue URL in order to whitelist a block of code'
+ )
+ end
+
+ Transaction&.current&.whitelisted = true
+ end
+
+ def self.enable_whitelist?
+ Rails.env.development? || Rails.env.test?
+ end
+ end
+end
diff --git a/lib/gitlab/query_limiting/active_support_subscriber.rb b/lib/gitlab/query_limiting/active_support_subscriber.rb
new file mode 100644
index 00000000000..66049c94ec6
--- /dev/null
+++ b/lib/gitlab/query_limiting/active_support_subscriber.rb
@@ -0,0 +1,11 @@
+module Gitlab
+ module QueryLimiting
+ class ActiveSupportSubscriber < ActiveSupport::Subscriber
+ attach_to :active_record
+
+ def sql(*)
+ Transaction.current&.increment
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/query_limiting/middleware.rb b/lib/gitlab/query_limiting/middleware.rb
new file mode 100644
index 00000000000..949ae79a047
--- /dev/null
+++ b/lib/gitlab/query_limiting/middleware.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QueryLimiting
+ # Middleware for reporting (or raising) when a request performs more than a
+ # certain amount of database queries.
+ class Middleware
+ CONTROLLER_KEY = 'action_controller.instance'.freeze
+ ENDPOINT_KEY = 'api.endpoint'.freeze
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ transaction, retval = Transaction.run do
+ @app.call(env)
+ end
+
+ transaction.action = action_name(env)
+ transaction.act_upon_results
+
+ retval
+ end
+
+ def action_name(env)
+ if env[CONTROLLER_KEY]
+ action_for_rails(env)
+ elsif env[ENDPOINT_KEY]
+ action_for_grape(env)
+ end
+ end
+
+ private
+
+ def action_for_rails(env)
+ controller = env[CONTROLLER_KEY]
+ action = "#{controller.class.name}##{controller.action_name}"
+
+ if controller.content_type == 'text/html'
+ action
+ else
+ "#{action} (#{controller.content_type})"
+ end
+ end
+
+ def action_for_grape(env)
+ endpoint = env[ENDPOINT_KEY]
+ route = endpoint.route rescue nil
+
+ "#{route.request_method} #{route.path}" if route
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/query_limiting/transaction.rb b/lib/gitlab/query_limiting/transaction.rb
new file mode 100644
index 00000000000..3cbafadb0d0
--- /dev/null
+++ b/lib/gitlab/query_limiting/transaction.rb
@@ -0,0 +1,83 @@
+module Gitlab
+ module QueryLimiting
+ class Transaction
+ THREAD_KEY = :__gitlab_query_counts_transaction
+
+ attr_accessor :count, :whitelisted
+
+ # The name of the action (e.g. `UsersController#show`) that is being
+ # executed.
+ attr_accessor :action
+
+ # The maximum number of SQL queries that can be executed in a request. For
+ # the sake of keeping things simple we hardcode this value here, it's not
+ # supposed to be changed very often anyway.
+ THRESHOLD = 100
+
+ # Error that is raised whenever exceeding the maximum number of queries.
+ ThresholdExceededError = Class.new(StandardError)
+
+ def self.current
+ Thread.current[THREAD_KEY]
+ end
+
+ # Starts a new transaction and returns it and the blocks' return value.
+ #
+ # Example:
+ #
+ # transaction, retval = Transaction.run do
+ # 10
+ # end
+ #
+ # retval # => 10
+ def self.run
+ transaction = new
+ Thread.current[THREAD_KEY] = transaction
+
+ [transaction, yield]
+ ensure
+ Thread.current[THREAD_KEY] = nil
+ end
+
+ def initialize
+ @action = nil
+ @count = 0
+ @whitelisted = false
+ end
+
+ # Sends a notification based on the number of executed SQL queries.
+ def act_upon_results
+ return unless threshold_exceeded?
+
+ error = ThresholdExceededError.new(error_message)
+
+ if raise_error?
+ raise(error)
+ else
+ # Raven automatically logs to the Rails log if disabled, thus we don't
+ # need to manually log anything in case Sentry support is not enabled.
+ Raven.capture_exception(error)
+ end
+ end
+
+ def increment
+ @count += 1 unless whitelisted
+ end
+
+ def raise_error?
+ Rails.env.test?
+ end
+
+ def threshold_exceeded?
+ count > THRESHOLD
+ end
+
+ def error_message
+ header = 'Too many SQL queries were executed'
+ header += " in #{action}" if action
+
+ "#{header}: a maximum of #{THRESHOLD} is allowed but #{count} SQL queries were executed"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index c463dd487a0..c9efa28d7e7 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -1,12 +1,10 @@
module Gitlab
module Recaptcha
- extend Gitlab::CurrentSettings
-
def self.load_configurations!
- if current_application_settings.recaptcha_enabled
+ if Gitlab::CurrentSettings.recaptcha_enabled
::Recaptcha.configure do |config|
- config.public_key = current_application_settings.recaptcha_site_key
- config.private_key = current_application_settings.recaptcha_private_key
+ config.public_key = Gitlab::CurrentSettings.recaptcha_site_key
+ config.private_key = Gitlab::CurrentSettings.recaptcha_private_key
end
true
@@ -14,7 +12,7 @@ module Gitlab
end
def self.enabled?
- current_application_settings.recaptcha_enabled
+ Gitlab::CurrentSettings.recaptcha_enabled
end
end
end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 159d0e7952e..4a22fc80f75 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -1,9 +1,7 @@
module Gitlab
module Sentry
- extend Gitlab::CurrentSettings
-
def self.enabled?
- Rails.env.production? && current_application_settings.sentry_enabled?
+ Rails.env.production? && Gitlab::CurrentSettings.sentry_enabled?
end
def self.context(current_user = nil)
diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb
index 89ca1298120..545e7c74f7e 100644
--- a/lib/gitlab/ssh_public_key.rb
+++ b/lib/gitlab/ssh_public_key.rb
@@ -21,6 +21,22 @@ module Gitlab
technology(name)&.supported_sizes
end
+ def self.sanitize(key_content)
+ ssh_type, *parts = key_content.strip.split
+
+ return key_content if parts.empty?
+
+ parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index|
+ content << part
+
+ if Gitlab::SSHPublicKey.new(content).valid?
+ break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present
+ elsif parts.size == index + 1 # return original content if we've reached the last element
+ break key_content
+ end
+ end
+ end
+
attr_reader :key_text, :key
# Unqualified MD5 fingerprint for compatibility
@@ -37,23 +53,23 @@ module Gitlab
end
def valid?
- key.present?
+ key.present? && bits && technology.supported_sizes.include?(bits)
end
def type
- technology.name if valid?
+ technology.name if key.present?
end
def bits
- return unless valid?
+ return if key.blank?
case type
when :rsa
- key.n.num_bits
+ key.n&.num_bits
when :dsa
- key.p.num_bits
+ key.p&.num_bits
when :ecdsa
- key.group.order.num_bits
+ key.group.order&.num_bits
when :ed25519
256
else
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 2adcc9809b3..9d13d1d781f 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -1,8 +1,6 @@
module Gitlab
class UsageData
class << self
- include Gitlab::CurrentSettings
-
def data(force_refresh: false)
Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data }
end
@@ -19,7 +17,7 @@ module Gitlab
def license_usage_data
usage_data = {
- uuid: current_application_settings.uuid,
+ uuid: Gitlab::CurrentSettings.uuid,
hostname: Gitlab.config.gitlab.host,
version: Gitlab::VERSION,
active_user_count: User.active.count,
@@ -79,9 +77,9 @@ module Gitlab
def features_usage_data_ce
{
- signup: current_application_settings.allow_signup?,
+ signup: Gitlab::CurrentSettings.allow_signup?,
ldap: Gitlab.config.ldap.enabled,
- gravatar: current_application_settings.gravatar_enabled?,
+ gravatar: Gitlab::CurrentSettings.gravatar_enabled?,
omniauth: Gitlab.config.omniauth.enabled,
reply_by_email: Gitlab::IncomingEmail.enabled?,
container_registry: Gitlab.config.registry.enabled,
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 6ced06a863d..2612208a927 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -5,7 +5,6 @@
#
module Gitlab
module VisibilityLevel
- extend CurrentSettings
extend ActiveSupport::Concern
included do
@@ -58,9 +57,9 @@ module Gitlab
end
def allowed_levels
- restricted_levels = current_application_settings.restricted_visibility_levels
+ restricted_levels = Gitlab::CurrentSettings.restricted_visibility_levels
- self.values - restricted_levels
+ self.values - Array(restricted_levels)
end
def closest_allowed_level(target_level)
@@ -81,7 +80,7 @@ module Gitlab
end
def non_restricted_level?(level)
- restricted_levels = current_application_settings.restricted_visibility_levels
+ restricted_levels = Gitlab::CurrentSettings.restricted_visibility_levels
if restricted_levels.nil?
true
diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake
index b1e012e70c5..4b4881cecb8 100644
--- a/lib/tasks/flay.rake
+++ b/lib/tasks/flay.rake
@@ -2,7 +2,7 @@ desc 'Code duplication analyze via flay'
task :flay do
output = `bundle exec flay --mass 35 app/ lib/gitlab/ 2> #{File::NULL}`
- if output.include? "Similar code found"
+ if output.include?("Similar code found") || output.include?("IDENTICAL code found")
puts output
exit 1
end
diff --git a/package.json b/package.json
index c68cf648932..26908e9f905 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"scripts": {
- "dev-server": "nodemon --watch config/webpack.config.js -- ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js",
+ "dev-server": "nodemon -w 'config/webpack.config.js' -w 'app/assets/javascripts/dispatcher.js' -w 'app/assets/javascripts/pages/**/index.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
"eslint": "eslint --max-warnings 0 --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
@@ -47,6 +47,7 @@
"exports-loader": "^0.6.4",
"file-loader": "^0.11.1",
"fuzzaldrin-plus": "^0.5.0",
+ "glob": "^7.1.2",
"imports-loader": "^0.7.1",
"jed": "^1.1.1",
"jquery": "^2.2.4",
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 2cead1770c9..387ca46ef6f 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -5,7 +5,7 @@ describe HealthCheckController do
let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
- let(:token) { current_application_settings.health_check_access_token }
+ 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/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index 95946def5f9..542eddc2d16 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -4,7 +4,7 @@ describe HealthController do
include StubENV
let(:json_response) { JSON.parse(response.body) }
- let(:token) { current_application_settings.health_check_access_token }
+ 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/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index b38652e7ab9..1195f44f37d 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -16,8 +16,7 @@ describe Oauth::ApplicationsController do
end
it 'redirects back to profile page if OAuth applications are disabled' do
- settings = double(user_oauth_applications?: false)
- allow_any_instance_of(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(settings)
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:user_oauth_applications?).and_return(false)
get :index
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
new file mode 100644
index 00000000000..48e2ff75cac
--- /dev/null
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe UserCalloutsController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe "POST #create" do
+ subject { post :create, feature_name: feature_name, format: :json }
+
+ context 'with valid feature name' do
+ let(:feature_name) { UserCallout.feature_names.keys.first }
+
+ context 'when callout entry does not exist' do
+ it 'should create a callout entry with dismissed state' do
+ expect { subject }.to change { UserCallout.count }.by(1)
+ end
+
+ it 'should return success' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when callout entry already exists' do
+ let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
+
+ it 'should return success' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with invalid feature name' do
+ let(:feature_name) { 'bogus_feature_name' }
+
+ it 'should return bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index f0c43f3d6f5..23a98a899f1 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -5,6 +5,10 @@ FactoryBot.define do
title
key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com' }
+ factory :key_without_comment do
+ key { Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate }
+ end
+
factory :deploy_key, class: 'DeployKey'
factory :personal_key do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 16d328a5bc2..20976977f21 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -93,6 +93,12 @@ FactoryBot.define do
avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
+ trait :with_export do
+ after(:create) do |project, evaluator|
+ ProjectExportWorker.new.perform(project.creator.id, project.id)
+ end
+ end
+
trait :broken_storage do
after(:create) do |project|
project.update_column(:repository_storage, 'broken')
diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb
new file mode 100644
index 00000000000..528e442c14b
--- /dev/null
+++ b/spec/factories/user_callouts.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :user_callout do
+ feature_name :gke_cluster_integration
+
+ user
+ end
+end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index ac3392b49f9..3693e5882f9 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -17,7 +17,7 @@ feature "Admin Health Check", :feature do
page.has_text? 'Health Check'
page.has_text? 'Health information can be retrieved'
- token = current_application_settings.health_check_access_token
+ token = Gitlab::CurrentSettings.health_check_access_token
expect(page).to have_content("Access token is #{token}")
expect(page).to have_selector('#health-check-token', text: token)
@@ -25,7 +25,7 @@ feature "Admin Health Check", :feature do
describe 'reload access token' do
it 'changes the access token' do
- orig_token = current_application_settings.health_check_access_token
+ orig_token = Gitlab::CurrentSettings.health_check_access_token
click_button 'Reset health check access token'
expect(page).to have_content('New health check access token has been generated!')
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index c1c54177167..a01c129defd 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -156,7 +156,7 @@ describe "Admin Runners" do
end
describe 'runners registration token' do
- let!(:token) { current_application_settings.runners_registration_token }
+ let!(:token) { Gitlab::CurrentSettings.runners_registration_token }
before do
visit admin_runners_path
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 1218ea52227..cc0849d1cc6 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -38,12 +38,12 @@ feature 'Admin updates settings' do
uncheck 'Project export enabled'
click_button 'Save'
- expect(current_application_settings.gravatar_enabled).to be_falsey
- expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/"
- expect(current_application_settings.help_page_text).to eq "Example text"
- expect(current_application_settings.help_page_hide_commercial_content).to be_truthy
- expect(current_application_settings.help_page_support_url).to eq "http://example.com/help"
- expect(current_application_settings.project_export_enabled).to be_falsey
+ expect(Gitlab::CurrentSettings.gravatar_enabled).to be_falsey
+ expect(Gitlab::CurrentSettings.home_page_url).to eq "https://about.gitlab.com/"
+ expect(Gitlab::CurrentSettings.help_page_text).to eq "Example text"
+ expect(Gitlab::CurrentSettings.help_page_hide_commercial_content).to be_truthy
+ expect(Gitlab::CurrentSettings.help_page_support_url).to eq "http://example.com/help"
+ expect(Gitlab::CurrentSettings.project_export_enabled).to be_falsey
expect(page).to have_content "Application settings saved successfully"
end
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index c7cfd01f588..a75ca1d42b3 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -9,7 +9,7 @@ describe 'New issue', :js do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- current_application_settings.update!(
+ Gitlab::CurrentSettings.update!(
akismet_enabled: true,
akismet_api_key: 'testkey',
recaptcha_enabled: true,
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index a2b78a5e021..f13d78d24e3 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -259,6 +259,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links
end
+
+ it 'includes ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
context 'wiki pipeline' do
@@ -320,6 +324,10 @@ describe 'GitLab Markdown' do
it 'includes VideoLinkFilter' do
expect(doc).to parse_video_links
end
+
+ it 'includes ColorFilter' do
+ expect(doc).to parse_colors
+ end
end
# Fake a `current_user` helper
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 821ce88a402..f51001edcd7 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -18,7 +18,7 @@ feature 'test coverage badge' do
show_test_coverage_badge
- expect_coverage_badge('95%')
+ expect_coverage_badge('95.00%')
end
scenario 'user requests coverage badge for specific job' do
@@ -30,7 +30,7 @@ feature 'test coverage badge' do
show_test_coverage_badge(job: 'coverage')
- expect_coverage_badge('85%')
+ expect_coverage_badge('85.00%')
end
scenario 'user requests coverage badge for pipeline without coverage' do
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index e76bc6f1220..c1fccf4a40b 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -1,44 +1,37 @@
require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', :js do
- let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
- let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
+ let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') }
- let(:project) { create(:project) }
-
- background do
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ before do
+ allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do
FileUtils.rm_rf(export_path, secure: true)
end
- context 'admin user' do
+ shared_examples_for 'handling project exports on namespace change' do
+ let!(:old_export_path) { project.export_path }
+
before do
sign_in(create(:admin))
+
+ setup_export_project
end
context 'moving the namespace' do
- scenario 'removes the export file' do
- setup_export_project
-
- old_export_path = project.export_path.dup
-
+ it 'removes the export file' do
expect(File).to exist(old_export_path)
- project.namespace.update(path: 'new_path')
+ project.namespace.update!(path: build(:namespace).path)
expect(File).not_to exist(old_export_path)
end
end
context 'deleting the namespace' do
- scenario 'removes the export file' do
- setup_export_project
-
- old_export_path = project.export_path.dup
-
+ it 'removes the export file' do
expect(File).to exist(old_export_path)
project.namespace.destroy
@@ -46,17 +39,29 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(File).not_to exist(old_export_path)
end
end
+ end
- def setup_export_project
- visit edit_project_path(project)
+ describe 'legacy storage' do
+ let(:project) { create(:project) }
- expect(page).to have_content('Export project')
+ it_behaves_like 'handling project exports on namespace change'
+ end
+
+ describe 'hashed storage' do
+ let(:project) { create(:project, :hashed) }
- find(:link, 'Export project').send_keys(:return)
+ it_behaves_like 'handling project exports on namespace change'
+ end
- visit edit_project_path(project)
+ def setup_export_project
+ visit edit_project_path(project)
- expect(page).to have_content('Download export')
- end
+ expect(page).to have_content('Export project')
+
+ find(:link, 'Export project').send_keys(:return)
+
+ visit edit_project_path(project)
+
+ expect(page).to have_content('Download export')
end
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index fa2f7a1fd78..65e24862d43 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -168,11 +168,11 @@ feature 'Pipeline Schedules', :js do
scenario 'user sees the new variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123')
- expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB')
- expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123')
+ expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB')
+ expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123')
end
end
end
@@ -185,16 +185,18 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- all('[name="schedule[variables_attributes][][key]"]')[0].set('foo')
- all('[name="schedule[variables_attributes][][value]"]')[0].set('bar')
+
+ find('.js-ci-variable-list-section .js-secret-value-reveal-button').click
+ first('.js-ci-variable-input-key').set('foo')
+ first('.js-ci-variable-input-value').set('bar')
click_button 'Save pipeline schedule'
end
scenario 'user sees the updated variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('foo')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('bar')
end
end
end
@@ -207,15 +209,15 @@ feature 'Pipeline Schedules', :js do
visit_pipelines_schedules
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- find('.pipeline-variable-list .pipeline-variable-row-remove-button').click
+ find('.ci-variable-list .ci-variable-row-remove-button').click
click_button 'Save pipeline schedule'
end
scenario 'user does not see the removed variable in edit window' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
- page.within('.pipeline-variable-list') do
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('')
- expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('')
+ page.within('.ci-variable-list') do
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('')
+ expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('')
end
end
end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 71abb6da607..da32a46675f 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -280,6 +280,18 @@ However the wrapping tags cannot be mixed as such:
![My Video](/assets/videos/gitlab-demo.mp4)
+### Colors
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.7)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.7)`
+
### Mermaid
> If this is not rendered correctly, see
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
new file mode 100644
index 00000000000..27455705d23
--- /dev/null
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -0,0 +1,47 @@
+require "spec_helper"
+
+describe UserCalloutsHelper do
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ describe '.show_gke_cluster_integration_callout?' do
+ let(:project) { create(:project) }
+
+ subject { helper.show_gke_cluster_integration_callout?(project) }
+
+ context 'when user can create a cluster' do
+ before do
+ allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
+ .and_return(true)
+ end
+
+ context 'when user has not dismissed' do
+ before do
+ allow(helper).to receive(:user_dismissed?).and_return(false)
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when user dismissed' do
+ before do
+ allow(helper).to receive(:user_dismissed?).and_return(true)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+
+ context 'when user can not create a cluster' do
+ before do
+ allow(helper).to receive(:can?).with(anything, :create_cluster, anything)
+ .and_return(false)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index b5b15726816..9d4e34abef5 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -4,7 +4,7 @@ describe VersionCheckHelper do
describe '#version_status_badge' do
it 'should return nil if not dev environment and not enabled' do
allow(Rails.env).to receive(:production?) { false }
- allow(helper.current_application_settings).to receive(:version_check_enabled) { false }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false }
expect(helper.version_status_badge).to be(nil)
end
@@ -12,7 +12,7 @@ describe VersionCheckHelper do
context 'when production and enabled' do
before do
allow(Rails.env).to receive(:production?) { true }
- allow(helper.current_application_settings).to receive(:version_check_enabled) { true }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true }
allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
@image_tag = helper.version_status_badge
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
new file mode 100644
index 00000000000..0170ab458d4
--- /dev/null
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -0,0 +1,163 @@
+import VariableList from '~/ci_variable_list/ci_variable_list';
+import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
+
+describe('VariableList', () => {
+ preloadFixtures('pipeline_schedules/edit.html.raw');
+ preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+
+ let $wrapper;
+ let variableList;
+
+ describe('with only key/value inputs', () => {
+ describe('with no variables', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ variableList.init();
+ });
+
+ it('should remove the row when clicking the remove button', () => {
+ $wrapper.find('.js-row-remove-button').trigger('click');
+
+ expect($wrapper.find('.js-row').length).toBe(0);
+ });
+
+ it('should add another row when editing the last rows key input', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-key')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $keyInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
+ expect($keyInput.val()).toBe('');
+ });
+
+ it('should add another row when editing the last rows value textarea', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-value')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $valueInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-key');
+ expect($valueInput.val()).toBe('');
+ });
+
+ it('should remove empty row after blurring', () => {
+ const $row = $wrapper.find('.js-row');
+ $row.find('.js-ci-variable-input-key')
+ .val('foo')
+ .trigger('input');
+
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ $row.find('.js-ci-variable-input-key')
+ .val('')
+ .trigger('input')
+ .trigger('blur');
+
+ expect($wrapper.find('.js-row').length).toBe(1);
+ });
+ });
+
+ describe('with persisted variables', () => {
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ variableList.init();
+ });
+
+ it('should have "Reveal values" button initially when there are already variables', () => {
+ expect($wrapper.find('.js-secret-value-reveal-button').text()).toBe('Reveal values');
+ });
+
+ it('should reveal hidden values', () => {
+ const $row = $wrapper.find('.js-row:first-child');
+ const $inputValue = $row.find('.js-ci-variable-input-value');
+ const $placeholder = $row.find('.js-secret-value-placeholder');
+
+ expect($placeholder.hasClass('hide')).toBe(false);
+ expect($inputValue.hasClass('hide')).toBe(true);
+
+ // Reveal values
+ $wrapper.find('.js-secret-value-reveal-button').click();
+
+ expect($placeholder.hasClass('hide')).toBe(true);
+ expect($inputValue.hasClass('hide')).toBe(false);
+ });
+ });
+ });
+
+ describe('with all inputs(key, value, protected)', () => {
+ beforeEach(() => {
+ // This markup will be replaced with a fixture when we can render the
+ // CI/CD settings page with the new dynamic variable list in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4110
+ $wrapper = $(`<form class="js-variable-list">
+ <ul>
+ <li class="js-row">
+ <div class="ci-variable-body-item">
+ <input class="js-ci-variable-input-key" name="variables[variables_attributes][][key]">
+ </div>
+
+ <div class="ci-variable-body-item">
+ <textarea class="js-ci-variable-input-value" name="variables[variables_attributes][][value]"></textarea>
+ </div>
+
+ <div class="ci-variable-body-item ci-variable-protected-item">
+ <button type="button" class="js-project-feature-toggle project-feature-toggle">
+ <input
+ type="hidden"
+ class="js-ci-variable-input-protected js-project-feature-toggle-input"
+ name="variables[variables_attributes][][protected]"
+ value="true"
+ />
+ </button>
+ </div>
+
+ <button type="button" class="js-row-remove-button"></button>
+ </li>
+ </ul>
+ <button type="button" class="js-secret-value-reveal-button">
+ Reveal values
+ </button>
+ </form>`);
+
+ variableList = new VariableList({
+ container: $wrapper,
+ formField: 'variables',
+ });
+ variableList.init();
+ });
+
+ it('should add another row when editing the last rows protected checkbox', (done) => {
+ const $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find('.js-row').length).toBe(2);
+
+ // Check for the correct default in the new row
+ const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected');
+ expect($protectedInput.val()).toBe('true');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
new file mode 100644
index 00000000000..eb508a7f059
--- /dev/null
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -0,0 +1,30 @@
+import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
+
+describe('NativeFormVariableList', () => {
+ preloadFixtures('pipeline_schedules/edit.html.raw');
+
+ let $wrapper;
+
+ beforeEach(() => {
+ loadFixtures('pipeline_schedules/edit.html.raw');
+ $wrapper = $('.js-ci-variable-list-section');
+
+ setupNativeFormVariableList({
+ container: $wrapper,
+ formField: 'schedule',
+ });
+ });
+
+ describe('onFormSubmit', () => {
+ it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
+ const $row = $wrapper.find('.js-row');
+ expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('schedule[variables_attributes][][key]');
+ expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][value]');
+
+ $wrapper.closest('form').trigger('trigger-submit');
+
+ expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('');
+ expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('');
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
new file mode 100644
index 00000000000..56f27ea7df1
--- /dev/null
+++ b/spec/javascripts/fixtures/pipeline_schedules.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :public, :repository) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
+ let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('pipeline_schedules/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'pipeline_schedules/edit.html.raw' do |example|
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule.id
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+
+ it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule_populated.id
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 6f357306ec7..50a587ef351 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -130,16 +130,25 @@ describe('GfmAutoComplete', function () {
});
describe('should not match special sequences', () => {
- const ShouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+ const shouldNotBeFollowedBy = flags.concat(['\x00', '\x10', '\x3f', '\n', ' ']);
+ const shouldNotBePrependedBy = ['`'];
flagsUseDefaultMatcher.forEach((atSign) => {
- ShouldNotBeFollowedBy.forEach((followedSymbol) => {
+ shouldNotBeFollowedBy.forEach((followedSymbol) => {
const seq = atSign + followedSymbol;
it(`should not match "${seq}"`, () => {
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
});
});
+
+ shouldNotBePrependedBy.forEach((prependedSymbol) => {
+ const seq = prependedSymbol + atSign;
+
+ it(`should not match "${seq}"`, () => {
+ expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
+ });
+ });
});
});
});
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 2cd2e63b15d..5245bf6455c 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
import '~/lib/utils/text_utility';
@@ -88,20 +90,24 @@ describe('Issue', function() {
[true, false].forEach((isIssueInitiallyOpen) => {
describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
const action = isIssueInitiallyOpen ? 'close' : 'reopen';
+ let mock;
- function ajaxSpy(req) {
- if (req.url === this.$triggeredButton.attr('href')) {
- expect(req.type).toBe('PUT');
+ function mockCloseButtonResponseSuccess(url, response) {
+ mock.onPut(url).reply(() => {
expectNewBranchButtonState(true, false);
- return this.issueStateDeferred;
- } else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) {
- expect(req.type).toBe('GET');
- expectNewBranchButtonState(true, false);
- return this.canCreateBranchDeferred;
- }
- expect(req.url).toBe('unexpected');
- return null;
+ return [200, response];
+ });
+ }
+
+ function mockCloseButtonResponseError(url) {
+ mock.onPut(url).networkError();
+ }
+
+ function mockCanCreateBranch(canCreateBranch) {
+ mock.onGet(/(.*)\/can_create_branch$/).reply(200, {
+ can_create_branch: canCreateBranch,
+ });
}
beforeEach(function() {
@@ -111,6 +117,11 @@ describe('Issue', function() {
loadFixtures('issues/closed-issue.html.raw');
}
+ mock = new MockAdapter(axios);
+
+ mock.onGet(/(.*)\/related_branches$/).reply(200, {});
+ mock.onGet(/(.*)\/referenced_merge_requests$/).reply(200, {});
+
findElements(isIssueInitiallyOpen);
this.issue = new Issue();
expectIssueState(isIssueInitiallyOpen);
@@ -120,71 +131,89 @@ describe('Issue', function() {
this.$projectIssuesCounter = $('.issue_counter').first();
this.$projectIssuesCounter.text('1,001');
- this.issueStateDeferred = new jQuery.Deferred();
- this.canCreateBranchDeferred = new jQuery.Deferred();
+ spyOn(axios, 'get').and.callThrough();
+ });
- spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
+ afterEach(() => {
+ mock.restore();
+ $('div.flash-alert').remove();
});
- it(`${action}s the issue`, function() {
- this.$triggeredButton.trigger('click');
- this.issueStateDeferred.resolve({
+ it(`${action}s the issue`, function(done) {
+ mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
id: 34
});
- this.canCreateBranchDeferred.resolve({
- can_create_branch: !isIssueInitiallyOpen
- });
+ mockCanCreateBranch(!isIssueInitiallyOpen);
- expectIssueState(!isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
- expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+ this.$triggeredButton.trigger('click');
+
+ setTimeout(() => {
+ expectIssueState(!isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+ expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+
+ done();
+ });
});
- it(`fails to ${action} the issue if saved:false`, function() {
- this.$triggeredButton.trigger('click');
- this.issueStateDeferred.resolve({
+ it(`fails to ${action} the issue if saved:false`, function(done) {
+ mockCloseButtonResponseSuccess(this.$triggeredButton.attr('href'), {
saved: false
});
- this.canCreateBranchDeferred.resolve({
- can_create_branch: isIssueInitiallyOpen
- });
+ mockCanCreateBranch(isIssueInitiallyOpen);
- expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ this.$triggeredButton.trigger('click');
+
+ setTimeout(() => {
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+
+ done();
+ });
});
- it(`fails to ${action} the issue if HTTP error occurs`, function() {
+ it(`fails to ${action} the issue if HTTP error occurs`, function(done) {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ mockCanCreateBranch(isIssueInitiallyOpen);
+
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
- this.canCreateBranchDeferred.resolve({
- can_create_branch: isIssueInitiallyOpen
- });
- expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
- expectErrorMessage();
- expect(this.$projectIssuesCounter.text()).toBe('1,001');
- expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ setTimeout(() => {
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+
+ done();
+ });
});
it('disables the new branch button if Ajax call fails', function() {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
+ mock.onGet(/(.*)\/can_create_branch$/).networkError();
+
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
- this.canCreateBranchDeferred.reject();
expectNewBranchButtonState(false, false);
});
- it('does not trigger Ajax call if new branch button is missing', function() {
+ it('does not trigger Ajax call if new branch button is missing', function(done) {
+ mockCloseButtonResponseError(this.$triggeredButton.attr('href'));
Issue.$btnNewBranch = $();
this.canCreateBranchDeferred = null;
this.$triggeredButton.trigger('click');
- this.issueStateDeferred.reject();
+
+ setTimeout(() => {
+ expect(axios.get).not.toHaveBeenCalled();
+
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index 0452934ea9e..03b58e9c1d0 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -1,3 +1,5 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import '~/lib/utils/datetime_utility';
@@ -6,11 +8,29 @@ import '~/breakpoints';
describe('Job', () => {
const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`;
+ let mock;
+ let response;
+
+ function waitForPromise() {
+ return new Promise(resolve => requestAnimationFrame(resolve));
+ }
preloadFixtures('builds/build-with-artifacts.html.raw');
beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw');
+
+ spyOn(urlUtils, 'visitUrl');
+
+ mock = new MockAdapter(axios);
+
+ mock.onGet(new RegExp(`${JOB_URL}/trace.json?(.*)`)).reply(() => [200, response]);
+ });
+
+ afterEach(() => {
+ mock.restore();
+
+ response = {};
});
describe('class constructor', () => {
@@ -55,161 +75,159 @@ describe('Job', () => {
});
describe('running build', () => {
- it('updates the build trace on an interval', function () {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('updates the build trace on an interval', function (done) {
+ response = {
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
append: true,
complete: false,
- });
-
- deferred2.resolve({
- html: '<span>More</span>',
- status: 'running',
- state: 'finalstate',
- append: true,
- complete: true,
- });
+ };
this.job = new Job();
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
- expect(this.job.state).toBe('newstate');
-
- jasmine.clock().tick(4001);
-
- expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
- expect(this.job.state).toBe('finalstate');
+ waitForPromise()
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+ expect(this.job.state).toBe('newstate');
+
+ response = {
+ html: '<span>More</span>',
+ status: 'running',
+ state: 'finalstate',
+ append: true,
+ complete: true,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
+ expect(this.job.state).toBe('finalstate');
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('replaces the entire build trace', () => {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
-
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
-
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('replaces the entire build trace', (done) => {
+ response = {
html: '<span>Update<span>',
status: 'running',
append: false,
complete: false,
- });
-
- deferred2.resolve({
- html: '<span>Different</span>',
- status: 'running',
- append: false,
- });
+ };
this.job = new Job();
- expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
-
- jasmine.clock().tick(4001);
-
- expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
- expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+ waitForPromise()
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
+
+ response = {
+ html: '<span>Different</span>',
+ status: 'running',
+ append: false,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
+ expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('truncated information', () => {
describe('when size is less than total', () => {
- it('shows information about truncated log', () => {
- spyOn(urlUtils, 'visitUrl');
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
-
- deferred.resolve({
+ it('shows information about truncated log', (done) => {
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
- expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ waitForPromise()
+ .then(() => {
+ expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden');
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('shows the size in KiB', () => {
+ it('shows the size in KiB', (done) => {
const size = 50;
- spyOn(urlUtils, 'visitUrl');
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size,
total: 100,
- });
+ };
this.job = new Job();
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(size)}`);
+ waitForPromise()
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(size)}`);
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('shows incremented size', () => {
- const deferred1 = $.Deferred();
- const deferred2 = $.Deferred();
-
- spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise());
-
- spyOn(urlUtils, 'visitUrl');
-
- deferred1.resolve({
+ it('shows incremented size', (done) => {
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(50)}`);
-
- jasmine.clock().tick(4001);
-
- deferred2.resolve({
- html: '<span>Update</span>',
- status: 'success',
- append: true,
- size: 10,
- total: 100,
- });
-
- expect(
- document.querySelector('.js-truncated-info-size').textContent.trim(),
- ).toEqual(`${numberToHumanSize(60)}`);
+ waitForPromise()
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(50)}`);
+
+ response = {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: true,
+ size: 10,
+ total: 100,
+ };
+ })
+ .then(() => jasmine.clock().tick(4001))
+ .then(waitForPromise)
+ .then(() => {
+ expect(
+ document.querySelector('.js-truncated-info-size').textContent.trim(),
+ ).toEqual(`${numberToHumanSize(60)}`);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('renders the raw link', () => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
@@ -220,50 +238,50 @@ describe('Job', () => {
});
describe('when size is equal than total', () => {
- it('does not show the trunctated information', () => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ it('does not show the trunctated information', (done) => {
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 100,
total: 100,
- });
+ };
this.job = new Job();
- expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ waitForPromise()
+ .then(() => {
+ expect(document.querySelector('.js-truncated-info').classList).toContain('hidden');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('output trace', () => {
- beforeEach(() => {
- const deferred = $.Deferred();
- spyOn(urlUtils, 'visitUrl');
-
- spyOn($, 'ajax').and.returnValue(deferred.promise());
- deferred.resolve({
+ beforeEach((done) => {
+ response = {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
- });
+ };
this.job = new Job();
+
+ waitForPromise()
+ .then(done)
+ .catch(done.fail);
});
it('should render trace controls', () => {
const controllers = document.querySelector('.controllers');
- expect(controllers.querySelector('.js-raw-link-controller')).toBeDefined();
- expect(controllers.querySelector('.js-erase-link')).toBeDefined();
- expect(controllers.querySelector('.js-scroll-up')).toBeDefined();
- expect(controllers.querySelector('.js-scroll-down')).toBeDefined();
+ expect(controllers.querySelector('.js-raw-link-controller')).not.toBeNull();
+ expect(controllers.querySelector('.js-scroll-up')).not.toBeNull();
+ expect(controllers.querySelector('.js-scroll-down')).not.toBeNull();
});
it('should render received output', () => {
@@ -276,13 +294,13 @@ describe('Job', () => {
describe('getBuildTrace', () => {
it('should request build trace with state parameter', (done) => {
- spyOn(jQuery, 'ajax').and.callThrough();
+ spyOn(axios, 'get').and.callThrough();
// eslint-disable-next-line no-new
new Job();
setTimeout(() => {
- expect(jQuery.ajax).toHaveBeenCalledWith(
- { url: `${JOB_URL}/trace.json`, data: { state: '' } },
+ expect(axios.get).toHaveBeenCalledWith(
+ `${JOB_URL}/trace.json`, { params: { state: '' } },
);
done();
}, 0);
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index a197b35f6fb..7d992f62f64 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context';
import LabelsSelect from '~/labels_select';
@@ -10,35 +12,44 @@ import '~/users_select';
(() => {
let saveLabelCount = 0;
+ let mock;
+
describe('Issue dropdown sidebar', () => {
preloadFixtures('static/issue_sidebar_label.html.raw');
beforeEach(() => {
loadFixtures('static/issue_sidebar_label.html.raw');
+
+ mock = new MockAdapter(axios);
+
new IssuableContext('{"id":1,"name":"Administrator","username":"root"}');
new LabelsSelect();
- spyOn(jQuery, 'ajax').and.callFake((req) => {
- const d = $.Deferred();
- let LABELS_DATA = [];
+ mock.onGet('/root/test/labels.json').reply(() => {
+ const labels = Array(10).fill().map((_, i) => ({
+ id: i,
+ title: `test ${i}`,
+ color: '#5CB85C',
+ }));
- if (req.url === '/root/test/labels.json') {
- for (let i = 0; i < 10; i += 1) {
- LABELS_DATA.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
- }
- } else if (req.url === '/root/test/issues/2.json') {
- const tmp = [];
- for (let i = 0; i < saveLabelCount; i += 1) {
- tmp.push({ id: i, title: `test ${i}`, color: '#5CB85C' });
- }
- LABELS_DATA = { labels: tmp };
- }
+ return [200, labels];
+ });
+
+ mock.onPut('/root/test/issues/2.json').reply(() => {
+ const labels = Array(saveLabelCount).fill().map((_, i) => ({
+ id: i,
+ title: `test ${i}`,
+ color: '#5CB85C',
+ }));
- d.resolve(LABELS_DATA);
- return d.promise();
+ return [200, { labels }];
});
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('changes collapsed tooltip when changing labels when less than 5', (done) => {
saveLabelCount = 5;
$('.edit-link').get(0).click();
diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js
index 49971bd91e2..7603400b55e 100644
--- a/spec/javascripts/lib/utils/ajax_cache_spec.js
+++ b/spec/javascripts/lib/utils/ajax_cache_spec.js
@@ -1,3 +1,5 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import AjaxCache from '~/lib/utils/ajax_cache';
describe('AjaxCache', () => {
@@ -87,66 +89,53 @@ describe('AjaxCache', () => {
});
describe('retrieve', () => {
- let ajaxSpy;
+ let mock;
beforeEach(() => {
- spyOn(jQuery, 'ajax').and.callFake(url => ajaxSpy(url));
+ mock = new MockAdapter(axios);
+
+ spyOn(axios, 'get').and.callThrough();
+ });
+
+ afterEach(() => {
+ mock.restore();
});
it('stores and returns data from Ajax call if cache is empty', (done) => {
- ajaxSpy = (url) => {
- expect(url).toBe(dummyEndpoint);
- const deferred = $.Deferred();
- deferred.resolve(dummyResponse);
- return deferred.promise();
- };
+ mock.onGet(dummyEndpoint).reply(200, dummyResponse);
AjaxCache.retrieve(dummyEndpoint)
.then((data) => {
- expect(data).toBe(dummyResponse);
- expect(AjaxCache.internalStorage[dummyEndpoint]).toBe(dummyResponse);
+ expect(data).toEqual(dummyResponse);
+ expect(AjaxCache.internalStorage[dummyEndpoint]).toEqual(dummyResponse);
})
.then(done)
.catch(fail);
});
- it('makes no Ajax call if request is pending', () => {
- const responseDeferred = $.Deferred();
-
- ajaxSpy = (url) => {
- expect(url).toBe(dummyEndpoint);
- // neither reject nor resolve to keep request pending
- return responseDeferred.promise();
- };
-
- const unexpectedResponse = data => fail(`Did not expect response: ${data}`);
+ it('makes no Ajax call if request is pending', (done) => {
+ mock.onGet(dummyEndpoint).reply(200, dummyResponse);
AjaxCache.retrieve(dummyEndpoint)
- .then(unexpectedResponse)
+ .then(done)
.catch(fail);
AjaxCache.retrieve(dummyEndpoint)
- .then(unexpectedResponse)
+ .then(done)
.catch(fail);
- expect($.ajax.calls.count()).toBe(1);
+ expect(axios.get.calls.count()).toBe(1);
});
it('returns undefined if Ajax call fails and cache is empty', (done) => {
- const dummyStatusText = 'exploded';
- const dummyErrorMessage = 'server exploded';
- ajaxSpy = (url) => {
- expect(url).toBe(dummyEndpoint);
- const deferred = $.Deferred();
- deferred.reject(null, dummyStatusText, dummyErrorMessage);
- return deferred.promise();
- };
+ const errorMessage = 'Network Error';
+ mock.onGet(dummyEndpoint).networkError();
AjaxCache.retrieve(dummyEndpoint)
.then(data => fail(`Received unexpected data: ${JSON.stringify(data)}`))
.catch((error) => {
- expect(error.message).toBe(`${dummyEndpoint}: ${dummyErrorMessage}`);
- expect(error.textStatus).toBe(dummyStatusText);
+ expect(error.message).toBe(`${dummyEndpoint}: ${errorMessage}`);
+ expect(error.textStatus).toBe(errorMessage);
done();
})
.catch(fail);
@@ -154,7 +143,9 @@ describe('AjaxCache', () => {
it('makes no Ajax call if matching data exists', (done) => {
AjaxCache.internalStorage[dummyEndpoint] = dummyResponse;
- ajaxSpy = () => fail(new Error('expected no Ajax call!'));
+ mock.onGet(dummyEndpoint).reply(() => {
+ fail(new Error('expected no Ajax call!'));
+ });
AjaxCache.retrieve(dummyEndpoint)
.then((data) => {
@@ -171,12 +162,7 @@ describe('AjaxCache', () => {
AjaxCache.internalStorage[dummyEndpoint] = oldDummyResponse;
- ajaxSpy = (url) => {
- expect(url).toBe(dummyEndpoint);
- const deferred = $.Deferred();
- deferred.resolve(dummyResponse);
- return deferred.promise();
- };
+ mock.onGet(dummyEndpoint).reply(200, dummyResponse);
// Call without forceRetrieve param
AjaxCache.retrieve(dummyEndpoint)
@@ -189,7 +175,7 @@ describe('AjaxCache', () => {
// Call with forceRetrieve param
AjaxCache.retrieve(dummyEndpoint, true)
.then((data) => {
- expect(data).toBe(dummyResponse);
+ expect(data).toEqual(dummyResponse);
})
.then(done)
.catch(fail);
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 1052b4e7c20..80430011aed 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,7 +1,6 @@
/* eslint-disable promise/catch-or-return */
-
-import * as commonUtils from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
+import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
describe('common_utils', () => {
@@ -460,17 +459,6 @@ describe('common_utils', () => {
});
});
- describe('ajaxPost', () => {
- it('should perform `$.ajax` call and do `POST` request', () => {
- const requestURL = '/some/random/api';
- const data = { keyname: 'value' };
- const ajaxSpy = spyOn($, 'ajax').and.callFake(() => {});
-
- commonUtils.ajaxPost(requestURL, data);
- expect(ajaxSpy.calls.allArgs()[0][0].type).toEqual('POST');
- });
- });
-
describe('spriteIcon', () => {
let beforeGon;
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index a6be474805b..fda24db98b4 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
-
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
@@ -46,7 +47,7 @@ import 'vendor/jquery.scrollTo';
describe('activateTab', function () {
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function () {});
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.subject = this.class.activateTab;
});
@@ -148,7 +149,7 @@ import 'vendor/jquery.scrollTo';
describe('setCurrentAction', function () {
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function () {});
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
this.subject = this.class.setCurrentAction;
});
@@ -214,13 +215,21 @@ import 'vendor/jquery.scrollTo';
});
describe('tabShown', () => {
+ let mock;
+
beforeEach(function () {
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success({ html: '' });
+ mock = new MockAdapter(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, {
+ data: { html: '' },
});
+
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
});
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('with "Side-by-side"/parallel diff view', () => {
beforeEach(function () {
this.class.diffViewType = () => 'parallel';
@@ -292,16 +301,20 @@ import 'vendor/jquery.scrollTo';
it('triggers Ajax request to JSON endpoint', function (done) {
const url = '/foo/bar/merge_requests/1/diffs';
- spyOn(this.class, 'ajaxGet').and.callFake((options) => {
- expect(options.url).toEqual(`${url}.json`);
+
+ spyOn(axios, 'get').and.callFake((reqUrl) => {
+ expect(reqUrl).toBe(`${url}.json`);
+
done();
+
+ return Promise.resolve({ data: {} });
});
this.class.loadDiff(url);
});
it('triggers scroll event when diff already loaded', function (done) {
- spyOn(this.class, 'ajaxGet').and.callFake(() => done.fail());
+ spyOn(axios, 'get').and.callFake(done.fail);
spyOn(document, 'dispatchEvent');
this.class.diffsLoaded = true;
@@ -316,6 +329,7 @@ import 'vendor/jquery.scrollTo';
describe('with inline diff', () => {
let noteId;
let noteLineNumId;
+ let mock;
beforeEach(() => {
const diffsResponse = getJSONFixture(inlineChangesTabJsonFixture);
@@ -330,29 +344,40 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success(diffsResponse);
- });
+ mock = new MockAdapter(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
+ });
+
+ afterEach(() => {
+ mock.restore();
});
describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'old',
- forceShow: true,
+ setTimeout(() => {
+ expect(noteId.length).toBeGreaterThan(0);
+ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'old',
+ forceShow: true,
+ });
+
+ done();
});
});
- it('should gracefully ignore non-existant fragment hash', function () {
+ it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ setTimeout(() => {
+ expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+
+ done();
+ });
});
});
@@ -370,6 +395,7 @@ import 'vendor/jquery.scrollTo';
describe('with parallel diff', () => {
let noteId;
let noteLineNumId;
+ let mock;
beforeEach(() => {
const diffsResponse = getJSONFixture(parallelChangesTabJsonFixture);
@@ -384,30 +410,40 @@ import 'vendor/jquery.scrollTo';
.attr('href')
.replace('#', '');
- spyOn($, 'ajax').and.callFake(function (options) {
- options.success(diffsResponse);
- });
+ mock = new MockAdapter(axios);
+ mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
+ });
+
+ afterEach(() => {
+ mock.restore();
});
describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'new',
- forceShow: true,
+ setTimeout(() => {
+ expect(noteId.length).toBeGreaterThan(0);
+ expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'new',
+ forceShow: true,
+ });
+
+ done();
});
});
- it('should gracefully ignore non-existant fragment hash', function () {
+ it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ setTimeout(() => {
+ expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
+ done();
+ });
});
});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index a40821a5693..2fb385bd79f 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,11 +1,14 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
import _ from 'underscore';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
import Notes from '~/notes';
+import timeoutPromise from './helpers/set_timeout_promise_helper';
(function() {
window.gon || (window.gon = {});
@@ -119,6 +122,7 @@ import Notes from '~/notes';
let noteEntity;
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
this.notes = new Notes('', []);
@@ -136,24 +140,32 @@ import Notes from '~/notes';
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').val(sampleComment);
+
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, noteEntity);
});
- it('updates note and resets edit form', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('updates note and resets edit form', (done) => {
spyOn(this.notes, 'revertNoteEditForm');
spyOn(this.notes, 'setupNewNote');
$('.js-comment-button').click();
- deferred.resolve(noteEntity);
- const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
- const updatedNote = Object.assign({}, noteEntity);
- updatedNote.note = 'bar';
- this.notes.updateNote(updatedNote, $targetNote);
+ setTimeout(() => {
+ const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
+ const updatedNote = Object.assign({}, noteEntity);
+ updatedNote.note = 'bar';
+ this.notes.updateNote(updatedNote, $targetNote);
+
+ expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+ expect(this.notes.setupNewNote).toHaveBeenCalled();
- expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
- expect(this.notes.setupNewNote).toHaveBeenCalled();
+ done();
+ });
});
});
@@ -479,8 +491,19 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
+
+ function mockNotesPost() {
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+ }
+
+ function mockNotesPostError() {
+ mock.onPost(/(.*)\/notes$/).networkError();
+ }
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -489,63 +512,92 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').val(sampleComment);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should show placeholder note while new comment is being posted', () => {
+ mockNotesPost();
+
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
});
- it('should remove placeholder note when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should remove placeholder note when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($notesContainer.find('.note.being-posted').length).toEqual(0);
+ setTimeout(() => {
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0);
+
+ done();
+ });
});
- it('should show actual note element when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show actual note element when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+ setTimeout(() => {
+ expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+
+ done();
+ });
});
- it('should reset Form when new comment is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should reset Form when new comment is done posting', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- expect($form.find('textarea.js-note-text').val()).toEqual('');
+ setTimeout(() => {
+ expect($form.find('textarea.js-note-text').val()).toEqual('');
+
+ done();
+ });
});
- it('should show flash error message when new comment failed to be posted', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show flash error message when new comment failed to be posted', (done) => {
+ mockNotesPostError();
+
$('.js-comment-button').click();
- deferred.reject();
- expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
+ setTimeout(() => {
+ expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
+
+ done();
+ });
});
- it('should show flash error message when comment failed to be updated', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ it('should show flash error message when comment failed to be updated', (done) => {
+ mockNotesPost();
+
$('.js-comment-button').click();
- deferred.resolve(note);
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').val(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ timeoutPromise()
+ .then(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').val(updatedComment);
- deferred.reject();
- const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
- expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
- expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
+ mock.restore();
+
+ mockNotesPostError();
+
+ $noteEl.find('.js-comment-save-button').click();
+ })
+ .then(timeoutPromise)
+ .then(() => {
+ const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
+ expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
+ expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
+
+ done();
+ })
+ .catch(done.fail);
});
});
@@ -563,8 +615,12 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -582,15 +638,20 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove slash command placeholder when comment with slash commands is done posting', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should remove slash command placeholder when comment with slash commands is done posting', (done) => {
spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
$('.js-comment-button').click();
expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
- deferred.resolve(note);
- expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+
+ setTimeout(() => {
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
+ done();
+ });
});
});
@@ -607,8 +668,12 @@ import Notes from '~/notes';
};
let $form;
let $notesContainer;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onPost(/(.*)\/notes$/).reply(200, note);
+
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
@@ -617,19 +682,24 @@ import Notes from '~/notes';
$form.find('textarea.js-note-text').html(sampleComment);
});
- it('should not render a script tag', () => {
- const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should not render a script tag', (done) => {
$('.js-comment-button').click();
- deferred.resolve(note);
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').html(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ setTimeout(() => {
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').html(updatedComment);
+ $noteEl.find('.js-comment-save-button').click();
+
+ const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
- const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
+ done();
+ });
});
});
diff --git a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
deleted file mode 100644
index 5b316b319a5..00000000000
--- a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import {
- setupPipelineVariableList,
- insertRow,
- removeRow,
-} from '~/pipeline_schedules/setup_pipeline_variable_list';
-
-describe('Pipeline Variable List', () => {
- let $markup;
-
- describe('insertRow', () => {
- it('should insert another row', () => {
- $markup = $(`<div>
- <li class="js-row">
- <input>
- <textarea></textarea>
- </li>
- </div>`);
-
- insertRow($markup.find('.js-row'));
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should clear `data-is-persisted` on cloned row', () => {
- $markup = $(`<div>
- <li class="js-row" data-is-persisted="true"></li>
- </div>`);
-
- insertRow($markup.find('.js-row'));
-
- const $lastRow = $markup.find('.js-row').last();
- expect($lastRow.attr('data-is-persisted')).toBe(undefined);
- });
-
- it('should clear inputs on cloned row', () => {
- $markup = $(`<div>
- <li class="js-row">
- <input value="foo">
- <textarea>bar</textarea>
- </li>
- </div>`);
-
- insertRow($markup.find('.js-row'));
-
- const $lastRow = $markup.find('.js-row').last();
- expect($lastRow.find('input').val()).toBe('');
- expect($lastRow.find('textarea').val()).toBe('');
- });
- });
-
- describe('removeRow', () => {
- it('should remove dynamic row', () => {
- $markup = $(`<div>
- <li class="js-row">
- <input>
- <textarea></textarea>
- </li>
- </div>`);
-
- removeRow($markup.find('.js-row'));
-
- expect($markup.find('.js-row').length).toBe(0);
- });
-
- it('should hide and mark to destroy with already persisted rows', () => {
- $markup = $(`<div>
- <li class="js-row" data-is-persisted="true">
- <input class="js-destroy-input">
- </li>
- </div>`);
-
- const $row = $markup.find('.js-row');
- removeRow($row);
-
- expect($row.find('.js-destroy-input').val()).toBe('1');
- expect($markup.find('.js-row').length).toBe(1);
- });
- });
-
- describe('setupPipelineVariableList', () => {
- beforeEach(() => {
- $markup = $(`<form>
- <li class="js-row">
- <input class="js-user-input" name="schedule[variables_attributes][][key]">
- <textarea class="js-user-input" name="schedule[variables_attributes][][value]"></textarea>
- <button class="js-row-remove-button"></button>
- <button class="js-row-add-button"></button>
- </li>
- </form>`);
-
- setupPipelineVariableList($markup);
- });
-
- it('should remove the row when clicking the remove button', () => {
- $markup.find('.js-row-remove-button').trigger('click');
-
- expect($markup.find('.js-row').length).toBe(0);
- });
-
- it('should add another row when editing the last rows key input', () => {
- const $row = $markup.find('.js-row');
- $row.find('input.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should add another row when editing the last rows value textarea', () => {
- const $row = $markup.find('.js-row');
- $row.find('textarea.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
- });
-
- it('should remove empty row after blurring', () => {
- const $row = $markup.find('.js-row');
- $row.find('input.js-user-input')
- .val('foo')
- .trigger('input');
-
- expect($markup.find('.js-row').length).toBe(2);
-
- $row.find('input.js-user-input')
- .val('')
- .trigger('input')
- .trigger('blur');
-
- expect($markup.find('.js-row').length).toBe(1);
- });
-
- it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
- const $row = $markup.find('.js-row');
- expect($row.find('input').attr('name')).toBe('schedule[variables_attributes][][key]');
- expect($row.find('textarea').attr('name')).toBe('schedule[variables_attributes][][value]');
-
- $markup.filter('form').submit();
-
- expect($row.find('input').attr('name')).toBe('');
- expect($row.find('textarea').attr('name')).toBe('');
- });
- });
-});
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
new file mode 100644
index 00000000000..a1cb0c07b06
--- /dev/null
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe Banzai::ColorParser do
+ describe '.parse' do
+ context 'HEX format' do
+ [
+ '#abc', '#ABC',
+ '#d2d2d2', '#D2D2D2',
+ '#123a', '#123A',
+ '#123456aa', '#123456AA'
+ ].each do |color|
+ it "parses the valid hex color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ '#', '#1', '#12', '#12g', '#12G',
+ '#12345', '#r2r2r2', '#R2R2R2', '#1234567',
+ '# 123', '# 1234', '# 123456', '# 12345678',
+ '#1 2 3', '#123 4', '#12 34 56', '#123456 78'
+ ].each do |color|
+ it "does not parse the invalid hex color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+
+ context 'RGB format' do
+ [
+ 'rgb(0,0,0)', 'rgb(255,255,255)',
+ 'rgb(0, 0, 0)', 'RGB(0,0,0)',
+ 'rgb(0,0,0,0)', 'rgb(0,0,0,0.0)', 'rgb(0,0,0,.0)',
+ 'rgb(0,0,0, 0)', 'rgb(0,0,0, 0.0)', 'rgb(0,0,0, .0)',
+ 'rgb(0,0,0,1)', 'rgb(0,0,0,1.0)',
+ 'rgba(0,0,0)', 'rgba(0,0,0,0)', 'RGBA(0,0,0)',
+ 'rgb(0%,0%,0%)', 'rgba(0%,0%,0%,0%)'
+ ].each do |color|
+ it "parses the valid rgb color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ 'FOOrgb(0,0,0)', 'rgb(0,0,0)BAR',
+ 'rgb(0,0,-1)', 'rgb(0,0,-0)', 'rgb(0,0,256)',
+ 'rgb(0,0,0,-0.1)', 'rgb(0,0,0,-0.0)', 'rgb(0,0,0,-.1)',
+ 'rgb(0,0,0,1.1)', 'rgb(0,0,0,2)',
+ 'rgba(0,0,0,)', 'rgba(0,0,0,0.)', 'rgba(0,0,0,1.)',
+ 'rgb(0,0,0%)', 'rgb(101%,0%,0%)'
+ ].each do |color|
+ it "does not parse the invalid rgb color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+
+ context 'HSL format' do
+ [
+ 'hsl(0,0%,0%)', 'hsl(0,100%,100%)',
+ 'hsl(540,0%,0%)', 'hsl(-720,0%,0%)',
+ 'hsl(0deg,0%,0%)', 'hsl(0DEG,0%,0%)',
+ 'hsl(0, 0%, 0%)', 'HSL(0,0%,0%)',
+ 'hsl(0,0%,0%,0)', 'hsl(0,0%,0%,0.0)', 'hsl(0,0%,0%,.0)',
+ 'hsl(0,0%,0%, 0)', 'hsl(0,0%,0%, 0.0)', 'hsl(0,0%,0%, .0)',
+ 'hsl(0,0%,0%,1)', 'hsl(0,0%,0%,1.0)',
+ 'hsla(0,0%,0%)', 'hsla(0,0%,0%,0)', 'HSLA(0,0%,0%)',
+ 'hsl(1rad,0%,0%)', 'hsl(1.1rad,0%,0%)', 'hsl(.1rad,0%,0%)',
+ 'hsl(-1rad,0%,0%)', 'hsl(1RAD,0%,0%)'
+ ].each do |color|
+ it "parses the valid hsl color #{color}" do
+ expect(subject.parse(color)).to eq(color)
+ end
+ end
+
+ [
+ 'hsl(+0,0%,0%)', 'hsl(0,0,0%)', 'hsl(0,0%,0)', 'hsl(0 deg,0%,0%)',
+ 'hsl(0,-0%,0%)', 'hsl(0,101%,0%)', 'hsl(0,-1%,0%)',
+ 'hsl(0,0%,0%,-0.1)', 'hsl(0,0%,0%,-.1)',
+ 'hsl(0,0%,0%,1.1)', 'hsl(0,0%,0%,2)',
+ 'hsl(0,0%,0%,)', 'hsl(0,0%,0%,0.)', 'hsl(0,0%,0%,1.)',
+ 'hsl(deg,0%,0%)', 'hsl(rad,0%,0%)'
+ ].each do |color|
+ it "does not parse the invalid hsl color #{color}" do
+ expect(subject.parse(color)).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
new file mode 100644
index 00000000000..a098b037510
--- /dev/null
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ColorFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:color) { '#F00' }
+ let(:color_chip_selector) { 'code > span.gfm-color_chip > span' }
+
+ ['#123', '#1234', '#123456', '#12345678',
+ 'rgb(0,0,0)', 'RGB(0, 0, 0)', 'rgba(0,0,0,1)', 'RGBA(0,0,0,0.7)',
+ 'hsl(270,30%,50%)', 'HSLA(270, 30%, 50%, .7)'].each do |color|
+ it "inserts color chip for supported color format #{color}" do
+ content = code_tag(color)
+ doc = filter(content)
+ color_chip = doc.at_css(color_chip_selector)
+
+ expect(color_chip.content).to be_empty
+ expect(color_chip.parent[:class]).to eq 'gfm-color_chip'
+ expect(color_chip[:style]).to eq "background-color: #{color};"
+ end
+ end
+
+ it 'ignores valid color code without backticks(code tags)' do
+ doc = filter(color)
+
+ expect(doc.css('span.gfm-color_chip').size).to be_zero
+ end
+
+ it 'ignores valid color code with prepended space' do
+ content = code_tag(' ' + color)
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores valid color code with appended space' do
+ content = code_tag(color + ' ')
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores valid color code surrounded by spaces' do
+ content = code_tag(' ' + color + ' ')
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ it 'ignores invalid color code' do
+ invalid_color = '#BAR'
+ content = code_tag(invalid_color)
+ doc = filter(content)
+
+ expect(doc.css(color_chip_selector).size).to be_zero
+ end
+
+ def code_tag(string)
+ "<code>#{string}</code>"
+ end
+end
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
index 383bae6e087..d9c21a22590 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Badge::Coverage::Template do
- let(:badge) { double(entity: 'coverage', status: 90) }
+ let(:badge) { double(entity: 'coverage', status: 90.00) }
let(:template) { described_class.new(badge) }
describe '#key_text' do
@@ -13,7 +13,17 @@ describe Gitlab::Badge::Coverage::Template do
describe '#value_text' do
context 'when coverage is known' do
it 'returns coverage percentage' do
- expect(template.value_text).to eq '90%'
+ expect(template.value_text).to eq '90.00%'
+ end
+ end
+
+ context 'when coverage is known to many digits' do
+ before do
+ allow(badge).to receive(:status).and_return(92.349)
+ end
+
+ it 'returns rounded coverage percentage' do
+ expect(template.value_text).to eq '92.35%'
end
end
@@ -37,7 +47,7 @@ describe Gitlab::Badge::Coverage::Template do
describe '#value_width' do
context 'when coverage is known' do
it 'is narrower when coverage is known' do
- expect(template.value_width).to eq 36
+ expect(template.value_width).to eq 54
end
end
@@ -113,7 +123,7 @@ describe Gitlab::Badge::Coverage::Template do
describe '#width' do
context 'when coverage is known' do
it 'returns the key width plus value width' do
- expect(template.width).to eq 98
+ expect(template.width).to eq 116
end
end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 492659a82b0..4ddcbd7eb66 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -8,22 +8,37 @@ describe Gitlab::CurrentSettings do
end
describe '#current_application_settings' do
+ it 'allows keys to be called directly' do
+ db_settings = create(:application_setting,
+ home_page_url: 'http://mydomain.com',
+ signup_enabled: false)
+
+ expect(described_class.home_page_url).to eq(db_settings.home_page_url)
+ expect(described_class.signup_enabled?).to be_falsey
+ expect(described_class.signup_enabled).to be_falsey
+ expect(described_class.metrics_sample_interval).to be(15)
+ end
+
context 'with DB available' do
before do
- allow_any_instance_of(described_class).to receive(:connect_to_db?).and_return(true)
+ # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues
+ # during the initialization phase of the test suite, so instead let's mock the internals of it
+ allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true)
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).and_call_original
+ allow(ActiveRecord::Base.connection).to receive(:table_exists?).with('application_settings').and_return(true)
end
it 'attempts to use cached values first' do
expect(ApplicationSetting).to receive(:cached)
- expect(current_application_settings).to be_a(ApplicationSetting)
+ expect(described_class.current_application_settings).to be_a(ApplicationSetting)
end
it 'falls back to DB if Redis returns an empty value' do
expect(ApplicationSetting).to receive(:cached).and_return(nil)
expect(ApplicationSetting).to receive(:last).and_call_original.twice
- expect(current_application_settings).to be_a(ApplicationSetting)
+ expect(described_class.current_application_settings).to be_a(ApplicationSetting)
end
it 'falls back to DB if Redis fails' do
@@ -32,14 +47,14 @@ describe Gitlab::CurrentSettings do
expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
- expect(current_application_settings).to eq(db_settings)
+ expect(described_class.current_application_settings).to eq(db_settings)
end
it 'creates default ApplicationSettings if none are present' do
expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
- settings = current_application_settings
+ settings = described_class.current_application_settings
expect(settings).to be_a(ApplicationSetting)
expect(settings).to be_persisted
@@ -52,7 +67,7 @@ describe Gitlab::CurrentSettings do
end
it 'returns an in-memory ApplicationSetting object' do
- settings = current_application_settings
+ settings = described_class.current_application_settings
expect(settings).to be_a(OpenStruct)
expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
@@ -63,7 +78,7 @@ describe Gitlab::CurrentSettings do
db_settings = create(:application_setting,
home_page_url: 'http://mydomain.com',
signup_enabled: false)
- settings = current_application_settings
+ settings = described_class.current_application_settings
app_defaults = ApplicationSetting.last
expect(settings).to be_a(OpenStruct)
@@ -80,15 +95,16 @@ describe Gitlab::CurrentSettings do
context 'with DB unavailable' do
before do
- allow_any_instance_of(described_class).to receive(:connect_to_db?).and_return(false)
- allow_any_instance_of(described_class).to receive(:retrieve_settings_from_database_cache?).and_return(nil)
+ # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues
+ # during the initialization phase of the test suite, so instead let's mock the internals of it
+ allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false)
end
it 'returns an in-memory ApplicationSetting object' do
expect(ApplicationSetting).not_to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
- expect(current_application_settings).to be_a(OpenStruct)
+ expect(described_class.current_application_settings).to be_a(OpenStruct)
end
end
@@ -101,8 +117,8 @@ describe Gitlab::CurrentSettings do
expect(ApplicationSetting).not_to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
- expect(current_application_settings).to be_a(ApplicationSetting)
- expect(current_application_settings).not_to be_persisted
+ expect(described_class.current_application_settings).to be_a(ApplicationSetting)
+ expect(described_class.current_application_settings).not_to be_persisted
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 96a442f782f..ec1c7a96f92 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1162,14 +1162,27 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do
it_behaves_like 'finding a branch'
- it 'should reload Rugged::Repository and return master' do
- expect(Rugged::Repository).to receive(:new).twice.and_call_original
+ context 'force_reload is true' do
+ it 'should reload Rugged::Repository' do
+ expect(Rugged::Repository).to receive(:new).twice.and_call_original
- repository.find_branch('master')
- branch = repository.find_branch('master', force_reload: true)
+ repository.find_branch('master')
+ branch = repository.find_branch('master', force_reload: true)
- expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
- expect(branch.name).to eq('master')
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
+ end
+
+ context 'force_reload is false' do
+ it 'should not reload Rugged::Repository' do
+ expect(Rugged::Repository).to receive(:new).once.and_call_original
+
+ branch = repository.find_branch('master', force_reload: false)
+
+ expect(branch).to be_a_kind_of(Gitlab::Git::Branch)
+ expect(branch.name).to eq('master')
+ end
end
end
end
@@ -2183,7 +2196,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.squash(user, squash_id, opts)
end
- context 'sparse checkout' do
+ context 'sparse checkout', :skip_gitaly_mock do
let(:expected_files) { %w(files files/js files/js/application.js) }
before do
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index d9ec28ab02e..9fbdd73ee0e 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -123,4 +123,53 @@ describe Gitlab::GitalyClient::OperationService do
expect(subject.branch_created).to be(false)
end
end
+
+ describe '#user_squash' do
+ let(:branch_name) { 'my-branch' }
+ let(:squash_id) { '1' }
+ let(:start_sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
+ let(:end_sha) { '54cec5282aa9f21856362fe321c800c236a61615' }
+ let(:commit_message) { 'Squash message' }
+ let(:request) do
+ Gitaly::UserSquashRequest.new(
+ repository: repository.gitaly_repository,
+ user: gitaly_user,
+ squash_id: squash_id.to_s,
+ branch: branch_name,
+ start_sha: start_sha,
+ end_sha: end_sha,
+ author: gitaly_user,
+ commit_message: commit_message
+ )
+ end
+ let(:squash_sha) { 'f00' }
+ let(:response) { Gitaly::UserSquashResponse.new(squash_sha: squash_sha) }
+
+ subject do
+ client.user_squash(user, squash_id, branch_name, start_sha, end_sha, user, commit_message)
+ end
+
+ it 'sends a user_squash message and returns the squash sha' do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_squash).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect(subject).to eq(squash_sha)
+ end
+
+ context "when git_error is present" do
+ let(:response) do
+ Gitaly::UserSquashResponse.new(git_error: "something failed")
+ end
+
+ it "throws a PreReceive exception" do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_squash).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect { subject }.to raise_error(
+ Gitlab::Git::Repository::GitError, "something failed")
+ end
+ end
+ end
end
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 9dfd879a1bc..d076007e4bc 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -236,12 +236,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
labels = project.issues.first.labels
expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0))
+ expect(labels.where(type: "ProjectLabel").where.not(group_id: nil).count).to eq(0)
end
end
shared_examples 'restores group correctly' do |**results|
it 'has group label' do
expect(project.group.labels.size).to eq(results.fetch(:labels, 0))
+ expect(project.group.labels.where(type: "GroupLabel").where.not(project_id: nil).count).to eq(0)
end
it 'has group milestone' do
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 0b8e97b8948..ebb6033f71e 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -63,14 +63,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should mount configMap specification in the volume' do
spec = subject.generate.spec
- expect(spec.volumes.first.configMap['name']).to eq('values-content-configuration')
+ expect(spec.volumes.first.configMap['name']).to eq("values-content-configuration-#{app.name}")
expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
end
end
context 'without a configuration file' do
- let(:app) { create(:clusters_applications_ingress, cluster: cluster) }
+ let(:app) { create(:clusters_applications_helm, cluster: cluster) }
it_behaves_like 'helm pod'
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 9e405e9f736..03c185ddc07 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Metrics do
context 'prometheus metrics enabled in config' do
before do
- allow(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
+ allow(Gitlab::CurrentSettings).to receive(:prometheus_metrics_enabled).and_return(true)
end
context 'when metrics folder is present' do
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
new file mode 100644
index 00000000000..b49bc5c328c
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
+ describe '#sql' do
+ it 'increments the number of executed SQL queries' do
+ transaction = double(:transaction)
+
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
+
+ expect(transaction)
+ .to receive(:increment)
+ .at_least(:once)
+
+ User.count
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
new file mode 100644
index 00000000000..a04bcdecb4b
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::Middleware do
+ describe '#call' do
+ it 'runs the application with query limiting in place' do
+ middleware = described_class.new(-> (env) { env })
+
+ expect_any_instance_of(Gitlab::QueryLimiting::Transaction)
+ .to receive(:act_upon_results)
+
+ expect(middleware.call({ number: 10 }))
+ .to eq({ number: 10 })
+ end
+ end
+
+ describe '#action_name' do
+ let(:middleware) { described_class.new(-> (env) { env }) }
+
+ context 'using a Rails request' do
+ it 'returns the name of the controller and action' do
+ env = {
+ described_class::CONTROLLER_KEY => double(
+ :controller,
+ action_name: 'show',
+ class: double(:class, name: 'UsersController'),
+ content_type: 'text/html'
+ )
+ }
+
+ expect(middleware.action_name(env)).to eq('UsersController#show')
+ end
+
+ it 'includes the content type if this is not text/html' do
+ env = {
+ described_class::CONTROLLER_KEY => double(
+ :controller,
+ action_name: 'show',
+ class: double(:class, name: 'UsersController'),
+ content_type: 'application/json'
+ )
+ }
+
+ expect(middleware.action_name(env))
+ .to eq('UsersController#show (application/json)')
+ end
+ end
+
+ context 'using a Grape API request' do
+ it 'returns the name of the request method and endpoint path' do
+ env = {
+ described_class::ENDPOINT_KEY => double(
+ :endpoint,
+ route: double(:route, request_method: 'GET', path: '/foo')
+ )
+ }
+
+ expect(middleware.action_name(env)).to eq('GET /foo')
+ end
+
+ it 'returns nil if the route can not be retrieved' do
+ endpoint = double(:endpoint)
+ env = { described_class::ENDPOINT_KEY => endpoint }
+
+ allow(endpoint)
+ .to receive(:route)
+ .and_raise(RuntimeError)
+
+ expect(middleware.action_name(env)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
new file mode 100644
index 00000000000..b4231fcd0fa
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting::Transaction do
+ after do
+ Thread.current[described_class::THREAD_KEY] = nil
+ end
+
+ describe '.current' do
+ it 'returns nil when there is no transaction' do
+ expect(described_class.current).to be_nil
+ end
+
+ it 'returns the transaction when present' do
+ Thread.current[described_class::THREAD_KEY] = described_class.new
+
+ expect(described_class.current).to be_an_instance_of(described_class)
+ end
+ end
+
+ describe '.run' do
+ it 'runs a transaction and returns it and its return value' do
+ trans, ret = described_class.run do
+ 10
+ end
+
+ expect(trans).to be_an_instance_of(described_class)
+ expect(ret).to eq(10)
+ end
+
+ it 'removes the transaction from the current thread upon completion' do
+ described_class.run do
+ 10
+ end
+
+ expect(Thread.current[described_class::THREAD_KEY]).to be_nil
+ end
+ end
+
+ describe '#act_upon_results' do
+ context 'when the query threshold is not exceeded' do
+ it 'does nothing' do
+ trans = described_class.new
+
+ expect(trans).not_to receive(:raise)
+
+ trans.act_upon_results
+ end
+ end
+
+ context 'when the query threshold is exceeded' do
+ let(:transaction) do
+ trans = described_class.new
+ trans.count = described_class::THRESHOLD + 1
+
+ trans
+ end
+
+ it 'raises an error when this is enabled' do
+ expect { transaction.act_upon_results }
+ .to raise_error(described_class::ThresholdExceededError)
+ end
+
+ it 'reports the error in Sentry if raising an error is disabled' do
+ expect(transaction)
+ .to receive(:raise_error?)
+ .and_return(false)
+
+ expect(Raven)
+ .to receive(:capture_exception)
+ .with(an_instance_of(described_class::ThresholdExceededError))
+
+ transaction.act_upon_results
+ end
+ end
+ end
+
+ describe '#increment' do
+ it 'increments the number of executed queries' do
+ transaction = described_class.new
+
+ expect(transaction.count).to be_zero
+
+ transaction.increment
+
+ expect(transaction.count).to eq(1)
+ end
+ end
+
+ describe '#raise_error?' do
+ it 'returns true in a test environment' do
+ transaction = described_class.new
+
+ expect(transaction.raise_error?).to eq(true)
+ end
+
+ it 'returns false in a production environment' do
+ transaction = described_class.new
+
+ expect(Rails.env)
+ .to receive(:test?)
+ .and_return(false)
+
+ expect(transaction.raise_error?).to eq(false)
+ end
+ end
+
+ describe '#threshold_exceeded?' do
+ it 'returns false when the threshold is not exceeded' do
+ transaction = described_class.new
+
+ expect(transaction.threshold_exceeded?).to eq(false)
+ end
+
+ it 'returns true when the threshold is exceeded' do
+ transaction = described_class.new
+ transaction.count = described_class::THRESHOLD + 1
+
+ expect(transaction.threshold_exceeded?).to eq(true)
+ end
+ end
+
+ describe '#error_message' do
+ it 'returns the error message to display when the threshold is exceeded' do
+ transaction = described_class.new
+ transaction.count = max = described_class::THRESHOLD
+
+ expect(transaction.error_message).to eq(
+ "Too many SQL queries were executed: a maximum of #{max} " \
+ "is allowed but #{max} SQL queries were executed"
+ )
+ end
+
+ it 'includes the action name in the error message when present' do
+ transaction = described_class.new
+ transaction.count = max = described_class::THRESHOLD
+ transaction.action = 'UsersController#show'
+
+ expect(transaction.error_message).to eq(
+ "Too many SQL queries were executed in UsersController#show: " \
+ "a maximum of #{max} is allowed but #{max} SQL queries were executed"
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
new file mode 100644
index 00000000000..2eddab0b8c3
--- /dev/null
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab::QueryLimiting do
+ describe '.enable?' do
+ it 'returns true in a test environment' do
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true in a development environment' do
+ allow(Rails.env).to receive(:development?).and_return(true)
+
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true on GitLab.com' do
+ allow(Gitlab).to receive(:com?).and_return(true)
+
+ expect(described_class.enable?).to eq(true)
+ end
+
+ it 'returns true in a non GitLab.com' do
+ expect(Gitlab).to receive(:com?).and_return(false)
+ expect(Rails.env).to receive(:development?).and_return(false)
+ expect(Rails.env).to receive(:test?).and_return(false)
+
+ expect(described_class.enable?).to eq(false)
+ end
+ end
+
+ describe '.whitelist' do
+ it 'raises ArgumentError when an invalid issue URL is given' do
+ expect { described_class.whitelist('foo') }
+ .to raise_error(ArgumentError)
+ end
+
+ context 'without a transaction' do
+ it 'does nothing' do
+ expect { described_class.whitelist('https://example.com') }
+ .not_to raise_error
+ end
+ end
+
+ context 'with a transaction' do
+ let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+
+ before do
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
+ end
+
+ it 'does not increment the number of SQL queries executed in the block' do
+ before = transaction.count
+
+ described_class.whitelist('https://example.com')
+
+ 2.times do
+ User.count
+ end
+
+ expect(transaction.count).to eq(before)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index 93d538141ce..c15e29774b6 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -37,6 +37,41 @@ describe Gitlab::SSHPublicKey, lib: true do
end
end
+ describe '.sanitize(key_content)' do
+ let(:content) { build(:key).key }
+
+ context 'when key has blank space characters' do
+ it 'removes the extra blank space characters' do
+ unsanitized = content.insert(100, "\n")
+ .insert(40, "\r\n")
+ .insert(30, ' ')
+
+ sanitized = described_class.sanitize(unsanitized)
+ _, body = sanitized.split
+
+ expect(sanitized).not_to eq(unsanitized)
+ expect(body).not_to match(/\s/)
+ end
+ end
+
+ context "when key doesn't have blank space characters" do
+ it "doesn't modify the content" do
+ sanitized = described_class.sanitize(content)
+
+ expect(sanitized).to eq(content)
+ end
+ end
+
+ context "when key is invalid" do
+ it 'returns the original content' do
+ unsanitized = "ssh-foo any content=="
+ sanitized = described_class.sanitize(unsanitized)
+
+ expect(sanitized).to eq(unsanitized)
+ end
+ end
+ end
+
describe '#valid?' do
subject { public_key }
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b5f2a15ada3..0e9ecff25a6 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -103,9 +103,9 @@ describe Gitlab::UsageData do
subject { described_class.features_usage_data_ce }
it 'gathers feature usage data' do
- expect(subject[:signup]).to eq(current_application_settings.allow_signup?)
+ expect(subject[:signup]).to eq(Gitlab::CurrentSettings.allow_signup?)
expect(subject[:ldap]).to eq(Gitlab.config.ldap.enabled)
- expect(subject[:gravatar]).to eq(current_application_settings.gravatar_enabled?)
+ expect(subject[:gravatar]).to eq(Gitlab::CurrentSettings.gravatar_enabled?)
expect(subject[:omniauth]).to eq(Gitlab.config.omniauth.enabled)
expect(subject[:reply_by_email]).to eq(Gitlab::IncomingEmail.enabled?)
expect(subject[:container_registry]).to eq(Gitlab.config.registry.enabled)
@@ -129,7 +129,7 @@ describe Gitlab::UsageData do
subject { described_class.license_usage_data }
it "gathers license data" do
- expect(subject[:uuid]).to eq(current_application_settings.uuid)
+ expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
expect(subject[:active_user_count]).to eq(User.active.count)
expect(subject[:recorded_at]).to be_a(Time)
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index d85dac630b4..2c1146ceff5 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -57,6 +57,15 @@ describe Gitlab::VisibilityLevel do
expect(described_class.allowed_levels)
.to contain_exactly(described_class::PRIVATE, described_class::PUBLIC)
end
+
+ it 'returns all levels when no visibility level was set' do
+ allow(described_class)
+ .to receive_message_chain('current_application_settings.restricted_visibility_levels')
+ .and_return(nil)
+
+ expect(described_class.allowed_levels)
+ .to contain_exactly(described_class::PRIVATE, described_class::INTERNAL, described_class::PUBLIC)
+ end
end
describe '.closest_allowed_level' do
diff --git a/spec/migrations/remove_project_labels_group_id_spec.rb b/spec/migrations/remove_project_labels_group_id_spec.rb
new file mode 100644
index 00000000000..d80d61af20b
--- /dev/null
+++ b/spec/migrations/remove_project_labels_group_id_spec.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180202111106_remove_project_labels_group_id.rb')
+
+describe RemoveProjectLabelsGroupId, :delete do
+ let(:migration) { described_class.new }
+ let(:group) { create(:group) }
+ let!(:project_label) { create(:label, group_id: group.id) }
+ let!(:group_label) { create(:group_label) }
+
+ describe '#up' do
+ it 'updates the project labels group ID' do
+ expect { migration.up }.to change { project_label.reload.group_id }.to(nil)
+ end
+
+ it 'keeps the group labels group ID' do
+ expect { migration.up }.not_to change { group_label.reload.group_id }
+ end
+ end
+end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 4cd9e3f4f1d..bf5703ac986 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,13 +1,6 @@
require 'spec_helper'
describe Key, :mailer do
- include Gitlab::CurrentSettings
-
- describe 'modules' do
- subject { described_class }
- it { is_expected.to include_module(Gitlab::CurrentSettings) }
- end
-
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
@@ -79,16 +72,53 @@ describe Key, :mailer do
expect(build(:key)).to be_valid
end
- it 'accepts a key with newline charecters after stripping them' do
- key = build(:key)
- key.key = key.key.insert(100, "\n")
- key.key = key.key.insert(40, "\r\n")
- expect(key).to be_valid
- end
-
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
+
+ where(:factory, :chars, :expected_sections) do
+ [
+ [:key, ["\n", "\r\n"], 3],
+ [:key, [' ', ' '], 3],
+ [:key_without_comment, [' ', ' '], 2]
+ ]
+ end
+
+ with_them do
+ let!(:key) { create(factory) }
+ let!(:original_fingerprint) { key.fingerprint }
+
+ it 'accepts a key with blank space characters after stripping them' do
+ modified_key = key.key.insert(100, chars.first).insert(40, chars.last)
+ _, content = modified_key.split
+
+ key.update!(key: modified_key)
+
+ expect(key).to be_valid
+ expect(key.key.split.size).to eq(expected_sections)
+
+ expect(content).not_to match(/\s/)
+ expect(original_fingerprint).to eq(key.fingerprint)
+ end
+ end
+ end
+
+ context 'validate size' do
+ where(:key_content, :result) do
+ [
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(512).generate, false],
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(8192).generate, false],
+ [Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate, true]
+ ]
+ end
+
+ with_them do
+ it 'validates the size of the key' do
+ key = build(:key, key: key_content)
+
+ expect(key.valid?).to eq(result)
+ end
+ end
end
context 'validate it meets key restrictions' do
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 6b7dbad128c..824cca66fb4 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -596,4 +596,24 @@ describe Namespace do
end
end
end
+
+ describe '#remove_exports' do
+ let(:legacy_project) { create(:project, :with_export, namespace: namespace) }
+ let(:hashed_project) { create(:project, :with_export, :hashed, namespace: namespace) }
+ let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') }
+ let(:legacy_export) { legacy_project.export_project_path }
+ let(:hashed_export) { hashed_project.export_project_path }
+
+ it 'removes exports for legacy and hashed projects' do
+ allow(Gitlab::ImportExport).to receive(:storage_path) { export_path }
+
+ expect(File.exist?(legacy_export)).to be_truthy
+ expect(File.exist?(hashed_export)).to be_truthy
+
+ namespace.remove_exports!
+
+ expect(File.exist?(legacy_export)).to be_falsy
+ expect(File.exist?(hashed_export)).to be_falsy
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 3d030927036..00dda7c9c60 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -17,8 +17,6 @@ describe Note do
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Awardable) }
-
- it { is_expected.to include_module(Gitlab::CurrentSettings) }
end
describe 'validation' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 31dcb543cbd..da940571bc1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -117,7 +117,6 @@ describe Project do
it { is_expected.to include_module(Gitlab::ConfigHelper) }
it { is_expected.to include_module(Gitlab::ShellAdapter) }
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
- it { is_expected.to include_module(Gitlab::CurrentSettings) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
end
@@ -2504,6 +2503,37 @@ describe Project do
end
end
+ describe '#remove_exports' do
+ let(:project) { create(:project, :with_export) }
+
+ it 'removes the exports directory for the project' do
+ expect(File.exist?(project.export_path)).to be_truthy
+
+ allow(FileUtils).to receive(:rm_rf).and_call_original
+ expect(FileUtils).to receive(:rm_rf).with(project.export_path).and_call_original
+ project.remove_exports
+
+ expect(File.exist?(project.export_path)).to be_falsy
+ end
+
+ it 'is a no-op when there is no namespace' do
+ export_path = project.export_path
+ project.update_column(:namespace_id, nil)
+
+ expect(FileUtils).not_to receive(:rm_rf).with(export_path)
+
+ project.remove_exports
+
+ expect(File.exist?(export_path)).to be_truthy
+ end
+
+ it 'is run when the project is destroyed' do
+ expect(project).to receive(:remove_exports).and_call_original
+
+ project.destroy
+ end
+ end
+
describe '#forks_count' do
it 'returns the number of forks' do
project = build(:project)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1102b1c9006..02a5ee54262 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -36,26 +36,49 @@ describe Repository do
end
describe '#branch_names_contains' do
- subject { repository.branch_names_contains(sample_commit.id) }
+ shared_examples '#branch_names_contains' do
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
- it { is_expected.to include('master') }
- it { is_expected.not_to include('feature') }
- it { is_expected.not_to include('fix') }
+ subject { repository.branch_names_contains(sample_commit.id) }
- describe 'when storage is broken', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error do
- broken_repository.branch_names_contains(sample_commit.id)
+ it { is_expected.to include('master') }
+ it { is_expected.not_to include('feature') }
+ it { is_expected.not_to include('fix') }
+
+ describe 'when storage is broken', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error do
+ broken_repository.branch_names_contains(sample_commit.id)
+ end
end
end
end
+
+ context 'when gitaly is enabled' do
+ it_behaves_like '#branch_names_contains'
+ end
+
+ context 'when gitaly is disabled', :skip_gitaly_mock do
+ it_behaves_like '#branch_names_contains'
+ end
end
describe '#tag_names_contains' do
- subject { repository.tag_names_contains(sample_commit.id) }
+ shared_examples '#tag_names_contains' do
+ subject { repository.tag_names_contains(sample_commit.id) }
- it { is_expected.to include('v1.1.0') }
- it { is_expected.not_to include('v1.0.0') }
+ it { is_expected.to include('v1.1.0') }
+ it { is_expected.not_to include('v1.0.0') }
+ end
+
+ context 'when gitaly is enabled' do
+ it_behaves_like '#tag_names_contains'
+ end
+
+ context 'when gitaly is enabled', :skip_gitaly_mock do
+ it_behaves_like '#tag_names_contains'
+ end
end
describe 'tags_sorted_by' do
@@ -959,19 +982,19 @@ describe Repository do
end
describe '#find_branch' do
- it 'loads a branch with a fresh repo' do
- expect(Gitlab::Git::Repository).to receive(:new).twice.and_call_original
+ context 'fresh_repo is true' do
+ it 'delegates the call to raw_repository' do
+ expect(repository.raw_repository).to receive(:find_branch).with('master', true)
- 2.times do
- expect(repository.find_branch('feature')).not_to be_nil
+ repository.find_branch('master', fresh_repo: true)
end
end
- it 'loads a branch with a cached repo' do
- expect(Gitlab::Git::Repository).to receive(:new).once.and_call_original
+ context 'fresh_repo is false' do
+ it 'delegates the call to raw_repository' do
+ expect(repository.raw_repository).to receive(:find_branch).with('master', false)
- 2.times do
- expect(repository.find_branch('feature', fresh_repo: false)).not_to be_nil
+ repository.find_branch('master', fresh_repo: false)
end
end
end
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
new file mode 100644
index 00000000000..64ba17c81fe
--- /dev/null
+++ b/spec/models/user_callout_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+describe UserCallout do
+ let!(:callout) { create(:user_callout) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+
+ it { is_expected.to validate_presence_of(:feature_name) }
+ it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity }
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 594f23718da..18c91d4cffd 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,14 +1,12 @@
require 'spec_helper'
describe User do
- include Gitlab::CurrentSettings
include ProjectForksHelper
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Gitlab::ConfigHelper) }
- it { is_expected.to include_module(Gitlab::CurrentSettings) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(TokenAuthenticatable) }
@@ -560,7 +558,7 @@ describe User do
stub_config_setting(default_can_create_group: true)
expect { user.update_attributes(external: false) }.to change { user.can_create_group }.to(true)
- .and change { user.projects_limit }.to(current_application_settings.default_projects_limit)
+ .and change { user.projects_limit }.to(Gitlab::CurrentSettings.default_projects_limit)
end
end
@@ -826,7 +824,7 @@ describe User do
end
end
- context 'when current_application_settings.user_default_external is true' do
+ context 'when Gitlab::CurrentSettings.user_default_external is true' do
before do
stub_application_setting(user_default_external: true)
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3c0b4728dc2..bb0034e3237 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -30,6 +30,21 @@ describe API::Groups do
expect(json_response)
.to satisfy_one { |group| group['name'] == group1.name }
end
+
+ it 'avoids N+1 queries' do
+ # Establish baseline
+ get api("/groups", admin)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/groups", admin)
+ end
+
+ create(:group)
+
+ expect do
+ get api("/groups", admin)
+ end.not_to exceed_query_limit(control)
+ end
end
context "when authenticated as user" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 97e7ffcd38e..f11cd638d96 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2,8 +2,6 @@
require 'spec_helper'
describe API::Projects do
- include Gitlab::CurrentSettings
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index 13e465e0b2d..5d99d9495f3 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe API::V3::Projects do
- include Gitlab::CurrentSettings
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index cb4c3e72aa0..e56d335a7d6 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -172,11 +172,32 @@ describe MergeRequests::BuildService do
end
end
- context 'branch starts with external issue IID followed by a hyphen' do
+ context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do
let(:source_branch) { '12345-fix-issue' }
before do
+ allow(project).to receive(:external_issue_tracker).and_return(false)
+ allow(project).to receive(:issues_enabled?).and_return(false)
+ end
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
+ end
+
+ it 'uses the description of the commit as the description of the merge request' do
+ commit_description = commit_1.safe_message.split(/\n+/, 2).last
+
+ expect(merge_request.description).to eq("#{commit_description}")
+ end
+ end
+
+ context 'branch starts with JIRA-formatted external issue IID followed by a hyphen' do
+ let(:source_branch) { 'EXMPL-12345-fix-issue' }
+
+ before do
allow(project).to receive(:external_issue_tracker).and_return(true)
+ allow(project).to receive(:issues_enabled?).and_return(false)
+ allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern)
end
it 'uses the title of the commit as the title of the merge request' do
@@ -186,7 +207,7 @@ describe MergeRequests::BuildService do
it 'uses the description of the commit as the description of the merge request and appends the closes text' do
commit_description = commit_1.safe_message.split(/\n+/, 2).last
- expect(merge_request.description).to eq("#{commit_description}\n\nCloses #12345")
+ expect(merge_request.description).to eq("#{commit_description}\n\nCloses EXMPL-12345")
end
end
end
@@ -252,19 +273,46 @@ describe MergeRequests::BuildService do
end
end
- context 'branch starts with external issue IID followed by a hyphen' do
+ context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do
let(:source_branch) { '12345-fix-issue' }
before do
- allow(project).to receive(:external_issue_tracker).and_return(true)
+ allow(project).to receive(:external_issue_tracker).and_return(false)
+ allow(project).to receive(:issues_enabled?).and_return(false)
end
it 'sets the title to the humanized branch title' do
expect(merge_request.title).to eq('12345 fix issue')
end
+ end
+
+ context 'branch starts with JIRA-formatted external issue IID' do
+ let(:source_branch) { 'EXMPL-12345' }
+
+ before do
+ allow(project).to receive(:external_issue_tracker).and_return(true)
+ allow(project).to receive(:issues_enabled?).and_return(false)
+ allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern)
+ end
+
+ it 'sets the title to the humanized branch title' do
+ expect(merge_request.title).to eq('Resolve EXMPL-12345')
+ end
it 'appends the closes text' do
- expect(merge_request.description).to eq('Closes #12345')
+ expect(merge_request.description).to eq('Closes EXMPL-12345')
+ end
+
+ context 'followed by hyphenated text' do
+ let(:source_branch) { 'EXMPL-12345-fix-issue' }
+
+ it 'sets the title to the humanized branch title' do
+ expect(merge_request.title).to eq('Resolve EXMPL-12345 "Fix issue"')
+ end
+
+ it 'appends the closes text' do
+ expect(merge_request.description).to eq('Closes EXMPL-12345')
+ end
end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7c3374c6113..903aa0a5078 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -74,6 +74,14 @@ describe MergeRequests::RefreshService do
expect(@fork_build_failed_todo).to be_done
end
+ it 'reloads source branch MRs memoization' do
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+
+ expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }.to change {
+ refresh_service.instance_variable_get("@source_merge_requests").first.merge_request_diff
+ }
+ end
+
context 'when source branch ref does not exists' do
before do
DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
@@ -392,37 +400,21 @@ describe MergeRequests::RefreshService do
end
it 'references the commit that caused the Work in Progress status' do
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- allow(refresh_service).to receive(:find_new_commits)
- refresh_service.instance_variable_set("@commits", [
- double(
- id: 'aaaaaaa',
- sha: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e',
- short_id: 'aaaaaaa',
- title: 'Fix issue',
- work_in_progress?: false
- ),
- double(
- id: 'bbbbbbb',
- sha: '498214de67004b1da3d820901307bed2a68a8ef6',
- short_id: 'bbbbbbb',
- title: 'fixup! Fix issue',
- work_in_progress?: true,
- to_reference: 'bbbbbbb'
- ),
- double(
- id: 'ccccccc',
- sha: '1b12f15a11fc6e62177bef08f47bc7b5ce50b141',
- short_id: 'ccccccc',
- title: 'fixup! Fix issue',
- work_in_progress?: true,
- to_reference: 'ccccccc'
- )
- ])
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
- reload_mrs
- expect(@merge_request.notes.last.note).to eq(
- "marked as a **Work In Progress** from bbbbbbb"
+ wip_merge_request = create(:merge_request,
+ source_project: @project,
+ source_branch: 'wip',
+ target_branch: 'master',
+ target_project: @project)
+
+ commits = wip_merge_request.commits
+ oldrev = commits.last.id
+ newrev = commits.first.id
+ wip_commit = wip_merge_request.commits.find(&:work_in_progress?)
+
+ refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
+
+ expect(wip_merge_request.reload.notes.last.note).to eq(
+ "marked as a **Work In Progress** from #{wip_commit.id}"
)
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index ab3aa18cf4e..5b5edc1aa0d 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -54,10 +54,11 @@ describe SystemNoteService do
expect(note_lines[0]).to eq "added #{new_commits.size} commits"
end
- it 'adds a message line for each commit' do
- new_commits.each_with_index do |commit, i|
- # Skip the header
- expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}"
+ it 'adds a message for each commit' do
+ decoded_note_content = HTMLEntities.new.decode(subject.note)
+
+ new_commits.each do |commit|
+ expect(decoded_note_content).to include("<li>#{commit.short_id} - #{commit.title}</li>")
end
end
end
@@ -69,7 +70,7 @@ describe SystemNoteService do
let(:old_commits) { [noteable.commits.last] }
it 'includes the existing commit' do
- expect(summary_line).to eq "* #{old_commits.first.short_id} - 1 commit from branch `feature`"
+ expect(summary_line).to start_with("<ul><li>#{old_commits.first.short_id} - 1 commit from branch <code>feature</code>")
end
end
@@ -79,22 +80,16 @@ describe SystemNoteService do
context 'with oldrev' do
let(:oldrev) { noteable.commits[2].id }
- it 'includes a commit range' do
- expect(summary_line).to start_with "* #{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id}"
- end
-
- it 'includes a commit count' do
- expect(summary_line).to end_with " - 26 commits from branch `feature`"
+ it 'includes a commit range and count' do
+ expect(summary_line)
+ .to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code>")
end
end
context 'without oldrev' do
- it 'includes a commit range' do
- expect(summary_line).to start_with "* #{old_commits[0].short_id}..#{old_commits[-1].short_id}"
- end
-
- it 'includes a commit count' do
- expect(summary_line).to end_with " - 26 commits from branch `feature`"
+ it 'includes a commit range and count' do
+ expect(summary_line)
+ .to start_with("<ul><li>#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch <code>feature</code>")
end
end
@@ -104,7 +99,7 @@ describe SystemNoteService do
end
it 'includes the project namespace' do
- expect(summary_line).to end_with "`#{noteable.target_project_namespace}:feature`"
+ expect(summary_line).to include("<code>#{noteable.target_project_namespace}:feature</code>")
end
end
end
@@ -693,7 +688,7 @@ describe SystemNoteService do
describe '.new_commit_summary' do
it 'escapes HTML titles' do
commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
- escaped = '&lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
+ escaped = '&lt;pre&gt;This is a test&lt;/pre&gt;'
expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/))
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index d12b2757427..ec4ec6f4038 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -190,6 +190,27 @@ module MarkdownMatchers
expect(video['src']).to end_with('/assets/videos/gitlab-demo.mp4')
end
end
+
+ # ColorFilter
+ matcher :parse_colors do
+ set_default_markdown_messages
+
+ match do |actual|
+ color_chips = actual.css('code > span.gfm-color_chip > span')
+
+ expect(color_chips.count).to eq(9)
+
+ [
+ '#F00', '#F00A', '#FF0000', '#FF0000AA', 'RGB(0,255,0)',
+ 'RGB(0%,100%,0%)', 'RGBA(0,255,0,0.7)', 'HSL(540,70%,50%)',
+ 'HSLA(540,70%,50%,0.7)'
+ ].each_with_index do |color, i|
+ parsed_color = Banzai::ColorParser.parse(color)
+ expect(color_chips[i]['style']).to match("background-color: #{parsed_color};")
+ expect(color_chips[i].parent.parent.content).to match(color)
+ end
+ end
+ end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index f9121cce985..52e47ae2d34 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -15,9 +15,7 @@ RSpec.configure do |config|
# Track the maximum number of failures
first_failure = Time.parse("2017-11-14 17:52:30")
last_failure = Time.parse("2017-11-14 18:54:37")
- failure_count = Gitlab::CurrentSettings
- .current_application_settings
- .circuitbreaker_failure_count_threshold + 1
+ failure_count = Gitlab::CurrentSettings.circuitbreaker_failure_count_threshold + 1
cache_key = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}broken:#{Gitlab::Environment.hostname}"
Gitlab::Git::Storage.redis.with do |redis|
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
index 695152e2d4e..36b90fc68d6 100644
--- a/spec/support/stub_env.rb
+++ b/spec/support/stub_env.rb
@@ -1,7 +1,5 @@
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV
- include Gitlab::CurrentSettings
-
def stub_env(key_or_hash, value = nil)
init_stub unless env_stubbed?
diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb
index 3d9705c9c05..e5c8ac6a004 100644
--- a/spec/support/unique_ip_check_shared_examples.rb
+++ b/spec/support/unique_ip_check_shared_examples.rb
@@ -9,7 +9,7 @@ shared_context 'unique ips sign in limit' do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- current_application_settings.update!(
+ Gitlab::CurrentSettings.update!(
unique_ips_limit_enabled: true,
unique_ips_limit_time_window: 10000
)
@@ -34,7 +34,7 @@ end
shared_examples 'user login operation with unique ip limit' do
include_context 'unique ips sign in limit' do
before do
- current_application_settings.update!(unique_ips_limit_per_user: 1)
+ Gitlab::CurrentSettings.update!(unique_ips_limit_per_user: 1)
end
it 'allows user authenticating from the same ip' do
@@ -52,7 +52,7 @@ end
shared_examples 'user login request with unique ip limit' do |success_status = 200|
include_context 'unique ips sign in limit' do
before do
- current_application_settings.update!(unique_ips_limit_per_user: 1)
+ Gitlab::CurrentSettings.update!(unique_ips_limit_per_user: 1)
end
it 'allows user authenticating from the same ip' do
diff --git a/vendor/ingress/values.yaml b/vendor/ingress/values.yaml
new file mode 100644
index 00000000000..cdb7da77e86
--- /dev/null
+++ b/vendor/ingress/values.yaml
@@ -0,0 +1,8 @@
+controller:
+ image:
+ tag: "0.10.2"
+ repository: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller"
+ stats.enabled: true
+ podAnnotations:
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "10254"
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index 5249449c7f8..db967514be7 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -2,7 +2,7 @@ alertmanager:
enabled: false
kubeStateMetrics:
- enabled: false
+ enabled: true
nodeExporter:
enabled: false
@@ -10,11 +10,15 @@ nodeExporter:
pushgateway:
enabled: false
+server:
+ image:
+ tag: v2.1.0
+
serverFiles:
- alerts: ""
- rules: ""
+ alerts: {}
+ rules: {}
- prometheus.yml: |-
+ prometheus.yml:
rule_files:
- /etc/config/rules
- /etc/config/alerts
@@ -26,92 +30,108 @@ serverFiles:
- job_name: kubernetes-cadvisor
scheme: https
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: node
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: node
relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels:
- - __meta_kubernetes_node_name
- regex: "(.+)"
- target_label: __metrics_path__
- replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor"
metric_relabel_configs:
- - source_labels:
- - pod_name
- target_label: environment
- regex: "(.+)-.+-.+"
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
+ - job_name: 'kubernetes-service-endpoints'
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
+ action: keep
+ regex: true
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
+ action: replace
+ target_label: __scheme__
+ regex: (https?)
+ - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
+ action: replace
+ target_label: __metrics_path__
+ regex: (.+)
+ - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
+ action: replace
+ target_label: __address__
+ regex: (.+)(?::\d+);(\d+)
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels: [__meta_kubernetes_namespace]
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels: [__meta_kubernetes_service_name]
+ action: replace
+ target_label: kubernetes_name
- job_name: kubernetes-nodes
scheme: https
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: node
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: node
relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - target_label: __address__
- replacement: kubernetes.default.svc:443
- - source_labels:
- - __meta_kubernetes_node_name
- regex: "(.+)"
- target_label: __metrics_path__
- replacement: "/api/v1/nodes/${1}/proxy/metrics"
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: "(.+)"
+ target_label: __metrics_path__
+ replacement: "/api/v1/nodes/${1}/proxy/metrics"
metric_relabel_configs:
- - source_labels:
- - pod_name
- target_label: environment
- regex: "(.+)-.+-.+"
+ - source_labels:
+ - pod_name
+ target_label: environment
+ regex: "(.+)-.+-.+"
- job_name: kubernetes-pods
tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- - role: pod
- api_server: https://kubernetes.default.svc:443
- tls_config:
- ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
- bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ - role: pod
relabel_configs:
- - source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_scrape
- action: keep
- regex: 'true'
- - source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_path
- action: replace
- target_label: __metrics_path__
- regex: "(.+)"
- - source_labels:
- - __address__
- - __meta_kubernetes_pod_annotation_prometheus_io_port
- action: replace
- regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
- replacement: "$1:$2"
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- action: replace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_pod_name
- action: replace
- target_label: kubernetes_pod_name
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: 'true'
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ target_label: __metrics_path__
+ regex: "(.+)"
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
+ replacement: "$1:$2"
+ target_label: __address__
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name
diff --git a/yarn.lock b/yarn.lock
index d10a4372a40..e6d5f239d83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3322,7 +3322,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^7.1.0, glob@~7.1.2:
+glob@^7.1.0, glob@^7.1.2, glob@~7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies: