Commit 9b13ce0b authored by Grzegorz Bizon's avatar Grzegorz Bizon

Improvements in issue move feaure (refactoring)

According to endbosses' suggestions.
parent fcf10689
...@@ -88,11 +88,11 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -88,11 +88,11 @@ class Projects::IssuesController < Projects::ApplicationController
def update def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue) @issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
move_service = Issues::MoveService.new(project, current_user, issue_params,
@issue, params['move_to_project_id'])
if move_service.move? if params[:move_to_project_id].to_i > 0
@issue = move_service.execute new_project = Project.find(params[:move_to_project_id])
move_service = Issues::MoveService.new(project, current_user, issue_params)
@issue = move_service.execute(@issue, new_project)
end end
respond_to do |format| respond_to do |format|
......
...@@ -209,4 +209,13 @@ module Issuable ...@@ -209,4 +209,13 @@ module Issuable
Taskable.get_updated_tasks(old_content: previous_changes['description'].first, Taskable.get_updated_tasks(old_content: previous_changes['description'].first,
new_content: description) new_content: description)
end end
##
# Method that checks if issuable can be moved to another project.
#
# Should be overridden if issuable can be moved.
#
def can_move?(*)
false
end
end end
module Issues module Issues
class CreateService < Issues::BaseService class CreateService < Issues::BaseService
def execute(set_author: true) def execute
filter_params filter_params
label_params = params[:label_ids] label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids)) issue = project.issues.new(params.except(:label_ids))
issue.author = current_user if set_author issue.author = params[:author] || current_user
if issue.save if issue.save
issue.update_attributes(label_ids: label_params) issue.update_attributes(label_ids: label_params)
......
module Issues module Issues
class MoveService < Issues::BaseService class MoveService < Issues::BaseService
def initialize(project, current_user, params, issue, new_project_id) class MoveError < StandardError; end
super(project, current_user, params)
@issue_old = issue def execute(issue, new_project)
@issue_new = nil @old_issue = issue
@project_old = @project @old_project = @project
@new_project = new_project
if new_project_id.to_i > 0 unless issue.can_move?(current_user, new_project)
@project_new = Project.find(new_project_id) raise MoveError, 'Cannot move issue due to insufficient permissions!'
end end
if @project_new == @project_old if @project == new_project
raise StandardError, 'Cannot move issue to project it originates from!' raise MoveError, 'Cannot move issue to project it originates from!'
end end
end
def execute
return unless move?
# Using transaction because of a high resources footprint # Using transaction because of a high resources footprint
# on rewriting notes (unfolding references) # on rewriting notes (unfolding references)
...@@ -25,81 +21,72 @@ module Issues ...@@ -25,81 +21,72 @@ module Issues
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
# New issue tasks # New issue tasks
# #
create_new_issue @new_issue = create_new_issue
rewrite_notes rewrite_notes
add_moved_from_note add_note_moved_from
# Old issue tasks # Old issue tasks
# #
add_moved_to_note add_note_moved_to
close_old_issue close_issue
mark_as_moved mark_as_moved
end end
notify_participants notify_participants
@issue_new @new_issue
end
def move?
@project_new && can_move?
end end
private private
def can_move?
@issue_old.can_move?(@current_user) &&
@issue_old.can_move?(@current_user, @project_new)
end
def create_new_issue def create_new_issue
new_params = { id: nil, iid: nil, milestone: nil, label_ids: [], new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @project_new, author: @issue_old.author, project: @new_project, author: @old_issue.author,
description: unfold_references(@issue_old.description) } description: unfold_references(@old_issue.description) }
new_params = @issue_old.serializable_hash.merge(new_params)
create_service = CreateService.new(@project_new, @current_user,
new_params)
@issue_new = create_service.execute(set_author: false) new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
end end
def rewrite_notes def rewrite_notes
@issue_old.notes.find_each do |note| @old_issue.notes.find_each do |note|
new_note = note.dup new_note = note.dup
new_params = { project: @project_new, noteable: @issue_new, new_params = { project: @new_project, noteable: @new_issue,
note: unfold_references(new_note.note) } note: unfold_references(new_note.note) }
new_note.update(new_params) new_note.update(new_params)
end end
end end
def close_old_issue def close_issue
close_service = CloseService.new(@project_new, @current_user) close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@issue_old, notifications: false, system_note: false) close_service.execute(@old_issue, notifications: false, system_note: false)
end end
def add_moved_from_note def add_note_moved_from
SystemNoteService.noteable_moved(@issue_new, @project_new, SystemNoteService.noteable_moved(@new_issue, @new_project,
@issue_old, @current_user, direction: :from) @old_issue, @current_user,
direction: :from)
end end
def add_moved_to_note def add_note_moved_to
SystemNoteService.noteable_moved(@issue_old, @project_old, SystemNoteService.noteable_moved(@old_issue, @old_project,
@issue_new, @current_user, direction: :to) @new_issue, @current_user,
direction: :to)
end end
def unfold_references(content) def unfold_references(content)
unfolder = Gitlab::Gfm::ReferenceUnfolder.new(content, @project_old) unfolder = Gitlab::Gfm::ReferenceUnfolder.new(content, @old_project)
unfolder.unfold(@project_new) unfolder.unfold(@new_project)
end end
def notify_participants def notify_participants
notification_service.issue_moved(@issue_old, @issue_new, @current_user) notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end end
def mark_as_moved def mark_as_moved
@issue_old.update(moved_to: @issue_new) @old_issue.update(moved_to: @new_issue)
end end
end end
end end
...@@ -67,22 +67,22 @@ ...@@ -67,22 +67,22 @@
- if can? current_user, :admin_label, issuable.project - if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
- if issuable.is_a?(Issue) && issuable.can_move?(current_user) - if issuable.can_move?(current_user)
%hr %hr
.form-group .form-group
= label_tag :move_to_project_id, 'Move', class: 'control-label' = label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10 .col-sm-10
- projects = project_options(issuable, current_user, ability: :admin_issue) - projects = project_options(issuable, current_user, ability: :admin_issue)
= select_tag(:move_to_project_id, projects, include_blank: true, = select_tag(:move_to_project_id, projects, include_blank: true,
class: 'select2', data: { placeholder: 'Select project' }) class: 'select2', data: { placeholder: 'Select project' })
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%hr %hr
- if @merge_request.new_record? - if @merge_request.new_record?
.form-group .form-group
= f.label :source_branch, class: 'control-label' = f.label :source_branch, class: 'control-label'
.col-sm-10 .col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group .form-group
= f.label :target_branch, class: 'control-label' = f.label :target_branch, class: 'control-label'
.col-sm-10 .col-sm-10
......
class AddMovedToToIssue < ActiveRecord::Migration class AddMovedToToIssue < ActiveRecord::Migration
def change def change
add_reference :issues, :moved_to, references: :issues, index: true add_reference :issues, :moved_to, references: :issues
end end
end end
...@@ -425,7 +425,6 @@ ActiveRecord::Schema.define(version: 20160317092222) do ...@@ -425,7 +425,6 @@ ActiveRecord::Schema.define(version: 20160317092222) do
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
......
...@@ -7,7 +7,9 @@ module Gitlab ...@@ -7,7 +7,9 @@ module Gitlab
# in context of. # in context of.
# #
# `unfold` method tries to find all local references and unfold each of # `unfold` method tries to find all local references and unfold each of
# those local references to cross reference format. # those local references to cross reference format, assuming that the
# argument passed to this method is a project that references will be
# viewed from (see `Referable#to_reference method).
# #
# Examples: # Examples:
# #
...@@ -34,17 +36,12 @@ module Gitlab ...@@ -34,17 +36,12 @@ module Gitlab
end end
def unfold(from_project) def unfold(from_project)
return @text unless @text =~ references_pattern pattern = Gitlab::ReferenceExtractor.references_pattern
return @text unless @text =~ pattern
unfolded = @text.gsub(references_pattern) do |reference| @text.gsub(pattern) do |reference|
unfold_reference(reference, Regexp.last_match, from_project) unfold_reference(reference, Regexp.last_match, from_project)
end end
unless substitution_valid?(unfolded)
raise StandardError, 'Invalid references unfolding!'
end
unfolded
end end
private private
...@@ -61,16 +58,6 @@ module Gitlab ...@@ -61,16 +58,6 @@ module Gitlab
substitution_valid?(new_text) ? cross_reference : reference substitution_valid?(new_text) ? cross_reference : reference
end end
def references_pattern
return @pattern if @pattern
patterns = Gitlab::ReferenceExtractor::REFERABLES.map do |ref|
ref.to_s.classify.constantize.try(:reference_pattern)
end
@pattern = Regexp.union(patterns.compact)
end
def referables def referables
return @referables if @referables return @referables if @referables
......
...@@ -37,6 +37,16 @@ module Gitlab ...@@ -37,6 +37,16 @@ module Gitlab
@references.values.flatten @references.values.flatten
end end
def self.references_pattern
return @pattern if @pattern
patterns = REFERABLES.map do |ref|
ref.to_s.classify.constantize.try(:reference_pattern)
end
@pattern = Regexp.union(patterns.compact)
end
private private
def reference_context def reference_context
......
...@@ -134,4 +134,9 @@ describe Gitlab::ReferenceExtractor, lib: true do ...@@ -134,4 +134,9 @@ describe Gitlab::ReferenceExtractor, lib: true do
expect(subject.all).to match_array([issue, label]) expect(subject.all).to match_array([issue, label])
end end
end end
describe '.references_pattern' do
subject { described_class.references_pattern }
it { is_expected.to be_kind_of Regexp }
end
end end
...@@ -15,11 +15,7 @@ describe Issues::MoveService, services: true do ...@@ -15,11 +15,7 @@ describe Issues::MoveService, services: true do
end end
let(:move_service) do let(:move_service) do
described_class.new(old_project, user, issue_params, old_issue, new_project_id) described_class.new(old_project, user, issue_params)
end
shared_context 'issue move requested' do
let(:new_project_id) { new_project.id }
end end
shared_context 'user can move issue' do shared_context 'user can move issue' do
...@@ -29,19 +25,13 @@ describe Issues::MoveService, services: true do ...@@ -29,19 +25,13 @@ describe Issues::MoveService, services: true do
end end
end end
context 'issue movable' do describe '#execute' do
include_context 'issue move requested' shared_context 'issue move executed' do
include_context 'user can move issue' let!(:new_issue) { move_service.execute(old_issue, new_project) }
describe '#move?' do
subject { move_service.move? }
it { is_expected.to be_truthy }
end end
describe '#execute' do context 'issue movable' do
shared_context 'issue move executed' do include_context 'user can move issue'
let!(:new_issue) { move_service.execute }
end
context 'generic issue' do context 'generic issue' do
include_context 'issue move executed' include_context 'issue move executed'
...@@ -164,57 +154,33 @@ describe Issues::MoveService, services: true do ...@@ -164,57 +154,33 @@ describe Issues::MoveService, services: true do
end end
end end
end end
end
end
context 'moving to same project' do
let(:new_project) { old_project }
include_context 'issue move requested'
include_context 'user can move issue'
it 'raises error' do
expect { move_service }
.to raise_error(StandardError, /Cannot move issue/)
end
end
context 'issue move not requested' do
let(:new_project_id) { nil }
describe '#move?' do context 'moving to same project' do
subject { move_service.move? } let(:new_project) { old_project }
context 'user do not have permissions to move issue' do
it { is_expected.to be_falsey }
end
context 'user has permissions to move issue' do it 'raises error' do
include_context 'user can move issue' expect { move_service.execute(old_issue, new_project) }
it { is_expected.to be_falsey } .to raise_error(StandardError, /Cannot move issue/)
end
end end
end end
end
describe 'move permissions' do
include_context 'issue move requested'
describe '#move?' do describe 'move permissions' do
subject { move_service.move? } let(:move) { move_service.execute(old_issue, new_project) }
context 'user is reporter in both projects' do context 'user is reporter in both projects' do
include_context 'user can move issue' include_context 'user can move issue'
it { is_expected.to be_truthy } it { expect { move }.to_not raise_error }
end end
context 'user is reporter only in new project' do context 'user is reporter only in new project' do
before { new_project.team << [user, :reporter] } before { new_project.team << [user, :reporter] }
it { is_expected.to be_falsey } it { expect { move }.to raise_error(StandardError, /permissions/) }
end end
context 'user is reporter only in old project' do context 'user is reporter only in old project' do
before { old_project.team << [user, :reporter] } before { old_project.team << [user, :reporter] }
it { is_expected.to be_falsey } it { expect { move }.to raise_error(StandardError, /permissions/) }
end end
context 'user is reporter in one project and guest in another' do context 'user is reporter in one project and guest in another' do
...@@ -223,7 +189,7 @@ describe Issues::MoveService, services: true do ...@@ -223,7 +189,7 @@ describe Issues::MoveService, services: true do
old_project.team << [user, :reporter] old_project.team << [user, :reporter]
end end
it { is_expected.to be_falsey } it { expect { move }.to raise_error(StandardError, /permissions/) }
end end
context 'issue has already been moved' do context 'issue has already been moved' do
...@@ -236,7 +202,7 @@ describe Issues::MoveService, services: true do ...@@ -236,7 +202,7 @@ describe Issues::MoveService, services: true do
moved_to: moved_to_issue) moved_to: moved_to_issue)
end end
it { is_expected.to be_falsey } it { expect { move }.to raise_error(StandardError, /permissions/) }
end end
end 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