Commit b01a830a authored by Fatih Acet's avatar Fatih Acet

Merge branch '24877-bulk-edit-only-keeps-common-labels-when-searching' into 'master'

Improve bulk assignment

This MR improves current implementation of Label dropdown when used for bulk assignment on issuable pages (/:namespace/:project/issues, /:namespace/:project/merge_requests)

Previously this dropdown relied on `<input>` tags to get its active items and also to calculate items with indeterminate state.

Relying on `<input>` tags is not enough when we want to set/get multiple states on a dropdown.

For this case we want to get/set:

- Marked items 
- Unmarked items that were initially marked
- Unmarked items that were initially indeterminate
- Items with indeterminate state.

This MR makes the Label dropdown to save its own state as `data` so it will be easy to get and set whatever state we want no matter if the dropdown is filtering which is the issue that I initially wanted to solve as you can see in the following gif.

**Before** 
![2016-12-07_11.44.48](/uploads/cb697161b8b39cdee72fdbb95a531100/2016-12-07_11.44.48.gif)

**After**
![2016-12-07_11.32.43](/uploads/338255a302de0dd1367474f33232d2a3/2016-12-07_11.32.43.gif)

As you can see in the first gif the `bug` label is removed from the selected issues but the `enhancement` label should set but the `critical` should be kept. This is fixed on the next gif.

Fixes #24877

See merge request !7765
parents 07c7976d 51b2ffaf
...@@ -74,7 +74,9 @@ ...@@ -74,7 +74,9 @@
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
case 'projects:issues:index': case 'projects:issues:index':
Issuable.init(); Issuable.init();
new gl.IssuableBulkActions(); new gl.IssuableBulkActions({
prefixId: page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'
});
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:issues:show': case 'projects:issues:show':
...@@ -144,10 +146,6 @@ ...@@ -144,10 +146,6 @@
new ZenMode(); new ZenMode();
new MergedButtons(); new MergedButtons();
break; break;
case 'projects:merge_requests:index':
shortcut_handler = new ShortcutsNavigation();
Issuable.init();
break;
case 'dashboard:activity': case 'dashboard:activity':
new gl.Activities(); new gl.Activities();
break; break;
......
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true; this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
$inputContainer = this.input.parent(); $inputContainer = this.input.parent();
$clearButton = $inputContainer.find('.js-dropdown-input-clear'); $clearButton = $inputContainer.find('.js-dropdown-input-clear');
this.indeterminateIds = [];
$clearButton.on('click', (function(_this) { $clearButton.on('click', (function(_this) {
// Clear click // Clear click
return function(e) { return function(e) {
...@@ -348,12 +347,12 @@ ...@@ -348,12 +347,12 @@
$el = $(this); $el = $(this);
selected = self.rowClicked($el); selected = self.rowClicked($el);
if (self.options.clicked) { if (self.options.clicked) {
self.options.clicked(selected, $el, e); self.options.clicked(selected[0], $el, e, selected[1]);
} }
// Update label right after all modifications in dropdown has been done // Update label right after all modifications in dropdown has been done
if (self.options.toggleLabel) { if (self.options.toggleLabel) {
self.updateLabel(selected, $el, self); self.updateLabel(selected[0], $el, self);
} }
$el.trigger('blur'); $el.trigger('blur');
...@@ -444,12 +443,6 @@ ...@@ -444,12 +443,6 @@
this.resetRows(); this.resetRows();
this.addArrowKeyEvent(); this.addArrowKeyEvent();
if (this.options.setIndeterminateIds) {
this.options.setIndeterminateIds.call(this);
}
if (this.options.setActiveIds) {
this.options.setActiveIds.call(this);
}
// Makes indeterminate items effective // Makes indeterminate items effective
if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) { if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
this.parseData(this.fullData); this.parseData(this.fullData);
...@@ -483,11 +476,6 @@ ...@@ -483,11 +476,6 @@
if (this.options.filterable) { if (this.options.filterable) {
$input.blur().val(""); $input.blur().val("");
} }
// Triggering 'keyup' will re-render the dropdown which is not always required
// specially if we want to keep the state of the dropdown needed for bulk-assignment
if (!this.options.persistWhenHide) {
$input.trigger("input");
}
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
} }
...@@ -620,7 +608,8 @@ ...@@ -620,7 +608,8 @@
}; };
GitLabDropdown.prototype.rowClicked = function(el) { GitLabDropdown.prototype.rowClicked = function(el) {
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value, isMarking;
fieldName = this.options.fieldName; fieldName = this.options.fieldName;
isInput = $(this.el).is('input'); isInput = $(this.el).is('input');
if (this.renderedData) { if (this.renderedData) {
...@@ -641,7 +630,7 @@ ...@@ -641,7 +630,7 @@
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
} }
return selectedObject; return [selectedObject];
} }
field = []; field = [];
...@@ -659,6 +648,7 @@ ...@@ -659,6 +648,7 @@
} }
if (el.hasClass(ACTIVE_CLASS)) { if (el.hasClass(ACTIVE_CLASS)) {
isMarking = false;
el.removeClass(ACTIVE_CLASS); el.removeClass(ACTIVE_CLASS);
if (field && field.length) { if (field && field.length) {
if (isInput) { if (isInput) {
...@@ -668,6 +658,7 @@ ...@@ -668,6 +658,7 @@
} }
} }
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
isMarking = true;
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
if (field && field.length && value == null) { if (field && field.length && value == null) {
...@@ -677,6 +668,7 @@ ...@@ -677,6 +668,7 @@
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} }
} else { } else {
isMarking = true;
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) { if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
if (!isInput) { if (!isInput) {
...@@ -697,7 +689,7 @@ ...@@ -697,7 +689,7 @@
} }
} }
return selectedObject; return [selectedObject, isMarking];
}; };
GitLabDropdown.prototype.focusTextInput = function() { GitLabDropdown.prototype.focusTextInput = function() {
......
...@@ -144,6 +144,9 @@ ...@@ -144,6 +144,9 @@
const $issuesOtherFilters = $('.issues-other-filters'); const $issuesOtherFilters = $('.issues-other-filters');
const $issuesBulkUpdate = $('.issues_bulk_update'); const $issuesBulkUpdate = $('.issues_bulk_update');
this.issuableBulkActions.willUpdateLabels = false;
this.issuableBulkActions.setOriginalDropdownData();
if ($checkedIssues.length > 0) { if ($checkedIssues.length > 0) {
let ids = $.map($checkedIssues, function(value) { let ids = $.map($checkedIssues, function(value) {
return $(value).data('id'); return $(value).data('id');
...@@ -155,7 +158,6 @@ ...@@ -155,7 +158,6 @@
$updateIssuesIds.val([]); $updateIssuesIds.val([]);
$issuesBulkUpdate.hide(); $issuesBulkUpdate.hide();
$issuesOtherFilters.show(); $issuesOtherFilters.show();
this.issuableBulkActions.willUpdateLabels = false;
} }
return true; return true;
}, },
......
...@@ -5,9 +5,10 @@ ...@@ -5,9 +5,10 @@
((global) => { ((global) => {
class IssuableBulkActions { class IssuableBulkActions {
constructor({ container, form, issues } = {}) { constructor({ container, form, issues, prefixId } = {}) {
this.container = container || $('.content'), 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.issues = issues || this.getElement('.issues-list .issue'); this.issues = issues || this.getElement('.issues-list .issue');
this.form.data('bulkActions', this); this.form.data('bulkActions', this);
this.willUpdateLabels = false; this.willUpdateLabels = false;
...@@ -16,10 +17,6 @@ ...@@ -16,10 +17,6 @@
Issuable.initChecks(); Issuable.initChecks();
} }
getElement(selector) {
return this.container.find(selector);
}
bindEvents() { bindEvents() {
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
} }
...@@ -73,10 +70,7 @@ ...@@ -73,10 +70,7 @@
getUnmarkedIndeterminedLabels() { getUnmarkedIndeterminedLabels() {
const result = []; const result = [];
const labelsToKeep = []; const labelsToKeep = this.$labelDropdown.data('indeterminate');
this.getElement('.labels-filter .is-indeterminate')
.each((i, el) => labelsToKeep.push($(el).data('labelId')));
this.getLabelsFromSelection().forEach((id) => { this.getLabelsFromSelection().forEach((id) => {
if (labelsToKeep.indexOf(id) === -1) { if (labelsToKeep.indexOf(id) === -1) {
...@@ -106,45 +100,65 @@ ...@@ -106,45 +100,65 @@
} }
}; };
if (this.willUpdateLabels) { if (this.willUpdateLabels) {
this.getLabelsToApply().map(function(id) { formData.update.add_label_ids = this.$labelDropdown.data('marked');
return formData.update.add_label_ids.push(id); formData.update.remove_label_ids = this.$labelDropdown.data('unmarked');
});
this.getLabelsToRemove().map(function(id) {
return formData.update.remove_label_ids.push(id);
});
} }
return formData; return formData;
} }
getLabelsToApply() { setOriginalDropdownData() {
const labelIds = []; let $labelSelect = $('.bulk-update .js-label-select');
const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); $labelSelect.data('common', this.getOriginalCommonIds());
$labels.each(function(k, label) { $labelSelect.data('marked', this.getOriginalMarkedIds());
if (label) { $labelSelect.data('indeterminate', this.getOriginalIndeterminateIds());
return labelIds.push(parseInt($(label).val()));
} }
// From issuable's initial bulk selection
getOriginalCommonIds() {
let labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
}); });
return labelIds; return _.intersection.apply(this, labelIds);
} }
// From issuable's initial bulk selection
getOriginalMarkedIds() {
var labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(_, labelIds);
}
/** // From issuable's initial bulk selection
* Returns Label IDs that will be removed from issue selection getOriginalIndeterminateIds() {
* @return {Array} Array of labels IDs let uniqueIds = [];
*/ let labelIds = [];
let issuableLabels = [];
getLabelsToRemove() { // Collect unique label IDs for all checked issues
const result = []; this.getElement('.selected_issue:checked').each((i, el) => {
const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
const labelsToApply = this.getLabelsToApply(); issuableLabels.forEach((labelId) => {
indeterminatedLabels.map(function(id) { // Store unique IDs
// We need to exclude label IDs that will be applied if (uniqueIds.indexOf(labelId) === -1) {
// By not doing this will cause issues from selection to not add labels at all uniqueIds.push(labelId);
if (labelsToApply.indexOf(id) === -1) {
return result.push(id);
} }
}); });
return result; // Store array of IDs per issuable
labelIds.push(issuableLabels);
});
// Add uniqueIds to add it as argument for _.intersection
labelIds.unshift(uniqueIds);
// Return IDs that are present but not in all selected issueables
return _.difference(uniqueIds, _.intersection.apply(this, labelIds));
}
getElement(selector) {
this.scopeEl = this.scopeEl || $('.content');
return this.scopeEl.find(selector);
} }
} }
......
...@@ -8,8 +8,9 @@ ...@@ -8,8 +8,9 @@
var _this; var _this;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) { $('.js-label-select').each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container;
$dropdown = $(dropdown); $dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter');
$toggleText = $dropdown.find('.dropdown-toggle-text'); $toggleText = $dropdown.find('.dropdown-toggle-text');
namespacePath = $dropdown.data('namespace-path'); namespacePath = $dropdown.data('namespace-path');
projectPath = $dropdown.data('project-path'); projectPath = $dropdown.data('project-path');
...@@ -125,7 +126,7 @@ ...@@ -125,7 +126,7 @@
}); });
}); });
}; };
return $dropdown.glDropdown({ $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
return $.ajax({ return $.ajax({
...@@ -172,34 +173,41 @@ ...@@ -172,34 +173,41 @@
}); });
}, },
renderRow: function(label, instance) { renderRow: function(label, instance) {
var $a, $li, active, color, colorEl, indeterminate, removesAll, selectedClass, spacing; var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue;
$li = $('<li>'); $li = $('<li>');
$a = $('<a href="#">'); $a = $('<a href="#">');
selectedClass = []; selectedClass = [];
removesAll = label.id <= 0 || (label.id == null); removesAll = label.id <= 0 || (label.id == null);
if ($dropdown.hasClass('js-filter-bulk-update')) { if ($dropdown.hasClass('js-filter-bulk-update')) {
indeterminate = instance.indeterminateIds; indeterminate = $dropdown.data('indeterminate') || [];
active = instance.activeIds; marked = $dropdown.data('marked') || [];
if (indeterminate.indexOf(label.id) !== -1) { if (indeterminate.indexOf(label.id) !== -1) {
selectedClass.push('is-indeterminate'); selectedClass.push('is-indeterminate');
} }
if (active.indexOf(label.id) !== -1) {
if (marked.indexOf(label.id) !== -1) {
// Remove is-indeterminate class if the item will be marked as active // Remove is-indeterminate class if the item will be marked as active
i = selectedClass.indexOf('is-indeterminate'); i = selectedClass.indexOf('is-indeterminate');
if (i !== -1) { if (i !== -1) {
selectedClass.splice(i, 1); selectedClass.splice(i, 1);
} }
selectedClass.push('is-active'); selectedClass.push('is-active');
// Add input manually
instance.addInput(this.fieldName, label.id);
} }
} } else {
if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) { if (this.id(label)) {
dropdownName = $dropdown.data('fieldName');
dropdownValue = this.id(label).toString().replace(/'/g, '\\\'');
if ($form.find("input[type='hidden'][name='" + dropdownName + "'][value='" + dropdownValue + "']").length) {
selectedClass.push('is-active'); selectedClass.push('is-active');
} }
}
if ($dropdown.hasClass('js-multiselect') && removesAll) { if ($dropdown.hasClass('js-multiselect') && removesAll) {
selectedClass.push('dropdown-clear-active'); selectedClass.push('dropdown-clear-active');
} }
}
if (label.duplicate) { if (label.duplicate) {
spacing = 100 / label.color.length; spacing = 100 / label.color.length;
// Reduce the colors to 4 // Reduce the colors to 4
...@@ -234,7 +242,6 @@ ...@@ -234,7 +242,6 @@
// Return generated html // Return generated html
return $li.html($a).prop('outerHTML'); return $li.html($a).prop('outerHTML');
}, },
persistWhenHide: $dropdown.data('persistWhenHide'),
search: { search: {
fields: ['title'] fields: ['title']
}, },
...@@ -313,18 +320,15 @@ ...@@ -313,18 +320,15 @@
} }
} }
} }
if ($dropdown.hasClass('js-filter-bulk-update')) {
// If we are persisting state we need the classes
if (!this.options.persistWhenHide) {
return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
}
}
}, },
multiSelect: $dropdown.hasClass('js-multiselect'), multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e) { clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page; var isIssueIndex, isMRIndex, page;
_this.enableBulkLabelDropdown();
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) { if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
$dropdown.parent() $dropdown.parent()
...@@ -333,12 +337,11 @@ ...@@ -333,12 +337,11 @@
} }
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
_this.enableBulkLabelDropdown();
_this.setDropdownData($dropdown, isMarking, this.id(label));
return; return;
} }
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
...@@ -400,17 +403,10 @@ ...@@ -400,17 +403,10 @@
} }
} }
}, },
setIndeterminateIds: function() {
if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
return this.indeterminateIds = _this.getIndeterminateIds();
}
},
setActiveIds: function() {
if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
return this.activeIds = _this.getActiveIds();
}
}
}); });
// Set dropdown data
_this.setOriginalDropdownData($dropdownContainer, $dropdown);
}); });
this.bindEvents(); this.bindEvents();
} }
...@@ -423,34 +419,9 @@ ...@@ -423,34 +419,9 @@
if ($('.selected_issue:checked').length) { if ($('.selected_issue:checked').length) {
return; return;
} }
// Remove inputs
$('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
// Also restore button text
return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label'); return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
}; };
LabelsSelect.prototype.getIndeterminateIds = function() {
var label_ids;
label_ids = [];
$('.selected_issue:checked').each(function(i, el) {
var issue_id;
issue_id = $(el).data('id');
return label_ids.push($("#issue_" + issue_id).data('labels'));
});
return _.flatten(label_ids);
};
LabelsSelect.prototype.getActiveIds = function() {
var label_ids;
label_ids = [];
$('.selected_issue:checked').each(function(i, el) {
var issue_id;
issue_id = $(el).data('id');
return label_ids.push($("#issue_" + issue_id).data('labels'));
});
return _.intersection.apply(_, label_ids);
};
LabelsSelect.prototype.enableBulkLabelDropdown = function() { LabelsSelect.prototype.enableBulkLabelDropdown = function() {
var issuableBulkActions; var issuableBulkActions;
if ($('.selected_issue:checked').length) { if ($('.selected_issue:checked').length) {
...@@ -459,8 +430,59 @@ ...@@ -459,8 +430,59 @@
} }
}; };
return LabelsSelect; LabelsSelect.prototype.setDropdownData = function($dropdown, isMarking, value) {
var i, markedIds, unmarkedIds, indeterminateIds;
var issuableBulkActions = $('.bulk-update').data('bulkActions');
markedIds = $dropdown.data('marked') || [];
unmarkedIds = $dropdown.data('unmarked') || [];
indeterminateIds = $dropdown.data('indeterminate') || [];
if (isMarking) {
markedIds.push(value);
i = indeterminateIds.indexOf(value);
if (i > -1) {
indeterminateIds.splice(i, 1);
}
i = unmarkedIds.indexOf(value);
if (i > -1) {
unmarkedIds.splice(i, 1);
}
} else {
// If marked item (not common) is unmarked
i = markedIds.indexOf(value);
if (i > -1) {
markedIds.splice(i, 1);
}
// If an indeterminate item is being unmarked
if (issuableBulkActions.getOriginalIndeterminateIds().indexOf(value) > -1) {
unmarkedIds.push(value);
}
// If a marked item is being unmarked
// (a marked item could also be a label that is present in all selection)
if (issuableBulkActions.getOriginalCommonIds().indexOf(value) > -1) {
unmarkedIds.push(value);
}
}
$dropdown.data('marked', markedIds);
$dropdown.data('unmarked', unmarkedIds);
$dropdown.data('indeterminate', indeterminateIds);
};
LabelsSelect.prototype.setOriginalDropdownData = function($container, $dropdown) {
var labels = [];
$container.find('[name="label_name[]"]').map(function() {
return labels.push(this.value);
});
$dropdown.data('marked', labels);
};
return LabelsSelect;
})(); })();
}).call(this); }).call(this);
%li{ class: mr_css_classes(merge_request) } %li{ id: dom_id(merge_request), class: mr_css_classes(merge_request), data: { labels: merge_request.label_ids, id: merge_request.id } }
- if @bulk_edit - if @bulk_edit
.issue-check .issue-check
= check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue" = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
......
---
title: Improve bulk assignment for issuables
merge_request:
author:
...@@ -9,6 +9,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -9,6 +9,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
let!(:issue2) { create(:issue, project: project, title: "Issue 2") } let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
let!(:bug) { create(:label, project: project, title: 'bug') } let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature) { create(:label, project: project, title: 'feature') } let!(:feature) { create(:label, project: project, title: 'feature') }
let!(:wontfix) { create(:label, project: project, title: 'wontfix') }
context 'as an allowed user', js: true do context 'as an allowed user', js: true do
before do before do
...@@ -291,6 +292,45 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -291,6 +292,45 @@ feature 'Issues > Labels bulk assignment', feature: true do
expect(find("#issue_#{issue1.id}")).not_to have_content 'feature' expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
end end
end end
# Special case https://gitlab.com/gitlab-org/gitlab-ce/issues/24877
context 'unmarking common label' do
before do
issue1.labels << bug
issue1.labels << feature
issue2.labels << bug
visit namespace_project_issues_path(project.namespace, project)
end
it 'applies label from filtered results' do
check 'check_all_issues'
page.within('.issues_bulk_update') do
click_button 'Labels'
wait_for_ajax
expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active')
expect(find('.dropdown-menu-labels li', text: 'feature')).to have_css('.is-indeterminate')
click_link 'bug'
find('.dropdown-input-field', visible: true).set('wontfix')
click_link 'wontfix'
end
update_issues
page.within '.issues-holder' do
expect(find("#issue_#{issue1.id}")).not_to have_content 'bug'
expect(find("#issue_#{issue1.id}")).to have_content 'feature'
expect(find("#issue_#{issue1.id}")).to have_content 'wontfix'
expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
expect(find("#issue_#{issue2.id}")).not_to have_content 'feature'
expect(find("#issue_#{issue2.id}")).to have_content 'wontfix'
end
end
end
end end
context 'as a guest' do context 'as a guest' do
...@@ -320,7 +360,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -320,7 +360,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
def open_labels_dropdown(items = [], unmark = false) def open_labels_dropdown(items = [], unmark = false)
page.within('.issues_bulk_update') do page.within('.issues_bulk_update') do
click_button 'Label' click_button 'Labels'
wait_for_ajax wait_for_ajax
items.map do |item| items.map do |item|
click_link item click_link item
......
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