Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-07-16 21:28:27 +00:00
parent 8263de2834
commit 76f0739381
46 changed files with 448 additions and 122 deletions

View File

@ -17,7 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
before_action :authorize_read_group_member!, only: :index
before_action only: [:index] do
push_frontend_feature_flag(:bulk_import_user_mapping, current_user)
push_frontend_feature_flag(:importer_user_mapping, current_user)
push_frontend_feature_flag(:service_accounts_crud, @group)
push_frontend_feature_flag(:webui_members_inherited_users, current_user)
end

View File

@ -56,7 +56,7 @@ module Import
end
def check_feature_flag!
not_found unless Feature.enabled?(:bulk_import_user_mapping, current_user)
not_found unless Feature.enabled?(:importer_user_mapping, current_user)
end
# TODO: This is a placeholder for the proper UI to be provided

View File

@ -11,7 +11,10 @@ module Import
def execute
return Import::SourceUser.none unless authorized?
namespace.import_source_users
collection = namespace.import_source_users
collection = by_statuses(collection)
collection = by_search(collection)
sort(collection)
end
private
@ -21,5 +24,21 @@ module Import
def authorized?
Ability.allowed?(current_user, :admin_namespace, namespace)
end
def by_statuses(collection)
return collection unless params[:statuses].present?
collection.by_statuses(params[:statuses])
end
def by_search(collection)
return collection unless params[:search].present?
collection.search(params[:search])
end
def sort(collection)
collection.sort_by_attribute(params[:sort] || :source_name_asc)
end
end
end

View File

@ -18,8 +18,8 @@ module Mutations
authorize :admin_import_source_user
def resolve(args)
if Feature.disabled?(:bulk_import_user_mapping, current_user)
raise_resource_not_available_error! '`bulk_import_user_mapping` feature flag is disabled.'
if Feature.disabled?(:importer_user_mapping, current_user)
raise_resource_not_available_error! '`importer_user_mapping` feature flag is disabled.'
end
import_source_user = authorized_find!(id: args[:id])

View File

@ -18,8 +18,8 @@ module Mutations
authorize :admin_import_source_user
def resolve(args)
if Feature.disabled?(:bulk_import_user_mapping, current_user)
raise_resource_not_available_error! '`bulk_import_user_mapping` feature flag is disabled.'
if Feature.disabled?(:importer_user_mapping, current_user)
raise_resource_not_available_error! '`importer_user_mapping` feature flag is disabled.'
end
import_source_user = authorized_find!(id: args[:id])

View File

@ -23,8 +23,8 @@ module Mutations
authorize :admin_import_source_user
def resolve(args)
if Feature.disabled?(:bulk_import_user_mapping, current_user)
raise_resource_not_available_error! '`bulk_import_user_mapping` feature flag is disabled.'
if Feature.disabled?(:importer_user_mapping, current_user)
raise_resource_not_available_error! '`importer_user_mapping` feature flag is disabled.'
end
import_source_user = authorized_find!(id: args[:id])

View File

@ -18,8 +18,8 @@ module Mutations
authorize :admin_import_source_user
def resolve(args)
if Feature.disabled?(:bulk_import_user_mapping, current_user)
raise_resource_not_available_error! '`bulk_import_user_mapping` feature flag is disabled.'
if Feature.disabled?(:importer_user_mapping, current_user)
raise_resource_not_available_error! '`importer_user_mapping` feature flag is disabled.'
end
import_source_user = authorized_find!(id: args[:id])

View File

@ -11,10 +11,23 @@ module Resolvers
type Types::Import::SourceUserType.connection_type, null: true
argument :statuses, [::Types::Import::SourceUserStatusEnum],
required: false,
description: 'Filter mapping of users on source instance to users on destination instance by status.'
argument :search, GraphQL::Types::String,
required: false,
description: 'Query to search mappings by name or username of users on source instance.'
argument :sort, Types::Import::SourceUserSortEnum,
description: 'Sort mapping of users on source instance to users on destination instance by the criteria.',
required: false,
default_value: :source_name_asc
alias_method :namespace, :object
def resolve_with_lookahead(**args)
return [] if Feature.disabled?(:bulk_import_user_mapping, current_user)
return [] if Feature.disabled?(:importer_user_mapping, current_user)
apply_lookahead(::Import::SourceUsersFinder.new(namespace, context[:current_user], args).execute)
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Types
module Import
class SourceUserSortEnum < BaseEnum
graphql_name 'SourceUserSort'
description 'Values for sorting the mapping of users on source instance to users on destination instance.'
value 'STATUS_ASC', 'Status of the mapping by ascending order.', value: :status_asc
value 'STATUS_DESC', 'Status of the mapping by descending order.', value: :status_desc
value 'SOURCE_NAME_ASC', 'Instance source name by ascending order.', value: :source_name_asc
value 'SOURCE_NAME_DESC', 'Instance source name by descending order.', value: :source_name_desc
end
end
end

View File

@ -2,8 +2,17 @@
module Import
class SourceUser < ApplicationRecord
include Gitlab::SQL::Pattern
self.table_name = 'import_source_users'
SORT_ORDERS = {
source_name_asc: { order_by: 'source_name', sort: 'asc' },
source_name_desc: { order_by: 'source_name', sort: 'desc' },
status_asc: { order_by: 'status', sort: 'asc' },
status_desc: { order_by: 'status', sort: 'desc' }
}.freeze
belongs_to :placeholder_user, class_name: 'User', optional: true
belongs_to :reassign_to_user, class_name: 'User', optional: true
belongs_to :reassigned_by_user, class_name: 'User', optional: true
@ -12,6 +21,7 @@ module Import
validates :namespace_id, :import_type, :source_hostname, :source_user_identifier, :status, presence: true
scope :for_namespace, ->(namespace_id) { where(namespace_id: namespace_id) }
scope :by_statuses, ->(statuses) { where(status: statuses) }
state_machine :status, initial: :pending_reassignment do
state :pending_reassignment, value: 0
@ -55,15 +65,29 @@ module Import
end
end
def self.find_source_user(source_user_identifier:, namespace:, source_hostname:, import_type:)
return unless namespace
class << self
def find_source_user(source_user_identifier:, namespace:, source_hostname:, import_type:)
return unless namespace
find_by(
source_user_identifier: source_user_identifier,
namespace_id: namespace.id,
source_hostname: source_hostname,
import_type: import_type
)
find_by(
source_user_identifier: source_user_identifier,
namespace_id: namespace.id,
source_hostname: source_hostname,
import_type: import_type
)
end
def search(query)
return none unless query.is_a?(String)
fuzzy_search(query, [:source_name, :source_username])
end
def sort_by_attribute(method)
sort_order = SORT_ORDERS[method&.to_sym] || SORT_ORDERS[:source_name_asc]
reorder(sort_order[:order_by] => sort_order[:sort])
end
end
def reassignable_status?

View File

@ -2,8 +2,8 @@
module Import
class SourceUserPolicy < ::BasePolicy
condition(:admin_source_user_namespace) { can?(:admin_namespace, @subject.namespace) }
desc "User can administrate namespace"
condition(:admin_source_user_namespace) { can?(:admin_namespace, @subject.namespace) }
rule { admin_source_user_namespace }.policy do
enable :admin_import_source_user

View File

@ -5,6 +5,8 @@ module Import
class BaseService
private
attr_reader :import_source_user, :current_user
def error_invalid_permissions
ServiceResponse.error(
message: s_('Import|You have insufficient permissions to update the import source user'),
@ -19,6 +21,10 @@ module Import
payload: import_source_user
)
end
def send_user_reassign_email
Notify.import_source_user_reassign(import_source_user.id).deliver_now
end
end
end
end

View File

@ -21,8 +21,6 @@ module Import
private
attr_reader :import_source_user, :current_user, :params
def cancel_reassignment
import_source_user.reassign_to_user = nil
import_source_user.reassigned_by_user = nil

View File

@ -21,8 +21,6 @@ module Import
private
attr_reader :import_source_user, :current_user, :params
def keep_as_placeholder
import_source_user.reassigned_by_user = current_user
import_source_user.keep_as_placeholder

View File

@ -15,6 +15,8 @@ module Import
return error_invalid_assignee unless valid_assignee?(assignee_user)
if reassign_user
send_user_reassign_email
ServiceResponse.success(payload: import_source_user)
else
ServiceResponse.error(payload: import_source_user, message: import_source_user.errors.full_messages)
@ -23,7 +25,7 @@ module Import
private
attr_reader :import_source_user, :current_user, :assignee_user
attr_reader :assignee_user
def reassign_user
import_source_user.reassign_to_user = assignee_user

View File

@ -12,14 +12,10 @@ module Import
return error_invalid_permissions unless current_user.can?(:admin_import_source_user, import_source_user)
return error_invalid_status unless import_source_user.awaiting_approval?
# Notifier will be added in https://gitlab.com/gitlab-org/gitlab/-/issues/455912
send_user_reassign_email
ServiceResponse.success(payload: import_source_user)
end
private
attr_reader :import_source_user, :current_user
end
end
end

View File

@ -38,7 +38,7 @@ module BulkImports
log_extra_metadata_on_done(:batched, true)
BatchedRelationExportService.new(user, portable, relation, jid).execute
elsif config.user_contributions_relation?(relation)
return if Feature.disabled?(:bulk_import_user_mapping, user)
return if Feature.disabled?(:importer_user_mapping, user)
log_extra_metadata_on_done(:batched, false)
UserContributionsExportWorker.perform_async(portable_id, portable_class, user_id)

View File

@ -11,7 +11,7 @@ module Import
loggable_arguments 0, 1
def perform(import_source, import_uid, params = {})
return unless Feature.enabled?(:bulk_import_user_mapping, User.actor_from_id(params['current_user_id']))
return unless Feature.enabled?(:importer_user_mapping, User.actor_from_id(params['current_user_id']))
::Import::PlaceholderReferences::LoadService.new(
import_source: import_source,

View File

@ -1,8 +1,8 @@
---
name: bulk_import_user_mapping
name: importer_user_mapping
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/12378
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149735
rollout_issue_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472735
milestone: '17.0'
group: group::import and integrate
type: wip

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class AddIndexesToImportSourceUsers < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.3'
STATUS_INDEX = 'index_import_source_users_on_namespace_id_and_status'
NAMESPACE_INDEX = 'index_import_source_users_on_namespace_id'
def up
add_concurrent_index :import_source_users, [:namespace_id, :status], name: STATUS_INDEX
remove_concurrent_index_by_name :import_source_users, name: NAMESPACE_INDEX
end
def down
add_concurrent_index :import_source_users, [:namespace_id], name: NAMESPACE_INDEX
remove_concurrent_index_by_name :import_source_users, name: STATUS_INDEX
end
end

View File

@ -0,0 +1 @@
434b51c7e1faea1ce5406ed78f97235247580053c4132dd5d0e325a3a2e7034e

View File

@ -27536,7 +27536,7 @@ CREATE INDEX index_import_source_user_placeholder_references_on_namespace_id ON
CREATE INDEX index_import_source_user_placeholder_references_on_source_user_ ON import_source_user_placeholder_references USING btree (source_user_id);
CREATE INDEX index_import_source_users_on_namespace_id ON import_source_users USING btree (namespace_id);
CREATE INDEX index_import_source_users_on_namespace_id_and_status ON import_source_users USING btree (namespace_id, status);
CREATE INDEX index_import_source_users_on_placeholder_user_id ON import_source_users USING btree (placeholder_user_id);

View File

@ -22044,7 +22044,6 @@ GPG signature for a signed commit.
| <a id="groupgooglecloudloggingconfigurations"></a>`googleCloudLoggingConfigurations` | [`GoogleCloudLoggingConfigurationTypeConnection`](#googlecloudloggingconfigurationtypeconnection) | Google Cloud logging configurations that receive audit events belonging to the group. (see [Connections](#connections)) |
| <a id="groupgroupmemberscount"></a>`groupMembersCount` | [`Int!`](#int) | Count of direct members of this group. |
| <a id="groupid"></a>`id` | [`ID!`](#id) | ID of the namespace. |
| <a id="groupimportsourceusers"></a>`importSourceUsers` **{warning-solid}** | [`ImportSourceUserConnection`](#importsourceuserconnection) | **Introduced** in GitLab 17.2. **Status**: Experiment. Import source users of the namespace. This field can only be resolved for one namespace in any single request. |
| <a id="groupisadjourneddeletionenabled"></a>`isAdjournedDeletionEnabled` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 16.11. **Status**: Experiment. Indicates if delayed group deletion is enabled. |
| <a id="grouplfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. |
| <a id="grouplockduofeaturesenabled"></a>`lockDuoFeaturesEnabled` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in GitLab 16.10. **Status**: Experiment. Indicates if the GitLab Duo features enabled setting is enforced for all subgroups. |
@ -22607,6 +22606,28 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupgroupmemberssearch"></a>`search` | [`String`](#string) | Search query. |
| <a id="groupgroupmemberssort"></a>`sort` | [`MemberSort`](#membersort) | sort query. |
##### `Group.importSourceUsers`
Import source users of the namespace. This field can only be resolved for one namespace in any single request.
DETAILS:
**Introduced** in GitLab 17.2.
**Status**: Experiment.
Returns [`ImportSourceUserConnection`](#importsourceuserconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#pagination-arguments):
`before: String`, `after: String`, `first: Int`, and `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupimportsourceuserssearch"></a>`search` | [`String`](#string) | Query to search mappings by name or username of users on source instance. |
| <a id="groupimportsourceuserssort"></a>`sort` | [`SourceUserSort`](#sourceusersort) | Sort mapping of users on source instance to users on destination instance by the criteria. |
| <a id="groupimportsourceusersstatuses"></a>`statuses` | [`[ImportSourceUserStatus!]`](#importsourceuserstatus) | Filter mapping of users on source instance to users on destination instance by status. |
##### `Group.issues`
Issues for projects in this group.
@ -26500,7 +26521,6 @@ Product analytics events for a specific month and year.
| <a id="namespacefullname"></a>`fullName` | [`String!`](#string) | Full name of the namespace. |
| <a id="namespacefullpath"></a>`fullPath` | [`ID!`](#id) | Full path of the namespace. |
| <a id="namespaceid"></a>`id` | [`ID!`](#id) | ID of the namespace. |
| <a id="namespaceimportsourceusers"></a>`importSourceUsers` **{warning-solid}** | [`ImportSourceUserConnection`](#importsourceuserconnection) | **Introduced** in GitLab 17.2. **Status**: Experiment. Import source users of the namespace. This field can only be resolved for one namespace in any single request. |
| <a id="namespacelfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. |
| <a id="namespacename"></a>`name` | [`String!`](#string) | Name of the namespace. |
| <a id="namespacepackagesettings"></a>`packageSettings` | [`PackageSettings`](#packagesettings) | Package settings for the namespace. |
@ -26606,6 +26626,28 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="namespacecomplianceframeworksids"></a>`ids` | [`[ComplianceManagementFrameworkID!]`](#compliancemanagementframeworkid) | List of Global IDs of compliance frameworks to return. |
| <a id="namespacecomplianceframeworkssearch"></a>`search` | [`String`](#string) | Search framework with most similar names. |
##### `Namespace.importSourceUsers`
Import source users of the namespace. This field can only be resolved for one namespace in any single request.
DETAILS:
**Introduced** in GitLab 17.2.
**Status**: Experiment.
Returns [`ImportSourceUserConnection`](#importsourceuserconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#pagination-arguments):
`before: String`, `after: String`, `first: Int`, and `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="namespaceimportsourceuserssearch"></a>`search` | [`String`](#string) | Query to search mappings by name or username of users on source instance. |
| <a id="namespaceimportsourceuserssort"></a>`sort` | [`SourceUserSort`](#sourceusersort) | Sort mapping of users on source instance to users on destination instance by the criteria. |
| <a id="namespaceimportsourceusersstatuses"></a>`statuses` | [`[ImportSourceUserStatus!]`](#importsourceuserstatus) | Filter mapping of users on source instance to users on destination instance by status. |
##### `Namespace.pagesDeployments`
List of the namespaces's Pages Deployments.
@ -36256,6 +36298,17 @@ Values for sort direction.
| <a id="sortdirectionenumasc"></a>`ASC` | Ascending order. |
| <a id="sortdirectionenumdesc"></a>`DESC` | Descending order. |
### `SourceUserSort`
Values for sorting the mapping of users on source instance to users on destination instance.
| Value | Description |
| ----- | ----------- |
| <a id="sourceusersortsource_name_asc"></a>`SOURCE_NAME_ASC` | Instance source name by ascending order. |
| <a id="sourceusersortsource_name_desc"></a>`SOURCE_NAME_DESC` | Instance source name by descending order. |
| <a id="sourceusersortstatus_asc"></a>`STATUS_ASC` | Status of the mapping by ascending order. |
| <a id="sourceusersortstatus_desc"></a>`STATUS_DESC` | Status of the mapping by descending order. |
### `TestCaseStatus`
| Value | Description |

View File

@ -118,13 +118,13 @@ panels:
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/439737) in GitLab 16.9.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/440694) in GitLab 16.11. Feature flag `dora_performers_score_panel` removed.
The [DORA metrics](dora_metrics.md) Performers score panel is a group-level bar chart that visualizes the status of the organization's DevOps performance levels across different projects.
The [DORA](dora_metrics.md) Performers score panel is a group-level bar chart that visualizes the status of the organization's DevOps performance levels across different projects for the last full calendar month.
The chart is a breakdown of your project's DORA scores, [categorized](https://cloud.google.com/blog/products/devops-sre/dora-2022-accelerate-state-of-devops-report-now-out) as high, medium, or low.
The chart aggregates all the child projects in the group.
Each bar on the chart displays the sum of total projects per score category, calculated monthly.
To exclude data from the chart (for example, **Not Included**), in the legend select the series you want to exclude.
The chart bars display the total number of projects per score category, calculated monthly.
To exclude data from the chart (for example, **Not included**), in the legend select the series you want to exclude.
Hovering over each bar reveals a dialog that explains the score's definition.
For example, if a project has a high score for deployment frequency (velocity), it means that the project has one or more deploys to production per day.

View File

@ -257,7 +257,7 @@ module Gitlab
end
def after_read_callback(record)
if Feature.enabled?(:bulk_import_user_mapping, current_user)
if Feature.enabled?(:importer_user_mapping, current_user)
user_contributions_export_mapper.cache_user_contributions_on_record(record)
end

View File

@ -67,7 +67,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.105.0",
"@gitlab/ui": "^86.11.0",
"@gitlab/ui": "86.11.1",
"@gitlab/web-ide": "^0.0.1-dev-20240613133550",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.0.8-4",

View File

@ -49,6 +49,26 @@ RSpec.describe 'Broadcast Messages', feature_category: :notifications do
expect_message_dismissed
end
it 'broadcast message is still hidden after logout and log back in', :js do
gitlab_sign_in(user)
visit path
expect_to_be_on_explore_projects_page
find('body.page-initialised .js-dismiss-current-broadcast-notification').click
expect_message_dismissed
gitlab_sign_out
gitlab_sign_in(user)
visit path
expect_message_dismissed
end
end
describe 'banner type' do

View File

@ -5,10 +5,23 @@ require 'spec_helper'
RSpec.describe Import::SourceUsersFinder, feature_category: :importers do
let_it_be(:user) { build_stubbed(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:import_source_users) { create_list(:import_source_user, 3, namespace: group) }
let_it_be(:source_user_1) { create(:import_source_user, namespace: group, status: 0, source_name: 'b') }
let_it_be(:source_user_2) { create(:import_source_user, namespace: group, status: 1, source_name: 'c') }
let_it_be(:source_user_3) { create(:import_source_user, namespace: group, status: 2, source_name: 'a') }
let_it_be(:import_source_users) { [source_user_1, source_user_2, source_user_3] }
let(:params) { {} }
describe '#execute' do
subject(:source_user_result) { described_class.new(group, user).execute }
subject(:source_user_result) { described_class.new(group, user, params).execute }
context 'when user is not authorized to read the import source users' do
before do
stub_member_access_level(group, maintainer: user)
end
it { expect(source_user_result).to be_empty }
end
context 'when user is authorized to read the import source users' do
before do
@ -18,15 +31,57 @@ RSpec.describe Import::SourceUsersFinder, feature_category: :importers do
it 'returns all import source users' do
expect(source_user_result).to match_array(import_source_users)
end
end
context 'when user is not authorized to read the import source users' do
before do
stub_member_access_level(group, maintainer: user)
describe 'filtering by statuses' do
context 'when statuses are not provided' do
let(:params) { {} }
it 'returns all import source users' do
expect(source_user_result).to match_array(import_source_users)
end
end
context 'when statuses are is provided' do
let(:params) { { statuses: [0, 1] } }
it 'returns import source users with the corresponding status' do
expect(source_user_result.pluck(:status)).to match_array([0, 1])
end
end
end
it 'is empty' do
expect(source_user_result).to be_empty
describe 'filtering by search' do
context 'when search are not provided' do
let(:params) { {} }
it 'returns all import source users' do
expect(source_user_result).to match_array(import_source_users)
end
end
context 'when search is is provided' do
let(:params) { { search: 'b' } }
it 'returns import source users with matches the search query' do
expect(source_user_result).to match_array([source_user_1])
end
end
end
describe 'sorting' do
let(:params) { { sort: 'source_name_desc' } }
it 'returns import source users sorted by the provided method' do
expect(source_user_result.pluck(:source_name)).to eq(%w[c b a])
end
context 'when sort is not provided' do
let(:params) { {} }
it 'returns import source users sorted by source_name_asc' do
expect(source_user_result.pluck(:source_name)).to eq(%w[a b c])
end
end
end
end
end

View File

@ -94,7 +94,11 @@ exports[`FindingsDrawer General Rendering matches the snapshot with detected bad
<span
class="badge badge-pill badge-warning gl-badge text-capitalize"
>
detected
<span
class="gl-badge-content"
>
detected
</span>
</span>
</p>
</li>
@ -314,7 +318,11 @@ exports[`FindingsDrawer General Rendering matches the snapshot with dismissed ba
<span
class="badge badge-pill badge-warning gl-badge text-capitalize"
>
detected
<span
class="gl-badge-content"
>
detected
</span>
</span>
</p>
</li>

View File

@ -11,7 +11,11 @@ exports[`~/releases/components/issuable_stats.vue matches snapshot 1`] = `
<span
class="badge badge-muted badge-pill gl-badge"
>
10
<span
class="gl-badge-content"
>
10
</span>
</span>
</span>
<div

View File

@ -7,7 +7,11 @@ exports[`Beta badge component renders the badge 1`] = `
href="#"
target="_self"
>
Beta
<span
class="gl-badge-content"
>
Beta
</span>
</a>
<div
class="gl-popover"

View File

@ -7,7 +7,11 @@ exports[`Experiment badge component renders the badge 1`] = `
href="#"
target="_self"
>
Experiment
<span
class="gl-badge-content"
>
Experiment
</span>
</a>
<div
class="gl-popover"

View File

@ -30,13 +30,27 @@ RSpec.describe Resolvers::Import::SourceUsersResolver, feature_category: :import
it { expect(resolve_import_source_users).to eq(nil) }
end
context 'when `bulk_import_user_mapping` feature flag is diabled' do
context 'when `importer_user_mapping` feature flag is diabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it { expect(resolve_import_source_users).to be_empty }
end
describe 'arguments' do
let(:args) { { search: 'search', statuses: ['AWAITING_APPROVAL'], sort: 'STATUS_ASC' } }
it 'calls Import::SourceUsersFinder with the expected arguments' do
expected_args = { search: 'search', statuses: [1], sort: :status_asc }
expect_next_instance_of(::Import::SourceUsersFinder, group, current_user, expected_args) do |finder|
expect(finder).to receive(:execute)
end
resolve_import_source_users
end
end
end
def resolve_import_source_users

View File

@ -154,7 +154,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
end.new
end
context 'when :bulk_import_user_mapping feature flag is enabled' do
context 'when :importer_user_mapping feature flag is enabled' do
it 'caches existing referenced user_ids' do
expected_user_ref_ids = Issue.all.pluck(
:author_id, :updated_by_id, :last_edited_by_id, :closed_by_id
@ -168,9 +168,9 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
end
end
context 'when :bulk_import_user_mapping feature flag is disabled' do
context 'when :importer_user_mapping feature flag is disabled' do
it 'does not cache any contributing user ids' do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
expect(BulkImports::UserContributionsExportMapper).not_to receive(:new)
subject.execute
@ -216,7 +216,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
allow(json_writer).to receive(:write_relation)
end
context 'when :bulk_import_user_mapping feature flag is enabled' do
context 'when :importer_user_mapping feature flag is enabled' do
it 'caches existing referenced user_ids' do
expect_next_instance_of(BulkImports::UserContributionsExportMapper) do |contribution_mapper|
expect(contribution_mapper).to receive(:cache_user_contributions_on_record).with(group).once
@ -226,9 +226,9 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
end
end
context 'when :bulk_import_user_mapping feature flag is disabled' do
context 'when :importer_user_mapping feature flag is disabled' do
it 'does not cache any contributing user ids' do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
expect(BulkImports::UserContributionsExportMapper).not_to receive(:new)
subject.execute
@ -281,7 +281,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
project_member.update!(created_by: create(:user))
end
context 'when :bulk_import_user_mapping feature flag is enabled' do
context 'when :importer_user_mapping feature flag is enabled' do
it 'caches existing referenced user_ids' do
expected_user_ref_ids = [project_member.user_id, project_member.created_by_id].map(&:to_s)
@ -293,9 +293,9 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re
end
end
context 'when :bulk_import_user_mapping feature flag is disabled' do
context 'when :importer_user_mapping feature flag is disabled' do
it 'does not cache any contributing user ids' do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
expect(BulkImports::UserContributionsExportMapper).not_to receive(:new)
subject.execute

View File

@ -118,6 +118,74 @@ RSpec.describe Import::SourceUser, type: :model, feature_category: :importers do
end
end
describe '.search' do
let!(:source_user) do
create(:import_source_user, source_name: 'Source Name', source_username: 'Username')
end
it 'searches by source_name or source_username' do
expect(described_class.search('name')).to eq([source_user])
expect(described_class.search('username')).to eq([source_user])
expect(described_class.search('source')).to eq([source_user])
expect(described_class.search('inexistent')).to eq([])
end
end
describe '.sort_by_attribute' do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:source_user_1) { create(:import_source_user, namespace: namespace, status: 4, source_name: 'd') }
let_it_be(:source_user_2) { create(:import_source_user, namespace: namespace, status: 2, source_name: 'b') }
let_it_be(:source_user_3) { create(:import_source_user, namespace: namespace, status: 1, source_name: 'a') }
let_it_be(:source_user_4) { create(:import_source_user, namespace: namespace, status: 3, source_name: 'c') }
let(:sort_by_attribute) { described_class.sort_by_attribute(method).pluck(attribute) }
context 'with method status_asc' do
let(:method) { 'status_asc' }
let(:attribute) { :status }
it 'order by status_desc ascending' do
expect(sort_by_attribute).to eq([1, 2, 3, 4])
end
end
context 'with method status_desc' do
let(:method) { 'status_desc' }
let(:attribute) { :status }
it 'order by status_desc descending' do
expect(sort_by_attribute).to eq([4, 3, 2, 1])
end
end
context 'with method source_name_asc' do
let(:method) { 'source_name_asc' }
let(:attribute) { :source_name }
it 'order by source_name_asc ascending' do
expect(sort_by_attribute).to eq(%w[a b c d])
end
end
context 'with method source_name_desc' do
let(:method) { 'source_name_desc' }
let(:attribute) { :source_name }
it 'order by source_name_desc descending' do
expect(sort_by_attribute).to eq(%w[d c b a])
end
end
context 'with an unexpected method' do
let(:method) { 'id_asc' }
let(:attribute) { :source_name }
it 'order by source_name_asc ascending' do
expect(sort_by_attribute).to eq(%w[a b c d])
end
end
end
describe '#reassignable_status?' do
reassignable_statuses = [:pending_reassignment, :rejected]
all_states = described_class.state_machines[:status].states

View File

@ -75,9 +75,9 @@ RSpec.describe 'Cancel an reassignment of an import source user', feature_catego
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when feature flag `bulk_import_user_mapping`` disabled' do
context 'when feature flag `importer_user_mapping`` disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'returns a resource not available error' do
@ -85,7 +85,7 @@ RSpec.describe 'Cancel an reassignment of an import source user', feature_catego
expect(graphql_errors).to contain_exactly(
hash_including(
'message' => '`bulk_import_user_mapping` feature flag is disabled.'
'message' => '`importer_user_mapping` feature flag is disabled.'
)
)
end

View File

@ -67,9 +67,9 @@ RSpec.describe 'Keep as placeholder an import source user', feature_category: :i
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when feature flag `bulk_import_user_mapping`` disabled' do
context 'when feature flag `importer_user_mapping`` disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'returns a resource not available error' do
@ -77,7 +77,7 @@ RSpec.describe 'Keep as placeholder an import source user', feature_category: :i
expect(graphql_errors).to contain_exactly(
hash_including(
'message' => '`bulk_import_user_mapping` feature flag is disabled.'
'message' => '`importer_user_mapping` feature flag is disabled.'
)
)
end

View File

@ -44,6 +44,8 @@ RSpec.describe 'Reassign an import source user', feature_category: :importers do
end
it 'reassign import source user', :aggregate_failures do
expect(Notify).to receive_message_chain(:import_source_user_reassign, :deliver_now)
post_graphql_mutation(mutation, current_user: current_user)
import_source_user = mutation_response['importSourceUser']
@ -73,9 +75,9 @@ RSpec.describe 'Reassign an import source user', feature_category: :importers do
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when feature flag `bulk_import_user_mapping`` disabled' do
context 'when feature flag `importer_user_mapping`` disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'returns a resource not available error' do
@ -83,7 +85,7 @@ RSpec.describe 'Reassign an import source user', feature_category: :importers do
expect(graphql_errors).to contain_exactly(
hash_including(
'message' => '`bulk_import_user_mapping` feature flag is disabled.'
'message' => '`importer_user_mapping` feature flag is disabled.'
)
)
end

View File

@ -41,6 +41,8 @@ RSpec.describe 'Resend notification to the reassigned user of an import source u
context 'when user is authorized' do
it 'resends notification and does not change status', :aggregate_failures do
expect(Notify).to receive_message_chain(:import_source_user_reassign, :deliver_now)
post_graphql_mutation(mutation, current_user: current_user)
import_source_user = mutation_response['importSourceUser']
@ -69,9 +71,9 @@ RSpec.describe 'Resend notification to the reassigned user of an import source u
end
end
context 'when feature flag `bulk_import_user_mapping`` disabled' do
context 'when feature flag `importer_user_mapping`` disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'returns a resource not available error' do
@ -79,7 +81,7 @@ RSpec.describe 'Resend notification to the reassigned user of an import source u
expect(graphql_errors).to contain_exactly(
hash_including(
'message' => '`bulk_import_user_mapping` feature flag is disabled.'
'message' => '`importer_user_mapping` feature flag is disabled.'
)
)
end

View File

@ -18,7 +18,7 @@ RSpec.describe Groups::GroupMembersController, feature_category: :groups_and_pro
it 'pushes feature flag to frontend' do
request
expect(response.body).to have_pushed_frontend_feature_flags(bulkImportUserMapping: true)
expect(response.body).to have_pushed_frontend_feature_flags(importerUserMapping: true)
expect(response.body).to have_pushed_frontend_feature_flags(serviceAccountsCrud: true)
expect(response.body).to have_pushed_frontend_feature_flags(webuiMembersInheritedUsers: true)
end

View File

@ -6,7 +6,7 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
shared_examples 'it requires feature flag' do
context 'when :improved_user_mapping is disabled' do
it 'returns 404' do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
subject

View File

@ -12,6 +12,8 @@ RSpec.describe Import::SourceUsers::ReassignService, feature_category: :importer
describe '#execute' do
context 'when reassignment is successful' do
it 'returns success' do
expect(Notify).to receive_message_chain(:import_source_user_reassign, :deliver_now)
result = service.execute
expect(result).to be_success
@ -22,58 +24,53 @@ RSpec.describe Import::SourceUsers::ReassignService, feature_category: :importer
end
end
context 'when current user does not have permission' do
let(:current_user) { create(:user) }
shared_examples 'an error response' do |desc, error:|
it "returns #{desc} error" do
expect(Notify).not_to receive(:import_source_user_reassign)
it 'returns error no permissions' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq('You have insufficient permissions to update the import source user')
expect(result.message).to eq(error)
end
end
context 'when current user does not have permission' do
let(:current_user) { create(:user) }
it_behaves_like 'an error response', 'no permissions',
error: 'You have insufficient permissions to update the import source user'
end
context 'when import source user does not have an reassignable status' do
before do
allow(current_user).to receive(:can?).with(:admin_import_source_user, import_source_user).and_return(true)
allow(import_source_user).to receive(:reassignable_status?).and_return(false)
end
it 'returns error invalid status' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq('Import source user has an invalid status for this operation')
end
it_behaves_like 'an error response', 'invalid status',
error: 'Import source user has an invalid status for this operation'
end
context 'when assignee user does not exist' do
let(:assignee_user) { nil }
it 'returns invalid assignee error' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq('Only active regular, auditor, or administrator users can be assigned')
end
it_behaves_like 'an error response', 'invalid assignee',
error: 'Only active regular, auditor, or administrator users can be assigned'
end
context 'when assignee user is not a human' do
let(:assignee_user) { create(:user, :bot) }
it 'returns invalid assignee error' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq('Only active regular, auditor, or administrator users can be assigned')
end
it_behaves_like 'an error response', 'invalid assignee',
error: 'Only active regular, auditor, or administrator users can be assigned'
end
context 'when assignee user is not active' do
let(:assignee_user) { create(:user, :deactivated) }
it 'returns invalid assignee error' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq('Only active regular, auditor, or administrator users can be assigned')
end
it_behaves_like 'an error response', 'invalid assignee',
error: 'Only active regular, auditor, or administrator users can be assigned'
end
context 'when an error occurs' do
@ -83,12 +80,7 @@ RSpec.describe Import::SourceUsers::ReassignService, feature_category: :importer
full_messages: ['Error']))
end
it 'returns an error' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq(['Error'])
end
it_behaves_like 'an error response', 'active record', error: ['Error']
end
end
end

View File

@ -11,6 +11,8 @@ RSpec.describe Import::SourceUsers::ResendNotificationService, feature_category:
describe '#execute' do
context 'when notification is successfully sent' do
it 'returns success' do
expect(Notify).to receive_message_chain(:import_source_user_reassign, :deliver_now)
result = service.execute
expect(result).to be_success
@ -22,6 +24,8 @@ RSpec.describe Import::SourceUsers::ResendNotificationService, feature_category:
let(:current_user) { create(:user) }
it 'returns error no permissions' do
expect(Notify).not_to receive(:import_source_user_reassign)
result = service.execute
expect(result).to be_error
@ -35,6 +39,8 @@ RSpec.describe Import::SourceUsers::ResendNotificationService, feature_category:
end
it 'returns error invalid status' do
expect(Notify).not_to receive(:import_source_user_reassign)
result = service.execute
expect(result).to be_error
expect(result.message).to eq('Import source user has an invalid status for this operation')

View File

@ -65,7 +65,7 @@ RSpec.describe BulkImports::RelationExportWorker, feature_category: :importers d
context 'when export is user_contributions' do
let(:relation) { 'user_contributions' }
context 'and :bulk_import_user_mapping feature flag is enabled' do
context 'and :importer_user_mapping feature flag is enabled' do
it 'enqueues the UserContributionsExportWorker' do
expect(BulkImports::UserContributionsExportWorker).to receive(:perform_async).with(
group.id, group.class.name, user.id
@ -75,9 +75,9 @@ RSpec.describe BulkImports::RelationExportWorker, feature_category: :importers d
end
end
context 'and :bulk_import_user_mapping feature flag is disabled' do
context 'and :importer_user_mapping feature flag is disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'does not enqueue the UserContributionsExportWorker' do

View File

@ -23,9 +23,9 @@ RSpec.describe Import::LoadPlaceholderReferencesWorker, feature_category: :impor
let(:job_args) { [import_source, uid, params] }
end
context 'when bulk_import_user_mapping feature is disabled' do
context 'when importer_user_mapping feature is disabled' do
before do
stub_feature_flags(bulk_import_user_mapping: false)
stub_feature_flags(importer_user_mapping: false)
end
it 'does not execute LoadService' do
@ -34,9 +34,9 @@ RSpec.describe Import::LoadPlaceholderReferencesWorker, feature_category: :impor
perform
end
context 'when bulk_import_user_mapping feature is enabled for the user' do
context 'when importer_user_mapping feature is enabled for the user' do
before do
stub_feature_flags(bulk_import_user_mapping: user)
stub_feature_flags(importer_user_mapping: user)
end
it 'executes LoadService' do

View File

@ -1359,10 +1359,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.105.0.tgz#0d15978755093b79e5c9b883a27d8074bf316e9a"
integrity sha512-JrXE7T3j+9FyQakG4XVg/uhXL3TZvmFgTuggaiSXdyelVOqAzYdPPm1oergdp8C+Io0zBKlLkJv/4nWG3AgkfQ==
"@gitlab/ui@^86.11.0":
version "86.11.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-86.11.0.tgz#622fa5659f5ff241679efbc5cdad396807dff3b4"
integrity sha512-9+zZaKrpYySD3kkZFZ9ib/n8fAcavLZ2QC+9o5dvaKfQ54P4hRFQqUJpO6IXQufDAAkdBriPSw1RETa9lBkbpg==
"@gitlab/ui@86.11.1":
version "86.11.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-86.11.1.tgz#5de5de9416946375555ecb54f1510dd4323f2abf"
integrity sha512-/KvXyMGMJ3+Qq67EkqC9Rgju9rKmOKqE1Sq8yam3fZ2ZgcQQ8RYbHmJuKsGufcdCUBAfIp6qiI++QzZMJRU6mw==
dependencies:
"@floating-ui/dom" "1.4.3"
echarts "^5.3.2"