diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-21 09:09:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-21 09:09:06 +0300 |
commit | 7e6efee3b34f52a62360cb4a50f2e77cb67cf769 (patch) | |
tree | 6bfe2f6f8fc8f147269ab5020c42afad06f62785 | |
parent | 22400f4dd0bd5503b3c6e4914d8dd6e1167b6c98 (diff) |
Add latest changes from gitlab-org/gitlab@master
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 |