Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-21 09:09:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-21 09:09:06 +0300
commit7e6efee3b34f52a62360cb4a50f2e77cb67cf769 (patch)
tree6bfe2f6f8fc8f147269ab5020c42afad06f62785
parent22400f4dd0bd5503b3c6e4914d8dd6e1167b6c98 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue6
-rw-r--r--app/assets/javascripts/super_sidebar/components/user_name_group.vue1
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--config/feature_flags/development/remove_job_age_from_jobs_api.yml8
-rw-r--r--db/post_migrate/20230420065656_finalize_fix_incoherent_packages_size_on_project_statistics.rb25
-rw-r--r--db/post_migrate/20230420070009_drop_tmp_idx_package_files_on_non_zero_size.rb15
-rw-r--r--db/schema_migrations/202304200656561
-rw-r--r--db/schema_migrations/202304200700091
-rw-r--r--db/structure.sql2
-rw-r--r--doc/development/fips_compliance.md3
-rw-r--r--lib/api/ci/runner.rb6
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--qa/qa/page/main/login.rb2
-rw-r--r--qa/qa/page/main/menu.rb5
-rw-r--r--qa/qa/page/project/new.rb8
-rw-r--r--qa/qa/page/project/show.rb10
-rw-r--r--qa/qa/page/user/show.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb101
-rw-r--r--spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js264
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb35
22 files changed, 282 insertions, 223 deletions
diff --git a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
index 9f64ddc8721..34a4da946d6 100644
--- a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
@@ -194,7 +194,11 @@ export default {
<div v-if="hasNoSearchResults" class="gl-text-center gl-p-3">
{{ __('No matching results') }}
</div>
- <div v-if="failedToLoadResults" class="gl-text-center gl-p-3">
+ <div
+ v-if="failedToLoadResults"
+ data-testid="failed-load-results"
+ class="gl-text-center gl-p-3"
+ >
{{ __('Failed to load projects') }}
</div>
</div>
diff --git a/app/assets/javascripts/super_sidebar/components/user_name_group.vue b/app/assets/javascripts/super_sidebar/components/user_name_group.vue
index 57958a03edd..dfaaaccf4a4 100644
--- a/app/assets/javascripts/super_sidebar/components/user_name_group.vue
+++ b/app/assets/javascripts/super_sidebar/components/user_name_group.vue
@@ -41,6 +41,7 @@ export default {
item.extraAttrs = {
...USER_MENU_TRACKING_DEFAULTS,
'data-track-label': 'user_profile',
+ 'data-qa-selector': 'user_profile_link',
};
}
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 4b55ce149e1..e5b0683d601 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -130,7 +130,7 @@ module Ci
end
# pick builds that older than specified age
- if params.key?(:job_age)
+ if params.key?(:job_age) && Feature.disabled?(:remove_job_age_from_jobs_api)
builds = queue.builds_queued_before(builds, params[:job_age].seconds.ago)
end
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index d494d9cc36d..c834a0bc818 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,7 +1,7 @@
- is_project_overview = local_assigns.fetch(:is_project_overview, false)
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
- .tree-ref-holder.gl-max-w-26
+ .tree-ref-holder.gl-max-w-26{ data: { qa_selector: 'ref_dropdown_container' } }
#js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
diff --git a/config/feature_flags/development/remove_job_age_from_jobs_api.yml b/config/feature_flags/development/remove_job_age_from_jobs_api.yml
new file mode 100644
index 00000000000..2f03f71a757
--- /dev/null
+++ b/config/feature_flags/development/remove_job_age_from_jobs_api.yml
@@ -0,0 +1,8 @@
+---
+name: remove_job_age_from_jobs_api
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118045
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/406662
+milestone: '16.0'
+type: development
+group: group::pipeline execution
+default_enabled: true
diff --git a/db/post_migrate/20230420065656_finalize_fix_incoherent_packages_size_on_project_statistics.rb b/db/post_migrate/20230420065656_finalize_fix_incoherent_packages_size_on_project_statistics.rb
new file mode 100644
index 00000000000..aa03310eb0a
--- /dev/null
+++ b/db/post_migrate/20230420065656_finalize_fix_incoherent_packages_size_on_project_statistics.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class FinalizeFixIncoherentPackagesSizeOnProjectStatistics < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'FixIncoherentPackagesSizeOnProjectStatistics'
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: MIGRATION,
+ table_name: :project_statistics,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20230420070009_drop_tmp_idx_package_files_on_non_zero_size.rb b/db/post_migrate/20230420070009_drop_tmp_idx_package_files_on_non_zero_size.rb
new file mode 100644
index 00000000000..8279cff7afb
--- /dev/null
+++ b/db/post_migrate/20230420070009_drop_tmp_idx_package_files_on_non_zero_size.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class DropTmpIdxPackageFilesOnNonZeroSize < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'tmp_idx_package_files_on_non_zero_size'
+
+ def up
+ remove_concurrent_index :packages_package_files, %i[package_id size], name: INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :packages_package_files, %i[package_id size], where: 'size IS NOT NULL', name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230420065656 b/db/schema_migrations/20230420065656
new file mode 100644
index 00000000000..dd89c9e19e3
--- /dev/null
+++ b/db/schema_migrations/20230420065656
@@ -0,0 +1 @@
+e29be6311d828a76c594cf350d5212fac9913362dd3e9b96fda6f74c50edfcdb \ No newline at end of file
diff --git a/db/schema_migrations/20230420070009 b/db/schema_migrations/20230420070009
new file mode 100644
index 00000000000..7dfad0f6dee
--- /dev/null
+++ b/db/schema_migrations/20230420070009
@@ -0,0 +1 @@
+74b9c628c09856c3285452be85a853103e7b7860d1e33df664bdcae927f690d1 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 27f31e09207..84183c6720a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -32982,8 +32982,6 @@ CREATE INDEX tmp_idx_for_feedback_comment_processing ON vulnerability_feedback U
CREATE INDEX tmp_idx_for_vulnerability_feedback_migration ON vulnerability_feedback USING btree (id) WHERE ((migrated_to_state_transition = false) AND (feedback_type = 0));
-CREATE INDEX tmp_idx_package_files_on_non_zero_size ON packages_package_files USING btree (package_id, size) WHERE (size IS NOT NULL);
-
CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
CREATE INDEX tmp_index_ci_job_artifacts_on_expire_at_where_locked_unknown ON ci_job_artifacts USING btree (expire_at, job_id) WHERE ((locked = 2) AND (expire_at IS NOT NULL));
diff --git a/doc/development/fips_compliance.md b/doc/development/fips_compliance.md
index f0634107ba5..830a8e3cd2a 100644
--- a/doc/development/fips_compliance.md
+++ b/doc/development/fips_compliance.md
@@ -59,8 +59,7 @@ listed here that also do not work properly in FIPS mode:
- [Container Scanning](../user/application_security/container_scanning/index.md) support for scanning images in repositories that require authentication.
- [Code Quality](../ci/testing/code_quality.md) does not support operating in FIPS-compliant mode.
- [Dependency scanning](../user/application_security/dependency_scanning/index.md) support for Gradle.
-- [Dynamic Application Security Testing (DAST)](../user/application_security/dast/index.md)
- does not support operating in FIPS-compliant mode.
+- [Dynamic Application Security Testing (DAST)](../user/application_security/dast/index.md) supports a reduced set of analyzers. Browser-based and proxy-based analyzers are not available in FIPS mode today, however DAST API and DAST API Fuzzing images are available.
- [License compliance](../user/compliance/license_compliance/index.md).
- [Solutions for vulnerabilities](../user/application_security/vulnerabilities/index.md#resolve-a-vulnerability)
for yarn projects.
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index d61171ea9f4..b722a21ac33 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -141,7 +141,7 @@ module API
optional :certificate, type: String, desc: %q(Session's certificate)
optional :authorization, type: String, desc: %q(Session's authorization)
end
- optional :job_age, type: Integer, desc: %q(Job should be older than passed age in seconds to be ran on runner)
+ optional :job_age, type: Integer, desc: %q(DEPRECATED and will be REMOVED in GitLab 16.0. Job should be older than passed age in seconds to be ran on runner)
end
# Since we serialize the build output ourselves to ensure Gitaly
@@ -167,6 +167,10 @@ module API
runner_params = declared_params(include_missing: false)
+ if Feature.enabled?(:remove_job_age_from_jobs_api)
+ runner_params.delete(:job_age)
+ end
+
if current_runner.runner_queue_value_latest?(runner_params[:last_update])
header 'X-GitLab-Last-Update', runner_params[:last_update]
Gitlab::Metrics.add_event(:build_not_found_cached)
diff --git a/qa/Gemfile b/qa/Gemfile
index abb654b4a96..58bf10a1f75 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -9,7 +9,7 @@ gem 'capybara', '~> 3.39.0'
gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.0.6'
gem 'rspec', '~> 3.12'
-gem 'selenium-webdriver', '~> 4.8', '>= 4.8.6'
+gem 'selenium-webdriver', '~> 4.9'
gem 'airborne', '~> 0.3.7', require: false # airborne is messing with rspec sandboxed mode so not requiring by default
gem 'rest-client', '~> 2.1.0'
gem 'rspec-retry', '~> 0.6.2', require: 'rspec/retry'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index e86a5526fc8..e04cfe94c6d 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -260,7 +260,7 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
- selenium-webdriver (4.8.6)
+ selenium-webdriver (4.9.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@@ -335,7 +335,7 @@ DEPENDENCIES
rspec-retry (~> 0.6.2)
rspec_junit_formatter (~> 0.6.0)
ruby-debug-ide (~> 0.7.3)
- selenium-webdriver (~> 4.8, >= 4.8.6)
+ selenium-webdriver (~> 4.9)
slack-notifier (~> 2.4)
terminal-table (~> 3.0.2)
warning (~> 1.3)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 59371dbed39..bea01a5bbc7 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -249,7 +249,7 @@ module QA
terms.accept_terms if terms.visible?
end
- Page::Main::Menu.perform(&:enable_new_navigation) if Runtime::Env.super_sidebar_enabled? && !on_login_page?
+ Page::Main::Menu.perform(&:enable_new_navigation) if Runtime::Env.super_sidebar_enabled?
Page::Main::Menu.validate_elements_present! unless skip_page_validation
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index a46b2057327..c44ab24db50 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -25,6 +25,10 @@ module QA
element :sign_out_link
element :edit_profile_link
end
+
+ view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do
+ element :user_profile_link
+ end
else
view 'app/views/layouts/header/_default.html.haml' do
element :navbar, required: true
@@ -257,6 +261,7 @@ module QA
def enable_new_navigation
Runtime::Logger.info("Enabling super sidebar!")
+ return Runtime::Logger.info("User is not signed in, skipping") unless has_element?(:navbar, wait: 2)
return Runtime::Logger.info("Super sidebar is already enabled") if has_css?('[data-testid="super-sidebar"]')
within_user_menu { click_element(:new_navigation_toggle) }
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index f7434656be3..8624e4c3d83 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -48,11 +48,9 @@ module QA
end
def choose_namespace(namespace)
- retry_on_exception do
- click_element :select_namespace_dropdown
- fill_element :select_namespace_dropdown_search_field, namespace
- click_button namespace
- end
+ click_element :select_namespace_dropdown
+ fill_element :select_namespace_dropdown_search_field, namespace
+ within_element(:select_namespace_dropdown) { click_button namespace }
end
def click_import_project
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index a76717f4760..daaee280b84 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -74,6 +74,10 @@ module QA
element :download_source_code_button
end
+ view 'app/views/projects/tree/_tree_header.html.haml' do
+ element :ref_dropdown_container
+ end
+
def wait_for_viewers_to_load
has_no_element?(:spinner_placeholder, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
end
@@ -172,8 +176,10 @@ module QA
end
def switch_to_branch(branch_name)
- expand_select_list
- select_item(branch_name)
+ within_element(:ref_dropdown_container) do
+ expand_select_list
+ select_item(branch_name)
+ end
end
def wait_for_import
diff --git a/qa/qa/page/user/show.rb b/qa/qa/page/user/show.rb
index 9f5f0fae9bc..f14ddea3e8b 100644
--- a/qa/qa/page/user/show.rb
+++ b/qa/qa/page/user/show.rb
@@ -22,6 +22,8 @@ module QA
end
def click_following_tab
+ return click_element(:nav_item_link, submenu_item: 'Following') if Runtime::Env.super_sidebar_enabled?
+
click_element(:following_tab)
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index 3f5842d756e..f257f1edbc1 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -16,7 +16,8 @@ module QA
end
RSpec.describe 'Manage', :skip_signup_disabled, :requires_admin, product_group: :authentication_and_authorization do
- describe 'while LDAP is enabled', :orchestrated, :ldap_no_tls, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347934' do
+ describe 'while LDAP is enabled', :orchestrated, :ldap_no_tls,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347934' do
before do
# When LDAP is enabled, a previous test might have created a token for the LDAP 'tanuki' user who is not an admin
# So we need to set it to nil in order to create a new token for admin user so that we are able to set_application_settings
@@ -29,22 +30,22 @@ module QA
ldap_username = Runtime::Env.ldap_username
Runtime::Env.ldap_username = nil
- set_require_admin_approval_after_user_signup_via_api(false)
+ set_require_admin_approval_after_user_signup(false)
Runtime::Env.ldap_username = ldap_username
end
- it_behaves_like 'registration and login'
-
after do
Runtime::Env.personal_access_token = @personal_access_token
end
+
+ it_behaves_like 'registration and login'
end
describe 'standard', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347867' do
context 'when admin approval is not required' do
before(:all) do
- set_require_admin_approval_after_user_signup_via_api(false)
+ set_require_admin_approval_after_user_signup(false)
end
it_behaves_like 'registration and login'
@@ -70,7 +71,15 @@ module QA
Support::Waiter.wait_until(max_duration: 120, sleep_interval: 3) { !user.exists? }
end
- it 'allows recreating with same credentials', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
+ after do
+ if @recreated_user
+ @recreated_user.api_client = admin_api_client
+ @recreated_user.remove_via_api!
+ end
+ end
+
+ it 'allows recreating with same credentials', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
expect(Page::Main::Menu.perform(&:signed_in?)).to be_falsy
Flow::Login.sign_in(as: user, skip_page_validation: true)
@@ -86,13 +95,6 @@ module QA
expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy
end
- after do
- if @recreated_user
- @recreated_user.api_client = admin_api_client
- @recreated_user.remove_via_api!
- end
- end
-
def admin_api_client
@admin_api_client ||= Runtime::API::Client.as_admin
end
@@ -100,29 +102,42 @@ module QA
end
context 'when admin approval is required' do
- let(:signed_up_waiting_approval_text) { 'You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.' }
- let(:pending_approval_blocked_text) { 'Your account is pending approval from your GitLab administrator and hence blocked. Please contact your GitLab administrator if you think this is an error.' }
+ let(:signed_up_waiting_approval_text) do
+ 'You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.'
+ end
- before do
- enable_require_admin_approval_after_user_signup_via_ui
+ let(:pending_approval_blocked_text) do
+ 'Your account is pending approval from your GitLab administrator and hence blocked. Please contact your GitLab administrator if you think this is an error.'
+ end
- Support::Retrier.retry_on_exception do
- @user = Resource::User.fabricate_via_browser_ui! do |user|
- user.expect_fabrication_success = false
- end
+ let(:user) do
+ Resource::User.fabricate_via_browser_ui! do |user|
+ user.expect_fabrication_success = false
end
end
- it 'allows user login after approval', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347871' do
+ before do
+ set_require_admin_approval_after_user_signup(true)
+ end
+
+ after do
+ set_require_admin_approval_after_user_signup(false)
+ user.remove_via_api! if user
+ end
+
+ it 'allows user login after approval',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347871' do
+ user # sign up user
+
expect(page).to have_text(signed_up_waiting_approval_text)
- Flow::Login.sign_in(as: @user, skip_page_validation: true)
+ Flow::Login.sign_in(as: user, skip_page_validation: true)
expect(page).to have_text(pending_approval_blocked_text)
- approve_user(@user)
+ approve_user(user)
- Flow::Login.sign_in(as: @user, skip_page_validation: true)
+ Flow::Login.sign_in(as: user, skip_page_validation: true)
Flow::UserOnboarding.onboard_user
@@ -131,11 +146,6 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Dashboard::Welcome)
Page::Main::Menu.perform(&:has_personal_area?)
end
-
- after do
- set_require_admin_approval_after_user_signup_via_api(false)
- @user.remove_via_api! if @user
- end
end
end
@@ -158,36 +168,17 @@ module QA
end
end
- def set_require_admin_approval_after_user_signup_via_api(enable_or_disable)
- return if get_require_admin_approval_after_user_signup_via_api == enable_or_disable
+ def set_require_admin_approval_after_user_signup(enable_or_disable)
+ return if get_require_admin_approval_after_user_signup == enable_or_disable
Runtime::ApplicationSettings.set_application_settings(require_admin_approval_after_user_signup: enable_or_disable)
-
- sleep 10 # It takes a moment for the setting to come into effect
+ QA::Support::Retrier.retry_until(max_duration: 10, sleep_interval: 1) do
+ get_require_admin_approval_after_user_signup == enable_or_disable
+ end
end
- def get_require_admin_approval_after_user_signup_via_api
+ def get_require_admin_approval_after_user_signup
Runtime::ApplicationSettings.get_application_settings[:require_admin_approval_after_user_signup]
end
-
- def enable_require_admin_approval_after_user_signup_via_ui
- unless get_require_admin_approval_after_user_signup_via_api
- QA::Support::Retrier.retry_until do
- Flow::Login.while_signed_in_as_admin do
- Page::Main::Menu.perform(&:go_to_admin_area)
- QA::Page::Admin::Menu.perform(&:go_to_general_settings)
- Page::Admin::Settings::General.perform do |setting|
- setting.expand_sign_up_restrictions do |settings|
- settings.require_admin_approval_after_user_signup
- end
- end
- end
-
- sleep 15 # It takes a moment for the setting to come into effect
-
- get_require_admin_approval_after_user_signup_via_api
- end
- end
- end
end
end
diff --git a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
index 7c168a0ac41..ab3e71bdddb 100644
--- a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import {
GlIcon,
GlLoadingIcon,
@@ -7,12 +8,13 @@ import {
GlSearchBoxByType,
GlButton,
} from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-
-import { nextTick } from 'vue';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue';
+import { stubComponent } from 'helpers/stub_component';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
const mockProjects = [
{
@@ -45,20 +47,35 @@ const mockEvent = {
preventDefault: jest.fn(),
};
+const focusInputMock = jest.fn();
+const hideMock = jest.fn();
+
describe('IssuableMoveDropdown', () => {
let mock;
let wrapper;
const createComponent = (propsData = mockProps) => {
- wrapper = shallowMount(IssuableMoveDropdown, {
+ wrapper = shallowMountExtended(IssuableMoveDropdown, {
propsData,
+ stubs: {
+ GlDropdown: stubComponent(GlDropdown, {
+ methods: {
+ hide: hideMock,
+ },
+ }),
+ GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
+ methods: {
+ focusInput: focusInputMock,
+ },
+ }),
+ },
});
- wrapper.vm.$refs.dropdown.hide = jest.fn();
- wrapper.vm.$refs.searchInput.focusInput = jest.fn();
};
beforeEach(() => {
mock = new MockAdapter(axios);
+ mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, mockProjects);
+
createComponent();
});
@@ -66,38 +83,46 @@ describe('IssuableMoveDropdown', () => {
mock.restore();
});
+ const findCollapsedEl = () => wrapper.findByTestId('move-collapsed');
+ const findFooter = () => wrapper.findByTestId('footer');
+ const findHeader = () => wrapper.findByTestId('header');
+ const findFailedLoadResults = () => wrapper.findByTestId('failed-load-results');
+ const findDropdownContent = () => wrapper.findByTestId('content');
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findDropdownEl = () => wrapper.findComponent(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+
describe('watch', () => {
describe('searchKey', () => {
it('calls `fetchProjects` with value of the prop', async () => {
- jest.spyOn(wrapper.vm, 'fetchProjects');
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- searchKey: 'foo',
- });
+ jest.spyOn(axios, 'get');
+ findSearchBox().vm.$emit('input', 'foo');
- await nextTick();
+ await waitForPromises();
- expect(wrapper.vm.fetchProjects).toHaveBeenCalledWith('foo');
+ expect(axios.get).toHaveBeenCalledWith('/-/autocomplete/projects?project_id=1', {
+ params: { search: 'foo' },
+ });
});
});
});
describe('methods', () => {
describe('fetchProjects', () => {
- it('sets projectsListLoading to true and projectsListLoadFailed to false', () => {
- wrapper.vm.fetchProjects();
+ it('sets projectsListLoading to true and projectsListLoadFailed to false', async () => {
+ findDropdownEl().vm.$emit('shown');
+ await nextTick();
- expect(wrapper.vm.projectsListLoading).toBe(true);
- expect(wrapper.vm.projectsListLoadFailed).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findFailedLoadResults().exists()).toBe(false);
});
- it('calls `axios.get` with `projectsFetchPath` and query param `search`', () => {
- jest.spyOn(axios, 'get').mockResolvedValue({
- data: mockProjects,
- });
+ it('calls `axios.get` with `projectsFetchPath` and query param `search`', async () => {
+ jest.spyOn(axios, 'get');
- wrapper.vm.fetchProjects('foo');
+ findSearchBox().vm.$emit('input', 'foo');
+ await waitForPromises();
expect(axios.get).toHaveBeenCalledWith(
mockProps.projectsFetchPath,
@@ -110,74 +135,65 @@ describe('IssuableMoveDropdown', () => {
});
it('sets response to `projects` and focuses on searchInput when request is successful', async () => {
- jest.spyOn(axios, 'get').mockResolvedValue({
- data: mockProjects,
- });
+ jest.spyOn(axios, 'get');
- await wrapper.vm.fetchProjects('foo');
+ findSearchBox().vm.$emit('input', 'foo');
+ await waitForPromises();
- expect(wrapper.vm.projects).toBe(mockProjects);
- expect(wrapper.vm.$refs.searchInput.focusInput).toHaveBeenCalled();
+ expect(findAllDropdownItems()).toHaveLength(mockProjects.length);
+ expect(focusInputMock).toHaveBeenCalled();
});
it('sets projectsListLoadFailed to true when request fails', async () => {
jest.spyOn(axios, 'get').mockRejectedValue({});
- await wrapper.vm.fetchProjects('foo');
+ findSearchBox().vm.$emit('input', 'foo');
+ await waitForPromises();
- expect(wrapper.vm.projectsListLoadFailed).toBe(true);
+ expect(findFailedLoadResults().exists()).toBe(true);
});
it('sets projectsListLoading to false when request completes', async () => {
- jest.spyOn(axios, 'get').mockResolvedValue({
- data: mockProjects,
- });
+ jest.spyOn(axios, 'get');
- await wrapper.vm.fetchProjects('foo');
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
- expect(wrapper.vm.projectsListLoading).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
});
describe('isSelectedProject', () => {
it.each`
- project | selectedProject | title | returnValue
- ${mockProjects[0]} | ${mockProjects[0]} | ${'are same projects'} | ${true}
- ${mockProjects[0]} | ${mockProjects[1]} | ${'are different projects'} | ${false}
+ projectIndex | selectedProjectIndex | title | returnValue
+ ${0} | ${0} | ${'are same projects'} | ${true}
+ ${0} | ${1} | ${'are different projects'} | ${false}
`(
'returns $returnValue when selectedProject and provided project param $title',
- async ({ project, selectedProject, returnValue }) => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedProject,
- });
+ async ({ projectIndex, selectedProjectIndex, returnValue }) => {
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+
+ findAllDropdownItems().at(selectedProjectIndex).vm.$emit('click', mockEvent);
await nextTick();
- expect(wrapper.vm.isSelectedProject(project)).toBe(returnValue);
+ expect(findAllDropdownItems().at(projectIndex).props('isChecked')).toBe(returnValue);
},
);
it('returns false when selectedProject is null', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedProject: null,
- });
-
- await nextTick();
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
- expect(wrapper.vm.isSelectedProject(mockProjects[0])).toBe(false);
+ expect(findAllDropdownItems().at(0).props('isChecked')).toBe(false);
});
});
});
describe('template', () => {
- const findDropdownEl = () => wrapper.findComponent(GlDropdown);
-
it('renders collapsed state element with icon', () => {
- const collapsedEl = wrapper.find('[data-testid="move-collapsed"]');
+ const collapsedEl = findCollapsedEl();
expect(collapsedEl.exists()).toBe(true);
expect(collapsedEl.attributes('title')).toBe(mockProps.dropdownButtonTitle);
@@ -197,12 +213,11 @@ describe('IssuableMoveDropdown', () => {
it('renders disabled dropdown when `disabled` is true', () => {
createComponent({ ...mockProps, disabled: true });
-
- expect(findDropdownEl().attributes('disabled')).toBe('true');
+ expect(findDropdownEl().props('disabled')).toBe(true);
});
it('renders header element', () => {
- const headerEl = findDropdownEl().find('[data-testid="header"]');
+ const headerEl = findHeader();
expect(headerEl.exists()).toBe(true);
expect(headerEl.find('span').text()).toBe(mockProps.dropdownHeaderTitle);
@@ -220,125 +235,87 @@ describe('IssuableMoveDropdown', () => {
});
it('renders gl-loading-icon component when projectsListLoading prop is true', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projectsListLoading: true,
- });
-
+ findDropdownEl().vm.$emit('shown');
await nextTick();
- expect(findDropdownEl().findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
it('renders gl-dropdown-item components for available projects', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projects: mockProjects,
- selectedProject: mockProjects[0],
- });
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+ findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
await nextTick();
- const dropdownItems = wrapper.findAllComponents(GlDropdownItem);
-
- expect(dropdownItems).toHaveLength(mockProjects.length);
- expect(dropdownItems.at(0).props()).toMatchObject({
+ expect(findAllDropdownItems()).toHaveLength(mockProjects.length);
+ expect(findAllDropdownItems().at(0).props()).toMatchObject({
isCheckItem: true,
isChecked: true,
});
- expect(dropdownItems.at(0).text()).toBe(mockProjects[0].name_with_namespace);
+ expect(findAllDropdownItems().at(0).text()).toBe(mockProjects[0].name_with_namespace);
});
it('renders string "No matching results" when search does not yield any matches', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- searchKey: 'foo',
- });
-
- // Wait for `searchKey` watcher to run.
- await nextTick();
+ mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, []);
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projects: [],
- projectsListLoading: false,
- });
-
- await nextTick();
+ findSearchBox().vm.$emit('input', 'foo');
+ await waitForPromises();
- const dropdownContentEl = wrapper.find('[data-testid="content"]');
-
- expect(dropdownContentEl.text()).toContain('No matching results');
+ expect(findDropdownContent().text()).toContain('No matching results');
});
it('renders string "Failed to load projects" when loading projects list fails', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projects: [],
- projectsListLoading: false,
- projectsListLoadFailed: true,
- });
-
- await nextTick();
+ mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, []);
+ jest.spyOn(axios, 'get').mockRejectedValue({});
- const dropdownContentEl = wrapper.find('[data-testid="content"]');
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
- expect(dropdownContentEl.text()).toContain('Failed to load projects');
+ expect(findDropdownContent().text()).toContain('Failed to load projects');
});
it('renders gl-button within footer', async () => {
- const moveButtonEl = wrapper.find('[data-testid="footer"]').findComponent(GlButton);
+ const moveButtonEl = findFooter().findComponent(GlButton);
expect(moveButtonEl.text()).toBe('Move');
expect(moveButtonEl.attributes('disabled')).toBe('true');
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedProject: mockProjects[0],
- });
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+ findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
await nextTick();
- expect(
- wrapper.find('[data-testid="footer"]').findComponent(GlButton).attributes('disabled'),
- ).not.toBeDefined();
+ expect(findFooter().findComponent(GlButton).attributes('disabled')).not.toBeDefined();
});
});
describe('events', () => {
it('collapsed state element emits `toggle-collapse` event on component when clicked', () => {
- wrapper.find('[data-testid="move-collapsed"]').trigger('click');
+ findCollapsedEl().trigger('click');
expect(wrapper.emitted('toggle-collapse')).toHaveLength(1);
});
it('gl-dropdown component calls `fetchProjects` on `shown` event', () => {
- jest.spyOn(axios, 'get').mockResolvedValue({
- data: mockProjects,
- });
+ jest.spyOn(axios, 'get');
findDropdownEl().vm.$emit('shown');
expect(axios.get).toHaveBeenCalled();
});
- it('gl-dropdown component prevents dropdown body from closing on `hide` event when `projectItemClick` prop is true', () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projectItemClick: true,
- });
+ it('gl-dropdown component prevents dropdown body from closing on `hide` event when `projectItemClick` prop is true', async () => {
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+
+ findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
+ await nextTick();
findDropdownEl().vm.$emit('hide', mockEvent);
expect(mockEvent.preventDefault).toHaveBeenCalled();
- expect(wrapper.vm.projectItemClick).toBe(false);
});
it('gl-dropdown component emits `dropdown-close` event on component from `hide` event', () => {
@@ -347,38 +324,33 @@ describe('IssuableMoveDropdown', () => {
expect(wrapper.emitted('dropdown-close')).toHaveLength(1);
});
- it('close icon in dropdown header closes the dropdown when clicked', () => {
- wrapper.find('[data-testid="header"]').findComponent(GlButton).vm.$emit('click', mockEvent);
+ it('close icon in dropdown header closes the dropdown when clicked', async () => {
+ findHeader().findComponent(GlButton).vm.$emit('click', mockEvent);
- expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled();
+ await nextTick();
+ expect(hideMock).toHaveBeenCalled();
});
it('sets project for clicked gl-dropdown-item to selectedProject', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- projects: mockProjects,
- });
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+ findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
await nextTick();
- wrapper.findAllComponents(GlDropdownItem).at(0).vm.$emit('click', mockEvent);
-
- expect(wrapper.vm.selectedProject).toBe(mockProjects[0]);
+ expect(findAllDropdownItems().at(0).props('isChecked')).toBe(true);
});
it('hides dropdown and emits `move-issuable` event when move button is clicked', async () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- selectedProject: mockProjects[0],
- });
+ findDropdownEl().vm.$emit('shown');
+ await waitForPromises();
+ findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
await nextTick();
- wrapper.find('[data-testid="footer"]').findComponent(GlButton).vm.$emit('click');
+ findFooter().findComponent(GlButton).vm.$emit('click');
- expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled();
+ expect(hideMock).toHaveBeenCalled();
expect(wrapper.emitted('move-issuable')).toHaveLength(1);
expect(wrapper.emitted('move-issuable')[0]).toEqual([mockProjects[0]]);
});
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index f820e4a3504..e26f4ac8a2a 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -343,6 +343,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
end
+ # TODO: Remove this with https://gitlab.com/gitlab-org/gitlab/-/issues/334253
context 'when job filtered by job_age' do
let!(:job) do
create(:ci_build, :pending, :queued, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, queued_at: 60.seconds.ago)
@@ -355,20 +356,48 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
context 'job is queued less than job_age parameter' do
let(:job_age) { 120 }
- it 'gives 204' do
+ it 'ignores the param and picks the job' do
request_job(job_age: job_age)
- expect(response).to have_gitlab_http_status(:no_content)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(job.id)
end
end
context 'job is queued more than job_age parameter' do
let(:job_age) { 30 }
- it 'picks a job' do
+ it 'ignores the param and picks the job' do
request_job(job_age: job_age)
expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(job.id)
+ end
+ end
+
+ context 'when FF remove_job_age_from_jobs_api is disabled' do
+ before do
+ stub_feature_flags(remove_job_age_from_jobs_api: false)
+ end
+
+ context 'job is queued less than job_age parameter' do
+ let(:job_age) { 120 }
+
+ it 'gives 204' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'job is queued more than job_age parameter' do
+ let(:job_age) { 30 }
+
+ it 'picks a job' do
+ request_job(job_age: job_age)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
end
end
end