Commit 0f7b5de4 authored by Sean McGivern's avatar Sean McGivern

Merge branch '7249-group-bulk-edit-issues-milestone-ce' into 'master'

CE Port of Allow bulk update for group issues

See merge request gitlab-org/gitlab-ce!30358
parents a87e2c99 14e2412e
/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */ /* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
...@@ -7,7 +7,7 @@ import Flash from './flash'; ...@@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale'; import { __ } from './locale';
export default { export default {
init({ container, form, issues, prefixId } = {}) { init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_'; this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update'); this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select'); this.$labelDropdown = this.form.find('.js-label-select');
......
...@@ -2,26 +2,13 @@ import $ from 'jquery'; ...@@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
import { s__, __ } from './locale'; import { s__, __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar'; import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
export default class IssuableIndex { export default class IssuableIndex {
constructor(pagePrefix) { constructor(pagePrefix) {
this.initBulkUpdate(pagePrefix); issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken(); IssuableIndex.resetIncomingEmailToken();
} }
initBulkUpdate(pagePrefix) {
const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (userCanBulkUpdate && !alreadyInitialized) {
IssuableBulkUpdateActions.init({
prefixId: pagePrefix,
});
this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
}
}
static resetIncomingEmailToken() { static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset'); const $resetToken = $('.incoming-email-token-reset');
......
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import issuableBulkUpdateActions from './issuable_bulk_update_actions';
export default {
bulkUpdateSidebar: null,
init(prefixId) {
const bulkUpdateEl = document.querySelector('.issues-bulk-update');
const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (bulkUpdateEl && !alreadyInitialized) {
issuableBulkUpdateActions.init({ prefixId });
this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
}
return this.bulkUpdateSidebar;
},
};
import projectSelect from '~/project_select'; import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search'; import initFilteredSearch from '~/pages/search/init_filtered_search';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants'; import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering'; import initManualOrdering from '~/manual_ordering';
const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({ initFilteredSearch({
page: FILTERED_SEARCH.ISSUES, page: FILTERED_SEARCH.ISSUES,
......
...@@ -92,7 +92,7 @@ module IssuableActions ...@@ -92,7 +92,7 @@ module IssuableActions
end end
def bulk_update def bulk_update
result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name) result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count] quantity = result[:count]
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
...@@ -181,7 +181,7 @@ module IssuableActions ...@@ -181,7 +181,7 @@ module IssuableActions
end end
def authorize_admin_issuable! def authorize_admin_issuable!
unless can?(current_user, :"admin_#{resource_name}", @project) # rubocop:disable Gitlab/ModuleWithInstanceVariables unless can?(current_user, :"admin_#{resource_name}", parent)
return access_denied! return access_denied!
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
module Issuable module Issuable
class BulkUpdateService < IssuableBaseService class BulkUpdateService
include Gitlab::Allowable
attr_accessor :current_user, :params
def initialize(user = nil, params = {})
@current_user, @params = user, params.dup
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def execute(type) def execute(type)
model_class = type.classify.constantize model_class = type.classify.constantize
......
- @can_bulk_update = can?(current_user, :admin_issue, @group)
- page_title "Issues" - page_title "Issues"
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
...@@ -9,8 +11,15 @@ ...@@ -9,8 +11,15 @@
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
= render 'shared/issuable/feed_buttons' = render 'shared/issuable/feed_buttons'
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
= render 'shared/issues' = render 'shared/issues'
...@@ -2,14 +2,14 @@ import $ from 'jquery'; ...@@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter'; import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index'; import IssuableIndex from '~/issuable_index';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => { describe('Issuable', () => {
let Issuable;
describe('initBulkUpdate', () => { describe('initBulkUpdate', () => {
it('should not set bulkUpdateSidebar', () => { it('should not set bulkUpdateSidebar', () => {
Issuable = new IssuableIndex('issue_'); new IssuableIndex('issue_'); // eslint-disable-line no-new
expect(Issuable.bulkUpdateSidebar).not.toBeDefined(); expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeNull();
}); });
it('should set bulkUpdateSidebar', () => { it('should set bulkUpdateSidebar', () => {
...@@ -17,9 +17,9 @@ describe('Issuable', () => { ...@@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update'); element.classList.add('issues-bulk-update');
document.body.appendChild(element); document.body.appendChild(element);
Issuable = new IssuableIndex('issue_'); new IssuableIndex('issue_'); // eslint-disable-line no-new
expect(Issuable.bulkUpdateSidebar).toBeDefined(); expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
}); });
}); });
...@@ -36,7 +36,7 @@ describe('Issuable', () => { ...@@ -36,7 +36,7 @@ describe('Issuable', () => {
input.setAttribute('id', 'issuable_email'); input.setAttribute('id', 'issuable_email');
document.body.appendChild(input); document.body.appendChild(input);
Issuable = new IssuableIndex('issue_'); new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios); mock = new MockAdaptor(axios);
......
...@@ -11,343 +11,371 @@ describe Issuable::BulkUpdateService do ...@@ -11,343 +11,371 @@ describe Issuable::BulkUpdateService do
.reverse_merge(issuable_ids: Array(issuables).map(&:id).join(',')) .reverse_merge(issuable_ids: Array(issuables).map(&:id).join(','))
type = Array(issuables).first.model_name.param_key type = Array(issuables).first.model_name.param_key
Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute(type) Issuable::BulkUpdateService.new(user, bulk_update_params).execute(type)
end end
describe 'close issues' do shared_examples 'updates milestones' do
let(:issues) { create_list(:issue, 2, project: project) } it 'succeeds' do
result = bulk_update(issues, milestone_id: milestone.id)
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'close')
expect(result[:success]).to be_truthy expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count) expect(result[:count]).to eq(issues.count)
end end
it 'closes all the issues passed' do it 'updates the issues milestone' do
bulk_update(issues, state_event: 'close') bulk_update(issues, milestone_id: milestone.id)
expect(project.issues.opened).to be_empty issues.each do |issue|
expect(project.issues.closed).not_to be_empty expect(issue.reload.milestone).to eq(milestone)
end
end end
end
context 'when issue for a different project is created' do context 'with project issues' do
let(:private_project) { create(:project, :private) } describe 'close issues' do
let(:issue) { create(:issue, project: private_project, author: user) } let(:issues) { create_list(:issue, 2, project: project) }
context 'when user has access to the project' do it 'succeeds and returns the correct number of issues updated' do
it 'closes all issues passed' do result = bulk_update(issues, state_event: 'close')
private_project.add_maintainer(user)
bulk_update(issues + [issue], state_event: 'close') expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
expect(project.issues.opened).to be_empty it 'closes all the issues passed' do
expect(project.issues.closed).not_to be_empty bulk_update(issues, state_event: 'close')
expect(private_project.issues.closed).not_to be_empty
end expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
end end
context 'when user does not have access to project' do context 'when issue for a different project is created' do
it 'only closes all issues that the user has access to' do let(:private_project) { create(:project, :private) }
bulk_update(issues + [issue], state_event: 'close') let(:issue) { create(:issue, project: private_project, author: user) }
context 'when user has access to the project' do
it 'closes all issues passed' do
private_project.add_maintainer(user)
bulk_update(issues + [issue], state_event: 'close')
expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
expect(private_project.issues.closed).not_to be_empty
end
end
context 'when user does not have access to project' do
it 'only closes all issues that the user has access to' do
bulk_update(issues + [issue], state_event: 'close')
expect(project.issues.opened).to be_empty expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty expect(project.issues.closed).not_to be_empty
expect(private_project.issues.closed).to be_empty expect(private_project.issues.closed).to be_empty
end
end end
end end
end end
end
describe 'reopen issues' do describe 'reopen issues' do
let(:issues) { create_list(:closed_issue, 2, project: project) } let(:issues) { create_list(:closed_issue, 2, project: project) }
it 'succeeds and returns the correct number of issues updated' do it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'reopen') result = bulk_update(issues, state_event: 'reopen')
expect(result[:success]).to be_truthy expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count) expect(result[:count]).to eq(issues.count)
end end
it 'reopens all the issues passed' do it 'reopens all the issues passed' do
bulk_update(issues, state_event: 'reopen') bulk_update(issues, state_event: 'reopen')
expect(project.issues.closed).to be_empty expect(project.issues.closed).to be_empty
expect(project.issues.opened).not_to be_empty expect(project.issues.opened).not_to be_empty
end
end end
end
describe 'updating merge request assignee' do describe 'updating merge request assignee' do
let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) } let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
context 'when the new assignee ID is a valid user' do context 'when the new assignee ID is a valid user' do
it 'succeeds' do it 'succeeds' do
new_assignee = create(:user) new_assignee = create(:user)
project.add_developer(new_assignee) project.add_developer(new_assignee)
result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id]) result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
expect(result[:success]).to be_truthy expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1) expect(result[:count]).to eq(1)
end end
it 'updates the assignee to the user ID passed' do it 'updates the assignee to the user ID passed' do
assignee = create(:user) assignee = create(:user)
project.add_developer(assignee) project.add_developer(assignee)
expect { bulk_update(merge_request, assignee_ids: [assignee.id]) } expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
.to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id]) .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
end
end end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do context "when the new assignee ID is #{IssuableFinder::NONE}" do
it 'unassigns the issues' do it 'unassigns the issues' do
expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) } expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
.to change { merge_request.reload.assignee_ids }.to([]) .to change { merge_request.reload.assignee_ids }.to([])
end
end end
end
context 'when the new assignee ID is not present' do context 'when the new assignee ID is not present' do
it 'does not unassign' do it 'does not unassign' do
expect { bulk_update(merge_request, assignee_ids: []) } expect { bulk_update(merge_request, assignee_ids: []) }
.not_to change { merge_request.reload.assignee_ids } .not_to change { merge_request.reload.assignee_ids }
end
end end
end end
end
describe 'updating issue assignee' do describe 'updating issue assignee' do
let(:issue) { create(:issue, project: project, assignees: [user]) } let(:issue) { create(:issue, project: project, assignees: [user]) }
context 'when the new assignee ID is a valid user' do context 'when the new assignee ID is a valid user' do
it 'succeeds' do it 'succeeds' do
new_assignee = create(:user) new_assignee = create(:user)
project.add_developer(new_assignee) project.add_developer(new_assignee)
result = bulk_update(issue, assignee_ids: [new_assignee.id]) result = bulk_update(issue, assignee_ids: [new_assignee.id])
expect(result[:success]).to be_truthy expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1) expect(result[:count]).to eq(1)
end end
it 'updates the assignee to the user ID passed' do it 'updates the assignee to the user ID passed' do
assignee = create(:user) assignee = create(:user)
project.add_developer(assignee) project.add_developer(assignee)
expect { bulk_update(issue, assignee_ids: [assignee.id]) } expect { bulk_update(issue, assignee_ids: [assignee.id]) }
.to change { issue.reload.assignees.first }.from(user).to(assignee) .to change { issue.reload.assignees.first }.from(user).to(assignee)
end
end end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do context "when the new assignee ID is #{IssuableFinder::NONE}" do
it "unassigns the issues" do it "unassigns the issues" do
expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) } expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
.to change { issue.reload.assignees.count }.from(1).to(0) .to change { issue.reload.assignees.count }.from(1).to(0)
end
end end
end
context 'when the new assignee ID is not present' do context 'when the new assignee ID is not present' do
it 'does not unassign' do it 'does not unassign' do
expect { bulk_update(issue, assignee_ids: []) } expect { bulk_update(issue, assignee_ids: []) }
.not_to change { issue.reload.assignees } .not_to change { issue.reload.assignees }
end
end end
end end
end
describe 'updating milestones' do
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project) }
it 'succeeds' do describe 'updating milestones' do
result = bulk_update(issue, milestone_id: milestone.id) let(:issues) { [create(:issue, project: project)] }
let(:milestone) { create(:milestone, project: project) }
expect(result[:success]).to be_truthy it_behaves_like 'updates milestones'
expect(result[:count]).to eq(1)
end end
it 'updates the issue milestone' do describe 'updating labels' do
expect { bulk_update(issue, milestone_id: milestone.id) } def create_issue_with_labels(labels)
.to change { issue.reload.milestone }.from(nil).to(milestone) create(:labeled_issue, project: project, labels: labels)
end end
end
describe 'updating labels' do
def create_issue_with_labels(labels)
create(:labeled_issue, project: project, labels: labels)
end
let(:bug) { create(:label, project: project) } let(:bug) { create(:label, project: project) }
let(:regression) { create(:label, project: project) } let(:regression) { create(:label, project: project) }
let(:merge_requests) { create(:label, project: project) } let(:merge_requests) { create(:label, project: project) }
let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
let(:issue_no_labels) { create(:issue, project: project) }
let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
let(:labels) { [] }
let(:add_labels) { [] }
let(:remove_labels) { [] }
let(:bulk_update_params) do
{
label_ids: labels.map(&:id),
add_label_ids: add_labels.map(&:id),
remove_label_ids: remove_labels.map(&:id)
}
end
before do let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
bulk_update(issues, bulk_update_params) let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
end let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
let(:issue_no_labels) { create(:issue, project: project) }
let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
context 'when label_ids are passed' do let(:labels) { [] }
let(:issues) { [issue_all_labels, issue_no_labels] } let(:add_labels) { [] }
let(:labels) { [bug, regression] } let(:remove_labels) { [] }
it 'updates the labels of all issues passed to the labels passed' do let(:bulk_update_params) do
expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) {
label_ids: labels.map(&:id),
add_label_ids: add_labels.map(&:id),
remove_label_ids: remove_labels.map(&:id)
}
end end
it 'does not update issues not passed in' do before do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) bulk_update(issues, bulk_update_params)
end end
context 'when those label IDs are empty' do context 'when label_ids are passed' do
let(:labels) { [] } let(:issues) { [issue_all_labels, issue_no_labels] }
let(:labels) { [bug, regression] }
it 'updates the issues passed to have no labels' do it 'updates the labels of all issues passed to the labels passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
end end
end
end
context 'when add_label_ids are passed' do it 'does not update issues not passed in' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
let(:add_labels) { [bug, regression, merge_requests] } end
it 'adds those label IDs to all issues passed' do context 'when those label IDs are empty' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) let(:labels) { [] }
end
it 'does not update issues not passed in' do it 'updates the issues passed to have no labels' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
end
end
end end
end
context 'when remove_label_ids are passed' do context 'when add_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:remove_labels) { [bug, regression, merge_requests] } let(:add_labels) { [bug, regression, merge_requests] }
it 'removes those label IDs from all issues passed' do it 'adds those label IDs to all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
end end
it 'does not update issues not passed in' do it 'does not update issues not passed in' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end end
end
context 'when add_label_ids and remove_label_ids are passed' do context 'when remove_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:add_labels) { [bug] } let(:remove_labels) { [bug, regression, merge_requests] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do it 'removes those label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
end end
it 'removes the label IDs from all issues passed' do it 'does not update issues not passed in' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end end
it 'does not update issues not passed in' do context 'when add_label_ids and remove_label_ids are passed' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
end let(:add_labels) { [bug] }
end let(:remove_labels) { [merge_requests] }
context 'when add_label_ids and label_ids are passed' do it 'adds the label IDs to all issues passed' do
let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
let(:labels) { [merge_requests] } end
let(:add_labels) { [regression] }
it 'adds the label IDs to all issues passed' do it 'removes the label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
end end
it 'ignores the label IDs parameter' do it 'does not update issues not passed in' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end end
it 'does not update issues not passed in' do context 'when add_label_ids and label_ids are passed' do
expect(issue_no_labels.label_ids).to be_empty let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
end let(:labels) { [merge_requests] }
end let(:add_labels) { [regression] }
context 'when remove_label_ids and label_ids are passed' do it 'adds the label IDs to all issues passed' do
let(:issues) { [issue_no_labels, issue_bug_and_regression] } expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
let(:labels) { [merge_requests] } end
let(:remove_labels) { [regression] }
it 'removes the label IDs from all issues passed' do it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
end end
it 'ignores the label IDs parameter' do it 'does not update issues not passed in' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) expect(issue_no_labels.label_ids).to be_empty
end
end end
it 'does not update issues not passed in' do context 'when remove_label_ids and label_ids are passed' do
expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) let(:issues) { [issue_no_labels, issue_bug_and_regression] }
end let(:labels) { [merge_requests] }
end let(:remove_labels) { [regression] }
context 'when add_label_ids, remove_label_ids, and label_ids are passed' do it 'removes the label IDs from all issues passed' do
let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
let(:labels) { [regression] } end
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
end end
it 'removes the label IDs from all issues passed' do it 'does not update issues not passed in' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
end
end end
it 'ignores the label IDs parameter' do context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
end let(:labels) { [regression] }
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'does not update issues not passed in' do it 'adds the label IDs to all issues passed' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
end
it 'removes the label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
end
it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
end
it 'does not update issues not passed in' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end end
end end
end
describe 'subscribe to issues' do describe 'subscribe to issues' do
let(:issues) { create_list(:issue, 2, project: project) } let(:issues) { create_list(:issue, 2, project: project) }
it 'subscribes the given user' do it 'subscribes the given user' do
bulk_update(issues, subscription_event: 'subscribe') bulk_update(issues, subscription_event: 'subscribe')
expect(issues).to all(be_subscribed(user, project)) expect(issues).to all(be_subscribed(user, project))
end
end end
end
describe 'unsubscribe from issues' do describe 'unsubscribe from issues' do
let(:issues) do let(:issues) do
create_list(:closed_issue, 2, project: project) do |issue| create_list(:closed_issue, 2, project: project) do |issue|
issue.subscriptions.create(user: user, project: project, subscribed: true) issue.subscriptions.create(user: user, project: project, subscribed: true)
end
end
it 'unsubscribes the given user' do
bulk_update(issues, subscription_event: 'unsubscribe')
issues.each do |issue|
expect(issue).not_to be_subscribed(user, project)
end
end end
end end
end
it 'unsubscribes the given user' do context 'with group issues' do
bulk_update(issues, subscription_event: 'unsubscribe') let(:group) { create(:group) }
issues.each do |issue| context 'updating milestone' do
expect(issue).not_to be_subscribed(user, project) let(:milestone) { create(:milestone, group: group) }
let(:project1) { create(:project, :repository, group: group) }
let(:project2) { create(:project, :repository, group: group) }
let(:issue1) { create(:issue, project: project1) }
let(:issue2) { create(:issue, project: project2) }
let(:issues) { [issue1, issue2] }
before do
group.add_maintainer(user)
end end
it_behaves_like 'updates milestones'
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