From 115fca13c3712c787f2a637e936cb1174483555a Mon Sep 17 00:00:00 2001
From: Douglas Barbosa Alexandre <dbalexandre@gmail.com>
Date: Thu, 28 Jul 2016 03:33:41 -0300
Subject: [PATCH] Add service to move issues between lists

---
 app/services/boards/issues/move_service.rb    |  58 ++++++++
 .../boards/issues/move_service_spec.rb        | 125 ++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100644 app/services/boards/issues/move_service.rb
 create mode 100644 spec/services/boards/issues/move_service_spec.rb

diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
new file mode 100644
index 00000000000..d893a52cf9b
--- /dev/null
+++ b/app/services/boards/issues/move_service.rb
@@ -0,0 +1,58 @@
+module Boards
+  module Issues
+    class MoveService
+      def initialize(project, user, params = {})
+        @project = project
+        @user = user
+        @params = params.dup
+      end
+
+      def execute
+        return false unless issue.present?
+
+        update_service.execute(issue)
+        reopen_service.execute(issue) if moving_from.done?
+        close_service.execute(issue)  if moving_to.done?
+
+        true
+      end
+
+      private
+
+      attr_reader :project, :user, :params
+
+      delegate :board, to: :project
+
+      def issue
+        @issue ||= project.issues.visible_to_user(user).find_by!(iid: params[:id])
+      end
+
+      def moving_from
+        @moving_from ||= board.lists.find(params[:from])
+      end
+
+      def moving_to
+        @moving_to ||= board.lists.find(params[:to])
+      end
+
+      def close_service
+        ::Issues::CloseService.new(project, user)
+      end
+
+      def reopen_service
+        ::Issues::ReopenService.new(project, user)
+      end
+
+      def update_service
+        ::Issues::UpdateService.new(project, user, issue_params)
+      end
+
+      def issue_params
+        {
+          add_label_ids: [moving_to.label_id].compact,
+          remove_label_ids: [moving_from.label_id].compact
+        }
+      end
+    end
+  end
+end
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
new file mode 100644
index 00000000000..a0f72dddc09
--- /dev/null
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+describe Boards::Issues::MoveService, services: true do
+  describe '#execute' do
+    let(:user)    { create(:user) }
+    let(:project) { create(:project_with_board) }
+    let(:board)   { project.board }
+
+    let(:bug) { create(:label, project: project, name: 'Bug') }
+    let(:development) { create(:label, project: project, name: 'Development') }
+    let(:testing)  { create(:label, project: project, name: 'Testing') }
+
+    let(:backlog) { create(:backlog_list, board: board) }
+    let(:list1)   { create(:label_list, board: board, label: development) }
+    let(:list2)   { create(:label_list, board: board, label: testing) }
+    let(:done)    { create(:done_list, board: board) }
+
+    before do
+      project.team << [user, :developer]
+    end
+
+    context 'when moving from backlog' do
+      it 'adds the label of the list it goes to' do
+        issue = create(:labeled_issue, project: project, labels: [bug])
+        params = { id: issue.iid, from: backlog.id, to: list1.id }
+
+        described_class.new(project, user, params).execute
+
+        expect(issue.reload.labels).to contain_exactly(bug, development)
+      end
+    end
+
+    context 'when moving to backlog' do
+      it 'remove the label of the list it came from' do
+        issue = create(:labeled_issue, project: project, labels: [bug, development])
+        params = { id: issue.iid, from: list1.id, to: backlog.id }
+
+        described_class.new(project, user, params).execute
+
+        expect(issue.reload.labels).to contain_exactly(bug)
+      end
+    end
+
+    context 'when moving from backlog to done' do
+      it 'closes the issue' do
+        issue = create(:labeled_issue, project: project, labels: [bug])
+        params = { id: issue.iid, from: backlog.id, to: done.id }
+
+        described_class.new(project, user, params).execute
+        issue.reload
+
+        expect(issue.labels).to contain_exactly(bug)
+        expect(issue).to be_closed
+      end
+    end
+
+    context 'when moving an issue between lists' do
+      let(:issue)  { create(:labeled_issue, project: project, labels: [bug, development]) }
+      let(:params) { { id: issue.iid, from: list1.id, to: list2.id } }
+
+      it 'delegates the label changes to Issues::UpdateService' do
+        expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
+
+        described_class.new(project, user, params).execute
+      end
+
+      it 'removes the label from the list it came from and adds the label of the list it goes to' do
+        described_class.new(project, user, params).execute
+
+        expect(issue.reload.labels).to contain_exactly(bug, testing)
+      end
+    end
+
+    context 'when moving to done' do
+      let(:issue)  { create(:labeled_issue, project: project, labels: [bug, testing]) }
+      let(:params) { { id: issue.iid, from: list2.id, to: done.id } }
+
+      it 'delegates the close proceedings to Issues::CloseService' do
+        expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
+
+        described_class.new(project, user, params).execute
+      end
+
+      it 'remove the label of the list it came from and close the issue' do
+        described_class.new(project, user, params).execute
+        issue.reload
+
+        expect(issue.labels).to contain_exactly(bug)
+        expect(issue).to be_closed
+      end
+    end
+
+    context 'when moving from done' do
+      let(:issue)  { create(:labeled_issue, :closed, project: project, labels: [bug]) }
+      let(:params) { { id: issue.iid, from: done.id, to: list2.id } }
+
+      it 'delegates the re-open proceedings to Issues::ReopenService' do
+        expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
+
+        described_class.new(project, user, params).execute
+      end
+
+      it 'adds the label of the list it goes to and reopen the issue' do
+        described_class.new(project, user, params).execute
+        issue.reload
+
+        expect(issue.labels).to contain_exactly(bug, testing)
+        expect(issue).to be_reopened
+      end
+    end
+
+    context 'when moving from done to backlog' do
+      it 'reopens the issue' do
+        issue = create(:labeled_issue, :closed, project: project, labels: [bug])
+        params = { id: issue.iid, from: done.id, to: backlog.id }
+
+        described_class.new(project, user, params).execute
+        issue.reload
+
+        expect(issue.labels).to contain_exactly(bug)
+        expect(issue).to be_reopened
+      end
+    end
+  end
+end
-- 
GitLab