CE Port of Allow bulk update for group issues

This is a port MR for CE

Original MR:

https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14141
This commit is contained in:
Fatih Acet 2019-07-09 11:46:16 +00:00 committed by Sean McGivern
parent a87e2c99dc
commit 14e2412ee4
9 changed files with 408 additions and 353 deletions

View file

@ -1,4 +1,4 @@
/* 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 'underscore';
@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale';
export default {
init({ container, form, issues, prefixId } = {}) {
init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');

View file

@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { s__, __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
this.initBulkUpdate(pagePrefix);
issuableInitBulkUpdateSidebar.init(pagePrefix);
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() {
const $resetToken = $('.incoming-email-token-reset');

View file

@ -0,0 +1,19 @@
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;
},
};

View file

@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering';
const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,

View file

@ -92,7 +92,7 @@ module IssuableActions
end
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]
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
@ -181,7 +181,7 @@ module IssuableActions
end
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!
end
end

View file

@ -1,7 +1,15 @@
# frozen_string_literal: true
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
def execute(type)
model_class = type.classify.constantize

View file

@ -1,3 +1,5 @@
- @can_bulk_update = can?(current_user, :admin_issue, @group)
- page_title "Issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
@ -9,8 +11,15 @@
= render 'shared/issuable/nav', type: :issues
.nav-controls
= 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/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'

View file

@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => {
let Issuable;
describe('initBulkUpdate', () => {
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', () => {
@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update');
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', () => {
input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
Issuable = new IssuableIndex('issue_');
new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios);

View file

@ -11,343 +11,371 @@ describe Issuable::BulkUpdateService do
.reverse_merge(issuable_ids: Array(issuables).map(&:id).join(','))
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
describe 'close issues' do
let(:issues) { create_list(:issue, 2, project: project) }
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[:count]).to eq(issues.count)
end
it 'closes all the issues passed' do
bulk_update(issues, state_event: 'close')
expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
end
context 'when issue for a different project is created' do
let(:private_project) { create(:project, :private) }
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.closed).not_to be_empty
expect(private_project.issues.closed).to be_empty
end
end
end
end
describe 'reopen issues' do
let(:issues) { create_list(:closed_issue, 2, project: project) }
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'reopen')
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
it 'reopens all the issues passed' do
bulk_update(issues, state_event: 'reopen')
expect(project.issues.closed).to be_empty
expect(project.issues.opened).not_to be_empty
end
end
describe 'updating merge request assignee' do
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
it 'succeeds' do
new_assignee = create(:user)
project.add_developer(new_assignee)
result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
assignee = create(:user)
project.add_developer(assignee)
expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
.to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
it 'unassigns the issues' do
expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
.to change { merge_request.reload.assignee_ids }.to([])
end
end
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(merge_request, assignee_ids: []) }
.not_to change { merge_request.reload.assignee_ids }
end
end
end
describe 'updating issue assignee' do
let(:issue) { create(:issue, project: project, assignees: [user]) }
context 'when the new assignee ID is a valid user' do
it 'succeeds' do
new_assignee = create(:user)
project.add_developer(new_assignee)
result = bulk_update(issue, assignee_ids: [new_assignee.id])
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
assignee = create(:user)
project.add_developer(assignee)
expect { bulk_update(issue, assignee_ids: [assignee.id]) }
.to change { issue.reload.assignees.first }.from(user).to(assignee)
end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
it "unassigns the issues" do
expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
.to change { issue.reload.assignees.count }.from(1).to(0)
end
end
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(issue, assignee_ids: []) }
.not_to change { issue.reload.assignees }
end
end
end
describe 'updating milestones' do
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project) }
shared_examples 'updates milestones' do
it 'succeeds' do
result = bulk_update(issue, milestone_id: milestone.id)
result = bulk_update(issues, milestone_id: milestone.id)
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
expect(result[:count]).to eq(issues.count)
end
it 'updates the issue milestone' do
expect { bulk_update(issue, milestone_id: milestone.id) }
.to change { issue.reload.milestone }.from(nil).to(milestone)
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(:regression) { 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
bulk_update(issues, bulk_update_params)
end
context 'when label_ids are passed' do
let(:issues) { [issue_all_labels, issue_no_labels] }
let(:labels) { [bug, regression] }
it 'updates the labels of all issues passed to the labels passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&: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
context 'when those label IDs are empty' do
let(:labels) { [] }
it 'updates the issues passed to have no labels' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
end
end
end
context 'when add_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:add_labels) { [bug, regression, merge_requests] }
it 'adds those label IDs to all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&: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
context 'when remove_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:remove_labels) { [bug, regression, merge_requests] }
it 'removes those label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
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
context 'when add_label_ids and remove_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do
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 'does not update issues not passed in' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end
context 'when add_label_ids and label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
let(:labels) { [merge_requests] }
let(:add_labels) { [regression] }
it 'adds the label IDs to all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
end
it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
end
it 'does not update issues not passed in' do
expect(issue_no_labels.label_ids).to be_empty
end
end
context 'when remove_label_ids and label_ids are passed' do
let(:issues) { [issue_no_labels, issue_bug_and_regression] }
let(:labels) { [merge_requests] }
let(:remove_labels) { [regression] }
it 'removes the label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
end
it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
end
it 'does not update issues not passed in' do
expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
end
end
context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
let(:labels) { [regression] }
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do
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
describe 'subscribe to issues' do
let(:issues) { create_list(:issue, 2, project: project) }
it 'subscribes the given user' do
bulk_update(issues, subscription_event: 'subscribe')
expect(issues).to all(be_subscribed(user, project))
end
end
describe 'unsubscribe from issues' do
let(:issues) do
create_list(:closed_issue, 2, project: project) do |issue|
issue.subscriptions.create(user: user, project: project, subscribed: true)
end
end
it 'unsubscribes the given user' do
bulk_update(issues, subscription_event: 'unsubscribe')
it 'updates the issues milestone' do
bulk_update(issues, milestone_id: milestone.id)
issues.each do |issue|
expect(issue).not_to be_subscribed(user, project)
expect(issue.reload.milestone).to eq(milestone)
end
end
end
context 'with project issues' do
describe 'close issues' do
let(:issues) { create_list(:issue, 2, project: project) }
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[:count]).to eq(issues.count)
end
it 'closes all the issues passed' do
bulk_update(issues, state_event: 'close')
expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
end
context 'when issue for a different project is created' do
let(:private_project) { create(:project, :private) }
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.closed).not_to be_empty
expect(private_project.issues.closed).to be_empty
end
end
end
end
describe 'reopen issues' do
let(:issues) { create_list(:closed_issue, 2, project: project) }
it 'succeeds and returns the correct number of issues updated' do
result = bulk_update(issues, state_event: 'reopen')
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
it 'reopens all the issues passed' do
bulk_update(issues, state_event: 'reopen')
expect(project.issues.closed).to be_empty
expect(project.issues.opened).not_to be_empty
end
end
describe 'updating merge request assignee' do
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
it 'succeeds' do
new_assignee = create(:user)
project.add_developer(new_assignee)
result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
assignee = create(:user)
project.add_developer(assignee)
expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
.to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
it 'unassigns the issues' do
expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
.to change { merge_request.reload.assignee_ids }.to([])
end
end
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(merge_request, assignee_ids: []) }
.not_to change { merge_request.reload.assignee_ids }
end
end
end
describe 'updating issue assignee' do
let(:issue) { create(:issue, project: project, assignees: [user]) }
context 'when the new assignee ID is a valid user' do
it 'succeeds' do
new_assignee = create(:user)
project.add_developer(new_assignee)
result = bulk_update(issue, assignee_ids: [new_assignee.id])
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the assignee to the user ID passed' do
assignee = create(:user)
project.add_developer(assignee)
expect { bulk_update(issue, assignee_ids: [assignee.id]) }
.to change { issue.reload.assignees.first }.from(user).to(assignee)
end
end
context "when the new assignee ID is #{IssuableFinder::NONE}" do
it "unassigns the issues" do
expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
.to change { issue.reload.assignees.count }.from(1).to(0)
end
end
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(issue, assignee_ids: []) }
.not_to change { issue.reload.assignees }
end
end
end
describe 'updating milestones' do
let(:issues) { [create(:issue, project: project)] }
let(:milestone) { create(:milestone, project: project) }
it_behaves_like 'updates milestones'
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(:regression) { 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
bulk_update(issues, bulk_update_params)
end
context 'when label_ids are passed' do
let(:issues) { [issue_all_labels, issue_no_labels] }
let(:labels) { [bug, regression] }
it 'updates the labels of all issues passed to the labels passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&: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
context 'when those label IDs are empty' do
let(:labels) { [] }
it 'updates the issues passed to have no labels' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
end
end
end
context 'when add_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:add_labels) { [bug, regression, merge_requests] }
it 'adds those label IDs to all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&: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
context 'when remove_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:remove_labels) { [bug, regression, merge_requests] }
it 'removes those label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
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
context 'when add_label_ids and remove_label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do
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 'does not update issues not passed in' do
expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
end
end
context 'when add_label_ids and label_ids are passed' do
let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
let(:labels) { [merge_requests] }
let(:add_labels) { [regression] }
it 'adds the label IDs to all issues passed' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
end
it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
end
it 'does not update issues not passed in' do
expect(issue_no_labels.label_ids).to be_empty
end
end
context 'when remove_label_ids and label_ids are passed' do
let(:issues) { [issue_no_labels, issue_bug_and_regression] }
let(:labels) { [merge_requests] }
let(:remove_labels) { [regression] }
it 'removes the label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
end
it 'ignores the label IDs parameter' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
end
it 'does not update issues not passed in' do
expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
end
end
context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
let(:labels) { [regression] }
let(:add_labels) { [bug] }
let(:remove_labels) { [merge_requests] }
it 'adds the label IDs to all issues passed' do
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
describe 'subscribe to issues' do
let(:issues) { create_list(:issue, 2, project: project) }
it 'subscribes the given user' do
bulk_update(issues, subscription_event: 'subscribe')
expect(issues).to all(be_subscribed(user, project))
end
end
describe 'unsubscribe from issues' do
let(:issues) do
create_list(:closed_issue, 2, project: project) do |issue|
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
context 'with group issues' do
let(:group) { create(:group) }
context 'updating milestone' do
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
it_behaves_like 'updates milestones'
end
end
end