diff --git a/app/assets/javascripts/token_access/index.js b/app/assets/javascripts/token_access/index.js
index 6a29883290a..8d29a65d705 100644
--- a/app/assets/javascripts/token_access/index.js
+++ b/app/assets/javascripts/token_access/index.js
@@ -6,7 +6,7 @@ import TokenAccess from './components/token_access.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
export const initTokenAccess = (containerId = 'js-ci-token-access-app') => {
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index fa5ab590232..dfbdee8e1c8 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -10,7 +10,6 @@
@import './pages/groups';
@import './pages/help';
@import './pages/issuable';
-@import './pages/issues/issue_count_badge';
@import './pages/issues';
@import './pages/labels';
@import './pages/login';
diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
deleted file mode 100644
index f2283e02ad2..00000000000
--- a/app/assets/stylesheets/pages/issues/issue_count_badge.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-.issue-count-badge,
-.mr-count-badge {
- padding: 5px $gl-padding-8;
-}
-
-.issue-count-badge-count,
-.mr-count-badge-count {
- display: inline-flex;
- align-items: center;
-}
diff --git a/app/services/ci/stuck_builds/drop_running_service.rb b/app/services/ci/stuck_builds/drop_running_service.rb
new file mode 100644
index 00000000000..ef23bd7e7bd
--- /dev/null
+++ b/app/services/ci/stuck_builds/drop_running_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Ci
+ module StuckBuilds
+ class DropRunningService
+ include DropHelpers
+
+ BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
+
+ def execute
+ Gitlab::AppLogger.info "#{self.class}: Cleaning running, timed-out builds"
+
+ drop(running_timed_out_builds, failure_reason: :stuck_or_timeout_failure)
+ end
+
+ private
+
+ def running_timed_out_builds
+ Ci::Build.running.updated_at_before(BUILD_RUNNING_OUTDATED_TIMEOUT.ago)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/stuck_builds/drop_service.rb b/app/services/ci/stuck_builds/drop_service.rb
index 3fee9a94381..dbc1860eb2b 100644
--- a/app/services/ci/stuck_builds/drop_service.rb
+++ b/app/services/ci/stuck_builds/drop_service.rb
@@ -5,7 +5,6 @@ module Ci
class DropService
include DropHelpers
- BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
BUILD_PENDING_OUTDATED_TIMEOUT = 1.day
BUILD_SCHEDULED_OUTDATED_TIMEOUT = 1.hour
BUILD_PENDING_STUCK_TIMEOUT = 1.hour
@@ -14,8 +13,6 @@ module Ci
def execute
Gitlab::AppLogger.info "#{self.class}: Cleaning stuck builds"
- drop(running_timed_out_builds, failure_reason: :stuck_or_timeout_failure)
-
drop(
pending_builds(BUILD_PENDING_OUTDATED_TIMEOUT.ago),
failure_reason: :stuck_or_timeout_failure
@@ -50,13 +47,6 @@ module Ci
BUILD_SCHEDULED_OUTDATED_TIMEOUT.ago
)
end
-
- def running_timed_out_builds
- Ci::Build.running.where( # rubocop: disable CodeReuse/ActiveRecord
- 'ci_builds.updated_at < ?',
- BUILD_RUNNING_OUTDATED_TIMEOUT.ago
- )
- end
end
end
end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index ed9b5ef3a5e..1c294ea9204 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -228,6 +228,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: cronjob:ci_stuck_builds_drop_running
+ :worker_name: Ci::StuckBuilds::DropRunningWorker
+ :feature_category: :continuous_integration
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:container_expiration_policy
:worker_name: ContainerExpirationPolicyWorker
:feature_category: :container_registry
diff --git a/app/workers/ci/stuck_builds/drop_running_worker.rb b/app/workers/ci/stuck_builds/drop_running_worker.rb
new file mode 100644
index 00000000000..35326bf74c9
--- /dev/null
+++ b/app/workers/ci/stuck_builds/drop_running_worker.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Ci
+ module StuckBuilds
+ class DropRunningWorker
+ include ApplicationWorker
+
+ idempotent!
+
+ # rubocop:disable Scalability/CronWorkerContext
+ # This is an instance-wide cleanup query, so there's no meaningful
+ # scope to consider this in the context of.
+ include CronjobQueue
+ # rubocop:enable Scalability/CronWorkerContext
+
+ data_consistency :always
+
+ feature_category :continuous_integration
+
+ EXCLUSIVE_LEASE_KEY = 'ci_stuck_builds_drop_running_worker_lease'
+
+ def perform
+ return unless try_obtain_lease
+
+ begin
+ Ci::StuckBuilds::DropRunningService.new.execute
+ ensure
+ remove_lease
+ end
+ end
+
+ private
+
+ def try_obtain_lease
+ @uuid = Gitlab::ExclusiveLease.new(EXCLUSIVE_LEASE_KEY, timeout: 30.minutes).try_obtain
+ end
+
+ def remove_lease
+ Gitlab::ExclusiveLease.cancel(EXCLUSIVE_LEASE_KEY, @uuid)
+ end
+ end
+ end
+end
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index a2b2686c8d5..20bc201f5f0 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -17,6 +17,8 @@ class StuckCiJobsWorker # rubocop:disable Scalability/IdempotentWorker
EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'
def perform
+ Ci::StuckBuilds::DropRunningWorker.perform_in(20.minutes)
+
return unless try_obtain_lease
Ci::StuckBuilds::DropService.new.execute
diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb
index e9f10abd0b9..7aedf9013ae 100644
--- a/config/initializers/database_config.rb
+++ b/config/initializers/database_config.rb
@@ -1,15 +1,5 @@
# frozen_string_literal: true
-def log_pool_size(db, previous_pool_size, current_pool_size)
- log_message = ["#{db} connection pool size: #{current_pool_size}"]
-
- if previous_pool_size && current_pool_size > previous_pool_size
- log_message << "(increased from #{previous_pool_size} to match thread count)"
- end
-
- Gitlab::AppLogger.debug(log_message.join(' '))
-end
-
Gitlab.ee do
# We need to initialize the Geo database before
# setting the Geo DB connection pool size.
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index f3946131914..8a7add3e9e6 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -252,7 +252,7 @@ graph RL;
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end
- 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"];
+ 2_2-2["rspec-all frontend_fixture (7 minutes)"];
class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (3.5 minutes)"];
@@ -284,7 +284,7 @@ graph RL;
3_1-1["jest (14.5 minutes)"];
class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
- subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
+ subgraph "Needs `rspec-all frontend_fixture`";
3_1-1 --> 2_2-2;
end
@@ -355,7 +355,7 @@ graph RL;
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end
- 2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (7 minutes)"];
+ 2_2-2["rspec-all frontend_fixture (7 minutes)"];
class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (3.5 minutes)"];
@@ -395,7 +395,7 @@ graph RL;
3_1-1["jest (14.5 minutes)"];
class 3_1-1 criticalPath;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
- subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
+ subgraph "Needs `rspec-all frontend_fixture`";
3_1-1 --> 2_2-2;
end
diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md
index c6ff70103d9..682a9a24712 100644
--- a/doc/operations/incident_management/alerts.md
+++ b/doc/operations/incident_management/alerts.md
@@ -53,6 +53,8 @@ immediately identify which alerts you should prioritize investigating:
Alerts contain one of the following icons:
+
+
| Severity | Icon | Color (hexadecimal) |
|----------|-------------------------|---------------------|
| Critical | **{severity-critical}** | `#8b2615` |
@@ -62,6 +64,8 @@ Alerts contain one of the following icons:
| Info | **{severity-info}** | `#418cd8` |
| Unknown | **{severity-unknown}** | `#bababa` |
+
+
## Alert details page
Navigate to the Alert details view by visiting the [Alert list](alerts.md)
diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md
index 8e03369a702..303427d31e9 100644
--- a/doc/user/application_security/coverage_fuzzing/index.md
+++ b/doc/user/application_security/coverage_fuzzing/index.md
@@ -10,7 +10,7 @@ type: reference, howto
Coverage-guided fuzzing sends random inputs to an instrumented version of your application in an
effort to cause unexpected behavior. Such behavior indicates a bug that you should address.
GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover
-bugs and potential security issues that other QA processes may miss.
+bugs and potential security issues that other QA processes may miss.
We recommend that you use fuzz testing in addition to the other security scanners in [GitLab Secure](../index.md)
and your own test processes. If you're using [GitLab CI/CD](../../../ci/index.md),
@@ -248,6 +248,8 @@ which shows an overview of all the security vulnerabilities in your groups, proj
Clicking the vulnerability opens a modal that provides additional information about the
vulnerability:
+
+
- Status: The vulnerability's status. As with any type of vulnerability, a coverage fuzzing
vulnerability can be Detected, Confirmed, Dismissed, or Resolved.
- Project: The project in which the vulnerability exists.
@@ -261,3 +263,5 @@ vulnerability:
- Scanner: The scanner that detected the vulnerability (for example, Coverage Fuzzing).
- Scanner Provider: The engine that did the scan. For Coverage Fuzzing, this can be any of the
engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages).
+
+
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 3caa1771a5b..d713ee0d5bc 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -27,6 +27,8 @@ analysis are available in the [security dashboards](../security_dashboard/index.
The results are sorted by the priority of the vulnerability:
+
+
1. Critical
1. High
1. Medium
@@ -34,6 +36,8 @@ The results are sorted by the priority of the vulnerability:
1. Info
1. Unknown
+
+
A pipeline consists of multiple jobs, including SAST and DAST scanning. If any job fails to finish
for any reason, the security dashboard does not show SAST scanner output. For example, if the SAST
job finishes but the DAST job fails, the security dashboard does not show SAST results. On failure,
diff --git a/doc/user/application_security/vulnerability_report/index.md b/doc/user/application_security/vulnerability_report/index.md
index 8b811c62ec3..f5b0194c320 100644
--- a/doc/user/application_security/vulnerability_report/index.md
+++ b/doc/user/application_security/vulnerability_report/index.md
@@ -45,6 +45,8 @@ From the Vulnerability Report you can:
You can filter the vulnerabilities table by:
+
+
| Filter | Available options |
|:---------|:------------------|
| Status | Detected, Confirmed, Dismissed, Resolved. |
@@ -53,6 +55,8 @@ You can filter the vulnerabilities table by:
| Project | For more details, see [Project filter](#project-filter). |
| Activity | For more details, see [Activity filter](#activity-filter). |
+
+
### Filter the list of vulnerabilities
To filter the list of vulnerabilities:
diff --git a/doc/user/project/import/gitea.md b/doc/user/project/import/gitea.md
index 9364ac4f954..3bbc70b4337 100644
--- a/doc/user/project/import/gitea.md
+++ b/doc/user/project/import/gitea.md
@@ -1,5 +1,4 @@
---
-type: reference, howto
stage: Manage
group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
@@ -10,30 +9,30 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Import your projects from Gitea to GitLab with minimal effort.
NOTE:
-This requires Gitea `v1.0.0` or newer.
+This requires Gitea `v1.0.0` or later.
The Gitea importer can import:
-- Repository description (GitLab 8.15+)
-- Git repository data (GitLab 8.15+)
-- Issues (GitLab 8.15+)
-- Pull requests (GitLab 8.15+)
-- Milestones (GitLab 8.15+)
-- Labels (GitLab 8.15+)
+- Repository description
+- Git repository data
+- Issues
+- Pull requests
+- Milestones
+- Labels
When importing, repository public access is retained. If a repository is private in Gitea, it's
created as private in GitLab as well.
## How it works
-Since Gitea is currently not an OAuth provider, author/assignee cannot be mapped
-to users in your GitLab instance. This means that the project creator (most of
-the times the current user that started the import process) is set as the author,
-but a reference on the issue about the original Gitea author is kept.
+Because Gitea isn't an OAuth provider, author/assignee can't be mapped to users
+in your GitLab instance. This means the project creator (usually the user that
+started the import process) is set as the author. A reference, however, is kept
+on the issue about the original Gitea author.
-The importer creates any new namespaces (groups) if they don't exist or in
-the case the namespace is taken, the repository is imported under the user's
-namespace that started the import process.
+The importer creates any new namespaces (groups) if they don't exist. If the
+namespace is taken, the repository is imported under the user's namespace
+that started the import process.
## Import your Gitea repositories
@@ -41,7 +40,7 @@ The importer page is visible when you create a new project.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
-Click the **Gitea** link and the import authorization process starts.
+Select the **Gitea** link to start the import authorization process.
![New Gitea project import](img/import_projects_from_gitea_new_import.png)
@@ -52,13 +51,13 @@ GitLab access your repositories:
1. Go to `https://your-gitea-instance/user/settings/applications` (replace
`your-gitea-instance` with the host of your Gitea instance).
-1. Click **Generate New Token**.
+1. Select **Generate New Token**.
1. Enter a token description.
-1. Click **Generate Token**.
+1. Select **Generate Token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the Gitea importer.
-1. Hit the **List Your Gitea Repositories** button and wait while GitLab reads
- your repositories' information. Once done, you are taken to the importer
+1. Select **List Your Gitea Repositories** and wait while GitLab reads
+ your repositories' information. After it's done, GitLab displays the importer
page to select the repositories to import.
### Select which repositories to import
@@ -66,19 +65,19 @@ GitLab access your repositories:
After you've authorized access to your Gitea repositories, you are
redirected to the Gitea importer page.
-From there, you can see the import statuses of your Gitea repositories.
+From there, you can view the import statuses of your Gitea repositories:
-- Those that are being imported show a _started_ status,
-- those already successfully imported are green with a _done_ status,
-- whereas those that are not yet imported have an **Import** button on the
+- Those that are being imported show a _started_ status.
+- Those already successfully imported are green with a _done_ status.
+- Those that aren't yet imported have an **Import** button on the
right side of the table.
You also can:
-- Import all your Gitea projects in one go by hitting **Import all projects** in
- the upper left corner.
-- Filter projects by name. If filter is applied, hitting **Import all projects**
- only imports matched projects.
+- Import all of your Gitea projects in one go by selecting **Import all projects**
+ in the upper left corner.
+- Filter projects by name. If filter is applied, selecting **Import all projects**
+ imports only matched projects.
![Gitea importer page](img/import_projects_from_gitea_importer_v12_3.png)
diff --git a/package.json b/package.json
index ae33b5ef76a..5f2c72beea2 100644
--- a/package.json
+++ b/package.json
@@ -55,9 +55,9 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
- "@gitlab/svgs": "1.212.0",
+ "@gitlab/svgs": "1.213.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "32.11.4",
+ "@gitlab/ui": "32.12.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.3-2",
"@rails/ujs": "6.1.3-2",
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
index c3be58fda74..c136d14c1e5 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
@@ -58,8 +58,8 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1806',
issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252',
issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678',
- # mostly impacts testing as it makes small groups import slower
- issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351'
+ issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351',
+ except: { job: 'instance-image-slow-network' }
) do
Page::Group::BulkImport.perform do |import_page|
import_page.import_group(imported_group.path, imported_group.sandbox.path)
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index 797d9188f81..280a1586de3 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -109,14 +109,18 @@ function rspec_paralellized_job() {
local test_level="${job_name[1]}"
local report_name=$(echo "${CI_JOB_NAME}" | sed -E 's|[/ ]|_|g') # e.g. 'rspec unit pg12 1/24' would become 'rspec_unit_pg12_1_24'
local rspec_opts="${1}"
- local spec_folder_prefix=""
+ local spec_folder_prefixes=""
if [[ "${test_tool}" =~ "-ee" ]]; then
- spec_folder_prefix="ee/"
+ spec_folder_prefixes="'ee/'"
fi
if [[ "${test_tool}" =~ "-jh" ]]; then
- spec_folder_prefix="jh/"
+ spec_folder_prefixes="'jh/'"
+ fi
+
+ if [[ "${test_tool}" =~ "-all" ]]; then
+ spec_folder_prefixes="['', 'ee/']"
fi
export KNAPSACK_LOG_LEVEL="debug"
@@ -131,7 +135,7 @@ function rspec_paralellized_job() {
cp "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "${KNAPSACK_REPORT_PATH}"
if [[ -z "${KNAPSACK_TEST_FILE_PATTERN}" ]]; then
- pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(%(${spec_folder_prefix})).pattern(:${test_level})")
+ pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})")
export KNAPSACK_TEST_FILE_PATTERN="${pattern}"
fi
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index 020d80775d2..f88d31bda88 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -68,7 +68,7 @@ RSpec.describe 'Issue Boards new issue', :js do
wait_for_requests
- page.within(first('.board .issue-count-badge-count')) do
+ page.within(first('.board [data-testid="issue-count-badge"]')) do
expect(page).to have_content('1')
end
diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb
index 36682036d48..6a09e3c9506 100644
--- a/spec/features/boards/reload_boards_on_browser_back_spec.rb
+++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do
visit project_board_path(project, board)
wait_for_requests
- page.within(first('.board .issue-count-badge-count')) do
+ page.within(first('.board [data-testid="issue-count-badge"]')) do
expect(page).to have_content('0')
end
end
@@ -35,7 +35,7 @@ RSpec.describe 'Ensure Boards do not show stale data on browser back', :js do
page.go_back
wait_for_requests
- page.within(first('.board .issue-count-badge-count')) do
+ page.within(first('.board [data-testid="issue-count-badge"]')) do
expect(page).to have_content('1')
end
diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb
index afe36dabcb5..aece6d790b5 100644
--- a/spec/features/groups/board_spec.rb
+++ b/spec/features/groups/board_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Group Boards' do
it 'adds an issue to the backlog' do
page.within(find('.board', match: :first)) do
issue_title = 'New Issue'
- find(:css, '.issue-count-badge-add-button').click
+ click_button 'New issue'
wait_for_requests
diff --git a/spec/features/issues/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb
index 837859bbe26..a8933ed9c30 100644
--- a/spec/features/issues/related_issues_spec.rb
+++ b/spec/features/issues/related_issues_spec.rb
@@ -41,13 +41,13 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
end
context 'when logged in but not a member' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows widget when internal project' do
@@ -57,7 +57,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
it 'does not show widget when private project' do
@@ -76,7 +76,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
it 'shows widget on their own public issue' do
@@ -86,13 +86,13 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
end
context 'when logged in and a guest' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows widget when internal project' do
@@ -103,7 +103,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
it 'shows widget when private project' do
@@ -114,7 +114,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
it 'shows widget when public project' do
@@ -125,13 +125,13 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).not_to have_selector('.js-issue-count-badge-add-button')
+ expect(page).not_to have_button 'Add a related issue'
end
end
context 'when logged in and a reporter' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows widget when internal project' do
@@ -142,7 +142,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).to have_selector('.js-issue-count-badge-add-button')
+ expect(page).to have_button 'Add a related issue'
end
it 'shows widget when private project' do
@@ -153,7 +153,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).to have_selector('.js-issue-count-badge-add-button')
+ expect(page).to have_button 'Add a related issue'
end
it 'shows widget when public project' do
@@ -164,7 +164,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).to have_selector('.js-issue-count-badge-add-button')
+ expect(page).to have_button 'Add a related issue'
end
it 'shows widget on their own public issue' do
@@ -175,7 +175,7 @@ RSpec.describe 'Related issues', :js do
visit project_issue_path(project, issue)
expect(page).to have_css('.related-issues-block')
- expect(page).to have_selector('.js-issue-count-badge-add-button')
+ expect(page).to have_button 'Add a related issue'
end
end
end
@@ -186,7 +186,7 @@ RSpec.describe 'Related issues', :js do
before do
project.add_guest(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'visiting some issue someone else created' do
@@ -216,7 +216,7 @@ RSpec.describe 'Related issues', :js do
before do
project.add_maintainer(user)
project_b.add_maintainer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'without existing related issues' do
@@ -230,7 +230,7 @@ RSpec.describe 'Related issues', :js do
end
it 'add related issue' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "#{issue_b.to_reference(project)} "
find('.js-add-issuable-form-add-button').click
@@ -247,7 +247,7 @@ RSpec.describe 'Related issues', :js do
end
it 'add cross-project related issue' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} "
find('.js-add-issuable-form-add-button').click
@@ -261,7 +261,7 @@ RSpec.describe 'Related issues', :js do
end
it 'pressing enter should submit the form' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} "
find('.js-add-issuable-form-input').native.send_key(:enter)
@@ -275,7 +275,7 @@ RSpec.describe 'Related issues', :js do
end
it 'disallows duplicate entries' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set 'duplicate duplicate duplicate'
items = all('.js-add-issuable-form-token-list-item')
@@ -288,7 +288,7 @@ RSpec.describe 'Related issues', :js do
it 'allows us to remove pending issues' do
# Tests against https://gitlab.com/gitlab-org/gitlab/issues/11625
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set 'issue1 issue2 issue3 '
items = all('.js-add-issuable-form-token-list-item')
@@ -351,7 +351,7 @@ RSpec.describe 'Related issues', :js do
end
it 'add related issue' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "##{issue_d.iid} "
find('.js-add-issuable-form-add-button').click
@@ -367,7 +367,7 @@ RSpec.describe 'Related issues', :js do
end
it 'add invalid related issue' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "#9999999 "
find('.js-add-issuable-form-add-button').click
@@ -382,7 +382,7 @@ RSpec.describe 'Related issues', :js do
end
it 'add unauthorized related issue' do
- find('.js-issue-count-badge-add-button').click
+ click_button 'Add a related issue'
find('.js-add-issuable-form-input').set "#{issue_project_unauthorized_a.to_reference(project)} "
find('.js-add-issuable-form-add-button').click
diff --git a/spec/services/ci/stuck_builds/drop_running_service_spec.rb b/spec/services/ci/stuck_builds/drop_running_service_spec.rb
new file mode 100644
index 00000000000..d2132914a02
--- /dev/null
+++ b/spec/services/ci/stuck_builds/drop_running_service_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::StuckBuilds::DropRunningService do
+ let!(:runner) { create :ci_runner }
+ let!(:job) { create :ci_build, runner: runner }
+ let(:created_at) { }
+ let(:updated_at) { }
+
+ subject(:service) { described_class.new }
+
+ before do
+ job_attributes = { status: status }
+ job_attributes[:created_at] = created_at if created_at
+ job_attributes[:updated_at] = updated_at if updated_at
+ job.update!(job_attributes)
+ end
+
+ context 'when job is running' do
+ let(:status) { 'running' }
+
+ context 'when job was updated_at more than an hour ago' do
+ let(:updated_at) { 2.hours.ago }
+
+ it_behaves_like 'job is dropped'
+ end
+
+ context 'when job was updated in less than 1 hour ago' do
+ let(:updated_at) { 30.minutes.ago }
+
+ it_behaves_like 'job is unchanged'
+ end
+ end
+
+ %w(success skipped failed canceled scheduled pending).each do |status|
+ context "when job is #{status}" do
+ let(:status) { status }
+ let(:updated_at) { 2.days.ago }
+
+ context 'when created_at is the same as updated_at' do
+ let(:created_at) { 2.days.ago }
+
+ it_behaves_like 'job is unchanged'
+ end
+
+ context 'when created_at is before updated_at' do
+ let(:created_at) { 3.days.ago }
+
+ it_behaves_like 'job is unchanged'
+ end
+ end
+ end
+
+ context 'for deleted project' do
+ let(:status) { 'running' }
+ let(:updated_at) { 2.days.ago }
+
+ before do
+ job.project.update!(pending_delete: true)
+ end
+
+ it_behaves_like 'job is dropped'
+ end
+end
diff --git a/spec/services/ci/stuck_builds/drop_service_spec.rb b/spec/services/ci/stuck_builds/drop_service_spec.rb
index 8dfd1bc1b3d..4aca19ae7b9 100644
--- a/spec/services/ci/stuck_builds/drop_service_spec.rb
+++ b/spec/services/ci/stuck_builds/drop_service_spec.rb
@@ -17,48 +17,6 @@ RSpec.describe Ci::StuckBuilds::DropService do
job.update!(job_attributes)
end
- shared_examples 'job is dropped' do
- it 'changes status' do
- expect(service).to receive(:drop).exactly(3).times.and_call_original
- expect(service).to receive(:drop_stuck).exactly(:once).and_call_original
-
- service.execute
- job.reload
-
- expect(job).to be_failed
- expect(job).to be_stuck_or_timeout_failure
- end
-
- context 'when job have data integrity problem' do
- it "does drop the job and logs the reason" do
- job.update_columns(yaml_variables: '[{"key" => "value"}]')
-
- expect(Gitlab::ErrorTracking).to receive(:track_exception)
- .with(anything, a_hash_including(build_id: job.id))
- .once
- .and_call_original
-
- service.execute
- job.reload
-
- expect(job).to be_failed
- expect(job).to be_data_integrity_failure
- end
- end
- end
-
- shared_examples 'job is unchanged' do
- it 'does not change status' do
- expect(service).to receive(:drop).exactly(3).times.and_call_original
- expect(service).to receive(:drop_stuck).exactly(:once).and_call_original
-
- service.execute
- job.reload
-
- expect(job.status).to eq(status)
- end
- end
-
context 'when job is pending' do
let(:status) { 'pending' }
@@ -195,7 +153,7 @@ RSpec.describe Ci::StuckBuilds::DropService do
context 'when job was updated_at more than an hour ago' do
let(:updated_at) { 2.hours.ago }
- it_behaves_like 'job is dropped'
+ it_behaves_like 'job is unchanged'
end
context 'when job was updated in less than 1 hour ago' do
@@ -238,7 +196,7 @@ RSpec.describe Ci::StuckBuilds::DropService do
job.project.update!(pending_delete: true)
end
- it_behaves_like 'job is dropped'
+ it_behaves_like 'job is unchanged'
end
describe 'drop stale scheduled builds' do
diff --git a/spec/support/helpers/feature_flag_helpers.rb b/spec/support/helpers/feature_flag_helpers.rb
index 51ba9039b70..4e57002a7c6 100644
--- a/spec/support/helpers/feature_flag_helpers.rb
+++ b/spec/support/helpers/feature_flag_helpers.rb
@@ -71,7 +71,7 @@ module FeatureFlagHelpers
end
def add_linked_issue_button
- find('.js-issue-count-badge-add-button')
+ find_button 'Add a related issue'
end
def remove_linked_issue_button
diff --git a/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb
new file mode 100644
index 00000000000..2f26dac46e2
--- /dev/null
+++ b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'job is dropped' do
+ it 'changes status' do
+ service.execute
+ job.reload
+
+ expect(job).to be_failed
+ expect(job).to be_stuck_or_timeout_failure
+ end
+
+ context 'when job has data integrity problem' do
+ it 'drops the job and logs the reason' do
+ job.update_columns(yaml_variables: '[{"key" => "value"}]')
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(anything, a_hash_including(build_id: job.id))
+ .once
+ .and_call_original
+
+ service.execute
+ job.reload
+
+ expect(job).to be_failed
+ expect(job).to be_data_integrity_failure
+ end
+ end
+end
+
+RSpec.shared_examples 'job is unchanged' do
+ it 'does not change status' do
+ service.execute
+ job.reload
+
+ expect(job.status).to eq(status)
+ end
+end
diff --git a/spec/tooling/quality/test_level_spec.rb b/spec/tooling/quality/test_level_spec.rb
index 89abe337347..0623a67a60e 100644
--- a/spec/tooling/quality/test_level_spec.rb
+++ b/spec/tooling/quality/test_level_spec.rb
@@ -63,7 +63,14 @@ RSpec.describe Quality::TestLevel do
context 'with a prefix' do
it 'returns a pattern' do
expect(described_class.new('ee/').pattern(:system))
- .to eq("ee/spec/{features}{,/**/}*_spec.rb")
+ .to eq("{ee/}spec/{features}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'with several prefixes' do
+ it 'returns a pattern' do
+ expect(described_class.new(['', 'ee/', 'jh/']).pattern(:system))
+ .to eq("{,ee/,jh/}spec/{features}{,/**/}*_spec.rb")
end
end
@@ -138,7 +145,14 @@ RSpec.describe Quality::TestLevel do
context 'with a prefix' do
it 'returns a regexp' do
expect(described_class.new('ee/').regexp(:system))
- .to eq(%r{ee/spec/(features)})
+ .to eq(%r{(ee/)spec/(features)})
+ end
+ end
+
+ context 'with several prefixes' do
+ it 'returns a regexp' do
+ expect(described_class.new(['', 'ee/', 'jh/']).regexp(:system))
+ .to eq(%r{(|ee/|jh/)spec/(features)})
end
end
diff --git a/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb b/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb
new file mode 100644
index 00000000000..68b0696145a
--- /dev/null
+++ b/spec/workers/ci/stuck_builds/drop_running_worker_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::StuckBuilds::DropRunningWorker do
+ include ExclusiveLeaseHelpers
+
+ let(:worker_lease_key) { Ci::StuckBuilds::DropRunningWorker::EXCLUSIVE_LEASE_KEY }
+ let(:worker_lease_uuid) { SecureRandom.uuid }
+ let(:worker2) { described_class.new }
+
+ subject(:worker) { described_class.new }
+
+ before do
+ stub_exclusive_lease(worker_lease_key, worker_lease_uuid)
+ end
+
+ describe '#perform' do
+ it_behaves_like 'an idempotent worker'
+
+ it 'executes an instance of Ci::StuckBuilds::DropRunningService' do
+ expect_next_instance_of(Ci::StuckBuilds::DropRunningService) do |service|
+ expect(service).to receive(:execute).exactly(:once)
+ end
+
+ worker.perform
+ end
+
+ context 'with an exclusive lease' do
+ it 'does not execute concurrently' do
+ expect(worker).to receive(:remove_lease).exactly(:once)
+ expect(worker2).not_to receive(:remove_lease)
+
+ worker.perform
+
+ stub_exclusive_lease_taken(worker_lease_key)
+
+ worker2.perform
+ end
+
+ it 'can execute in sequence' do
+ expect(worker).to receive(:remove_lease).at_least(:once)
+ expect(worker2).to receive(:remove_lease).at_least(:once)
+
+ worker.perform
+ worker2.perform
+ end
+
+ it 'cancels exclusive leases after worker perform' do
+ expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid)
+
+ worker.perform
+ end
+
+ context 'when the DropRunningService fails' do
+ it 'ensures cancellation of the exclusive lease' do
+ expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid)
+
+ allow_next_instance_of(Ci::StuckBuilds::DropRunningService) do |service|
+ allow(service).to receive(:execute) do
+ raise 'The query timed out'
+ end
+ end
+
+ expect { worker.perform }.to raise_error(/The query timed out/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index e0a5d3c6c1c..ca7d2a7016b 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -16,7 +16,13 @@ RSpec.describe StuckCiJobsWorker do
end
describe '#perform' do
- it 'executes an instance of Ci::StuckBuildsDropService' do
+ it 'enqueues a Ci::StuckBuilds::DropRunningWorker job' do
+ expect(Ci::StuckBuilds::DropRunningWorker).to receive(:perform_in).with(20.minutes).exactly(:once)
+
+ worker.perform
+ end
+
+ it 'executes an instance of Ci::StuckBuilds::DropService' do
expect_next_instance_of(Ci::StuckBuilds::DropService) do |service|
expect(service).to receive(:execute).exactly(:once)
end
diff --git a/tooling/quality/test_level.rb b/tooling/quality/test_level.rb
index ad9de067375..83cbe7a1f19 100644
--- a/tooling/quality/test_level.rb
+++ b/tooling/quality/test_level.rb
@@ -60,20 +60,20 @@ module Quality
system: ['features']
}.freeze
- attr_reader :prefix
+ attr_reader :prefixes
- def initialize(prefix = nil)
- @prefix = prefix
+ def initialize(prefixes = nil)
+ @prefixes = Array(prefixes)
@patterns = {}
@regexps = {}
end
def pattern(level)
- @patterns[level] ||= "#{prefix}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}"
+ @patterns[level] ||= "#{prefixes_for_pattern}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}"
end
def regexp(level)
- @regexps[level] ||= Regexp.new("#{prefix}spec/#{folders_regex(level)}").freeze
+ @regexps[level] ||= Regexp.new("#{prefixes_for_regex}spec/#{folders_regex(level)}").freeze
end
def level_for(file_path)
@@ -102,6 +102,20 @@ module Quality
private
+ def prefixes_for_pattern
+ return '' if prefixes.empty?
+
+ "{#{prefixes.join(',')}}"
+ end
+
+ def prefixes_for_regex
+ return '' if prefixes.empty?
+
+ regex_prefix = prefixes.map(&Regexp.method(:escape)).join('|')
+
+ "(#{regex_prefix})"
+ end
+
def suffix(level)
case level
when :frontend_fixture
diff --git a/yarn.lock b/yarn.lock
index 3e303259e36..c75060bdcf2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -964,20 +964,20 @@
stylelint-declaration-strict-value "1.7.7"
stylelint-scss "3.18.0"
-"@gitlab/svgs@1.212.0":
- version "1.212.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.212.0.tgz#21a5df04c52b10cc1b8521cd8ff7c7d6d13716db"
- integrity sha512-dv0bYTHA3hwi3mNU3bGMq1cd4HVKKFNwCNPgkF91JSp4Xt8DDtJ0Yq4X49ASsq4zCJ3odgkq2aPjEa/Sr5nINQ==
+"@gitlab/svgs@1.213.0":
+ version "1.213.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.213.0.tgz#fcd9794049d2b15f5796dbab2a3d501679153582"
+ integrity sha512-3d9EGpEkPDeW92Xx3FueFCJFZ/yL+uv5MWCUHmSt1tP9YmUhMXw/51c43c5+V17FuCyvhJS5tm3aEg3VYoWIRA==
"@gitlab/tributejs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@32.11.4":
- version "32.11.4"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.11.4.tgz#ead781c4fd57b4f7bc6818de99e56d023f94c397"
- integrity sha512-plxOvtAIo+PIng1jBssj/QCx2ATIbZqQvLvCeezgAABgY107XhqZDw50VZL0texTX+0jGwB5PaXthmU/wnCk2w==
+"@gitlab/ui@32.12.0":
+ version "32.12.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.12.0.tgz#a2e45a7587beb2b9a5014f36c10bac30e6f90f78"
+ integrity sha512-WP7uyf8Ujd2ONOIYLG6s3oIl8/IWpI2H12r7rwNG+yD8EOhvHdK8QS9+b5gpAewYJjT4r2AV8m9QJXZChmmO6w==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.18.1"