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
This commit is contained in:
commit
2f02843fe9
|
@ -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';
|
||||||
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;
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* 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);
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,165 @@
|
||||||
|
/* 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 [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,33 @@
|
||||||
/* 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 @@
|
||||||
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 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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 @@
|
||||||
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 @@
|
||||||
$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 @@
|
||||||
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 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
|
|
||||||
},
|
|
||||||
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;
|
if (userCanBulkUpdate && !alreadyInitialized) {
|
||||||
this.issuableBulkActions.setOriginalDropdownData();
|
IssuableBulkUpdateActions.init({
|
||||||
|
prefixId: pagePrefix,
|
||||||
if ($checkedIssues.length > 0) {
|
|
||||||
const ids = $.map($checkedIssues, function(value) {
|
|
||||||
return $(value).data('id');
|
|
||||||
});
|
});
|
||||||
$updateIssuesIds.val(ids);
|
|
||||||
$issuesOtherFilters.hide();
|
|
||||||
$issuesBulkUpdate.show();
|
|
||||||
} else {
|
|
||||||
$updateIssuesIds.val([]);
|
|
||||||
$issuesBulkUpdate.hide();
|
|
||||||
$issuesOtherFilters.show();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
|
||||||
|
}
|
||||||
|
},
|
||||||
resetIncomingEmailToken: function() {
|
resetIncomingEmailToken: function() {
|
||||||
$('.incoming-email-token-reset').on('click', function(e) {
|
$('.incoming-email-token-reset').on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
|
@ -1,166 +0,0 @@
|
||||||
/* 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 @@
|
||||||
/* 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 @@
|
||||||
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 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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';
|
||||||
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 {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled-content {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 @@
|
||||||
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 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.issues-holder .issue-check {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rss-btn {
|
.rss-btn {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 @@
|
||||||
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 @@
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- @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 @@
|
||||||
.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 @@
|
||||||
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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- @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 @@
|
||||||
.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 @@
|
||||||
|
|
||||||
= 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
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
- 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 @@
|
||||||
= 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 @@
|
||||||
.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 @@
|
||||||
- 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 @@
|
||||||
|
|
||||||
.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 @@
|
||||||
= 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 @@
|
||||||
- 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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%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'}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* 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';
|
||||||
|
|
||||||
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';
|
||||||
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';
|
||||||
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';
|
||||||
// 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);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue