Skip to content
Snippets Groups Projects
Commit c2dbbff0 authored by James Nutt's avatar James Nutt
Browse files

Add controllers for user contribution mapping

This MR introduces the basic controller functionality to support the
assignee-facing side of #443555.

There is more work to be done on the interface, but this MR enables the
basic flow of accepting/rejecting assignment, and introduces the flag to
be used across the feature.
parent 7b03368b
No related branches found
No related tags found
1 merge request!150736Add controllers for user contribution mapping
# frozen_string_literal: true
module Import
class SourceUsersController < ApplicationController
prepend_before_action :check_feature_flag!
before_action :source_user
before_action :check_current_user_matches_invite!
before_action :load_invite_details
respond_to :html
feature_category :importers
def accept
if current_user_matches_invite? && source_user.accept
# TODO: This is where we enqueue the job to assign the contributions.
# OTHER TODO: This is a placeholder for the proper UI to be provided
# in a follow-up MR.
notice = "You have approved the reassignment of contributions from "
notice += "%{source_username} on %{source_hostname} "
notice += "to yourself on %{destination_group}."
redirect_to(root_path, notice: format(notice, @invite_details))
else
redirect_back_or_default(options: { alert: _("The invitation could not be accepted.") })
end
end
def decline
if current_user_matches_invite? && source_user.reject
# TODO: This is a placeholder for the proper UI to be provided
# in a follow-up MR.
notice = "You have rejected the reassignment of contributions from "
notice += "%{source_username} on %{source_hostname} to yourself on "
notice += "%{destination_group}."
redirect_to(root_path, notice: format(notice, @invite_details))
else
redirect_back_or_default(options: { alert: _("The invitation could not be declined.") })
end
end
private
def check_current_user_matches_invite!
not_found unless current_user_matches_invite?
end
def current_user_matches_invite?
current_user.id == @source_user.reassign_to_user_id
end
def source_user
Import::SourceUser.find(params[:id])
end
strong_memoize_attr :source_user
def authenticate_user!
return if current_user
redirect_to new_user_session_path, notice: sign_in_notice
end
def sign_in_notice
if Gitlab::CurrentSettings.allow_signup?
_("To accept this invitation, sign in or create an account.")
else
_("To accept this invitation, sign in.")
end
end
def load_invite_details
@invite_details ||= {
source_username: source_user.source_username,
source_hostname: source_user.source_hostname,
destination_group: source_user.namespace.name
}
end
def check_feature_flag!
not_found unless Feature.enabled?(:improved_user_mapping, current_user)
end
end
end
......@@ -18,6 +18,14 @@ class SourceUser < ApplicationRecord
state :rejected, value: 2
state :failed, value: 3
state :completed, value: 4
event :accept do
transition awaiting_approval: :completed
end
event :reject do
transition awaiting_approval: :rejected
end
end
def self.find_source_user(source_user_identifier:, namespace:, source_hostname:, import_type:)
......
---
name: improved_user_mapping
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/443555
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150736
rollout_issue_url: n/a
milestone: '17.0'
group: group::import and integrate
type: wip
default_enabled: false
......@@ -88,4 +88,11 @@
get :realtime_changes
post :upload
end
resources :source_users, only: [] do
member do
get :accept
get :decline
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Import::SourceUsersController, feature_category: :importers do
shared_examples 'it requires feature flag' do |action|
context 'when :improved_user_mapping is disabled' do
it 'returns 404' do
stub_feature_flags(improved_user_mapping: false)
get action, params: { id: source_user.to_param }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
shared_examples 'it requires the user to be signed in' do |action|
context 'when the user is not signed in' do
it 'redirects to the login screen' do
get action, params: { id: source_user.to_param }
expect(response).to redirect_to(new_user_session_path)
expect(flash[:notice]).to match(/To accept this invitation/)
end
end
end
let_it_be_with_reload(:source_user) { create(:import_source_user, :with_reassign_to_user, :awaiting_approval) }
describe 'GET /accept' do
context 'when signed in' do
before do
sign_in(source_user.reassign_to_user)
end
subject(:accept_invite) { get :accept, params: { id: source_user.to_param } }
it { expect { accept_invite }.to change { source_user.reload.completed? }.from(false).to(true) }
it 'redirects with a notice when accepted' do
accept_invite
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to match(/You have approved the reassignment/)
end
it 'can only be accepted by the reassign_to_user' do
source_user.update!(reassign_to_user: create(:user))
expect { accept_invite }.not_to change { source_user.reload.status }
expect(response).to have_gitlab_http_status(:not_found)
end
end
it_behaves_like 'it requires feature flag', :accept
it_behaves_like 'it requires the user to be signed in', :accept
end
describe 'GET /decline' do
context 'when signed in' do
before do
sign_in(source_user.reassign_to_user)
end
subject(:accept_invite) { get :decline, params: { id: source_user.to_param } }
it { expect { accept_invite }.to change { source_user.reload.rejected? }.from(false).to(true) }
it 'redirects with a notice' do
accept_invite
expect(response).to redirect_to(root_path)
expect(flash[:notice]).to match(/You have rejected the reassignment/)
end
end
it_behaves_like 'it requires feature flag', :decline
it_behaves_like 'it requires the user to be signed in', :decline
end
end
......@@ -14,5 +14,9 @@
trait :with_reassign_to_user do
reassign_to_user factory: :user
end
trait :awaiting_approval do
status { 1 }
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment