Commit b789a326 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '7328-reorder-epics-in-an-epic' into 'master'

Add reordering of child epics

See merge request gitlab-org/gitlab-ee!9283
parents c437d33c 4c0292a4
...@@ -1116,6 +1116,7 @@ ActiveRecord::Schema.define(version: 20190220150130) do ...@@ -1116,6 +1116,7 @@ ActiveRecord::Schema.define(version: 20190220150130) do
t.integer "closed_by_id" t.integer "closed_by_id"
t.datetime "closed_at" t.datetime "closed_at"
t.integer "parent_id" t.integer "parent_id"
t.integer "relative_position"
t.index ["assignee_id"], name: "index_epics_on_assignee_id", using: :btree t.index ["assignee_id"], name: "index_epics_on_assignee_id", using: :btree
t.index ["author_id"], name: "index_epics_on_author_id", using: :btree t.index ["author_id"], name: "index_epics_on_author_id", using: :btree
t.index ["closed_by_id"], name: "index_epics_on_closed_by_id", using: :btree t.index ["closed_by_id"], name: "index_epics_on_closed_by_id", using: :btree
......
...@@ -73,9 +73,9 @@ These are dynamic dates in that if milestones are re-assigned to the issues, if ...@@ -73,9 +73,9 @@ These are dynamic dates in that if milestones are re-assigned to the issues, if
milestone dates change, or if issues are added or removed from the epic, then milestone dates change, or if issues are added or removed from the epic, then
the re-calculation will happen immediately to set a new dynamic date. the re-calculation will happen immediately to set a new dynamic date.
## Reordering issues in an epic ## Reordering issues and child epics
Drag and drop to reorder issues in an epic. New issues added to an epic appear at the top of the list. Drag and drop to reorder issues and child epics. New issues and child epics added to an epic appear at the top of the list.
## Deleting an epic ## Deleting an epic
......
...@@ -124,8 +124,8 @@ export default { ...@@ -124,8 +124,8 @@ export default {
const nextItemEl = itemEl.nextElementSibling; const nextItemEl = itemEl.nextElementSibling;
return { return {
beforeId: prevItemEl && parseInt(prevItemEl.dataset.epicIssueId, 0), beforeId: prevItemEl && parseInt(prevItemEl.dataset.orderingId, 0),
afterId: nextItemEl && parseInt(nextItemEl.dataset.epicIssueId, 0), afterId: nextItemEl && parseInt(nextItemEl.dataset.orderingId, 0),
}; };
}, },
reordered(event) { reordered(event) {
...@@ -147,6 +147,9 @@ export default { ...@@ -147,6 +147,9 @@ export default {
removeDraggingCursor() { removeDraggingCursor() {
document.body.classList.remove('is-dragging'); document.body.classList.remove('is-dragging');
}, },
issuableOrderingId({ epic_issue_id: epicIssueId, id }) {
return this.issuableType === 'issue' ? epicIssueId : id;
},
}, },
}; };
</script> </script>
...@@ -231,7 +234,7 @@ export default { ...@@ -231,7 +234,7 @@ export default {
'card-slim': canReorder, 'card-slim': canReorder,
}" }"
:data-key="issue.id" :data-key="issue.id"
:data-epic-issue-id="issue.epic_issue_id" :data-ordering-id="issuableOrderingId(issue)"
class="js-related-issues-token-list-item list-item pt-0 pb-0" class="js-related-issues-token-list-item list-item pt-0 pb-0"
> >
<issue-item <issue-item
......
...@@ -5,6 +5,12 @@ class Groups::EpicLinksController < Groups::EpicsController ...@@ -5,6 +5,12 @@ class Groups::EpicLinksController < Groups::EpicsController
before_action :check_nested_support! before_action :check_nested_support!
def update
result = EpicLinks::UpdateService.new(child_epic, current_user, params[:epic]).execute
render json: { message: result[:message] }, status: result[:http_status]
end
def destroy def destroy
result = ::Epics::UpdateService.new(group, current_user, { parent: nil }).execute(child_epic) result = ::Epics::UpdateService.new(group, current_user, { parent: nil }).execute(child_epic)
......
...@@ -13,6 +13,7 @@ module EE ...@@ -13,6 +13,7 @@ module EE
include Awardable include Awardable
include LabelEventable include LabelEventable
include Descendant include Descendant
include RelativePositioning
enum state: { opened: 1, closed: 2 } enum state: { opened: 1, closed: 2 }
...@@ -44,6 +45,10 @@ module EE ...@@ -44,6 +45,10 @@ module EE
validates :group, presence: true validates :group, presence: true
alias_attribute :parent_ids, :parent_id
scope :in_parents, -> (parent_ids) { where(parent_id: parent_ids) }
scope :order_start_or_end_date_asc, -> do scope :order_start_or_end_date_asc, -> do
# mysql returns null values first in opposite to postgres which # mysql returns null values first in opposite to postgres which
# returns them last by default # returns them last by default
...@@ -67,6 +72,10 @@ module EE ...@@ -67,6 +72,10 @@ module EE
reorder(::Gitlab::Database.nulls_last_order('start_date', 'DESC'), 'id DESC') reorder(::Gitlab::Database.nulls_last_order('start_date', 'DESC'), 'id DESC')
end end
scope :order_relative_position, -> do
reorder('relative_position ASC', 'id DESC')
end
def etag_caching_enabled? def etag_caching_enabled?
true true
end end
...@@ -118,6 +127,7 @@ module EE ...@@ -118,6 +127,7 @@ module EE
when 'start_date_desc' then order_start_date_desc when 'start_date_desc' then order_start_date_desc
when 'end_date_asc' then order_end_date_asc when 'end_date_asc' then order_end_date_asc
when 'end_date_desc' then order_end_date_desc when 'end_date_desc' then order_end_date_desc
when 'relative_position' then order_relative_position
else else
super super
end end
...@@ -127,6 +137,11 @@ module EE ...@@ -127,6 +137,11 @@ module EE
::Group ::Group
end end
# Column name used by RelativePositioning for scoping. This is not related to `parent_class` above.
def parent_column
:parent_id
end
# Return the deepest relation level for an epic. # Return the deepest relation level for an epic.
# Example 1: # Example 1:
# epic1 - parent: nil # epic1 - parent: nil
......
...@@ -14,10 +14,17 @@ module EpicLinks ...@@ -14,10 +14,17 @@ module EpicLinks
affected_epics = [issuable] affected_epics = [issuable]
affected_epics << referenced_epic if referenced_epic.parent affected_epics << referenced_epic if referenced_epic.parent
referenced_epic.update(parent: issuable) set_child_epic!(referenced_epic)
affected_epics.each(&:update_start_and_due_dates) affected_epics.each(&:update_start_and_due_dates)
end end
def set_child_epic!(child_epic)
child_epic.parent = issuable
child_epic.move_to_start
child_epic.save!
end
def linkable_issuables(epics) def linkable_issuables(epics)
@linkable_issuables ||= begin @linkable_issuables ||= begin
return [] unless can?(current_user, :admin_epic, issuable.group) return [] unless can?(current_user, :admin_epic, issuable.group)
......
...@@ -9,7 +9,7 @@ module EpicLinks ...@@ -9,7 +9,7 @@ module EpicLinks
def child_issuables def child_issuables
return [] unless issuable&.group&.feature_available?(:epics) return [] unless issuable&.group&.feature_available?(:epics)
EpicsFinder.new(current_user, parent_id: issuable.id, group_id: issuable.group.id).execute EpicsFinder.new(current_user, parent_id: issuable.id, group_id: issuable.group.id, sort: 'relative_position').execute
end end
override :serializer override :serializer
......
# frozen_string_literal: true
module EpicLinks
class UpdateService < BaseService
attr_reader :epic
private :epic
def initialize(epic, user, params)
@epic = epic
@current_user = user
@params = params
end
def execute
move_epic!
success
rescue ActiveRecord::RecordNotFound
error('Epic not found for given params', 404)
end
private
def move_epic!
return unless params[:move_after_id] || params[:move_before_id]
before_epic = Epic.in_parents(epic.parent_id).find(params[:move_before_id]) if params[:move_before_id]
after_epic = Epic.in_parents(epic.parent_id).find(params[:move_after_id]) if params[:move_after_id]
epic.move_between(before_epic, after_epic)
epic.save!
end
end
end
---
title: Add reordering of child epics
merge_request: 9283
author:
type: added
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddRelativePositionToEpics < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :epics, :relative_position, :integer
end
def down
remove_column :epics, :relative_position
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class SetDefaultPositionForChildEpics < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
current_position = nil
current_parent_id = nil
connection.exec_query(existing_child_epics_query.to_sql).rows.each do |id, parent_id|
if parent_id != current_parent_id
current_position = Gitlab::Database::MAX_INT_VALUE / 2
current_parent_id = parent_id
else
current_position += 500
end
update_position(id, current_position)
end
end
def down
end
private
def epics_table
@epics_table ||= Arel::Table.new(:epics)
end
def existing_child_epics_query
epics_table.project(epics_table[:id], epics_table[:parent_id])
.where(epics_table[:parent_id].not_eq(nil))
.order(epics_table[:parent_id], epics_table[:id].desc)
end
def update_position(epic_id, position)
execute "UPDATE epics SET relative_position = #{position} WHERE id = #{epic_id}"
end
end
...@@ -98,6 +98,61 @@ describe Groups::EpicLinksController, :postgresql do ...@@ -98,6 +98,61 @@ describe Groups::EpicLinksController, :postgresql do
end end
end end
describe 'PUT #update' do
before do
epic1.update(parent: parent_epic)
epic2.update(parent: parent_epic)
end
let(:move_before_epic) { epic2 }
subject do
put :update, params: { group_id: group, epic_id: parent_epic.to_param, id: epic1.id, epic: { move_before_id: move_before_epic.id } }
end
it_behaves_like 'unlicensed epics action'
context 'when epics are enabled' do
before do
stub_licensed_features(epics: true)
end
context 'when user has permissions to reorder epics' do
before do
group.add_developer(user)
end
it 'returns status 200' do
subject
expect(response).to have_gitlab_http_status(200)
end
it 'updates the epic position' do
expect { subject }.to change { epic1.reload.relative_position }
end
context 'when move_before_id is not a sibling epic' do
let(:move_before_epic) { create(:epic, group: group) }
it 'returns status 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
end
context 'when user does not have permissions to reorder epics' do
it 'returns status 403' do
subject
expect(response).to have_gitlab_http_status(403)
end
end
end
end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
before do before do
epic1.update(parent: parent_epic) epic1.update(parent: parent_epic)
......
...@@ -18,6 +18,13 @@ describe 'Epic Issues', :js do ...@@ -18,6 +18,13 @@ describe 'Epic Issues', :js do
] ]
end end
let!(:nested_epics) do
[
create(:epic, group: group, parent_id: epic.id, relative_position: 1),
create(:epic, group: group, parent_id: epic.id, relative_position: 2)
]
end
def visit_epic def visit_epic
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
...@@ -43,14 +50,23 @@ describe 'Epic Issues', :js do ...@@ -43,14 +50,23 @@ describe 'Epic Issues', :js do
expect(page).not_to have_selector('.js-related-issues-block h3.card-title button') expect(page).not_to have_selector('.js-related-issues-block h3.card-title button')
end end
it 'user cannot add new epics to the epic', :postgresql do
expect(page).not_to have_selector('.js-related-epics-block h3.card-title button')
end
it 'user cannot reorder issues in epic' do it 'user cannot reorder issues in epic' do
expect(page).not_to have_selector('.js-related-issues-block .js-related-issues-token-list-item.user-can-drag') expect(page).not_to have_selector('.js-related-issues-block .js-related-issues-token-list-item.user-can-drag')
end end
it 'user cannot reorder epics in epic', :postgresql do
expect(page).not_to have_selector('.js-related-epics-block .js-related-epics-token-list-item.user-can-drag')
end
end end
context 'when user is a group member' do context 'when user is a group member' do
let(:issue_to_add) { create(:issue, project: private_project) } let(:issue_to_add) { create(:issue, project: private_project) }
let(:issue_invalid) { create(:issue) } let(:issue_invalid) { create(:issue) }
let(:epic_to_add) { create(:epic, group: group) }
def add_issues(references) def add_issues(references)
find('.js-related-issues-block h3.card-title button').click find('.js-related-issues-block h3.card-title button').click
...@@ -64,6 +80,16 @@ describe 'Epic Issues', :js do ...@@ -64,6 +80,16 @@ describe 'Epic Issues', :js do
wait_for_requests wait_for_requests
end end
def add_epics(references)
find('.js-related-epics-block h3.card-title button').click
find('.js-related-epics-block .js-add-issuable-form-input').set(references)
find('.js-related-epics-block .js-add-issuable-form-input').send_keys(:tab)
find('.js-related-epics-block .js-add-issuable-form-add-button').click
wait_for_requests
end
before do before do
group.add_developer(user) group.add_developer(user)
visit_epic visit_epic
...@@ -85,6 +111,22 @@ describe 'Epic Issues', :js do ...@@ -85,6 +111,22 @@ describe 'Epic Issues', :js do
end end
end end
it 'user can see all epics of the group and delete the associations', :postgresql do
within('.js-related-epics-block ul.related-items-list') do
expect(page).to have_selector('li', count: 2)
expect(page).to have_content(nested_epics[0].title)
expect(page).to have_content(nested_epics[1].title)
first('li button.js-issue-item-remove-button').click
end
wait_for_requests
within('.js-related-epics-block ul.related-items-list') do
expect(page).to have_selector('li', count: 1)
end
end
it 'user cannot add new issues to the epic from another group' do it 'user cannot add new issues to the epic from another group' do
add_issues("#{issue_invalid.to_reference(full: true)}") add_issues("#{issue_invalid.to_reference(full: true)}")
...@@ -106,6 +148,19 @@ describe 'Epic Issues', :js do ...@@ -106,6 +148,19 @@ describe 'Epic Issues', :js do
end end
end end
it 'user can add new epics to the epic', :postgresql do
references = "#{epic_to_add.to_reference(full: true)}"
add_epics(references)
expect(page).not_to have_selector('.content-wrapper .alert-wrapper .flash-text')
expect(page).not_to have_content('No Epic found for given params')
within('.js-related-epics-block ul.related-items-list') do
expect(page).to have_selector('li', count: 3)
expect(page).to have_content(epic_to_add.title)
end
end
it 'user can reorder issues in epic' do it 'user can reorder issues in epic' do
expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(public_issue.title) expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(public_issue.title)
expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(private_issue.title) expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(private_issue.title)
...@@ -115,5 +170,15 @@ describe 'Epic Issues', :js do ...@@ -115,5 +170,15 @@ describe 'Epic Issues', :js do
expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(private_issue.title) expect(first('.js-related-issues-block .js-related-issues-token-list-item')).to have_content(private_issue.title)
expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(public_issue.title) expect(page.all('.js-related-issues-block .js-related-issues-token-list-item').last).to have_content(public_issue.title)
end end
it 'user can reorder epics in epic', :postgresql do
expect(first('.js-related-epics-block .js-related-issues-token-list-item')).to have_content(nested_epics[0].title)
expect(page.all('.js-related-epics-block .js-related-issues-token-list-item').last).to have_content(nested_epics[1].title)
drag_to(selector: '.js-related-epics-block .related-items-list', to_index: 1)
expect(first('.js-related-epics-block .js-related-issues-token-list-item')).to have_content(nested_epics[1].title)
expect(page.all('.js-related-epics-block .js-related-issues-token-list-item').last).to have_content(nested_epics[0].title)
end
end end
end end
...@@ -173,4 +173,72 @@ describe('RelatedIssuesBlock', () => { ...@@ -173,4 +173,72 @@ describe('RelatedIssuesBlock', () => {
}); });
}); });
}); });
describe('issuableOrderingId returns correct issuable order id when', () => {
it('issuableType is epic', () => {
vm = new RelatedIssuesBlock({
propsData: {
pathIdSeparator: '#',
issuableType: 'issue',
},
}).$mount();
const orderId = vm.issuableOrderingId(issuable1);
expect(orderId).toBe(issuable1.epic_issue_id);
});
it('issuableType is issue', () => {
vm = new RelatedIssuesBlock({
propsData: {
pathIdSeparator: '#',
issuableType: 'epic',
},
}).$mount();
const orderId = vm.issuableOrderingId(issuable1);
expect(orderId).toBe(issuable1.id);
});
});
describe('renders correct ordering id when', () => {
let relatedIssues;
beforeAll(() => {
relatedIssues = [issuable1, issuable2, issuable3, issuable4, issuable5];
});
it('issuableType is epic', () => {
vm = new RelatedIssuesBlock({
propsData: {
pathIdSeparator: '#',
issuableType: 'epic',
relatedIssues,
},
}).$mount();
const listItems = vm.$el.querySelectorAll('.list-item');
Array.from(listItems).forEach((item, index) => {
expect(Number(item.dataset.orderingId)).toBe(relatedIssues[index].id);
});
});
it('issuableType is issue', () => {
vm = new RelatedIssuesBlock({
propsData: {
pathIdSeparator: '#',
issuableType: 'issue',
relatedIssues,
},
}).$mount();
const listItems = vm.$el.querySelectorAll('.list-item');
Array.from(listItems).forEach((item, index) => {
expect(Number(item.dataset.orderingId)).toBe(relatedIssues[index].epic_issue_id);
});
});
});
}); });
...@@ -33,10 +33,10 @@ describe Epic do ...@@ -33,10 +33,10 @@ describe Epic do
end end
describe 'ordering' do describe 'ordering' do
let!(:epic1) { create(:epic, start_date: 7.days.ago, end_date: 3.days.ago, updated_at: 3.days.ago, created_at: 7.days.ago) } let!(:epic1) { create(:epic, start_date: 7.days.ago, end_date: 3.days.ago, updated_at: 3.days.ago, created_at: 7.days.ago, relative_position: 3) }
let!(:epic2) { create(:epic, start_date: 3.days.ago, updated_at: 10.days.ago, created_at: 12.days.ago) } let!(:epic2) { create(:epic, start_date: 3.days.ago, updated_at: 10.days.ago, created_at: 12.days.ago, relative_position: 1) }
let!(:epic3) { create(:epic, end_date: 5.days.ago, updated_at: 5.days.ago, created_at: 6.days.ago) } let!(:epic3) { create(:epic, end_date: 5.days.ago, updated_at: 5.days.ago, created_at: 6.days.ago, relative_position: 2) }
let!(:epic4) { create(:epic) } let!(:epic4) { create(:epic, relative_position: 4) }
def epics(order_by) def epics(order_by)
described_class.order_by(order_by) described_class.order_by(order_by)
...@@ -77,6 +77,10 @@ describe Epic do ...@@ -77,6 +77,10 @@ describe Epic do
it 'orders by created_at DESC' do it 'orders by created_at DESC' do
expect(epics(:created_desc)).to eq([epic4, epic3, epic1, epic2]) expect(epics(:created_desc)).to eq([epic4, epic3, epic1, epic2])
end end
it 'orders by relative_position ASC' do
expect(epics(:relative_position)).to eq([epic2, epic3, epic1, epic4])
end
end end
describe '#ancestors', :nested_groups do describe '#ancestors', :nested_groups do
......
...@@ -18,6 +18,14 @@ describe EpicLinks::CreateService, :postgresql do ...@@ -18,6 +18,14 @@ describe EpicLinks::CreateService, :postgresql do
expect(epic.reload.children).to include(epic_to_add) expect(epic.reload.children).to include(epic_to_add)
end end
it 'moves the new child epic to the top and moves the existing ones down' do
existing_child_epic = create(:epic, group: group, parent: epic, relative_position: 1000)
subject
expect(epic_to_add.reload.relative_position).to be < existing_child_epic.reload.relative_position
end
it 'returns success status' do it 'returns success status' do
expect(subject).to eq(status: :success) expect(subject).to eq(status: :success)
end end
......
...@@ -7,8 +7,8 @@ describe EpicLinks::ListService, :postgresql do ...@@ -7,8 +7,8 @@ describe EpicLinks::ListService, :postgresql do
let(:group) { create(:group, :public) } let(:group) { create(:group, :public) }
let(:parent_epic) { create(:epic, group: group) } let(:parent_epic) { create(:epic, group: group) }
let!(:epic1) { create :epic, group: group, parent: parent_epic } let!(:epic1) { create :epic, group: group, parent: parent_epic, relative_position: 1 }
let!(:epic2) { create :epic, group: group, parent: parent_epic } let!(:epic2) { create :epic, group: group, parent: parent_epic, relative_position: 2 }
def epics_to_results(epics) def epics_to_results(epics)
epics.map do |epic| epics.map do |epic|
...@@ -47,22 +47,22 @@ describe EpicLinks::ListService, :postgresql do ...@@ -47,22 +47,22 @@ describe EpicLinks::ListService, :postgresql do
it 'returns related issues JSON' do it 'returns related issues JSON' do
expected_result = epics_to_results([epic1, epic2]) expected_result = epics_to_results([epic1, epic2])
expect(subject).to match_array(expected_result) expect(subject).to eq(expected_result)
end end
end end
context 'with nested groups' do context 'with nested groups' do
let(:subgroup1) { create(:group, :private, parent: group) } let(:subgroup1) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) } let(:subgroup2) { create(:group, :private, parent: group) }
let!(:epic_subgroup1) { create :epic, group: subgroup1, parent: parent_epic } let!(:epic_subgroup1) { create :epic, group: subgroup1, parent: parent_epic, relative_position: 10 }
let!(:epic_subgroup2) { create :epic, group: subgroup2, parent: parent_epic } let!(:epic_subgroup2) { create :epic, group: subgroup2, parent: parent_epic, relative_position: 5 }
it 'returns all child epics for a group member' do it 'returns all child epics for a group member' do
group.add_developer(user) group.add_developer(user)
expected_result = epics_to_results([epic1, epic2, epic_subgroup1, epic_subgroup2]) expected_result = epics_to_results([epic1, epic2, epic_subgroup2, epic_subgroup1])
expect(subject).to match_array(expected_result) expect(subject).to eq(expected_result)
end end
it 'returns only some child epics for a subgroup member' do it 'returns only some child epics for a subgroup member' do
...@@ -70,7 +70,7 @@ describe EpicLinks::ListService, :postgresql do ...@@ -70,7 +70,7 @@ describe EpicLinks::ListService, :postgresql do
expected_result = epics_to_results([epic1, epic2, epic_subgroup2]) expected_result = epics_to_results([epic1, epic2, epic_subgroup2])
expect(subject).to match_array(expected_result) expect(subject).to eq(expected_result)
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe EpicLinks::UpdateService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:parent_epic) { create(:epic, group: group) }
let(:child_epic1) { create(:epic, group: group, parent: parent_epic, relative_position: 1) }
let(:child_epic2) { create(:epic, group: group, parent: parent_epic, relative_position: 2) }
let(:child_epic3) { create(:epic, group: group, parent: parent_epic, relative_position: 300) }
let(:child_epic4) { create(:epic, group: group, parent: parent_epic, relative_position: 400) }
let(:epic_to_move) { child_epic3 }
subject do
described_class.new(epic_to_move, user, params).execute
end
def ordered_epics
Epic.where(parent_id: parent_epic.id).order('relative_position, id DESC')
end
describe '#execute' do
context 'when params are nil' do
let(:params) { { move_before_id: nil, move_after_id: nil } }
it 'does not change order of child epics' do
expect(subject).to include(status: :success)
expect(ordered_epics).to eq([child_epic1, child_epic2, child_epic3, child_epic4])
end
end
context 'when moving to start' do
let(:params) { { move_before_id: nil, move_after_id: child_epic1.id } }
it 'reorders child epics' do
expect(subject).to include(status: :success)
expect(ordered_epics).to eq([child_epic3, child_epic1, child_epic2, child_epic4])
end
end
context 'when moving to end' do
let(:params) { { move_before_id: child_epic4.id, move_after_id: nil } }
it 'reorders child epics' do
expect(subject).to include(status: :success)
expect(ordered_epics).to eq([child_epic1, child_epic2, child_epic4, child_epic3])
end
end
context 'when moving between siblings' do
let(:params) { { move_before_id: child_epic1.id, move_after_id: child_epic2.id } }
it 'reorders child epics' do
expect(subject).to include(status: :success)
expect(ordered_epics).to eq([child_epic1, child_epic3, child_epic2, child_epic4])
end
end
context 'when params are invalid' do
let(:other_epic) { create(:epic, group: group) }
shared_examples 'returns error' do
it 'does not change order of child epics and returns error' do
expect(subject).to include(message: 'Epic not found for given params', status: :error, http_status: 404)
expect(ordered_epics).to eq([child_epic1, child_epic2, child_epic3, child_epic4])
end
end
context 'when move_before_id is not a child of parent epic' do
let(:params) { { move_before_id: other_epic.id, move_after_id: child_epic2.id } }
it_behaves_like 'returns error'
end
context 'when move_after_id is not a child of parent epic' do
let(:params) { { move_before_id: child_epic1.id, move_after_id: other_epic.id } }
it_behaves_like 'returns error'
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