summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml1
-rw-r--r--app/assets/javascripts/issuable/components/csv_export_modal.vue1
-rw-r--r--app/assets/javascripts/issuable/components/csv_import_export_buttons.vue17
-rw-r--r--app/assets/javascripts/issuable/components/csv_import_modal.vue8
-rw-r--r--app/assets/javascripts/issuable/components/issuable_by_email.vue10
-rw-r--r--app/assets/javascripts/issuable/components/status_box.vue6
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js18
-rw-r--r--app/assets/stylesheets/framework/gfm.scss3
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/assets/stylesheets/utilities.scss1
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml5
-rw-r--r--changelogs/unreleased/sh-smplify-external-validation-error-codes.yml5
-rw-r--r--config/initializers/cluster_events_before_phased_restart.rb8
-rw-r--r--doc/administration/external_pipeline_validation.md2
-rw-r--r--doc/install/installation.md11
-rw-r--r--doc/install/requirements.md49
-rw-r--r--doc/update/upgrading_from_source.md10
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/external.rb16
-rw-r--r--lib/gitlab/cluster/mixins/unicorn_http_server.rb34
-rw-r--r--lib/gitlab/health_checks/unicorn_check.rb41
-rw-r--r--lib/gitlab/metrics/exporter/web_exporter.rb3
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--rubocop/cop/usage_data/instrumentation_superclass.rb63
-rw-r--r--rubocop/rubocop-usage-data.yml8
-rw-r--r--spec/features/issues/csv_spec.rb4
-rw-r--r--spec/features/issues/user_resets_their_incoming_email_token_spec.rb4
-rw-r--r--spec/features/merge_requests/user_exports_as_csv_spec.rb8
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb6
-rw-r--r--spec/frontend/issuable/components/csv_export_modal_spec.js44
-rw-r--r--spec/frontend/issuable/components/csv_import_export_buttons_spec.js58
-rw-r--r--spec/frontend/issuable/components/csv_import_modal_spec.js48
-rw-r--r--spec/frontend/issuable/components/issuable_by_email_spec.js11
-rw-r--r--spec/frontend/issuable/components/status_box_spec.js4
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js30
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb30
-rw-r--r--spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb117
-rw-r--r--spec/lib/gitlab/health_checks/unicorn_check_spec.rb67
-rw-r--r--spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb64
-rw-r--r--yarn.lock8
44 files changed, 314 insertions, 523 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 6f33057a057..72f0b5028a5 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -54,6 +54,7 @@ review-deploy:
extends:
- .review-workflow-base
- .review:rules:review-deploy
+ retry: 2
stage: review
needs: ["review-build-cng"]
resource_group: "review/${CI_COMMIT_REF_NAME}"
diff --git a/app/assets/javascripts/issuable/components/csv_export_modal.vue b/app/assets/javascripts/issuable/components/csv_export_modal.vue
index f17440a4a14..5c880cbfad8 100644
--- a/app/assets/javascripts/issuable/components/csv_export_modal.vue
+++ b/app/assets/javascripts/issuable/components/csv_export_modal.vue
@@ -51,7 +51,6 @@ export default {
</template>
<div
v-if="issuableCount > -1"
- data-testid="issuable-count-note"
class="gl-justify-content-start gl-align-items-center gl-p-4 gl-border-b-solid gl-border-1 gl-border-gray-50"
>
<gl-icon name="check" class="gl-color-green-400" />
diff --git a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
index fb4d5aca2f5..5d75b87fe76 100644
--- a/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_export_buttons.vue
@@ -72,9 +72,6 @@ export default {
importModalId() {
return `${this.issuableType}-import-modal`;
},
- importButtonText() {
- return this.showLabel ? this.$options.i18n.importIssuesText : null;
- },
importButtonTooltipText() {
return this.showLabel ? null : this.$options.i18n.importIssuesText;
},
@@ -90,29 +87,25 @@ export default {
<gl-button-group>
<gl-button
v-if="showExportButton"
- v-gl-tooltip.hover="$options.i18n.exportAsCsvButtonText"
+ v-gl-tooltip="$options.i18n.exportAsCsvButtonText"
v-gl-modal="exportModalId"
icon="export"
:aria-label="$options.i18n.exportAsCsvButtonText"
data-qa-selector="export_as_csv_button"
- data-testid="export-csv-button"
/>
<gl-dropdown
v-if="showImportButton"
- v-gl-tooltip.hover="importButtonTooltipText"
+ v-gl-tooltip="importButtonTooltipText"
data-qa-selector="import_issues_dropdown"
- data-testid="import-csv-dropdown"
- :text="importButtonText"
+ :text="$options.i18n.importIssuesText"
+ :text-sr-only="!showLabel"
:icon="importButtonIcon"
>
- <gl-dropdown-item v-gl-modal="importModalId" data-testid="import-csv-link">{{
- __('Import CSV')
- }}</gl-dropdown-item>
+ <gl-dropdown-item v-gl-modal="importModalId">{{ __('Import CSV') }}</gl-dropdown-item>
<gl-dropdown-item
v-if="canEdit"
:href="projectImportJiraPath"
data-qa-selector="import_from_jira_link"
- data-testid="import-from-jira-link"
>{{ __('Import from Jira') }}</gl-dropdown-item
>
</gl-dropdown>
diff --git a/app/assets/javascripts/issuable/components/csv_import_modal.vue b/app/assets/javascripts/issuable/components/csv_import_modal.vue
index 77fc2f31583..c85efd60b8b 100644
--- a/app/assets/javascripts/issuable/components/csv_import_modal.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_modal.vue
@@ -48,13 +48,7 @@ export default {
<template>
<gl-modal :modal-id="modalId" :title="__('Import issues')">
- <form
- ref="form"
- :action="importCsvIssuesPath"
- enctype="multipart/form-data"
- method="post"
- data-testid="import-csv-form"
- >
+ <form ref="form" :action="importCsvIssuesPath" enctype="multipart/form-data" method="post">
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<p>
{{
diff --git a/app/assets/javascripts/issuable/components/issuable_by_email.vue b/app/assets/javascripts/issuable/components/issuable_by_email.vue
index d0ce8c2c34b..97807ba1317 100644
--- a/app/assets/javascripts/issuable/components/issuable_by_email.vue
+++ b/app/assets/javascripts/issuable/components/issuable_by_email.vue
@@ -91,7 +91,7 @@ export default {
<template>
<div>
- <gl-button v-gl-modal="$options.modalId" variant="link" data-testid="issuable-email-modal-btn"
+ <gl-button v-gl-modal="$options.modalId" variant="link"
><gl-sprintf :message="__('Email a new %{name} to this project')"
><template #name>{{ issuableName }}</template></gl-sprintf
></gl-button
@@ -122,7 +122,6 @@ export default {
:title="$options.i18n.sendEmail"
:aria-label="$options.i18n.sendEmail"
icon="mail"
- data-testid="mail-to-btn"
/>
</template>
</gl-form-input-group>
@@ -156,12 +155,7 @@ export default {
/></gl-link>
</template>
<template #resetLink="{ content }">
- <gl-button
- variant="link"
- data-testid="incoming-email-token-reset"
- @click="resetIncomingEmailToken"
- >{{ content }}</gl-button
- >
+ <gl-button variant="link" @click="resetIncomingEmailToken">{{ content }}</gl-button>
</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue
index cb768f2bc5b..bd6fdc131cb 100644
--- a/app/assets/javascripts/issuable/components/status_box.vue
+++ b/app/assets/javascripts/issuable/components/status_box.vue
@@ -91,11 +91,7 @@ export default {
<template>
<div :class="statusBoxClass" class="issuable-status-box status-box">
- <gl-icon
- :name="statusIconName"
- class="gl-display-block gl-sm-display-none!"
- data-testid="status-icon"
- />
+ <gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" />
<span class="gl-display-none gl-sm-display-block">
{{ statusHumanName }}
</span>
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 5b3aa3cf9dc..a989a1f77da 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -484,13 +484,17 @@ export const setUrlParams = (
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
const keyName = railsArraySyntax ? `${key}[]` : key;
- params[key].forEach((val, idx) => {
- if (idx === 0) {
- searchParams.set(keyName, val);
- } else {
- searchParams.append(keyName, val);
- }
- });
+ if (params[key].length === 0) {
+ searchParams.delete(keyName);
+ } else {
+ params[key].forEach((val, idx) => {
+ if (idx === 0) {
+ searchParams.set(keyName, val);
+ } else {
+ searchParams.append(keyName, val);
+ }
+ });
+ }
} else {
searchParams.set(key, params[key]);
}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 579a68ac8e4..40e11b50eba 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -4,7 +4,8 @@
.gfm-commit,
.gfm-commit_range {
- @extend .commit-sha;
+ @include gl-font-monospace;
+ font-size: 95%;
}
.gfm-project_member {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index cd99c667001..f83ba89daae 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -45,7 +45,6 @@ input[type='checkbox']:hover {
margin: 0 8px;
form {
- @extend .form-control;
margin: 0;
padding: 4px;
width: $search-input-width;
@@ -139,7 +138,6 @@ input[type='checkbox']:hover {
&.search-active {
form {
- @extend .form-control:focus;
border-color: $blue-300;
box-shadow: none;
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index c22a1ae1187..b8183a0d82f 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -83,7 +83,6 @@
}
}
-.gl-text-purple { color: $purple; }
.gl-bg-purple-light { background-color: $purple-light; }
// move this to GitLab UI once onboarding experiment is considered a success
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 2032d1e95a6..e617b4358e3 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,5 +1,5 @@
.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } }
- = form_tag search_path, method: :get, class: 'form-inline' do |_f|
+ = form_tag search_path, method: :get, class: 'form-inline form-control' do |_f|
.search-input-container
.search-input-wrap
.dropdown{ data: { url: search_autocomplete_path } }
diff --git a/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml b/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml
new file mode 100644
index 00000000000..4867f75652e
--- /dev/null
+++ b/changelogs/unreleased/330402-remove-unicorn-gitlab-health-check.yml
@@ -0,0 +1,5 @@
+---
+title: Remove UnicornCheck service
+merge_request: 62204
+author:
+type: removed
diff --git a/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml b/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml
new file mode 100644
index 00000000000..4f6ab6ea472
--- /dev/null
+++ b/changelogs/unreleased/sh-smplify-external-validation-error-codes.yml
@@ -0,0 +1,5 @@
+---
+title: Simplify error code handling for external pipeline validation
+merge_request: 61190
+author:
+type: changed
diff --git a/config/initializers/cluster_events_before_phased_restart.rb b/config/initializers/cluster_events_before_phased_restart.rb
index d029adbe363..f84682c1436 100644
--- a/config/initializers/cluster_events_before_phased_restart.rb
+++ b/config/initializers/cluster_events_before_phased_restart.rb
@@ -7,8 +7,6 @@
#
# Follow-up the issue: https://gitlab.com/gitlab-org/gitlab/issues/34107
-if Gitlab::Runtime.puma?
- Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)
-elsif Gitlab::Runtime.unicorn?
- Unicorn::HttpServer.prepend(::Gitlab::Cluster::Mixins::UnicornHttpServer)
-end
+return unless Gitlab::Runtime.puma?
+
+Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster)
diff --git a/doc/administration/external_pipeline_validation.md b/doc/administration/external_pipeline_validation.md
index 89543e446ac..23207de4999 100644
--- a/doc/administration/external_pipeline_validation.md
+++ b/doc/administration/external_pipeline_validation.md
@@ -17,7 +17,7 @@ data as payload. The response code from the external service determines if GitLa
should accept or reject the pipeline. If the response is:
- `200`, the pipeline is accepted.
-- `4XX`, the pipeline is rejected.
+- `406`, the pipeline is rejected.
- Other codes, the pipeline is accepted and logged.
If there's an error or the request times out, the pipeline is accepted.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 3af3d2bfe7a..b9bde01c2fa 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -45,6 +45,15 @@ You can select the branch in the version dropdown in the top left corner of GitL
If the highest number stable branch is unclear, check the [GitLab blog](https://about.gitlab.com/blog/) for installation guide links by version.
+## Software requirements
+
+| Software | Minimum version | Notes |
+| -------- | --------------- | ----- |
+| [Ruby](#2-ruby) | `2.7` | From GitLab 13.6, Ruby 2.7 is required. Ruby 3.0 is not supported yet (see [the relevant epic](https://gitlab.com/groups/gitlab-org/-/epics/5149) for the current status). You must use the standard MRI implementation of Ruby. We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab needs several Gems that have native extensions. |
+| [Go](#3-go) | `1.15` | |
+| [Git](#git) | `2.31.x` | From GitLab 13.11, Git 2.31.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). |
+| [Node.js](#4-node) | `12.22.1` | GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets. Node.js 14.x is recommended, as it's faster. You can check which version you're running with `node -v`. You need to update it to a newer version if needed. |
+
## GitLab directory structure
This is the main directory structure you end up with following the instructions
@@ -207,7 +216,7 @@ sudo apt-get install -y libimage-exiftool-perl
## 2. Ruby
The Ruby interpreter is required to run GitLab.
-See the [requirements page](requirements.md#ruby-versions) for the minimum
+See the [requirements section of this page](#software-requirements) for the minimum
Ruby requirements.
The use of Ruby version managers such as [`RVM`](https://rvm.io/), [`rbenv`](https://github.com/rbenv/rbenv) or [`chruby`](https://github.com/postmodern/chruby) with GitLab
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 9aa06030c24..5c723ee06cd 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -47,55 +47,6 @@ Please consider using a virtual machine to run GitLab.
## Software requirements
-### Ruby versions
-
-From GitLab 13.6:
-
-- Ruby 2.7 and later is required.
-
-From GitLab 12.2:
-
-- Ruby 2.6 and later is required.
-
-You must use the standard MRI implementation of Ruby.
-We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab
-needs several Gems that have native extensions.
-
-### Go versions
-
-The minimum required Go version is 1.13.
-
-### Git versions
-
-From GitLab 13.11:
-
-- Git 2.31.x and later is required. We recommend you use the
- [Git version provided by Gitaly](installation.md#git).
-
-From GitLab 13.6:
-
-- Git 2.29.x and later is required.
-
-From GitLab 13.1:
-
-- Git 2.24.x and later is required.
-- Git 2.28.x and later [is recommended](https://gitlab.com/gitlab-org/gitaly/-/issues/2959).
-
-### Node.js versions
-
-Beginning in GitLab 12.9, we only support Node.js 10.13.0 or higher, and we have dropped
-support for Node.js 8. (Node.js 6 support was dropped in GitLab 11.8)
-
-We recommend Node 14.x, as it's faster.
-
-GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets, which requires a minimum
-version of Node.js 10.13.0.
-
-You can check which version you're running with `node -v`. If you're running
-a version older than `v10.13.0`, you need to update it to a newer version. You
-can find instructions to install from community maintained packages or compile
-from source at the [Node.js website](https://nodejs.org/en/download/).
-
### Redis versions
GitLab 13.0 and later requires Redis version 4.0 or higher.
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 93d2c2cb288..78eb6fab10b 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -82,7 +82,7 @@ sudo make install
### 4. Update Node.js
-To check the minimum required Node.js version, see [Node.js versions](../install/requirements.md#nodejs-versions).
+To check the minimum required Node.js version, see [Node.js versions](../install/installation.md#software-requirements).
GitLab also requires the use of Yarn `>= v1.10.0` to manage JavaScript
dependencies.
@@ -99,7 +99,7 @@ More information can be found on the [Yarn website](https://classic.yarnpkg.com/
### 5. Update Go
-To check the minimum required Go version, see [Go versions](../install/requirements.md#go-versions).
+To check the minimum required Go version, see [Go versions](../install/installation.md#software-requirements).
You can check which version you are running with `go version`.
@@ -119,12 +119,8 @@ rm go1.13.5.linux-amd64.tar.gz
### 6. Update Git
-WARNING:
-From GitLab 13.1, you must use at least Git v2.24 (previous minimum version was v2.22).
-Git v2.28 is recommended.
-
To check you are running the minimum required Git version, see
-[Git versions](../install/requirements.md#git-versions).
+[Git versions](../install/installation.md#software-requirements).
From GitLab 13.6, we recommend you use the [Git version provided by
Gitaly](https://gitlab.com/gitlab-org/gitaly/-/issues/2729)
diff --git a/lib/gitlab/ci/pipeline/chain/validate/external.rb b/lib/gitlab/ci/pipeline/chain/validate/external.rb
index e8fe3b4f797..18675f80279 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/external.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/external.rb
@@ -12,8 +12,7 @@ module Gitlab
DEFAULT_VALIDATION_REQUEST_TIMEOUT = 5
ACCEPTED_STATUS = 200
- DOT_COM_REJECTED_STATUS = 406
- GENERAL_REJECTED_STATUS = (400..499).freeze
+ REJECTED_STATUS = 406
def perform!
pipeline_authorized = validate_external
@@ -34,14 +33,13 @@ module Gitlab
return true unless validation_service_url
# 200 - accepted
- # 406 - not accepted on GitLab.com
- # 4XX - not accepted for other installations
+ # 406 - rejected
# everything else - accepted and logged
response_code = validate_service_request.code
case response_code
when ACCEPTED_STATUS
true
- when rejected_status
+ when REJECTED_STATUS
false
else
raise InvalidResponseCode, "Unsupported response code received from Validation Service: #{response_code}"
@@ -52,14 +50,6 @@ module Gitlab
true
end
- def rejected_status
- if Gitlab.com?
- DOT_COM_REJECTED_STATUS
- else
- GENERAL_REJECTED_STATUS
- end
- end
-
def validate_service_request
headers = {
'X-Gitlab-Correlation-id' => Labkit::Correlation::CorrelationId.current_id,
diff --git a/lib/gitlab/cluster/mixins/unicorn_http_server.rb b/lib/gitlab/cluster/mixins/unicorn_http_server.rb
deleted file mode 100644
index 440ed02a355..00000000000
--- a/lib/gitlab/cluster/mixins/unicorn_http_server.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Cluster
- module Mixins
- module UnicornHttpServer
- def self.prepended(base)
- unless base.method_defined?(:reexec) && base.method_defined?(:stop)
- raise 'missing method Unicorn::HttpServer#reexec or Unicorn::HttpServer#stop'
- end
- end
-
- def reexec
- Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
-
- super
- end
-
- # The stop on non-graceful shutdown is executed twice:
- # `#stop(false)` and `#stop`.
- #
- # The first stop will wipe-out all workers, so we need to check
- # the flag and a list of workers
- def stop(graceful = true)
- if graceful && @workers.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
- Gitlab::Cluster::LifecycleEvents.do_before_graceful_shutdown
- end
-
- super
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/health_checks/unicorn_check.rb b/lib/gitlab/health_checks/unicorn_check.rb
deleted file mode 100644
index f0c6fdab600..00000000000
--- a/lib/gitlab/health_checks/unicorn_check.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module HealthChecks
- # This check can only be run on Unicorn `master` process
- class UnicornCheck
- extend SimpleAbstractCheck
-
- class << self
- include Gitlab::Utils::StrongMemoize
-
- private
-
- def metric_prefix
- 'unicorn_check'
- end
-
- def successful?(result)
- result > 0
- end
-
- def check
- return unless http_servers
-
- http_servers.sum(&:worker_processes)
- end
-
- # Traversal of ObjectSpace is expensive, on fully loaded application
- # it takes around 80ms. The instances of HttpServers are not a subject
- # to change so we can cache the list of servers.
- def http_servers
- strong_memoize(:http_servers) do
- next unless Gitlab::Runtime.unicorn?
-
- ObjectSpace.each_object(::Unicorn::HttpServer).to_a
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/exporter/web_exporter.rb b/lib/gitlab/metrics/exporter/web_exporter.rb
index 558454eaa1c..756e6b0641a 100644
--- a/lib/gitlab/metrics/exporter/web_exporter.rb
+++ b/lib/gitlab/metrics/exporter/web_exporter.rb
@@ -30,8 +30,7 @@ module Gitlab
# application: https://gitlab.com/gitlab-org/gitlab/issues/35343
self.readiness_checks = [
WebExporter::ExporterCheck.new(self),
- Gitlab::HealthChecks::PumaCheck,
- Gitlab::HealthChecks::UnicornCheck
+ Gitlab::HealthChecks::PumaCheck
]
end
diff --git a/package.json b/package.json
index d90dccf2820..74780bac936 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.197.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "29.27.0",
+ "@gitlab/ui": "29.28.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index 81ad1896075..b998ad24358 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -16,7 +16,7 @@ module QA
Flow::Login.sign_in
end
- it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1276' do
+ it 'creates a basic merge request', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1850' do
Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request|
merge_request.project = project
merge_request.title = merge_request_title
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
index 2c0fb5ea290..67b48d254ac 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Git push over HTTP', :smoke do
- it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1278' do
+ it 'user using a personal access token pushes code to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1848' do
Flow::Login.sign_in
access_token = Resource::PersonalAccessToken.fabricate!.token
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 8617e05f912..cdb64361549 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -24,7 +24,7 @@ module QA
runner.remove_via_api!
end
- it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1279' do
+ it 'users creates a pipeline which gets processed', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1849' do
Flow::Login.sign_in
Resource::Repository::Commit.fabricate_via_api! do |commit|
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index e2cf5c5b195..44888408f4e 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -115,7 +115,7 @@ module QA
end
end
- it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1277' do
+ it 'runs an AutoDevOps pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1847' do
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
diff --git a/rubocop/cop/usage_data/instrumentation_superclass.rb b/rubocop/cop/usage_data/instrumentation_superclass.rb
new file mode 100644
index 00000000000..2ff2ed47a23
--- /dev/null
+++ b/rubocop/cop/usage_data/instrumentation_superclass.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module UsageData
+ # This cop checks that metric instrumentation classes subclass one of the allowed base classes.
+ #
+ # @example
+ #
+ # # good
+ # class CountIssues < DatabaseMetric
+ # # ...
+ # end
+ #
+ # # bad
+ # class CountIssues < BaseMetric
+ # # ...
+ # end
+ class InstrumentationSuperclass < RuboCop::Cop::Cop
+ MSG = "Instrumentation classes should subclass one of the following: %{allowed_classes}."
+
+ BASE_PATTERN = "(const nil? !#allowed_class?)"
+
+ def_node_matcher :class_definition, <<~PATTERN
+ (class (const _ !#allowed_class?) #{BASE_PATTERN} ...)
+ PATTERN
+
+ def_node_matcher :class_new_definition, <<~PATTERN
+ [!^(casgn {nil? cbase} #allowed_class? ...)
+ !^^(casgn {nil? cbase} #allowed_class? (block ...))
+ (send (const {nil? cbase} :Class) :new #{BASE_PATTERN})]
+ PATTERN
+
+ def on_class(node)
+ class_definition(node) do
+ register_offense(node.children[1])
+ end
+ end
+
+ def on_send(node)
+ class_new_definition(node) do
+ register_offense(node.children.last)
+ end
+ end
+
+ private
+
+ def allowed_class?(class_name)
+ allowed_classes.include?(class_name)
+ end
+
+ def allowed_classes
+ cop_config['AllowedClasses'] || []
+ end
+
+ def register_offense(offense_node)
+ message = format(MSG, allowed_classes: allowed_classes.join(', '))
+ add_offense(offense_node, message: message)
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop-usage-data.yml b/rubocop/rubocop-usage-data.yml
index a03f21e491a..5b88a3349c8 100644
--- a/rubocop/rubocop-usage-data.yml
+++ b/rubocop/rubocop-usage-data.yml
@@ -57,3 +57,11 @@ UsageData/DistinctCountByLargeForeignKey:
- 'user_id'
- 'resource_owner_id'
- 'requirement_id'
+UsageData/InstrumentationSuperclass:
+ Enabled: true
+ Include:
+ - 'lib/gitlab/usage/metrics/instrumentations/**/*.rb'
+ AllowedClasses:
+ - :DatabaseMetric
+ - :GenericMetric
+ - :RedisHLLMetric
diff --git a/spec/features/issues/csv_spec.rb b/spec/features/issues/csv_spec.rb
index d41a41c4383..51e0d54ca5e 100644
--- a/spec/features/issues/csv_spec.rb
+++ b/spec/features/issues/csv_spec.rb
@@ -16,9 +16,7 @@ RSpec.describe 'Issues csv', :js do
def request_csv(params = {})
visit project_issues_path(project, params)
- page.within('.nav-controls') do
- find('[data-testid="export-csv-button"]').click
- end
+ click_button 'Export as CSV'
click_on 'Export issues'
end
diff --git a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
index 2b1c25174c2..9e47639d80f 100644
--- a/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
+++ b/spec/features/issues/user_resets_their_incoming_email_token_spec.rb
@@ -16,11 +16,11 @@ RSpec.describe 'Issues > User resets their incoming email token' do
end
it 'changes incoming email address token', :js do
- page.find('[data-testid="issuable-email-modal-btn"]').click
+ click_button 'Email a new issue to this project'
page.within '#issuable-email-modal' do
previous_token = page.find('input[type="text"]').value
- page.find('[data-testid="incoming-email-token-reset"]').click
+ click_button 'reset it'
wait_for_requests
diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb
index 725b8366d04..351e714b612 100644
--- a/spec/features/merge_requests/user_exports_as_csv_spec.rb
+++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb
@@ -12,15 +12,9 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js do
visit(project_merge_requests_path(project))
end
- subject { page.find('.nav-controls') }
-
- it { is_expected.to have_selector '[data-testid="export-csv-button"]' }
-
context 'button is clicked' do
before do
- page.within('.nav-controls') do
- find('[data-testid="export-csv-button"]').click
- end
+ click_button 'Export as CSV'
end
it 'shows a success message' do
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index bf90e86c263..862bae45fc6 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -116,7 +116,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
click_on('Save changes')
end
- find('.flash-notice')
+ wait_for_all_requests
+
checkbox = find_field('project_printing_merge_request_link_enabled')
expect(checkbox).not_to be_checked
@@ -139,7 +140,8 @@ RSpec.describe 'Projects > Settings > User manages merge request settings' do
click_on('Save changes')
end
- find('.flash-notice')
+ wait_for_all_requests
+
checkbox = find_field('project_remove_source_branch_after_merge')
expect(checkbox).not_to be_checked
diff --git a/spec/frontend/issuable/components/csv_export_modal_spec.js b/spec/frontend/issuable/components/csv_export_modal_spec.js
index 7eb85a946ae..34094d22e68 100644
--- a/spec/frontend/issuable/components/csv_export_modal_spec.js
+++ b/spec/frontend/issuable/components/csv_export_modal_spec.js
@@ -1,7 +1,6 @@
import { GlModal, GlIcon, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
describe('CsvExportModal', () => {
@@ -9,26 +8,24 @@ describe('CsvExportModal', () => {
function createComponent(options = {}) {
const { injectedProperties = {}, props = {} } = options;
- return extendedWrapper(
- mount(CsvExportModal, {
- propsData: {
- modalId: 'csv-export-modal',
- exportCsvPath: 'export/csv/path',
- issuableCount: 1,
- ...props,
- },
- provide: {
- issuableType: 'issues',
- ...injectedProperties,
- },
- stubs: {
- GlModal: stubComponent(GlModal, {
- template:
- '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
- }),
- },
- }),
- );
+ return mount(CsvExportModal, {
+ propsData: {
+ modalId: 'csv-export-modal',
+ exportCsvPath: 'export/csv/path',
+ issuableCount: 1,
+ ...props,
+ },
+ provide: {
+ issuableType: 'issues',
+ ...injectedProperties,
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ template:
+ '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ },
+ });
}
afterEach(() => {
@@ -61,14 +58,13 @@ describe('CsvExportModal', () => {
describe('issuable count info text', () => {
it('displays the info text when issuableCount is > -1', () => {
wrapper = createComponent({ props: { issuableCount: 10 } });
- expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(true);
- expect(wrapper.findByTestId('issuable-count-note').text()).toContain('10 issues selected');
+ expect(wrapper.text()).toContain('10 issues selected');
expect(findIcon().exists()).toBe(true);
});
it("doesn't display the info text when issuableCount is -1", () => {
wrapper = createComponent({ props: { issuableCount: -1 } });
- expect(wrapper.findByTestId('issuable-count-note').exists()).toBe(false);
+ expect(wrapper.text()).not.toContain('issues selected');
});
});
diff --git a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
index 2fe8d28a333..118c12d968b 100644
--- a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
+++ b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
@@ -1,6 +1,6 @@
-import { shallowMount } from '@vue/test-utils';
+import { GlButton, GlDropdown } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import CsvImportModal from '~/issuable/components/csv_import_modal.vue';
@@ -14,35 +14,33 @@ describe('CsvImportExportButtons', () => {
function createComponent(injectedProperties = {}) {
glModalDirective = jest.fn();
- return extendedWrapper(
- shallowMount(CsvImportExportButtons, {
- directives: {
- GlTooltip: createMockDirective(),
- glModal: {
- bind(_, { value }) {
- glModalDirective(value);
- },
+ return mountExtended(CsvImportExportButtons, {
+ directives: {
+ GlTooltip: createMockDirective(),
+ glModal: {
+ bind(_, { value }) {
+ glModalDirective(value);
},
},
- provide: {
- ...injectedProperties,
- },
- propsData: {
- exportCsvPath,
- issuableCount,
- },
- }),
- );
+ },
+ provide: {
+ ...injectedProperties,
+ },
+ propsData: {
+ exportCsvPath,
+ issuableCount,
+ },
+ });
}
afterEach(() => {
wrapper.destroy();
});
- const findExportCsvButton = () => wrapper.findByTestId('export-csv-button');
- const findImportDropdown = () => wrapper.findByTestId('import-csv-dropdown');
- const findImportCsvButton = () => wrapper.findByTestId('import-csv-dropdown');
- const findImportFromJiraLink = () => wrapper.findByTestId('import-from-jira-link');
+ const findExportCsvButton = () => wrapper.findComponent(GlButton);
+ const findImportDropdown = () => wrapper.findComponent(GlDropdown);
+ const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
+ const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' });
const findExportCsvModal = () => wrapper.findComponent(CsvExportModal);
const findImportCsvModal = () => wrapper.findComponent(CsvImportModal);
@@ -97,7 +95,7 @@ describe('CsvImportExportButtons', () => {
expect(findImportDropdown().exists()).toBe(true);
});
- it('renders the import button', () => {
+ it('renders the import csv menu item', () => {
expect(findImportCsvButton().exists()).toBe(true);
});
@@ -106,8 +104,11 @@ describe('CsvImportExportButtons', () => {
wrapper = createComponent({ showImportButton: true, showLabel: false });
});
- it('does not have a button text', () => {
- expect(findImportCsvButton().props('text')).toBe(null);
+ it('hides button text', () => {
+ expect(findImportDropdown().props()).toMatchObject({
+ text: 'Import issues',
+ textSrOnly: true,
+ });
});
it('import button has a tooltip', () => {
@@ -124,7 +125,10 @@ describe('CsvImportExportButtons', () => {
});
it('displays a button text', () => {
- expect(findImportCsvButton().props('text')).toBe('Import issues');
+ expect(findImportDropdown().props()).toMatchObject({
+ text: 'Import issues',
+ textSrOnly: false,
+ });
});
it('import button has no tooltip', () => {
diff --git a/spec/frontend/issuable/components/csv_import_modal_spec.js b/spec/frontend/issuable/components/csv_import_modal_spec.js
index ce9d738f77b..0c88b6b1283 100644
--- a/spec/frontend/issuable/components/csv_import_modal_spec.js
+++ b/spec/frontend/issuable/components/csv_import_modal_spec.js
@@ -1,8 +1,6 @@
-import { GlModal } from '@gitlab/ui';
-import { getByRole, getByLabelText } from '@testing-library/dom';
-import { mount } from '@vue/test-utils';
+import { GlButton, GlModal } from '@gitlab/ui';
import { stubComponent } from 'helpers/stub_component';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvImportModal from '~/issuable/components/csv_import_modal.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
@@ -13,23 +11,21 @@ describe('CsvImportModal', () => {
function createComponent(options = {}) {
const { injectedProperties = {}, props = {} } = options;
- return extendedWrapper(
- mount(CsvImportModal, {
- propsData: {
- modalId: 'csv-import-modal',
- ...props,
- },
- provide: {
- issuableType: 'issues',
- ...injectedProperties,
- },
- stubs: {
- GlModal: stubComponent(GlModal, {
- template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
- }),
- },
- }),
- );
+ return mountExtended(CsvImportModal, {
+ propsData: {
+ modalId: 'csv-import-modal',
+ ...props,
+ },
+ provide: {
+ issuableType: 'issues',
+ ...injectedProperties,
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ },
+ });
}
beforeEach(() => {
@@ -41,9 +37,9 @@ describe('CsvImportModal', () => {
});
const findModal = () => wrapper.findComponent(GlModal);
- const findPrimaryButton = () => getByRole(wrapper.element, 'button', { name: 'Import issues' });
- const findForm = () => wrapper.findByTestId('import-csv-form');
- const findFileInput = () => getByLabelText(wrapper.element, 'Upload CSV file');
+ const findPrimaryButton = () => wrapper.findComponent(GlButton);
+ const findForm = () => wrapper.find('form');
+ const findFileInput = () => wrapper.findByLabelText('Upload CSV file');
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
describe('template', () => {
@@ -76,8 +72,8 @@ describe('CsvImportModal', () => {
expect(findPrimaryButton()).toExist();
});
- it('submits the form when the primary action is clicked', async () => {
- findPrimaryButton().click();
+ it('submits the form when the primary action is clicked', () => {
+ findPrimaryButton().trigger('click');
expect(formSubmitSpy).toHaveBeenCalled();
});
diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js
index 08a99f29479..f11c41fe25d 100644
--- a/spec/frontend/issuable/components/issuable_by_email_spec.js
+++ b/spec/frontend/issuable/components/issuable_by_email_spec.js
@@ -58,10 +58,11 @@ describe('IssuableByEmail', () => {
mockAxios.restore();
});
- const findFormInputGroup = () => wrapper.find(GlFormInputGroup);
+ const findButton = () => wrapper.findComponent(GlButton);
+ const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
const clickResetEmail = async () => {
- wrapper.findByTestId('incoming-email-token-reset').vm.$emit('click');
+ wrapper.findAllComponents(GlButton).at(2).trigger('click');
await waitForPromises();
};
@@ -75,14 +76,14 @@ describe('IssuableByEmail', () => {
'renders a link with "$buttonText" when type is "$issuableType"',
({ issuableType, buttonText }) => {
wrapper = createComponent({ issuableType });
- expect(wrapper.findByTestId('issuable-email-modal-btn').text()).toBe(buttonText);
+ expect(findButton().text()).toBe(buttonText);
},
);
it('opens the modal when the user clicks the button', () => {
wrapper = createComponent();
- wrapper.findByTestId('issuable-email-modal-btn').vm.$emit('click');
+ findButton().trigger('click');
expect(glModalDirective).toHaveBeenCalled();
});
@@ -105,7 +106,7 @@ describe('IssuableByEmail', () => {
initialEmail,
});
- expect(wrapper.findByTestId('mail-to-btn').attributes('href')).toBe(
+ expect(wrapper.findAllComponents(GlButton).at(1).attributes('href')).toBe(
`mailto:${initialEmail}?subject=${subject}&body=${body}`,
);
});
diff --git a/spec/frontend/issuable/components/status_box_spec.js b/spec/frontend/issuable/components/status_box_spec.js
index 990fac67f7e..9cbf023dbd6 100644
--- a/spec/frontend/issuable/components/status_box_spec.js
+++ b/spec/frontend/issuable/components/status_box_spec.js
@@ -1,4 +1,4 @@
-import { GlSprintf } from '@gitlab/ui';
+import { GlIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StatusBox from '~/issuable/components/status_box.vue';
@@ -64,7 +64,7 @@ describe('Merge request status box component', () => {
initialState: testCase.state,
});
- expect(wrapper.find('[data-testid="status-icon"]').props('name')).toBe(testCase.icon);
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe(testCase.icon);
});
});
});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index e12cd8b0e37..8a04e93e88c 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -798,15 +798,29 @@ describe('URL utility', () => {
);
});
- it('handles arrays properly', () => {
+ it('adds parameters from arrays', () => {
const url = 'https://gitlab.com/test';
- expect(urlUtils.setUrlParams({ label_name: ['foo', 'bar'] }, url)).toEqual(
- 'https://gitlab.com/test?label_name=foo&label_name=bar',
+ expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url)).toEqual(
+ 'https://gitlab.com/test?labels=foo&labels=bar',
);
});
- it('handles arrays properly when railsArraySyntax=true', () => {
+ it('removes parameters from empty arrays', () => {
+ const url = 'https://gitlab.com/test?labels=foo&labels=bar';
+
+ expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual('https://gitlab.com/test');
+ });
+
+ it('removes parameters from empty arrays while keeping other parameters', () => {
+ const url = 'https://gitlab.com/test?labels=foo&labels=bar&unrelated=unrelated';
+
+ expect(urlUtils.setUrlParams({ labels: [] }, url)).toEqual(
+ 'https://gitlab.com/test?unrelated=unrelated',
+ );
+ });
+
+ it('adds parameters from arrays when railsArraySyntax=true', () => {
const url = 'https://gitlab.com/test';
expect(urlUtils.setUrlParams({ labels: ['foo', 'bar'] }, url, false, true)).toEqual(
@@ -814,6 +828,14 @@ describe('URL utility', () => {
);
});
+ it('removes parameters from empty arrays when railsArraySyntax=true', () => {
+ const url = 'https://gitlab.com/test?labels%5B%5D=foo&labels%5B%5D=bar';
+
+ expect(urlUtils.setUrlParams({ labels: [] }, url, false, true)).toEqual(
+ 'https://gitlab.com/test',
+ );
+ });
+
it('decodes URI when decodeURI=true', () => {
const url = 'https://gitlab.com/test';
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index 58ad3d1b4d1..84377981cbc 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -43,7 +43,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
let(:save_incompleted) { true }
- let(:dot_com) { true }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted
@@ -57,7 +56,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
before do
stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url)
- allow(Gitlab).to receive(:com?).and_return(dot_com)
allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id')
end
@@ -199,34 +197,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
- context 'when not on .com' do
- let(:dot_com) { false }
-
- before do
- stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
- end
-
- it 'drops the pipeline' do
- perform!
-
- expect(pipeline.status).to eq('failed')
- expect(pipeline).to be_persisted
- expect(pipeline.errors.to_a).to include('External validation failed')
- end
-
- it 'breaks the chain' do
- perform!
-
- expect(step.break?).to be true
- end
-
- it 'logs the authorization' do
- expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id)
-
- perform!
- end
- end
-
context 'when validation returns 406 Not Acceptable' do
before do
stub_request(:post, validation_service_url).to_return(status: 406, body: "{}")
diff --git a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb b/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
deleted file mode 100644
index 7f7c95b2527..00000000000
--- a/spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# For easier debugging set `UNICORN_DEBUG=1`
-
-RSpec.describe Gitlab::Cluster::Mixins::UnicornHttpServer do
- before do
- stub_const('UNICORN_STARTUP_TIMEOUT', 30)
- end
-
- context 'when running Unicorn' do
- using RSpec::Parameterized::TableSyntax
-
- where(:signal, :exitstatus, :termsig) do
- # executes phased restart block
- :USR2 | 140 | nil
- :QUIT | 140 | nil
-
- # does not execute phased restart block
- :INT | 0 | nil
- :TERM | 0 | nil
- end
-
- with_them do
- it 'properly handles process lifecycle' do
- with_unicorn(workers: 1) do |pid|
- Process.kill(signal, pid)
-
- child_pid, child_status = Process.wait2(pid)
- expect(child_pid).to eq(pid)
- expect(child_status.exitstatus).to eq(exitstatus)
- expect(child_status.termsig).to eq(termsig)
- end
- end
- end
- end
-
- private
-
- def with_unicorn(workers:, timeout: UNICORN_STARTUP_TIMEOUT)
- with_unicorn_configs(workers: workers) do |unicorn_rb, config_ru|
- cmdline = [
- "bundle", "exec", "unicorn",
- "-I", Rails.root.to_s,
- "-c", unicorn_rb,
- config_ru
- ]
-
- IO.popen(cmdline) do |process|
- # wait for process to start:
- # I, [2019-10-15T13:21:27.565225 #3089] INFO -- : master process ready
- wait_for_output(process, /master process ready/, timeout: timeout)
- consume_output(process)
-
- yield(process.pid)
- ensure
- begin
- Process.kill(:KILL, process.pid)
- rescue Errno::ESRCH
- end
- end
- end
- end
-
- def with_unicorn_configs(workers:)
- Dir.mktmpdir do |dir|
- File.write "#{dir}/unicorn.rb", <<-EOF
- require './lib/gitlab/cluster/lifecycle_events'
- require './lib/gitlab/cluster/mixins/unicorn_http_server'
-
- worker_processes #{workers}
- listen "127.0.0.1:0"
- preload_app true
-
- Unicorn::HttpServer.prepend(#{described_class})
-
- mutex = Mutex.new
-
- Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do
- mutex.synchronize do
- exit(140)
- end
- end
-
- # redirect stderr to stdout
- $stderr.reopen($stdout)
- EOF
-
- File.write "#{dir}/config.ru", <<-EOF
- run -> (env) { [404, {}, ['']] }
- EOF
-
- yield("#{dir}/unicorn.rb", "#{dir}/config.ru")
- end
- end
-
- def wait_for_output(process, output, timeout:)
- Timeout.timeout(timeout) do
- loop do
- line = process.readline
- puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
- break if line =~ output
- end
- end
- end
-
- def consume_output(process)
- Thread.new do
- loop do
- line = process.readline
- puts "UNICORN_DEBUG: #{line}" if ENV['UNICORN_DEBUG']
- end
- rescue StandardError
- end
- end
-end
diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
deleted file mode 100644
index 1cc44016002..00000000000
--- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::HealthChecks::UnicornCheck do
- let(:result_class) { Gitlab::HealthChecks::Result }
- let(:readiness) { described_class.readiness }
- let(:metrics) { described_class.metrics }
-
- before do
- described_class.clear_memoization(:http_servers)
- end
-
- shared_examples 'with state' do |(state, message)|
- it "does provide readiness" do
- expect(readiness).to eq(result_class.new('unicorn_check', state, message))
- end
-
- it "does provide metrics" do
- expect(metrics).to include(
- an_object_having_attributes(name: 'unicorn_check_success', value: state ? 1 : 0))
- expect(metrics).to include(
- an_object_having_attributes(name: 'unicorn_check_latency_seconds', value: be >= 0))
- end
- end
-
- context 'when Unicorn is not loaded' do
- before do
- allow(Gitlab::Runtime).to receive(:unicorn?).and_return(false)
- hide_const('Unicorn')
- end
-
- it "does not provide readiness and metrics" do
- expect(readiness).to be_nil
- expect(metrics).to be_nil
- end
- end
-
- context 'when Unicorn is loaded' do
- let(:http_server_class) { Struct.new(:worker_processes) }
-
- before do
- allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true)
- stub_const('Unicorn::HttpServer', http_server_class)
- end
-
- context 'when no servers are running' do
- it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
- end
-
- context 'when servers without workers are running' do
- before do
- http_server_class.new(0)
- end
-
- it_behaves_like 'with state', [false, 'unexpected Unicorn check result: 0']
- end
-
- context 'when servers with workers are running' do
- before do
- http_server_class.new(1)
- end
-
- it_behaves_like 'with state', true
- end
- end
-end
diff --git a/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb b/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb
new file mode 100644
index 00000000000..31324331e61
--- /dev/null
+++ b/spec/rubocop/cop/usage_data/instrumentation_superclass_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require_relative '../../../../rubocop/cop/usage_data/instrumentation_superclass'
+
+RSpec.describe RuboCop::Cop::UsageData::InstrumentationSuperclass do
+ let(:allowed_classes) { %i[GenericMetric DatabaseMetric RedisHllMetric] }
+ let(:msg) { "Instrumentation classes should subclass one of the following: #{allowed_classes.join(', ')}." }
+
+ let(:config) do
+ RuboCop::Config.new('UsageData/InstrumentationSuperclass' => {
+ 'AllowedClasses' => allowed_classes
+ })
+ end
+
+ subject(:cop) { described_class.new(config) }
+
+ context 'with class definition' do
+ context 'when inheriting from allowed superclass' do
+ it 'does not register an offense' do
+ expect_no_offenses('class NewMetric < GenericMetric; end')
+ end
+ end
+
+ context 'when inheriting from some other superclass' do
+ it 'registers an offense' do
+ expect_offense(<<~CODE)
+ class NewMetric < BaseMetric; end
+ ^^^^^^^^^^ #{msg}
+ CODE
+ end
+ end
+
+ context 'when not inheriting' do
+ it 'does not register an offense' do
+ expect_no_offenses('class NewMetric; end')
+ end
+ end
+ end
+
+ context 'with dynamic class definition' do
+ context 'when inheriting from allowed superclass' do
+ it 'does not register an offense' do
+ expect_no_offenses('NewMetric = Class.new(GenericMetric)')
+ end
+ end
+
+ context 'when inheriting from some other superclass' do
+ it 'registers an offense' do
+ expect_offense(<<~CODE)
+ NewMetric = Class.new(BaseMetric)
+ ^^^^^^^^^^ #{msg}
+ CODE
+ end
+ end
+
+ context 'when not inheriting' do
+ it 'does not register an offense' do
+ expect_no_offenses('NewMetric = Class.new')
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index ef7d8021ca9..031b2001f93 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -908,10 +908,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@29.27.0":
- version "29.27.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.27.0.tgz#c5cbe1fee2164705ea6a431c85f6fdaa2ff7e166"
- integrity sha512-VoiYrozWyE9l/ddX308vsu+wQqaJJN3csngIlrJit3DzVZV9Z/OVWU/X9CWV8m/ubyr5ysEMqnrtnsQClR9FiA==
+"@gitlab/ui@29.28.0":
+ version "29.28.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.28.0.tgz#3444f6d26114f503d78b85fca67b5cc340a4a667"
+ integrity sha512-7jHqbnEy3P5J/G0/b+Nu+iw8XSOyTWLvyOEtNdFpBras1RxCE3C4AnPyGT7jei+WafTQN2Vzxz8VIgAxZ6PPvg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"