Commit ca7c37ef authored by Eugenia Grieff's avatar Eugenia Grieff

Add service to transfer group epics

- When transfering a project if it
contains issue with assigned epics
they will be recreated in the new group

Add specs for epics transfer service

- Include specs for epics transfer service
- Add specs for projects transfer service
parent c984a22a
......@@ -79,11 +79,7 @@ module Projects
# Directories on disk
move_project_folders(project)
# Move missing group labels to project
Labels::TransferService.new(current_user, @old_group, project).execute
# Move missing group milestones
Milestones::TransferService.new(current_user, @old_group, project).execute
transfer_missing_group_resources(@old_group)
# Move uploads
move_project_uploads(project)
......@@ -107,6 +103,12 @@ module Projects
refresh_permissions
end
def transfer_missing_group_resources(group)
Labels::TransferService.new(current_user, group, project).execute
Milestones::TransferService.new(current_user, group, project).execute
end
def allowed_transfer?(current_user, project)
@new_namespace &&
can?(current_user, :change_namespace, project) &&
......
......@@ -19,6 +19,13 @@ module EE
old_path_with_namespace: old_path
).create!
end
override :transfer_missing_group_resources
def transfer_missing_group_resources(group)
super
::Epics::TransferService.new(current_user, group, project).execute
end
end
end
end
# frozen_string_literal: true
# Epics::TransferService class
#
# Used for recreating the missing epics when transferring a project to a new group
#
module Epics
class TransferService
attr_reader :current_user, :old_group, :project
def initialize(current_user, old_group, project)
@current_user = current_user
@old_group = old_group
@project = project
end
def execute
return unless old_group.present? && project.group.present?
# If the old group is an ancestor of the new group the epic can remain assigned
return if project.group.ancestors.include?(old_group)
Epic.transaction do
epics_to_transfer.find_each do |epic|
new_epic = create_epic(epic)
update_issues_epic(epic, new_epic)
end
end
end
private
# rubocop: disable CodeReuse/ActiveRecord
def epics_to_transfer
Epic.joins(:issues)
.where(
issues: { project_id: project.id },
group_id: old_group.self_and_descendants
)
end
# rubocop: enable CodeReuse/ActiveRecord
def create_epic(epic)
return unless current_user.can?(:create_epic, project.group)
epic_params = epic.attributes
.slice('title', 'description', 'start_date', 'end_date', 'confidential')
CreateService.new(project.group, current_user, epic_params).execute
end
# rubocop: disable CodeReuse/ActiveRecord
def update_issues_epic(old_epic, new_epic)
issues = old_epic.issues.where(project: project)
issues.each do |issue|
if new_epic.present?
create_epic_issue_link(issue, new_epic)
else
destroy_epic_issue_link(issue, old_epic)
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def create_epic_issue_link(issue, epic)
link_params = { target_issuable: issue, skip_epic_dates_update: true }
EpicIssues::CreateService.new(epic, current_user, link_params).execute
end
def destroy_epic_issue_link(issue, epic)
link = EpicIssue.find_by_issue_id(issue.id)
EpicIssues::DestroyService.new(link, current_user).execute
end
end
end
---
title: Transfer missing epics when a project is transferred
merge_request:
author:
type: security
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Epics::TransferService do
describe '#execute' do
let_it_be(:user) { create(:admin) }
let_it_be(:new_group, refind: true) { create(:group) }
let_it_be(:old_group, refind: true) { create(:group) }
subject(:service) { described_class.new(user, old_group, project) }
context 'when old_group is present' do
let_it_be(:project) { create(:project, namespace: old_group) }
let_it_be(:epic) { create(:epic, group: old_group, title: 'Epic 1')}
let_it_be(:issue_with_epic) { create(:issue, project: project, epic: epic) }
before do
stub_licensed_features(epics: true)
project.add_maintainer(user)
# simulate project transfer
project.update!(group: new_group)
end
context 'when user can create epics in the new group' do
before do
new_group.add_maintainer(user)
end
it 'recreates the missing group epics in the new group' do
expect { service.execute }.to change(project.group.epics, :count).by(1)
new_epic = issue_with_epic.reload.epic
expect(new_epic.group).to eq(new_group)
expect(new_epic.title).to eq(epic.title)
expect(new_epic.description).to eq(epic.description)
expect(new_epic.start_date).to eq(epic.start_date)
expect(new_epic.end_date).to eq(epic.end_date)
expect(new_epic.confidential).to eq(epic.confidential)
end
it 'does not recreate missing epics that are not applied to issues' do
unassigned_epic = create(:epic, group: old_group)
service.execute
new_epics_titles = project.group.reload.epics.pluck(:title)
expect(new_epics_titles).to include(epic.title).and exclude(unassigned_epic.title)
end
context 'when epic is from an descendant group' do
let_it_be(:old_group_subgroup) { create(:group, parent: old_group) }
it 'recreates the missing epic in the new group' do
create(:epic, group: old_group_subgroup)
expect { service.execute }.to change(project.group.epics, :count).by(1)
end
end
context 'when create_epic returns nil' do
before do
allow_next_instance_of(Epics::CreateService) do |instance|
allow(instance).to receive(:execute).and_return(nil)
end
end
it 'removes issues epic' do
service.execute
expect(issue_with_epic.reload.epic).to be_nil
end
end
context 'when assigned epic is confidential' do
before do
[issue_with_epic, epic].each { |issuable| issuable.update!(confidential: true) }
end
it 'creates a new confidential epic in the new group' do
expect { service.execute }.to change(project.group.epics, :count).by(1)
new_epic = issue_with_epic.reload.epic
expect(new_epic).not_to eq(epic.group)
expect(new_epic.title).to eq(epic.title)
expect(new_epic.confidential).to be_truthy
end
end
end
context 'when user is a guest of the new group' do
let_it_be(:guest) { create(:user) }
before do
old_group.add_owner(guest)
project.add_maintainer(user)
new_group.add_guest(guest)
end
it 'does not create a new epic but removes assigned epic' do
service = described_class.new(guest, old_group, project)
expect { service.execute }.not_to change(project.group.epics, :count)
expect(issue_with_epic.reload.epic).to be_nil
end
end
context 'when epics are disabled' do
before do
stub_licensed_features(epics: false)
end
it 'does not create a new epic' do
expect { service.execute }.not_to change(project.group.epics, :count)
end
end
end
context 'when old_group is not present' do
let_it_be(:project) { create(:project, namespace: create(:namespace)) }
let_it_be(:old_group) { nil }
before do
project.update!(namespace: new_group)
end
it 'returns nil' do
expect(described_class.new(user, old_group, project).execute).to be_nil
end
end
context 'when project group is not present' do
let_it_be(:project) { create(:project, group: old_group) }
before do
project.update!(namespace: user.namespace)
end
it 'returns nil' do
expect(described_class.new(user, old_group, project).execute).to be_nil
end
end
end
end
......@@ -53,4 +53,14 @@ RSpec.describe Projects::TransferService do
end
end
end
context 'missing epics applied to issues' do
it 'delegates transfer to Epics::TransferService' do
expect_next_instance_of(Epics::TransferService, user, project.group, project) do |epics_transfer_service|
expect(epics_transfer_service).to receive(:execute).once.and_call_original
end
subject.execute(group)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment