Commit 2f02843f authored by Phil Hughes's avatar Phil Hughes

Merge branch '28340-mass-edit-issues-and-mrs-from-sidebar' into 'master'

Move issuable bulk edit form into a sidebar

Closes #28340

See merge request !11351
parents a2a105f5 c9a67266
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
/* global ActiveTabMemoizer */ /* global ActiveTabMemoizer */
/* global ShortcutsNavigation */ /* global ShortcutsNavigation */
/* global Build */ /* global Build */
/* global Issuable */ /* global IssuableIndex */
/* global ShortcutsIssuable */ /* global ShortcutsIssuable */
/* global ZenMode */ /* global ZenMode */
/* global Milestone */ /* global Milestone */
...@@ -127,10 +127,9 @@ import ShortcutsBlob from './shortcuts_blob'; ...@@ -127,10 +127,9 @@ import ShortcutsBlob from './shortcuts_blob';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
Issuable.init(); const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
new gl.IssuableBulkActions({ IssuableIndex.init(pagePrefix);
prefixId: page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_',
});
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new UsersSelect(); new UsersSelect();
break; break;
......
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
/* global IssuableIndex */
/* global Flash */
export default {
init({ container, form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');
this.issues = issues || this.getElement('.issues-list .issue');
this.willUpdateLabels = false;
this.bindEvents();
},
bindEvents() {
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
},
onFormSubmit(e) {
e.preventDefault();
return this.submit();
},
submit() {
const _this = this;
const xhr = $.ajax({
url: this.form.attr('action'),
method: this.form.attr('method'),
dataType: 'JSON',
data: this.getFormDataAsObject()
});
xhr.done(() => window.location.reload());
xhr.fail(() => this.onFormSubmitFailure());
},
onFormSubmitFailure() {
this.form.find('[type="submit"]').enable();
return new Flash("Issue update failed");
},
getSelectedIssues() {
return this.issues.has('.selected_issue:checked');
},
getLabelsFromSelection() {
const labels = [];
this.getSelectedIssues().map(function() {
const labelsData = $(this).data('labels');
if (labelsData) {
return labelsData.map(function(labelId) {
if (labels.indexOf(labelId) === -1) {
return labels.push(labelId);
}
});
}
});
return labels;
},
/**
* Will return only labels that were marked previously and the user has unmarked
* @return {Array} Label IDs
*/
getUnmarkedIndeterminedLabels() {
const result = [];
const labelsToKeep = this.$labelDropdown.data('indeterminate');
this.getLabelsFromSelection().forEach((id) => {
if (labelsToKeep.indexOf(id) === -1) {
result.push(id);
}
});
return result;
},
/**
* Simple form serialization, it will return just what we need
* Returns key/value pairs from form data
*/
getFormDataAsObject() {
const formData = {
update: {
state_event: this.form.find('input[name="update[state_event]"]').val(),
// For Merge Requests
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
// For Issues
assignee_ids: [this.form.find('input[name="update[assignee_ids][]"]').val()],
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
add_label_ids: [],
remove_label_ids: []
}
};
if (this.willUpdateLabels) {
formData.update.add_label_ids = this.$labelDropdown.data('marked');
formData.update.remove_label_ids = this.$labelDropdown.data('unmarked');
}
return formData;
},
setOriginalDropdownData() {
const $labelSelect = $('.bulk-update .js-label-select');
$labelSelect.data('common', this.getOriginalCommonIds());
$labelSelect.data('marked', this.getOriginalMarkedIds());
$labelSelect.data('indeterminate', this.getOriginalIndeterminateIds());
},
// From issuable's initial bulk selection
getOriginalCommonIds() {
const labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
},
// From issuable's initial bulk selection
getOriginalMarkedIds() {
const labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
},
// From issuable's initial bulk selection
getOriginalIndeterminateIds() {
const uniqueIds = [];
const labelIds = [];
let issuableLabels = [];
// Collect unique label IDs for all checked issues
this.getElement('.selected_issue:checked').each((i, el) => {
issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
issuableLabels.forEach((labelId) => {
// Store unique IDs
if (uniqueIds.indexOf(labelId) === -1) {
uniqueIds.push(labelId);
}
});
// 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);
},
};
/* eslint-disable class-methods-use-this, no-new */
/* global LabelsSelect */
/* global MilestoneSelect */
/* global IssueStatusSelect */
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content';
const SIDEBAR_EXPANDED_CLASS = 'right-sidebar-expanded issuable-bulk-update-sidebar';
const SIDEBAR_COLLAPSED_CLASS = 'right-sidebar-collapsed issuable-bulk-update-sidebar';
export default class IssuableBulkUpdateSidebar {
constructor() {
this.initDomElements();
this.bindEvents();
this.initDropdowns();
this.setupBulkUpdateActions();
}
initDomElements() {
this.$page = $('.page-with-sidebar');
this.$sidebar = $('.right-sidebar');
this.$bulkEditCancelBtn = $('.js-bulk-update-menu-hide');
this.$bulkEditSubmitBtn = $('.update-selected-issues');
this.$bulkUpdateEnableBtn = $('.js-bulk-update-toggle');
this.$otherFilters = $('.issues-other-filters');
this.$checkAllContainer = $('.check-all-holder');
this.$issueChecks = $('.issue-check');
this.$issuesList = $('.selected_issue');
this.$issuableIdsInput = $('#update_issuable_ids');
}
bindEvents() {
this.$bulkUpdateEnableBtn.on('click', e => this.toggleBulkEdit(e, true));
this.$bulkEditCancelBtn.on('click', e => this.toggleBulkEdit(e, false));
this.$checkAllContainer.on('click', e => this.selectAll(e));
this.$issuesList.on('change', () => this.updateFormState());
this.$bulkEditSubmitBtn.on('click', () => this.prepForSubmit());
this.$checkAllContainer.on('click', () => this.updateFormState());
}
initDropdowns() {
new LabelsSelect();
new MilestoneSelect();
new IssueStatusSelect();
new SubscriptionSelect();
}
getNavHeight() {
const navbarHeight = $('.navbar-gitlab').outerHeight();
const layoutNavHeight = $('.layout-nav').outerHeight();
const subNavScroll = $('.sub-nav-scroll').outerHeight();
return navbarHeight + layoutNavHeight + subNavScroll;
}
initSidebar() {
if (!this.navHeight) {
this.navHeight = this.getNavHeight();
}
if (!this.sidebarInitialized) {
$(document).off('scroll').on('scroll', _.throttle(this.setSidebarHeight, 10).bind(this));
$(window).off('resize').on('resize', _.throttle(this.setSidebarHeight, 10).bind(this));
this.sidebarInitialized = true;
}
}
setupBulkUpdateActions() {
IssuableBulkUpdateActions.setOriginalDropdownData();
}
updateFormState() {
const noCheckedIssues = !$('.selected_issue:checked').length;
this.toggleSubmitButtonDisabled(noCheckedIssues);
this.updateSelectedIssuableIds();
IssuableBulkUpdateActions.setOriginalDropdownData();
}
prepForSubmit() {
// if submit button is disabled, submission is blocked. This ensures we disable after
// form submission is carried out
setTimeout(() => this.$bulkEditSubmitBtn.disable());
this.updateSelectedIssuableIds();
}
toggleBulkEdit(e, enable) {
e.preventDefault();
this.toggleSidebarDisplay(enable);
this.toggleBulkEditButtonDisabled(enable);
this.toggleOtherFiltersDisabled(enable);
this.toggleCheckboxDisplay(enable);
if (enable) {
this.initSidebar();
}
}
updateSelectedIssuableIds() {
this.$issuableIdsInput.val(IssuableBulkUpdateSidebar.getCheckedIssueIds());
}
selectAll() {
const checkAllButtonState = this.$checkAllContainer.find('input').prop('checked');
this.$issuesList.prop('checked', checkAllButtonState);
}
toggleSidebarDisplay(show) {
this.$page.toggleClass(SIDEBAR_EXPANDED_CLASS, show);
this.$page.toggleClass(SIDEBAR_COLLAPSED_CLASS, !show);
this.$sidebar.toggleClass(SIDEBAR_EXPANDED_CLASS, show);
this.$sidebar.toggleClass(SIDEBAR_COLLAPSED_CLASS, !show);
}
toggleBulkEditButtonDisabled(disable) {
if (disable) {
this.$bulkUpdateEnableBtn.disable();
} else {
this.$bulkUpdateEnableBtn.enable();
}
}
toggleCheckboxDisplay(show) {
this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show);
this.$issueChecks.toggleClass(HIDDEN_CLASS, !show);
}
toggleOtherFiltersDisabled(disable) {
this.$otherFilters.toggleClass(DISABLED_CONTENT_CLASS, disable);
}
toggleSubmitButtonDisabled(disable) {
if (disable) {
this.$bulkEditSubmitBtn.disable();
} else {
this.$bulkEditSubmitBtn.enable();
}
}
// loosely based on method of the same name in right_sidebar.js
setSidebarHeight() {
const currentScrollDepth = window.pageYOffset || 0;
const diff = this.navHeight - currentScrollDepth;
if (diff > 0) {
this.$sidebar.outerHeight(window.innerHeight - diff);
} else {
this.$sidebar.outerHeight('100%');
}
}
static getCheckedIssueIds() {
const $checkedIssues = $('.selected_issue:checked');
if ($checkedIssues.length > 0) {
return $.map($checkedIssues, value => $(value).data('id'));
}
return [];
}
}
/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ /* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */
/* global Issuable */ /* global IssuableIndex */
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
((global) => { ((global) => {
var issuable_created; var issuable_created;
issuable_created = false; issuable_created = false;
global.Issuable = { global.IssuableIndex = {
init: function() { init: function(pagePrefix) {
Issuable.initTemplates(); IssuableIndex.initTemplates();
Issuable.initSearch(); IssuableIndex.initSearch();
Issuable.initChecks(); IssuableIndex.initBulkUpdate(pagePrefix);
Issuable.initResetFilters(); IssuableIndex.initResetFilters();
Issuable.resetIncomingEmailToken(); IssuableIndex.resetIncomingEmailToken();
return Issuable.initLabelFilterRemove(); IssuableIndex.initLabelFilterRemove();
}, },
initTemplates: function() { initTemplates: function() {
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>'); return IssuableIndex.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
}, },
initSearch: function() { initSearch: function() {
const $searchInput = $('#issuable_search'); const $searchInput = $('#issuable_search');
Issuable.initSearchState($searchInput); IssuableIndex.initSearchState($searchInput);
// `immediate` param set to false debounces on the `trailing` edge, lets user finish typing // `immediate` param set to false debounces on the `trailing` edge, lets user finish typing
const debouncedExecSearch = _.debounce(Issuable.executeSearch, 1000, false); const debouncedExecSearch = _.debounce(IssuableIndex.executeSearch, 1000, false);
$searchInput.off('keyup').on('keyup', debouncedExecSearch); $searchInput.off('keyup').on('keyup', debouncedExecSearch);
...@@ -37,16 +40,16 @@ ...@@ -37,16 +40,16 @@
initSearchState: function($searchInput) { initSearchState: function($searchInput) {
const currentSearchVal = $searchInput.val(); const currentSearchVal = $searchInput.val();
Issuable.searchState = { IssuableIndex.searchState = {
elem: $searchInput, elem: $searchInput,
current: currentSearchVal current: currentSearchVal
}; };
Issuable.maybeFocusOnSearch(); IssuableIndex.maybeFocusOnSearch();
}, },
accessSearchPristine: function(set) { accessSearchPristine: function(set) {
// store reference to previous value to prevent search on non-mutating keyup // store reference to previous value to prevent search on non-mutating keyup
const state = Issuable.searchState; const state = IssuableIndex.searchState;
const currentSearchVal = state.elem.val(); const currentSearchVal = state.elem.val();
if (set) { if (set) {
...@@ -56,10 +59,10 @@ ...@@ -56,10 +59,10 @@
} }
}, },
maybeFocusOnSearch: function() { maybeFocusOnSearch: function() {
const currentSearchVal = Issuable.searchState.current; const currentSearchVal = IssuableIndex.searchState.current;
if (currentSearchVal && currentSearchVal !== '') { if (currentSearchVal && currentSearchVal !== '') {
const queryLength = currentSearchVal.length; const queryLength = currentSearchVal.length;
const $searchInput = Issuable.searchState.elem; const $searchInput = IssuableIndex.searchState.elem;
/* The following ensures that the cursor is initially placed at /* The following ensures that the cursor is initially placed at
* the end of search input when focus is applied. It accounts * the end of search input when focus is applied. It accounts
...@@ -80,7 +83,7 @@ ...@@ -80,7 +83,7 @@
const $searchValue = $search.val(); const $searchValue = $search.val();
const $filtersForm = $('.js-filter-form'); const $filtersForm = $('.js-filter-form');
const $input = $(`input[name='${$searchName}']`, $filtersForm); const $input = $(`input[name='${$searchName}']`, $filtersForm);
const isPristine = Issuable.accessSearchPristine(); const isPristine = IssuableIndex.accessSearchPristine();
if (isPristine) { if (isPristine) {
return; return;
...@@ -92,7 +95,7 @@ ...@@ -92,7 +95,7 @@
$input.val($searchValue); $input.val($searchValue);
} }
Issuable.filterResults($filtersForm); IssuableIndex.filterResults($filtersForm);
}, },
initLabelFilterRemove: function() { initLabelFilterRemove: function() {
return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
...@@ -103,7 +106,7 @@ ...@@ -103,7 +106,7 @@
return this.value === $button.data('label'); return this.value === $button.data('label');
}).remove(); }).remove();
// Submit the form to get new data // Submit the form to get new data
Issuable.filterResults($('.filter-form')); IssuableIndex.filterResults($('.filter-form'));
}); });
}, },
filterResults: (function(_this) { filterResults: (function(_this) {
...@@ -132,38 +135,18 @@ ...@@ -132,38 +135,18 @@
gl.utils.visitUrl(baseIssuesUrl); gl.utils.visitUrl(baseIssuesUrl);
}); });
}, },
initChecks: function() { initBulkUpdate: function(pagePrefix) {
this.issuableBulkActions = $('.bulk-update').data('bulkActions'); const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
$('.check_all_issues').off('click').on('click', function() { const alreadyInitialized = !!this.bulkUpdateSidebar;
$('.selected_issue').prop('checked', this.checked);
return Issuable.checkChanged(); if (userCanBulkUpdate && !alreadyInitialized) {
}); IssuableBulkUpdateActions.init({
return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this)); prefixId: pagePrefix,
},
checkChanged: function() {
const $checkedIssues = $('.selected_issue:checked');
const $updateIssuesIds = $('#update_issuable_ids');
const $issuesOtherFilters = $('.issues-other-filters');
const $issuesBulkUpdate = $('.issues_bulk_update');
this.issuableBulkActions.willUpdateLabels = false;
this.issuableBulkActions.setOriginalDropdownData();
if ($checkedIssues.length > 0) {
const ids = $.map($checkedIssues, function(value) {
return $(value).data('id');
}); });
$updateIssuesIds.val(ids);
$issuesOtherFilters.hide(); this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
$issuesBulkUpdate.show();
} else {
$updateIssuesIds.val([]);
$issuesBulkUpdate.hide();
$issuesOtherFilters.show();
} }
return true;
}, },
resetIncomingEmailToken: function() { resetIncomingEmailToken: function() {
$('.incoming-email-token-reset').on('click', function(e) { $('.incoming-email-token-reset').on('click', function(e) {
e.preventDefault(); e.preventDefault();
......
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
/* global Issuable */
/* global Flash */
((global) => {
class IssuableBulkActions {
constructor({ container, form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');
this.issues = issues || this.getElement('.issues-list .issue');
this.form.data('bulkActions', this);
this.willUpdateLabels = false;
this.bindEvents();
// Fixes bulk-assign not working when navigating through pages
Issuable.initChecks();
}
bindEvents() {
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
}
onFormSubmit(e) {
e.preventDefault();
return this.submit();
}
submit() {
const _this = this;
const xhr = $.ajax({
url: this.form.attr('action'),
method: this.form.attr('method'),
dataType: 'JSON',
data: this.getFormDataAsObject()
});
xhr.done(() => window.location.reload());
xhr.fail(() => new Flash("Issue update failed"));
return xhr.always(this.onFormSubmitAlways.bind(this));
}
onFormSubmitAlways() {
return this.form.find('[type="submit"]').enable();
}
getSelectedIssues() {
return this.issues.has('.selected_issue:checked');
}
getLabelsFromSelection() {
const labels = [];
this.getSelectedIssues().map(function() {
const labelsData = $(this).data('labels');
if (labelsData) {
return labelsData.map(function(labelId) {
if (labels.indexOf(labelId) === -1) {
return labels.push(labelId);
}
});
}
});
return labels;
}
/**
* Will return only labels that were marked previously and the user has unmarked
* @return {Array} Label IDs
*/
getUnmarkedIndeterminedLabels() {
const result = [];
const labelsToKeep = this.$labelDropdown.data('indeterminate');
this.getLabelsFromSelection().forEach((id) => {
if (labelsToKeep.indexOf(id) === -1) {
result.push(id);
}
});
return result;
}
/**
* Simple form serialization, it will return just what we need
* Returns key/value pairs from form data
*/
getFormDataAsObject() {
const formData = {
update: {
state_event: this.form.find('input[name="update[state_event]"]').val(),
// For Merge Requests
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
// For Issues
assignee_ids: [this.form.find('input[name="update[assignee_ids][]"]').val()],
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
add_label_ids: [],
remove_label_ids: []
}
};
if (this.willUpdateLabels) {
formData.update.add_label_ids = this.$labelDropdown.data('marked');
formData.update.remove_label_ids = this.$labelDropdown.data('unmarked');
}
return formData;
}
setOriginalDropdownData() {
const $labelSelect = $('.bulk-update .js-label-select');
$labelSelect.data('common', this.getOriginalCommonIds());
$labelSelect.data('marked', this.getOriginalMarkedIds());
$labelSelect.data('indeterminate', this.getOriginalIndeterminateIds());
}
// From issuable's initial bulk selection
getOriginalCommonIds() {
const labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
}
// From issuable's initial bulk selection
getOriginalMarkedIds() {
const labelIds = [];
this.getElement('.selected_issue:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
}
// From issuable's initial bulk selection
getOriginalIndeterminateIds() {
const uniqueIds = [];
const labelIds = [];
let issuableLabels = [];
// Collect unique label IDs for all checked issues
this.getElement('.selected_issue:checked').each((i, el) => {
issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
issuableLabels.forEach((labelId) => {
// Store unique IDs
if (uniqueIds.indexOf(labelId) === -1) {
uniqueIds.push(labelId);
}
});
// 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);
}
}
global.IssuableBulkActions = IssuableBulkActions;
})(window.gl || (window.gl = {}));
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
/* global Issuable */ /* global Issuable */
/* global ListLabel */ /* global ListLabel */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
function LabelsSelect(els) { function LabelsSelect(els) {
...@@ -430,20 +432,15 @@ ...@@ -430,20 +432,15 @@
if ($('.selected_issue:checked').length) { if ($('.selected_issue:checked').length) {
return; return;
} }
return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label'); return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label');
}; };
LabelsSelect.prototype.enableBulkLabelDropdown = function() { LabelsSelect.prototype.enableBulkLabelDropdown = function() {
var issuableBulkActions; IssuableBulkUpdateActions.willUpdateLabels = true;
if ($('.selected_issue:checked').length) {
issuableBulkActions = $('.bulk-update').data('bulkActions');
return issuableBulkActions.willUpdateLabels = true;
}
}; };
LabelsSelect.prototype.setDropdownData = function($dropdown, isMarking, value) { LabelsSelect.prototype.setDropdownData = function($dropdown, isMarking, value) {
var i, markedIds, unmarkedIds, indeterminateIds; var i, markedIds, unmarkedIds, indeterminateIds;
var issuableBulkActions = $('.bulk-update').data('bulkActions');
markedIds = $dropdown.data('marked') || []; markedIds = $dropdown.data('marked') || [];
unmarkedIds = $dropdown.data('unmarked') || []; unmarkedIds = $dropdown.data('unmarked') || [];
...@@ -469,13 +466,13 @@ ...@@ -469,13 +466,13 @@
} }
// If an indeterminate item is being unmarked // If an indeterminate item is being unmarked
if (issuableBulkActions.getOriginalIndeterminateIds().indexOf(value) > -1) { if (IssuableBulkUpdateActions.getOriginalIndeterminateIds().indexOf(value) > -1) {
unmarkedIds.push(value); unmarkedIds.push(value);
} }
// If a marked item is being unmarked // If a marked item is being unmarked
// (a marked item could also be a label that is present in all selection) // (a marked item could also be a label that is present in all selection)
if (issuableBulkActions.getOriginalCommonIds().indexOf(value) > -1) { if (IssuableBulkUpdateActions.getOriginalCommonIds().indexOf(value) > -1) {
unmarkedIds.push(value); unmarkedIds.push(value);
} }
} }
......
...@@ -104,12 +104,11 @@ import './group_label_subscription'; ...@@ -104,12 +104,11 @@ import './group_label_subscription';
import './groups_select'; import './groups_select';
import './header'; import './header';
import './importer_status'; import './importer_status';
import './issuable'; import './issuable_index';
import './issuable_context'; import './issuable_context';
import './issuable_form'; import './issuable_form';
import './issue'; import './issue';
import './issue_status_select'; import './issue_status_select';
import './issues_bulk_assignment';
import './label_manager'; import './label_manager';
import './labels'; import './labels';
import './labels_select'; import './labels_select';
......
...@@ -445,3 +445,9 @@ table { ...@@ -445,3 +445,9 @@ table {
word-wrap: break-word; word-wrap: break-word;
} }
} }
.disabled-content {
pointer-events: none;
opacity: .5;
}
...@@ -22,12 +22,6 @@ ...@@ -22,12 +22,6 @@
} }
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.issues_bulk_update {
.dropdown-menu-toggle {
width: 132px;
}
}
.filter-item:not(:last-child) { .filter-item:not(:last-child) {
margin-right: 6px; margin-right: 6px;
} }
...@@ -376,12 +370,6 @@ ...@@ -376,12 +370,6 @@
padding: 0; padding: 0;
} }
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.issue-bulk-update-dropdown-toggle {
width: 100px;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issues-details-filters { .issues-details-filters {
padding: 0 0 10px; padding: 0 0 10px;
......
...@@ -29,10 +29,6 @@ ...@@ -29,10 +29,6 @@
display: none; display: none;
} }
.issues-holder .issue-check {
display: none;
}
.rss-btn { .rss-btn {
display: none; display: none;
} }
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
&:not(.wiki-sidebar):not(.build-sidebar) .content-wrapper { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter_collapsed_width; padding-right: $gutter_collapsed_width;
} }
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
z-index: 300; z-index: 300;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
&:not(.wiki-sidebar):not(.build-sidebar) .content-wrapper { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter_collapsed_width; padding-right: $gutter_collapsed_width;
} }
} }
...@@ -88,3 +88,35 @@ ...@@ -88,3 +88,35 @@
min-height: 100%; min-height: 100%;
} }
} }
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
padding: 10px 20px;
}
.issues-bulk-update.right-sidebar {
@include maintain-sidebar-dimensions;
transition: right $sidebar-transition-duration;
right: -$gutter-width;
&.right-sidebar-expanded {
@include maintain-sidebar-dimensions;
right: 0;
}
&.right-sidebar-collapsed {
@include maintain-sidebar-dimensions;
right: -$gutter-width;
.block {
padding: 16px 0;
width: 250px;
border-bottom: 1px solid $border-color;
}
}
.issuable-sidebar {
padding: 0 3px;
}
}
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } } %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
.issue-box .issue-box
- if @bulk_edit - if @can_bulk_update
.issue-check .issue-check.hidden
= check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
.issue-info-container .issue-info-container
.issue-title.title .issue-title.title
......
- @no_container = true - @no_container = true
- @bulk_edit = can?(current_user, :admin_issue, @project) - @can_bulk_update = can?(current_user, :admin_issue, @project)
- page_title "Issues" - page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user) - new_issue_email = @project.new_issue_address(current_user)
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
.nav-controls .nav-controls
= link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
- if @can_bulk_update
= button_tag "Edit Issues", class: "btn btn-default js-bulk-update-toggle"
= link_to new_namespace_project_issue_path(@project.namespace, = link_to new_namespace_project_issue_path(@project.namespace,
@project, @project,
issue: { assignee_id: issues_finder.assignee.try(:id), issue: { assignee_id: issues_finder.assignee.try(:id),
...@@ -30,6 +32,9 @@ ...@@ -30,6 +32,9 @@
New issue New issue
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render 'shared/issuable/bulk_update_sidebar', type: :issues
.issues-holder .issues-holder
= render 'issues' = render 'issues'
- if new_issue_email - if new_issue_email
......
%li{ id: dom_id(merge_request), class: mr_css_classes(merge_request), data: { labels: merge_request.label_ids, id: merge_request.id } } %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 @can_bulk_update
.issue-check .issue-check.hidden
= 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"
.issue-info-container .issue-info-container
......
- @no_container = true - @no_container = true
- @bulk_edit = can?(current_user, :admin_merge_request, @project) - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- page_title "Merge Requests" - page_title "Merge Requests"
- unless @project.default_issues_tracker? - unless @project.default_issues_tracker?
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls
- if @can_bulk_update
= button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New merge request" do
...@@ -25,6 +27,9 @@ ...@@ -25,6 +27,9 @@
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
- if @can_bulk_update
= render 'shared/issuable/bulk_update_sidebar', type: :merge_requests
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
- else - else
......
- type = local_assigns.fetch(:type)
%aside.issues-bulk-update.js-right-sidebar.right-sidebar.affix-top{ data: { "offset-top" => "50", "spy" => "affix" }, "aria-live" => "polite" }
.issuable-sidebar
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
.block
.filter-item.inline.update-issues-btn.pull-left
= button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true
= button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide pull-right"
.block
.title
Status
.filter-item
= dropdown_tag("Select status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: "Status" } } ) do
%ul
%li
%a{ href: "#", data: { id: "reopen" } } Open
%li
%a{ href: "#", data: { id: "close" } } Closed
.block
.title
Assignee
.filter-item
- if type == :issues
- field_name = "update[assignee_ids][]"
- else
- field_name = "update[assignee_id]"
= dropdown_tag("Select assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
.block
.title
Milestone
.filter-item
= dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
.block
.title
Labels
.filter-item.labels-filter
= render "shared/issuable/label_dropdown", classes: ["js-filter-bulk-update", "js-multiselect"], dropdown_title: "Apply a label", show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: "Labels" }, label_name: "Select labels", no_default_styles: true
.block
.title
Subscriptions
.filter-item
= dropdown_tag("Select subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: "Subscription" } } ) do
%ul
%li
%a{ href: "#", data: { id: "subscribe" } } Subscribe
%li
%a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe
= hidden_field_tag "update[issuable_ids]", []
= hidden_field_tag :state_event, params[:state_event]
...@@ -6,10 +6,6 @@ ...@@ -6,10 +6,6 @@
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present? - if params[:search].present?
= hidden_field_tag :search, params[:search] = hidden_field_tag :search, params[:search]
- if @bulk_edit
.check-all-holder
= check_box_tag "check_all_issues", nil, false,
class: "check_all_issues left"
.issues-other-filters .issues-other-filters
.filter-item.inline .filter-item.inline
- if params[:author_id].present? - if params[:author_id].present?
...@@ -36,35 +32,6 @@ ...@@ -36,35 +32,6 @@
.pull-right .pull-right
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
- if @bulk_edit
.issues_bulk_update.hide
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do
.filter-item.inline
= dropdown_tag("Status", options: { toggle_class: "issue-bulk-update-dropdown-toggle js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: "Status" } } ) do
%ul
%li
%a{ href: "#", data: { id: "reopen" } } Open
%li
%a{ href: "#", data: {id: "close" } } Closed
.filter-item.inline
= dropdown_tag("Assignee", options: { toggle_class: "issue-bulk-update-dropdown-toggle js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]", default_label: "Assignee" } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'issue-bulk-update-dropdown-toggle js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", default_label: "Milestone", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], dropdown_title: 'Apply a label', show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
.filter-item.inline
= dropdown_tag("Subscription", options: { toggle_class: "issue-bulk-update-dropdown-toggle js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: "Subscription" } } ) do
%ul
%li
%a{ href: "#", data: { id: "subscribe" } } Subscribe
%li
%a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe
= hidden_field_tag 'update[issuable_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
- has_labels = @labels && @labels.any? - has_labels = @labels && @labels.any?
.row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
- if has_labels - if has_labels
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} - dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options) - dropdown_data.merge!(data_options)
- label_name = local_assigns.fetch(:label_name, "Labels")
- no_default_styles = local_assigns.fetch(:no_default_styles, false)
- classes << 'js-extra-options' if extra_options - classes << 'js-extra-options' if extra_options
- classes << 'js-filter-submit' if filter_submit - classes << 'js-filter-submit' if filter_submit
...@@ -20,8 +22,9 @@ ...@@ -20,8 +22,9 @@
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{ class: classes.join(' '), type: "button", data: dropdown_data } %button.dropdown-menu-toggle.js-label-select.js-multiselect{ class: classes.join(' '), type: "button", data: dropdown_data }
%span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } - apply_is_default_styles = (selected.nil? || selected.empty?) && !no_default_styles
= multi_label_name(selected, "Labels") %span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
= multi_label_name(selected, label_name)
= icon('chevron-down') = icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
......
...@@ -6,10 +6,9 @@ ...@@ -6,10 +6,9 @@
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present? - if params[:search].present?
= hidden_field_tag :search, params[:search] = hidden_field_tag :search, params[:search]
- if @bulk_edit - if @can_bulk_update
.check-all-holder .check-all-holder.hidden
= check_box_tag "check_all_issues", nil, false, = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
class: "check_all_issues left"
.issues-other-filters.filtered-search-wrapper .issues-other-filters.filtered-search-wrapper
.filtered-search-box .filtered-search-box
- if type != :boards_modal && type != :boards - if type != :boards_modal && type != :boards
...@@ -110,55 +109,11 @@ ...@@ -110,55 +109,11 @@
- elsif type != :boards_modal - elsif type != :boards_modal
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
- if @bulk_edit
.issues_bulk_update.hide
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do
.filter-item.inline
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: "Status" } } ) do
%ul
%li
%a{ href: "#", data: { id: "reopen" } } Open
%li
%a{ href: "#", data: { id: "close" } } Closed
.filter-item.inline
- if type == :issues
- field_name = "update[assignee_ids][]"
- else
- field_name = "update[assignee_id]"
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], dropdown_title: 'Apply a label', show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: "Labels" }
.filter-item.inline
= dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: "Subscription" } } ) do
%ul
%li
%a{ href: "#", data: { id: "subscribe" } } Subscribe
%li
%a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe
= hidden_field_tag 'update[issuable_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline.update-issues-btn
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
- unless type === :boards_modal - unless type === :boards_modal
:javascript :javascript
new LabelsSelect();
new MilestoneSelect();
new IssueStatusSelect();
new SubscriptionSelect();
$(document).off('page:restore').on('page:restore', function (event) { $(document).off('page:restore').on('page:restore', function (event) {
if (gl.FilteredSearchManager) { if (gl.FilteredSearchManager) {
const filteredSearchManager = new gl.FilteredSearchManager(); const filteredSearchManager = new gl.FilteredSearchManager();
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
Issuable.init();
new gl.IssuableBulkActions({
prefixId: 'issue_',
});
}); });
...@@ -18,13 +18,13 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -18,13 +18,13 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'can bulk assign' do context 'can bulk assign' do
before do before do
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
end end
context 'a label' do context 'a label' do
context 'to all issues' do context 'to all issues' do
before do before do
check 'check_all_issues' check 'check-all-issues'
open_labels_dropdown ['bug'] open_labels_dropdown ['bug']
update_issues update_issues
end end
...@@ -52,7 +52,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -52,7 +52,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'multiple labels' do context 'multiple labels' do
context 'to all issues' do context 'to all issues' do
before do before do
check 'check_all_issues' check 'check-all-issues'
open_labels_dropdown %w(bug feature) open_labels_dropdown %w(bug feature)
update_issues update_issues
end end
...@@ -86,9 +86,10 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -86,9 +86,10 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do before do
issue2.labels << bug issue2.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project)
check 'check_all_issues' enable_bulk_update
check 'check-all-issues'
open_labels_dropdown ['bug'] open_labels_dropdown ['bug']
update_issues update_issues
end end
...@@ -107,9 +108,8 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -107,9 +108,8 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue2.labels << bug issue2.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
check 'check-all-issues'
check 'check_all_issues'
unmark_labels_in_dropdown %w(bug feature) unmark_labels_in_dropdown %w(bug feature)
update_issues update_issues
end end
...@@ -127,8 +127,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -127,8 +127,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << bug issue1.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
check_issue issue1 check_issue issue1
unmark_labels_in_dropdown ['bug'] unmark_labels_in_dropdown ['bug']
update_issues update_issues
...@@ -147,8 +146,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -147,8 +146,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue2.labels << bug issue2.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
check_issue issue1 check_issue issue1
check_issue issue2 check_issue issue2
unmark_labels_in_dropdown ['bug'] unmark_labels_in_dropdown ['bug']
...@@ -171,14 +169,15 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -171,14 +169,15 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do before do
issue1.labels << bug issue1.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
end end
it 'keeps labels' do it 'keeps labels' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug' expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature' expect(find("#issue_#{issue2.id}")).to have_content 'feature'
check 'check_all_issues' check 'check-all-issues'
open_milestone_dropdown(['First Release']) open_milestone_dropdown(['First Release'])
update_issues update_issues
...@@ -192,14 +191,13 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -192,14 +191,13 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'setting a milestone and adding another label' do context 'setting a milestone and adding another label' do
before do before do
issue1.labels << bug issue1.labels << bug
enable_bulk_update
visit namespace_project_issues_path(project.namespace, project)
end end
it 'keeps existing label and new label is present' do it 'keeps existing label and new label is present' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug' expect(find("#issue_#{issue1.id}")).to have_content 'bug'
check 'check_all_issues' check 'check-all-issues'
open_milestone_dropdown ['First Release'] open_milestone_dropdown ['First Release']
open_labels_dropdown ['feature'] open_labels_dropdown ['feature']
update_issues update_issues
...@@ -218,7 +216,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -218,7 +216,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << feature issue1.labels << feature
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
end end
it 'keeps existing label and new label is present' do it 'keeps existing label and new label is present' do
...@@ -226,7 +224,8 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -226,7 +224,8 @@ feature 'Issues > Labels bulk assignment', feature: true do
expect(find("#issue_#{issue1.id}")).to have_content 'bug' expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature' expect(find("#issue_#{issue2.id}")).to have_content 'feature'
check 'check_all_issues' check 'check-all-issues'
open_milestone_dropdown ['First Release'] open_milestone_dropdown ['First Release']
unmark_labels_in_dropdown ['feature'] unmark_labels_in_dropdown ['feature']
update_issues update_issues
...@@ -248,7 +247,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -248,7 +247,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << bug issue1.labels << bug
issue2.labels << feature issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
end end
it 'keeps labels' do it 'keeps labels' do
...@@ -257,7 +256,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -257,7 +256,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
expect(find("#issue_#{issue2.id}")).to have_content 'feature' expect(find("#issue_#{issue2.id}")).to have_content 'feature'
expect(find("#issue_#{issue2.id}")).to have_content 'First Release' expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
check 'check_all_issues' check 'check-all-issues'
open_milestone_dropdown(['No Milestone']) open_milestone_dropdown(['No Milestone'])
update_issues update_issues
...@@ -272,8 +271,7 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -272,8 +271,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'toggling checked issues' do context 'toggling checked issues' do
before do before do
issue1.labels << bug issue1.labels << bug
enable_bulk_update
visit namespace_project_issues_path(project.namespace, project)
end end
it do it do
...@@ -298,14 +296,14 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -298,14 +296,14 @@ feature 'Issues > Labels bulk assignment', feature: true do
issue1.labels << feature issue1.labels << feature
issue2.labels << bug issue2.labels << bug
visit namespace_project_issues_path(project.namespace, project) enable_bulk_update
end end
it 'applies label from filtered results' do it 'applies label from filtered results' do
check 'check_all_issues' check 'check-all-issues'
page.within('.issues_bulk_update') do page.within('.issues-bulk-update') do
click_button 'Labels' click_button 'Select labels'
wait_for_requests wait_for_requests
expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active') expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active')
...@@ -340,15 +338,16 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -340,15 +338,16 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'cannot bulk assign labels' do context 'cannot bulk assign labels' do
it do it do
expect(page).not_to have_css '.check_all_issues' expect(page).not_to have_button 'Edit Issues'
expect(page).not_to have_css '.check-all-issues'
expect(page).not_to have_css '.issue-check' expect(page).not_to have_css '.issue-check'
end end
end end
end end
def open_milestone_dropdown(items = []) def open_milestone_dropdown(items = [])
page.within('.issues_bulk_update') do page.within('.issues-bulk-update') do
click_button 'Milestone' click_button 'Select milestone'
wait_for_requests wait_for_requests
items.map do |item| items.map do |item|
click_link item click_link item
...@@ -357,8 +356,8 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -357,8 +356,8 @@ feature 'Issues > Labels bulk assignment', feature: true do
end end
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 'Labels' click_button 'Select labels'
wait_for_requests wait_for_requests
items.map do |item| items.map do |item|
click_link item click_link item
...@@ -391,7 +390,12 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -391,7 +390,12 @@ feature 'Issues > Labels bulk assignment', feature: true do
end end
def update_issues def update_issues
click_button 'Update issues' click_button 'Update all'
wait_for_requests wait_for_requests
end end
def enable_bulk_update
visit namespace_project_issues_path(project.namespace, project)
click_button 'Edit Issues'
end
end end
...@@ -14,7 +14,8 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -14,7 +14,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'sets to closed' do it 'sets to closed' do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click click_button 'Edit Issues'
find('#check-all-issues').click
find('.js-issue-status').click find('.js-issue-status').click
find('.dropdown-menu-status a', text: 'Closed').click find('.dropdown-menu-status a', text: 'Closed').click
...@@ -26,7 +27,8 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -26,7 +27,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
create_closed create_closed
visit namespace_project_issues_path(project.namespace, project, state: 'closed') visit namespace_project_issues_path(project.namespace, project, state: 'closed')
find('#check_all_issues').click click_button 'Edit Issues'
find('#check-all-issues').click
find('.js-issue-status').click find('.js-issue-status').click
find('.dropdown-menu-status a', text: 'Open').click find('.dropdown-menu-status a', text: 'Open').click
...@@ -39,7 +41,8 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -39,7 +41,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'updates to current user' do it 'updates to current user' do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click click_button 'Edit Issues'
find('#check-all-issues').click
click_update_assignee_button click_update_assignee_button
find('.dropdown-menu-user-link', text: user.username).click find('.dropdown-menu-user-link', text: user.username).click
...@@ -54,7 +57,8 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -54,7 +57,8 @@ feature 'Multiple issue updating from issues#index', feature: true do
create_assigned create_assigned
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click click_button 'Edit Issues'
find('#check-all-issues').click
click_update_assignee_button click_update_assignee_button
click_link 'Unassigned' click_link 'Unassigned'
...@@ -69,8 +73,9 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -69,8 +73,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'updates milestone' do it 'updates milestone' do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click click_button 'Edit Issues'
find('.issues_bulk_update .js-milestone-select').click find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: milestone.title).click find('.dropdown-menu-milestone a', text: milestone.title).click
click_update_issues_button click_update_issues_button
...@@ -84,8 +89,9 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -84,8 +89,9 @@ feature 'Multiple issue updating from issues#index', feature: true do
expect(first('.issue')).to have_content milestone.title expect(first('.issue')).to have_content milestone.title
find('#check_all_issues').click click_button 'Edit Issues'
find('.issues_bulk_update .js-milestone-select').click find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: "No Milestone").click find('.dropdown-menu-milestone a', text: "No Milestone").click
click_update_issues_button click_update_issues_button
...@@ -112,7 +118,7 @@ feature 'Multiple issue updating from issues#index', feature: true do ...@@ -112,7 +118,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
end end
def click_update_issues_button def click_update_issues_button
find('.update_selected_issues').click find('.update-selected-issues').click
wait_for_requests wait_for_requests
end end
end end
...@@ -98,14 +98,16 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t ...@@ -98,14 +98,16 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
end end
def change_status(text) def change_status(text)
find('#check_all_issues').click click_button 'Edit Merge Requests'
find('#check-all-issues').click
find('.js-issue-status').click find('.js-issue-status').click
find('.dropdown-menu-status a', text: text).click find('.dropdown-menu-status a', text: text).click
click_update_merge_requests_button click_update_merge_requests_button
end end
def change_assignee(text) def change_assignee(text)
find('#check_all_issues').click click_button 'Edit Merge Requests'
find('#check-all-issues').click
find('.js-update-assignee').click find('.js-update-assignee').click
wait_for_requests wait_for_requests
...@@ -117,14 +119,15 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t ...@@ -117,14 +119,15 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
end end
def change_milestone(text) def change_milestone(text)
find('#check_all_issues').click click_button 'Edit Merge Requests'
find('.issues_bulk_update .js-milestone-select').click find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: text).click find('.dropdown-menu-milestone a', text: text).click
click_update_merge_requests_button click_update_merge_requests_button
end end
def click_update_merge_requests_button def click_update_merge_requests_button
find('.update_selected_issues').click find('.update-selected-issues').click
wait_for_requests wait_for_requests
end end
end end
%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'} %form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
%input{id: 'utf8', name: 'utf8', value: '✓'} %input{id: 'utf8', name: 'utf8', value: '✓'}
%input{id: 'check_all_issues', name: 'check_all_issues'} %input{id: 'check-all-issues', name: 'check-all-issues'}
%input{id: 'search', name: 'search'} %input{id: 'search', name: 'search'}
%input{id: 'author_id', name: 'author_id'} %input{id: 'author_id', name: 'author_id'}
%input{id: 'assignee_id', name: 'assignee_id'} %input{id: 'assignee_id', name: 'assignee_id'}
......
/* global Issuable */ /* global IssuableIndex */
import '~/lib/utils/url_utility'; import '~/lib/utils/url_utility';
import '~/issuable'; import '~/issuable_index';
(() => { (() => {
const BASE_URL = '/user/project/issues?scope=all&state=closed'; const BASE_URL = '/user/project/issues?scope=all&state=closed';
...@@ -24,11 +24,11 @@ import '~/issuable'; ...@@ -24,11 +24,11 @@ import '~/issuable';
beforeEach(() => { beforeEach(() => {
loadFixtures('static/issuable_filter.html.raw'); loadFixtures('static/issuable_filter.html.raw');
Issuable.init(); IssuableIndex.init();
}); });
it('should be defined', () => { it('should be defined', () => {
expect(window.Issuable).toBeDefined(); expect(window.IssuableIndex).toBeDefined();
}); });
describe('filtering', () => { describe('filtering', () => {
...@@ -43,7 +43,7 @@ import '~/issuable'; ...@@ -43,7 +43,7 @@ import '~/issuable';
it('should contain only the default parameters', () => { it('should contain only the default parameters', () => {
spyOn(gl.utils, 'visitUrl'); spyOn(gl.utils, 'visitUrl');
Issuable.filterResults($filtersForm); IssuableIndex.filterResults($filtersForm);
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS); expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS);
}); });
...@@ -52,7 +52,7 @@ import '~/issuable'; ...@@ -52,7 +52,7 @@ import '~/issuable';
spyOn(gl.utils, 'visitUrl'); spyOn(gl.utils, 'visitUrl');
updateForm({ search: 'broken' }, $filtersForm); updateForm({ search: 'broken' }, $filtersForm);
Issuable.filterResults($filtersForm); IssuableIndex.filterResults($filtersForm);
const params = `${DEFAULT_PARAMS}&search=broken`; const params = `${DEFAULT_PARAMS}&search=broken`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
...@@ -64,14 +64,14 @@ import '~/issuable'; ...@@ -64,14 +64,14 @@ import '~/issuable';
// initial filter // initial filter
updateForm({ milestone_title: 'v1.0' }, $filtersForm); updateForm({ milestone_title: 'v1.0' }, $filtersForm);
Issuable.filterResults($filtersForm); IssuableIndex.filterResults($filtersForm);
let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`; let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
// update filter // update filter
updateForm({ label_name: 'Frontend' }, $filtersForm); updateForm({ label_name: 'Frontend' }, $filtersForm);
Issuable.filterResults($filtersForm); IssuableIndex.filterResults($filtersForm);
params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`; params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`;
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params);
}); });
......
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