+### Approvers and Approver Group fields in Merge Request Approval API
+
+Planned removal: GitLab 16.0
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+The endpoint to get the configuration of approvals for a project returns empty arrays for `approvers` and `approval_groups`. These fields were deprecated in favor of the endpoint to [get project-level rules](https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules) for a merge request. API users are encouraged to switch to this endpoint instead. These fields will be removed from the `get configuration` endpoint in v5 of the GitLab REST API.
+
+
+
+
+
### Azure Storage Driver defaults to the correct root prefix
Planned removal: GitLab
16.0
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 1ab68b20da4..e41107370ec 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -281,6 +281,9 @@ module Gitlab
# target_column - The name of the referenced column, defaults to "id".
# on_delete - The action to perform when associated data is removed,
# defaults to "CASCADE".
+ # on_update - The action to perform when associated data is updated,
+ # defaults to nil. This is useful for multi column FKs if
+ # it's desirable to update one of the columns.
# name - The name of the foreign key.
# validate - Flag that controls whether the new foreign key will be validated after creation.
# If the flag is not set, the constraint will only be enforced for new data.
@@ -288,7 +291,8 @@ module Gitlab
# order of the ALTER TABLE. This can be useful in situations where the foreign
# key creation could deadlock with another process.
#
- def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, target_column: :id, name: nil, validate: true, reverse_lock_order: false)
+ # rubocop: disable Metrics/ParameterLists
+ def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, on_update: nil, target_column: :id, name: nil, validate: true, reverse_lock_order: false)
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
if transaction_open?
@@ -298,6 +302,7 @@ module Gitlab
options = {
column: column,
on_delete: on_delete,
+ on_update: on_update,
name: name.presence || concurrent_foreign_key_name(source, column),
primary_key: target_column
}
@@ -306,7 +311,8 @@ module Gitlab
warning_message = "Foreign key not created because it exists already " \
"(this may be due to an aborted migration or similar): " \
"source: #{source}, target: #{target}, column: #{options[:column]}, "\
- "name: #{options[:name]}, on_delete: #{options[:on_delete]}"
+ "name: #{options[:name]}, on_update: #{options[:on_update]}, "\
+ "on_delete: #{options[:on_delete]}"
Gitlab::AppLogger.warn warning_message
else
@@ -322,6 +328,7 @@ module Gitlab
ADD CONSTRAINT #{options[:name]}
FOREIGN KEY (#{multiple_columns(options[:column])})
REFERENCES #{target} (#{multiple_columns(target_column)})
+ #{on_update_statement(options[:on_update])}
#{on_delete_statement(options[:on_delete])}
NOT VALID;
EOF
@@ -343,6 +350,7 @@ module Gitlab
end
end
end
+ # rubocop: enable Metrics/ParameterLists
def validate_foreign_key(source, column, name: nil)
fk_name = name || concurrent_foreign_key_name(source, column)
@@ -1296,6 +1304,13 @@ into similar problems in the future (e.g. when new tables are created).
"ON DELETE #{on_delete.upcase}"
end
+ def on_update_statement(on_update)
+ return '' if on_update.blank?
+ return 'ON UPDATE SET NULL' if on_update == :nullify
+
+ "ON UPDATE #{on_update.upcase}"
+ end
+
def create_column_from(table, old, new, type: nil, batch_column_name: :id, type_cast_function: nil, limit: nil)
old_col = column_for(table, old)
new_type = type || old_col.type
diff --git a/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb b/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb
index d74072f1d4c..c77db02061c 100644
--- a/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb
+++ b/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder.rb
@@ -7,11 +7,11 @@ module Gitlab
class RunnerFleetPipelineSeeder
DEFAULT_JOB_COUNT = 400
- MAX_QUEUE_TIME_IN_SECONDS = 5 * 60
- PIPELINE_CREATION_RANGE_MIN_IN_MINUTES = 120
- PIPELINE_CREATION_RANGE_MAX_IN_MINUTES = 30 * 24 * 60
- PIPELINE_START_RANGE_MAX_IN_MINUTES = 60 * 60
- PIPELINE_FINISH_RANGE_MAX_IN_MINUTES = 60
+ MAX_QUEUE_TIME_IN_SECONDS = 5.minutes.to_i
+ PIPELINE_CREATION_RANGE_MIN_IN_SECONDS = 2.hours.to_i
+ PIPELINE_CREATION_RANGE_MAX_IN_SECONDS = 30.days.to_i
+ PIPELINE_START_RANGE_MAX_IN_SECONDS = 5.minutes.to_i
+ PIPELINE_FINISH_RANGE_MAX_IN_SECONDS = 1.hour.to_i
PROJECT_JOB_DISTRIBUTION = [
{ allocation: 70, job_count_default: 10 },
@@ -99,14 +99,14 @@ module Gitlab
sha = '00000000'
if ::Ci::HasStatus::ALIVE_STATUSES.include?(status) || ::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
- created_at = Random.rand(PIPELINE_CREATION_RANGE_MIN_IN_MINUTES..PIPELINE_CREATION_RANGE_MAX_IN_MINUTES)
- .minutes.ago
+ created_at = Random.rand(PIPELINE_CREATION_RANGE_MIN_IN_SECONDS..PIPELINE_CREATION_RANGE_MAX_IN_SECONDS)
+ .seconds.ago
if ::Ci::HasStatus::STARTED_STATUSES.include?(status) ||
::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
- started_at = created_at + Random.rand(1..PIPELINE_START_RANGE_MAX_IN_MINUTES).minutes
+ started_at = created_at + Random.rand(1..PIPELINE_START_RANGE_MAX_IN_SECONDS)
if ::Ci::HasStatus::COMPLETED_STATUSES.include?(status)
- finished_at = started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_MINUTES).minutes
+ finished_at = started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_SECONDS)
end
end
end
@@ -138,19 +138,15 @@ module Gitlab
started_at = pipeline.started_at
finished_at = pipeline.finished_at
- max_job_duration =
- if finished_at
- [MAX_QUEUE_TIME_IN_SECONDS, finished_at - started_at].min
- else
- Random.rand(1..5).seconds
- end
+ max_job_duration = [MAX_QUEUE_TIME_IN_SECONDS, 5, 2].sample
+ max_job_duration = (finished_at - started_at) if finished_at && max_job_duration > finished_at - started_at
job_created_at = pipeline.created_at
- job_started_at = started_at + Random.rand(1..max_job_duration) if started_at
+ job_started_at = job_created_at + Random.rand(1..max_job_duration) if started_at
if finished_at
job_finished_at = Random.rand(job_started_at..finished_at)
elsif job_status == 'running'
- job_finished_at = job_started_at + Random.rand(1..PIPELINE_FINISH_RANGE_MAX_IN_MINUTES).minutes
+ job_finished_at = job_started_at + Random.rand(1 * 60..PIPELINE_FINISH_RANGE_MAX_IN_SECONDS)
end
# Do not use the first 2 runner tags ('runner-fleet', "#{registration_prefix}runner").
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 83d453179be..f35fb3e14d6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2726,12 +2726,21 @@ msgstr ""
msgid "AdminDashboard|Error loading the statistics. Please try again"
msgstr ""
+msgid "AdminEmail|An error occurred fetching the groups and projects. Please refresh the page to try again."
+msgstr ""
+
msgid "AdminEmail|Body"
msgstr ""
msgid "AdminEmail|Body is required."
msgstr ""
+msgid "AdminEmail|Loading groups and projects."
+msgstr ""
+
+msgid "AdminEmail|No groups or projects found."
+msgstr ""
+
msgid "AdminEmail|Recipient group or project"
msgstr ""
@@ -5396,12 +5405,21 @@ msgstr ""
msgid "Artifacts|Delete artifact"
msgstr ""
+msgid "Artifacts|Help us improve this page"
+msgstr ""
+
+msgid "Artifacts|Take a quick survey"
+msgstr ""
+
msgid "Artifacts|This artifact will be permanently deleted. Any reports generated from this artifact will be empty."
msgstr ""
msgid "Artifacts|Total artifacts size"
msgstr ""
+msgid "Artifacts|We want you to be able to use this page to easily manage your CI/CD job artifacts. We are working to improve this experience and would appreciate any feedback you have about the improvements we are making."
+msgstr ""
+
msgid "As we continue to build more features for SAST, we'd love your feedback on the SAST configuration feature in %{linkStart}this issue%{linkEnd}."
msgstr ""
diff --git a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
index 3df6e988bfa..ad1abe6e5e9 100644
--- a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'User', :requires_admin, product_group: :workspace do
+ describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:sub_group) do
diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb
index ff036c18671..08e1b7a1e0c 100644
--- a/qa/qa/specs/features/api/1_manage/users_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/users_spec.rb
@@ -4,7 +4,7 @@ require 'airborne'
module QA
RSpec.describe 'Manage' do
- describe 'Users API', :reliable, product_group: :workspace do
+ describe 'Users API', :reliable, product_group: :organization do
let(:api_client) { Runtime::API::Client.new(:gitlab) }
let(:request) { Runtime::API::Request.new(api_client, '/users') }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
index 8fe4dc192bd..b2ccacfd142 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Subgroup transfer', product_group: :workspace do
+ describe 'Subgroup transfer', product_group: :organization do
let(:source_group) do
Resource::Group.fabricate_via_api! do |group|
group.path = "source-group-for-transfer_#{SecureRandom.hex(8)}"
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index d684eabe644..ef05f4cfecf 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Project transfer between groups', :reliable, product_group: :workspace do
+ describe 'Project transfer between groups', :reliable, product_group: :organization do
let(:source_group) do
Resource::Group.fabricate_via_api! do |group|
group.path = "source-group-#{SecureRandom.hex(8)}"
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 3f461e9247f..50e86b4d555 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :reliable, product_group: :workspace do
+ RSpec.describe 'Manage', :reliable, product_group: :organization do
describe 'Add project member' do
it 'user adds project member', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347887' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
index b251b3075dd..0a643d1e33b 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Create project badge', :reliable, product_group: :workspace do
+ describe 'Create project badge', :reliable, product_group: :organization do
let(:badge_name) { "project-badge-#{SecureRandom.hex(8)}" }
let(:expected_badge_link_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}" }
let(:expected_badge_image_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}/badges/main/pipeline.svg" }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 686cc8fe11e..c9e90cce84c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :smoke, product_group: :workspace do
+ RSpec.describe 'Manage', :smoke, product_group: :organization do
describe 'Project' do
shared_examples 'successful project creation' do
it 'creates a new project' do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
index 2abbb6ca73c..e609dfb9197 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', product_group: :workspace do
+ RSpec.describe 'Manage', product_group: :organization do
shared_examples 'loads all images' do |admin|
let(:api_client) { Runtime::API::Client.as_admin }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
index 164f86bffce..4479f2ebfab 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Invite group', :reliable, product_group: :workspace do
+ describe 'Invite group', :reliable, product_group: :organization do
shared_examples 'invites group to project' do
it 'verifies group is added and members can access project with correct access level' do
Page::Project::Menu.perform(&:click_members)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
index 98a08dd0d9a..59774ed7c49 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Project owner permissions', :reliable, product_group: :workspace do
+ describe 'Project owner permissions', :reliable, product_group: :organization do
let!(:owner) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index 33ca5f6009c..95305b7a4aa 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Project activity', :reliable, product_group: :workspace do
+ describe 'Project activity', :reliable, product_group: :organization do
it 'user creates an event in the activity page upon Git push',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347879' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
index b9b82baa6f1..94b383a746d 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'User', :requires_admin, product_group: :workspace do
+ describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let(:followed_user_api_client) { Runtime::API::Client.new(:gitlab, user: followed_user) }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
index a72d1bfb4ba..7caa6677787 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'User', :requires_admin, :reliable, product_group: :workspace do
+ describe 'User', :requires_admin, :reliable, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:user) do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb
index b7585f00630..9bb08cb66bc 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'User', :requires_admin, product_group: :workspace do
+ describe 'User', :requires_admin, product_group: :organization do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:sub_group) do
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index fd844808d81..ec63bad22b5 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -102,7 +102,7 @@ RSpec.describe Projects::DeployKeysController do
it 'shows an alert with the validations errors' do
post :create, params: create_params(nil)
- expect(flash[:alert]).to eq("Title can't be blank, Deploy keys projects deploy key title can't be blank")
+ expect(flash[:alert]).to eq("Title can't be blank")
end
end
@@ -126,8 +126,7 @@ RSpec.describe Projects::DeployKeysController do
it 'shows an alert with the validations errors' do
post :create, params: create_params
- expect(flash[:alert]).to eq("Fingerprint sha256 has already been taken, " \
- "Deploy keys projects deploy key fingerprint sha256 has already been taken")
+ expect(flash[:alert]).to eq("Fingerprint sha256 has already been taken")
end
end
end
diff --git a/spec/frontend/artifacts/components/feedback_banner_spec.js b/spec/frontend/artifacts/components/feedback_banner_spec.js
new file mode 100644
index 00000000000..3421486020a
--- /dev/null
+++ b/spec/frontend/artifacts/components/feedback_banner_spec.js
@@ -0,0 +1,63 @@
+import { GlBanner } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import FeedbackBanner from '~/artifacts/components/feedback_banner.vue';
+import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
+import {
+ I18N_FEEDBACK_BANNER_TITLE,
+ I18N_FEEDBACK_BANNER_BUTTON,
+ FEEDBACK_URL,
+} from '~/artifacts/constants';
+
+const mockBannerImagePath = 'banner/image/path';
+
+describe('Artifacts management feedback banner', () => {
+ let wrapper;
+ let userCalloutDismissSpy;
+
+ const findBanner = () => wrapper.findComponent(GlBanner);
+
+ const createComponent = ({ shouldShowCallout = true } = {}) => {
+ userCalloutDismissSpy = jest.fn();
+
+ wrapper = shallowMount(FeedbackBanner, {
+ provide: {
+ artifactsManagementFeedbackImagePath: mockBannerImagePath,
+ },
+ stubs: {
+ UserCalloutDismisser: makeMockUserCalloutDismisser({
+ dismiss: userCalloutDismissSpy,
+ shouldShowCallout,
+ }),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('is displayed with the correct props', () => {
+ createComponent();
+
+ expect(findBanner().props()).toMatchObject({
+ title: I18N_FEEDBACK_BANNER_TITLE,
+ buttonText: I18N_FEEDBACK_BANNER_BUTTON,
+ buttonLink: FEEDBACK_URL,
+ svgPath: mockBannerImagePath,
+ });
+ });
+
+ it('dismisses the callout when closed', () => {
+ createComponent();
+
+ findBanner().vm.$emit('close');
+
+ expect(userCalloutDismissSpy).toHaveBeenCalled();
+ });
+
+ it('is not displayed once it has been dismissed', () => {
+ createComponent({ shouldShowCallout: false });
+
+ expect(findBanner().exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
index 131b4b99bb2..8044608ccb3 100644
--- a/spec/frontend/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
@@ -5,6 +5,7 @@ import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/que
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import waitForPromises from 'helpers/wait_for_promises';
import JobArtifactsTable from '~/artifacts/components/job_artifacts_table.vue';
+import FeedbackBanner from '~/artifacts/components/feedback_banner.vue';
import ArtifactsTableRowDetails from '~/artifacts/components/artifacts_table_row_details.vue';
import ArtifactDeleteModal from '~/artifacts/components/artifact_delete_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -23,6 +24,8 @@ describe('JobArtifactsTable component', () => {
let wrapper;
let requestHandlers;
+ const findBanner = () => wrapper.findComponent(FeedbackBanner);
+
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(GlTable);
const findDetailsRows = () => wrapper.findAllComponents(ArtifactsTableRowDetails);
@@ -85,7 +88,10 @@ describe('JobArtifactsTable component', () => {
apolloProvider: createMockApollo([
[getJobArtifactsQuery, requestHandlers.getJobArtifactsQuery],
]),
- provide: { projectPath: 'project/path' },
+ provide: {
+ projectPath: 'project/path',
+ artifactsManagementFeedbackImagePath: 'banner/image/path',
+ },
data() {
return data;
},
@@ -96,6 +102,12 @@ describe('JobArtifactsTable component', () => {
wrapper.destroy();
});
+ it('renders feedback banner', () => {
+ createComponent();
+
+ expect(findBanner().exists()).toBe(true);
+ });
+
it('when loading, shows a loading state', () => {
createComponent();
diff --git a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
index 896e9356b7d..8413b8463c1 100644
--- a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
+++ b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
@@ -1,4 +1,4 @@
-import { mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import mockData from 'test_fixtures/issues/related_merge_requests.json';
import axios from '~/lib/utils/axios_utils';
@@ -20,7 +20,7 @@ describe('RelatedMergeRequests', () => {
mock = new MockAdapter(axios);
mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });
- wrapper = mount(RelatedMergeRequests, {
+ wrapper = shallowMount(RelatedMergeRequests, {
store: createStore(),
propsData: {
endpoint: API_ENDPOINT,
@@ -42,18 +42,14 @@ describe('RelatedMergeRequests', () => {
const assignees = [{ name: 'foo' }, { name: 'bar' }];
describe('when there is assignees array', () => {
- // https://gitlab.com/gitlab-org/gitlab/-/issues/387756
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should return assignees array', () => {
+ it('should return assignees array', () => {
const mr = { assignees };
expect(wrapper.vm.getAssignees(mr)).toEqual(assignees);
});
});
- // https://gitlab.com/gitlab-org/gitlab/-/issues/387789
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should return an array with single assingee', () => {
+ it('should return an array with single assignee', () => {
const mr = { assignee: assignees[0] };
expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]);
diff --git a/spec/frontend/read_more_spec.js b/spec/frontend/read_more_spec.js
index 80d7c941660..9eddc50d50a 100644
--- a/spec/frontend/read_more_spec.js
+++ b/spec/frontend/read_more_spec.js
@@ -1,21 +1,23 @@
-import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { loadHTMLFixture, resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
const fixtureName = 'projects/overview.html';
- beforeEach(() => {
- loadHTMLFixture(fixtureName);
- });
+ const findTrigger = () => document.querySelector('.js-read-more-trigger');
afterEach(() => {
resetHTMLFixture();
});
describe('expands target element', () => {
+ beforeEach(() => {
+ loadHTMLFixture(fixtureName);
+ });
+
it('adds "is-expanded" class to target element', () => {
const target = document.querySelector('.read-more-container');
- const trigger = document.querySelector('.js-read-more-trigger');
+ const trigger = findTrigger();
initReadMore();
trigger.click();
@@ -23,4 +25,25 @@ describe('Read more click-to-expand functionality', () => {
expect(target.classList.contains('is-expanded')).toEqual(true);
});
});
+
+ describe('given click on nested element', () => {
+ beforeEach(() => {
+ setHTMLFixture(`
+
Target
+
+ `);
+
+ const trigger = findTrigger();
+ const nestedElement = trigger.firstElementChild;
+ initReadMore();
+
+ nestedElement.click();
+ });
+
+ it('removes the trigger element', async () => {
+ expect(findTrigger()).toBe(null);
+ });
+ });
});
diff --git a/spec/frontend/search/store/utils_spec.js b/spec/frontend/search/store/utils_spec.js
index 20d764190b1..487ed7bfe03 100644
--- a/spec/frontend/search/store/utils_spec.js
+++ b/spec/frontend/search/store/utils_spec.js
@@ -5,7 +5,10 @@ import {
setFrequentItemToLS,
mergeById,
isSidebarDirty,
+ formatSearchResultCount,
+ getAggregationsUrl,
} from '~/search/store/utils';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import {
MOCK_LS_KEY,
MOCK_GROUPS,
@@ -241,4 +244,23 @@ describe('Global Search Store Utils', () => {
});
});
});
+ describe('formatSearchResultCount', () => {
+ it('returns zero as string if no count is provided', () => {
+ expect(formatSearchResultCount()).toStrictEqual('0');
+ });
+ it('returns 10K string for 10000 integer', () => {
+ expect(formatSearchResultCount(10000)).toStrictEqual('10K');
+ });
+ it('returns 23K string for "23,000+" string', () => {
+ expect(formatSearchResultCount('23,000+')).toStrictEqual('23K');
+ });
+ });
+
+ describe('getAggregationsUrl', () => {
+ useMockLocationHelper();
+ it('returns zero as string if no count is provided', () => {
+ const testURL = window.location.href;
+ expect(getAggregationsUrl()).toStrictEqual(`${testURL}search/aggregations`);
+ });
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
index c253dc63f23..81f266d8070 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
@@ -42,8 +42,8 @@ describe('Merge Request Collapsible Extension', () => {
expect(wrapper.find('[data-testid="collapsed-header"]').text()).toBe('hello there');
});
- it('renders chevron-lg-right icon', () => {
- expect(findIcon().props('name')).toBe('chevron-lg-right');
+ it('renders chevron-right icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-right');
});
describe('onClick', () => {
@@ -60,8 +60,8 @@ describe('Merge Request Collapsible Extension', () => {
expect(findTitle().text()).toBe('Collapse');
});
- it('renders chevron-lg-down icon', () => {
- expect(findIcon().props('name')).toBe('chevron-lg-down');
+ it('renders chevron-down icon', () => {
+ expect(findIcon().props('name')).toBe('chevron-down');
});
});
});
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index a1433b9a1d2..12fa115cc4e 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -743,6 +743,75 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ context 'ON UPDATE statements' do
+ context 'on_update: :nullify' do
+ it 'appends ON UPDATE SET NULL statement' do
+ expect(model).to receive(:with_lock_retries).and_call_original
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:statement_timeout_disabled?).and_return(false)
+ expect(model).to receive(:execute).with(/SET statement_timeout TO/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
+
+ expect(model).to receive(:execute).with(/ON UPDATE SET NULL/)
+
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_update: :nullify)
+ end
+ end
+
+ context 'on_update: :cascade' do
+ it 'appends ON UPDATE CASCADE statement' do
+ expect(model).to receive(:with_lock_retries).and_call_original
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:statement_timeout_disabled?).and_return(false)
+ expect(model).to receive(:execute).with(/SET statement_timeout TO/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
+
+ expect(model).to receive(:execute).with(/ON UPDATE CASCADE/)
+
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_update: :cascade)
+ end
+ end
+
+ context 'on_update: nil' do
+ it 'appends no ON UPDATE statement' do
+ expect(model).to receive(:with_lock_retries).and_call_original
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:statement_timeout_disabled?).and_return(false)
+ expect(model).to receive(:execute).with(/SET statement_timeout TO/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
+
+ expect(model).not_to receive(:execute).with(/ON UPDATE/)
+
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_update: nil)
+ end
+ end
+
+ context 'when on_update is not provided' do
+ it 'appends no ON UPDATE statement' do
+ expect(model).to receive(:with_lock_retries).and_call_original
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:statement_timeout_disabled?).and_return(false)
+ expect(model).to receive(:execute).with(/SET statement_timeout TO/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).ordered.with(/RESET statement_timeout/)
+
+ expect(model).not_to receive(:execute).with(/ON UPDATE/)
+
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id)
+ end
+ end
+ end
+
context 'when no custom key name is supplied' do
it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:with_lock_retries).and_call_original
@@ -760,6 +829,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
name = model.concurrent_foreign_key_name(:projects, :user_id)
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
column: :user_id,
+ on_update: nil,
on_delete: :cascade,
name: name,
primary_key: :id).and_return(true)
@@ -792,6 +862,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:foreign_key_exists?).with(:projects, :users,
name: :foo,
primary_key: :id,
+ on_update: nil,
on_delete: :cascade,
column: :user_id).and_return(true)
@@ -861,6 +932,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
"ADD CONSTRAINT fk_multiple_columns\n" \
"FOREIGN KEY \(partition_number, user_id\)\n" \
"REFERENCES users \(partition_number, id\)\n" \
+ "ON UPDATE CASCADE\n" \
"ON DELETE CASCADE\n" \
"NOT VALID;\n"
)
@@ -871,7 +943,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
column: [:partition_number, :user_id],
target_column: [:partition_number, :id],
validate: false,
- name: :fk_multiple_columns
+ name: :fk_multiple_columns,
+ on_update: :cascade
)
end
@@ -883,6 +956,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
{
column: [:partition_number, :user_id],
name: :fk_multiple_columns,
+ on_update: :cascade,
on_delete: :cascade,
primary_key: [:partition_number, :id]
}
@@ -898,6 +972,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
:users,
column: [:partition_number, :user_id],
target_column: [:partition_number, :id],
+ on_update: :cascade,
validate: false,
name: :fk_multiple_columns
)
diff --git a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
index 32510cd6e1e..2862bcc9719 100644
--- a/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
+++ b/spec/lib/gitlab/seeders/ci/runner/runner_fleet_pipeline_seeder_spec.rb
@@ -32,6 +32,9 @@ RSpec.describe ::Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder, feature
expect { seeder.seed }.to change { Ci::Build.count }.by(job_count)
.and change { Ci::Pipeline.count }.by(4)
+ expect(Ci::Pipeline.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes)
+ expect(Ci::Build.where.not(started_at: nil).map(&:queued_duration)).to all(be < 5.minutes)
+
projects_to_runners.first(3).each do |project|
expect(Ci::Build.where(runner_id: project[:runner_ids])).not_to be_empty
end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index 6f684eceaec..ef79ba28d5d 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Pages::LookupPath do
+RSpec.describe Pages::LookupPath, feature_category: :pages do
let(:project) { create(:project, :pages_private, pages_https_only: true) }
subject(:lookup_path) { described_class.new(project) }
@@ -126,14 +126,18 @@ RSpec.describe Pages::LookupPath do
describe '#prefix' do
it 'returns "/" for pages group root projects' do
- project = instance_double(Project, pages_group_root?: true)
+ project = instance_double(Project, pages_namespace_url: "namespace.test", pages_url: "namespace.test")
lookup_path = described_class.new(project, trim_prefix: 'mygroup')
expect(lookup_path.prefix).to eq('/')
end
it 'returns the project full path with the provided prefix removed' do
- project = instance_double(Project, pages_group_root?: false, full_path: 'mygroup/myproject')
+ project = instance_double(
+ Project,
+ pages_namespace_url: "namespace.test",
+ pages_url: "namespace.other",
+ full_path: 'mygroup/myproject')
lookup_path = described_class.new(project, trim_prefix: 'mygroup')
expect(lookup_path.prefix).to eq('/myproject/')
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e8adb406a73..9cc3954ea37 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2596,16 +2596,28 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#pages_url' do
+ describe '#pages_url', feature_category: :pages do
let(:group) { create(:group, name: group_name) }
- let(:project) { create(:project, namespace: group, name: project_name) }
+
+ let(:project_path) { project_name.downcase }
+ let(:project) do
+ create(
+ :project,
+ namespace: group,
+ name: project_name,
+ path: project_path)
+ end
+
let(:domain) { 'Example.com' }
+ let(:port) { nil }
subject { project.pages_url }
before do
allow(Settings.pages).to receive(:host).and_return(domain)
- allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
+ allow(Gitlab.config.pages)
+ .to receive(:url)
+ .and_return(['http://example.com', port].compact.join(':'))
end
context 'group page' do
@@ -2615,9 +2627,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to eq("http://group.example.com") }
context 'mixed case path' do
- before do
- project.update!(path: 'Group.example.com')
- end
+ let(:project_path) { 'Group.example.com' }
it { is_expected.to eq("http://group.example.com") }
end
@@ -2630,22 +2640,88 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to eq("http://group.example.com/project") }
context 'mixed case path' do
- before do
- project.update!(path: 'Project')
- end
+ let(:project_path) { 'Project' }
it { is_expected.to eq("http://group.example.com/Project") }
end
end
+
+ context 'when there is an explicit port' do
+ let(:port) { 3000 }
+
+ context 'when not in dev mode' do
+ before do
+ stub_rails_env('production')
+ end
+
+ context 'group page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq('http://group.example.com:3000/group.example.com') }
+
+ context 'mixed case path' do
+ let(:project_path) { 'Group.example.com' }
+
+ it { is_expected.to eq('http://group.example.com:3000/Group.example.com') }
+ end
+ end
+
+ context 'project page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com:3000/project") }
+
+ context 'mixed case path' do
+ let(:project_path) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com:3000/Project") }
+ end
+ end
+ end
+
+ context 'when in dev mode' do
+ before do
+ stub_rails_env('development')
+ end
+
+ context 'group page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'group.example.com' }
+
+ it { is_expected.to eq('http://group.example.com:3000') }
+
+ context 'mixed case path' do
+ let(:project_path) { 'Group.example.com' }
+
+ it { is_expected.to eq('http://group.example.com:3000') }
+ end
+ end
+
+ context 'project page' do
+ let(:group_name) { 'Group' }
+ let(:project_name) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com:3000/project") }
+
+ context 'mixed case path' do
+ let(:project_path) { 'Project' }
+
+ it { is_expected.to eq("http://group.example.com:3000/Project") }
+ end
+ end
+ end
+ end
end
- describe '#pages_group_url' do
+ describe '#pages_namespace_url', feature_category: :pages do
let(:group) { create(:group, name: group_name) }
let(:project) { create(:project, namespace: group, name: project_name) }
let(:domain) { 'Example.com' }
let(:port) { 1234 }
- subject { project.pages_group_url }
+ subject { project.pages_namespace_url }
before do
allow(Settings.pages).to receive(:host).and_return(domain)
@@ -6992,21 +7068,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#pages_group_root?' do
- it 'returns returns true if pages_url is same as pages_group_url' do
- project = build(:project)
- expect(project).to receive(:pages_url).and_return(project.pages_group_url)
-
- expect(project.pages_group_root?).to eq(true)
- end
-
- it 'returns returns false if pages_url is different than pages_group_url' do
- project = build(:project)
-
- expect(project.pages_group_root?).to eq(false)
- end
- end
-
describe '#closest_setting' do
shared_examples_for 'fetching closest setting' do
let!(:namespace) { create(:namespace) }
diff --git a/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb
index 5a7852fc32f..9a74f5ca07a 100644
--- a/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb
+++ b/spec/services/bulk_imports/create_pipeline_trackers_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe BulkImports::CreatePipelineTrackersService do
+RSpec.describe BulkImports::CreatePipelineTrackersService, feature_category: :importers do
describe '#execute!' do
context 'when entity is group' do
it 'creates trackers for group entity' do
diff --git a/spec/services/packages/conan/search_service_spec.rb b/spec/services/packages/conan/search_service_spec.rb
index 55dcdfe646d..9e8be164d8c 100644
--- a/spec/services/packages/conan/search_service_spec.rb
+++ b/spec/services/packages/conan/search_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Packages::Conan::SearchService do
+RSpec.describe Packages::Conan::SearchService, feature_category: :package_registry do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 608e9e08f87..f85a8eda7ee 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Projects::CreateService, '#execute' do
+RSpec.describe Projects::CreateService, '#execute', feature_category: :projects do
include ExternalAuthorizationServiceHelpers
let(:user) { create :user }