Commit 9c38fec6 authored by Winnie Hellmann's avatar Winnie Hellmann Committed by Clement Ho

Shorten protected branch / tag access level dropdown text

parent c79679f6
...@@ -935,11 +935,6 @@ pre.light-well { ...@@ -935,11 +935,6 @@ pre.light-well {
} }
} }
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
}
.flash-container { .flash-container {
padding: 0; padding: 0;
} }
......
.panel.panel-default.protected-branches-list.js-protected-branches-list .protected-branches-list.js-protected-branches-list
- if @protected_branches.empty? - if @protected_branches.empty?
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
.panel.panel-default.protected-tags-list.js-protected-tags-list .protected-tags-list.js-protected-tags-list
- if @protected_tags.empty? - if @protected_tags.empty?
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
/* eslint-disable no-underscore-dangle, class-methods-use-this */ /* eslint-disable no-underscore-dangle, class-methods-use-this */
import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash'; import Flash from '~/flash';
import { n__ } from '~/locale';
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants'; import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
export default class ProtectedBranchAccessDropdown { export default class AccessDropdown {
constructor(options) { constructor(options) {
const { const { $dropdown, accessLevel, accessLevelsData } = options;
$dropdown,
accessLevel,
accessLevelsData,
} = options;
this.options = options; this.options = options;
this.groups = []; this.groups = [];
this.accessLevel = accessLevel; this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles; this.accessLevelsData = accessLevelsData.roles;
this.$dropdown = $dropdown; this.$dropdown = $dropdown;
this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`); this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`);
this.$protectedTagsContainer = $('.js-protected-branches-container');
this.usersPath = '/autocomplete/users.json'; this.usersPath = '/autocomplete/users.json';
this.groupsPath = '/autocomplete/project_groups.json'; this.groupsPath = '/autocomplete/project_groups.json';
this.defaultLabel = this.$dropdown.data('defaultLabel'); this.defaultLabel = this.$dropdown.data('defaultLabel');
...@@ -47,7 +42,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -47,7 +42,7 @@ export default class ProtectedBranchAccessDropdown {
onHide(); onHide();
} }
}, },
clicked: (options) => { clicked: options => {
const { $el, e } = options; const { $el, e } = options;
const item = options.selectedObj; const item = options.selectedObj;
...@@ -56,7 +51,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -56,7 +51,7 @@ export default class ProtectedBranchAccessDropdown {
if ($el.is('.is-active')) { if ($el.is('.is-active')) {
if (item.id === this.noOneObj.id) { if (item.id === this.noOneObj.id) {
// remove all others selected items // remove all others selected items
this.accessLevelsData.forEach((level) => { this.accessLevelsData.forEach(level => {
if (level.id !== item.id) { if (level.id !== item.id) {
this.removeSelectedItem(level); this.removeSelectedItem(level);
} }
...@@ -65,7 +60,9 @@ export default class ProtectedBranchAccessDropdown { ...@@ -65,7 +60,9 @@ export default class ProtectedBranchAccessDropdown {
// remove selected item visually // remove selected item visually
this.$wrap.find(`.item-${item.type}`).removeClass('is-active'); this.$wrap.find(`.item-${item.type}`).removeClass('is-active');
} else { } else {
const $noOne = this.$wrap.find(`.is-active.item-${item.type}[data-role-id="${this.noOneObj.id}"]`); const $noOne = this.$wrap.find(
`.is-active.item-${item.type}[data-role-id="${this.noOneObj.id}"]`,
);
if ($noOne.length) { if ($noOne.length) {
$noOne.removeClass('is-active'); $noOne.removeClass('is-active');
this.removeSelectedItem(this.noOneObj); this.removeSelectedItem(this.noOneObj);
...@@ -86,6 +83,8 @@ export default class ProtectedBranchAccessDropdown { ...@@ -86,6 +83,8 @@ export default class ProtectedBranchAccessDropdown {
} }
}, },
}); });
this.$dropdown.find('.dropdown-toggle-text').text(this.toggleLabel());
} }
persistPreselectedItems() { persistPreselectedItems() {
...@@ -95,7 +94,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -95,7 +94,7 @@ export default class ProtectedBranchAccessDropdown {
return; return;
} }
const persistedItems = itemsToPreselect.map((item) => { const persistedItems = itemsToPreselect.map(item => {
const persistedItem = Object.assign({}, item); const persistedItem = Object.assign({}, item);
persistedItem.persisted = true; persistedItem.persisted = true;
return persistedItem; return persistedItem;
...@@ -120,7 +119,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -120,7 +119,7 @@ export default class ProtectedBranchAccessDropdown {
getInputData() { getInputData() {
const selectedItems = this.getAllSelectedItems(); const selectedItems = this.getAllSelectedItems();
const accessLevels = selectedItems.map((item) => { const accessLevels = selectedItems.map(item => {
const obj = {}; const obj = {};
if (typeof item.id !== 'undefined') { if (typeof item.id !== 'undefined') {
...@@ -222,14 +221,11 @@ export default class ProtectedBranchAccessDropdown { ...@@ -222,14 +221,11 @@ export default class ProtectedBranchAccessDropdown {
return true; return true;
} }
if (item.type === LEVEL_TYPES.USER && if (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) {
item.user_id === itemToDelete.id) {
index = i; index = i;
} else if (item.type === LEVEL_TYPES.ROLE && } else if (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) {
item.access_level === itemToDelete.id) {
index = i; index = i;
} else if (item.type === LEVEL_TYPES.GROUP && } else if (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id) {
item.group_id === itemToDelete.id) {
index = i; index = i;
} }
...@@ -256,34 +252,48 @@ export default class ProtectedBranchAccessDropdown { ...@@ -256,34 +252,48 @@ export default class ProtectedBranchAccessDropdown {
toggleLabel() { toggleLabel() {
const currentItems = this.getSelectedItems(); const currentItems = this.getSelectedItems();
const types = _.groupBy(currentItems, item => item.type); const $dropdownToggleText = this.$dropdown.find('.dropdown-toggle-text');
let label = [];
if (currentItems.length) { if (currentItems.length === 0) {
label = Object.keys(LEVEL_TYPES).map((levelType) => { $dropdownToggleText.addClass('is-default');
const typeName = LEVEL_TYPES[levelType]; return this.defaultLabel;
const numberOfTypes = types[typeName] ? types[typeName].length : 0; }
const text = numberOfTypes === 1 ? typeName : `${typeName}s`;
return `${numberOfTypes} ${text}`; $dropdownToggleText.removeClass('is-default');
});
} else { if (currentItems.length === 1 && currentItems[0].type === LEVEL_TYPES.ROLE) {
label.push(this.defaultLabel); const roleData = this.accessLevelsData.find(data => data.id === currentItems[0].access_level);
return roleData.text;
}
const labelPieces = [];
const counts = _.countBy(currentItems, item => item.type);
if (counts[LEVEL_TYPES.ROLE] > 0) {
labelPieces.push(n__('1 role', '%d roles', counts[LEVEL_TYPES.ROLE]));
} }
this.$dropdown.find('.dropdown-toggle-text').toggleClass('is-default', !currentItems.length); if (counts[LEVEL_TYPES.USER] > 0) {
labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER]));
}
if (counts[LEVEL_TYPES.GROUP] > 0) {
labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP]));
}
return label.join(', '); return labelPieces.join(', ');
} }
getData(query, callback) { getData(query, callback) {
Promise.all([ Promise.all([
this.getUsers(query), this.getUsers(query),
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
]).then(([usersResponse, groupsResponse]) => { ])
this.groupsData = groupsResponse; .then(([usersResponse, groupsResponse]) => {
callback(this.consolidateData(usersResponse.data, groupsResponse.data)); this.groupsData = groupsResponse;
}).catch(() => Flash('Failed to load groups & users.')); callback(this.consolidateData(usersResponse.data, groupsResponse.data));
})
.catch(() => Flash('Failed to load groups & users.'));
} }
consolidateData(usersResponse, groupsResponse) { consolidateData(usersResponse, groupsResponse) {
...@@ -308,12 +318,15 @@ export default class ProtectedBranchAccessDropdown { ...@@ -308,12 +318,15 @@ export default class ProtectedBranchAccessDropdown {
/* /*
* Build groups * Build groups
*/ */
const groups = groupsResponse.map(group => ({ ...group, type: LEVEL_TYPES.GROUP })); const groups = groupsResponse.map(group => ({
...group,
type: LEVEL_TYPES.GROUP,
}));
/* /*
* Build roles * Build roles
*/ */
const roles = this.accessLevelsData.map((level) => { const roles = this.accessLevelsData.map(level => {
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
// This re-assignment is intentional as // This re-assignment is intentional as
// level.type property is being used in removeSelectedItem() // level.type property is being used in removeSelectedItem()
...@@ -327,7 +340,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -327,7 +340,7 @@ export default class ProtectedBranchAccessDropdown {
/* /*
* Build users * Build users
*/ */
const users = selectedItems.filter(item => item.type === LEVEL_TYPES.USER).map((item) => { const users = selectedItems.filter(item => item.type === LEVEL_TYPES.USER).map(item => {
// Save identifiers for easy-checking more later // Save identifiers for easy-checking more later
map.push(LEVEL_TYPES.USER + item.user_id); map.push(LEVEL_TYPES.USER + item.user_id);
...@@ -342,7 +355,7 @@ export default class ProtectedBranchAccessDropdown { ...@@ -342,7 +355,7 @@ export default class ProtectedBranchAccessDropdown {
// Has to be checked against server response // Has to be checked against server response
// because the selected item can be in filter results // because the selected item can be in filter results
usersResponse.forEach((response) => { usersResponse.forEach(response => {
// Add is it has not been added // Add is it has not been added
if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) { if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) {
const user = Object.assign({}, response); const user = Object.assign({}, response);
...@@ -454,7 +467,9 @@ export default class ProtectedBranchAccessDropdown { ...@@ -454,7 +467,9 @@ export default class ProtectedBranchAccessDropdown {
groupRowHtml(group, isActive) { groupRowHtml(group, isActive) {
const isActiveClass = isActive || ''; const isActiveClass = isActive || '';
const avatarEl = group.avatar_url ? `<img src="${group.avatar_url}" class="avatar avatar-inline" width="30">` : ''; const avatarEl = group.avatar_url
? `<img src="${group.avatar_url}" class="avatar avatar-inline" width="30">`
: '';
return ` return `
<li> <li>
......
export const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group',
};
export const LEVEL_ID_PROP = {
ROLE: 'access_level',
USER: 'user_id',
GROUP: 'group_id',
};
export const ACCESS_LEVEL_NONE = 0;
...@@ -3,8 +3,8 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -3,8 +3,8 @@ import axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import Flash from '~/flash'; import Flash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown'; import CreateItemDropdown from '~/create_item_dropdown';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults'; const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
...@@ -30,7 +30,7 @@ export default class ProtectedBranchCreate { ...@@ -30,7 +30,7 @@ export default class ProtectedBranchCreate {
this.onSelectCallback = this.onSelect.bind(this); this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown // Allowed to Merge dropdown
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToMergeDropdown, $dropdown: $allowedToMergeDropdown,
accessLevelsData: gon.merge_access_levels, accessLevelsData: gon.merge_access_levels,
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
...@@ -38,7 +38,7 @@ export default class ProtectedBranchCreate { ...@@ -38,7 +38,7 @@ export default class ProtectedBranchCreate {
}); });
// Allowed to Push dropdown // Allowed to Push dropdown
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToPushDropdown, $dropdown: $allowedToPushDropdown,
accessLevelsData: gon.push_access_levels, accessLevelsData: gon.push_access_levels,
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
...@@ -82,12 +82,12 @@ export default class ProtectedBranchCreate { ...@@ -82,12 +82,12 @@ export default class ProtectedBranchCreate {
}, },
}; };
Object.keys(ACCESS_LEVELS).forEach((level) => { Object.keys(ACCESS_LEVELS).forEach(level => {
const accessLevel = ACCESS_LEVELS[level]; const accessLevel = ACCESS_LEVELS[level];
const selectedItems = this[`${accessLevel}_dropdown`].getSelectedItems(); const selectedItems = this[`${accessLevel}_dropdown`].getSelectedItems();
const levelAttributes = []; const levelAttributes = [];
selectedItems.forEach((item) => { selectedItems.forEach(item => {
if (item.type === LEVEL_TYPES.USER) { if (item.type === LEVEL_TYPES.USER) {
levelAttributes.push({ levelAttributes.push({
user_id: item.user_id, user_id: item.user_id,
...@@ -115,10 +115,14 @@ export default class ProtectedBranchCreate { ...@@ -115,10 +115,14 @@ export default class ProtectedBranchCreate {
if (savedDefaults != null) { if (savedDefaults != null) {
this[`${ACCESS_LEVELS.MERGE}_dropdown`].setSelectedItems(savedDefaults.merge); this[`${ACCESS_LEVELS.MERGE}_dropdown`].setSelectedItems(savedDefaults.merge);
let updatedLabel = this[`${ACCESS_LEVELS.MERGE}_dropdown`].toggleLabel(); let updatedLabel = this[`${ACCESS_LEVELS.MERGE}_dropdown`].toggleLabel();
this[`${ACCESS_LEVELS.MERGE}_dropdown`].$dropdown.find('.dropdown-toggle-text').text(updatedLabel); this[`${ACCESS_LEVELS.MERGE}_dropdown`].$dropdown
.find('.dropdown-toggle-text')
.text(updatedLabel);
this[`${ACCESS_LEVELS.PUSH}_dropdown`].setSelectedItems(savedDefaults.push); this[`${ACCESS_LEVELS.PUSH}_dropdown`].setSelectedItems(savedDefaults.push);
updatedLabel = this[`${ACCESS_LEVELS.PUSH}_dropdown`].toggleLabel(); updatedLabel = this[`${ACCESS_LEVELS.PUSH}_dropdown`].toggleLabel();
this[`${ACCESS_LEVELS.PUSH}_dropdown`].$dropdown.find('.dropdown-toggle-text').text(updatedLabel); this[`${ACCESS_LEVELS.PUSH}_dropdown`].$dropdown
.find('.dropdown-toggle-text')
.text(updatedLabel);
} }
} }
} }
......
...@@ -4,8 +4,8 @@ import $ from 'jquery'; ...@@ -4,8 +4,8 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash'; import Flash from '~/flash';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
export default class ProtectedBranchEdit { export default class ProtectedBranchEdit {
constructor(options) { constructor(options) {
...@@ -15,15 +15,19 @@ export default class ProtectedBranchEdit { ...@@ -15,15 +15,19 @@ export default class ProtectedBranchEdit {
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.$wraps[ACCESS_LEVELS.MERGE] = this.$allowedToMergeDropdown.closest(`.${ACCESS_LEVELS.MERGE}-container`); this.$wraps[ACCESS_LEVELS.MERGE] = this.$allowedToMergeDropdown.closest(
this.$wraps[ACCESS_LEVELS.PUSH] = this.$allowedToPushDropdown.closest(`.${ACCESS_LEVELS.PUSH}-container`); `.${ACCESS_LEVELS.MERGE}-container`,
);
this.$wraps[ACCESS_LEVELS.PUSH] = this.$allowedToPushDropdown.closest(
`.${ACCESS_LEVELS.PUSH}-container`,
);
this.buildDropdowns(); this.buildDropdowns();
} }
buildDropdowns() { buildDropdowns() {
// Allowed to merge dropdown // Allowed to merge dropdown
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.MERGE, accessLevel: ACCESS_LEVELS.MERGE,
accessLevelsData: gon.merge_access_levels, accessLevelsData: gon.merge_access_levels,
$dropdown: this.$allowedToMergeDropdown, $dropdown: this.$allowedToMergeDropdown,
...@@ -32,7 +36,7 @@ export default class ProtectedBranchEdit { ...@@ -32,7 +36,7 @@ export default class ProtectedBranchEdit {
}); });
// Allowed to push dropdown // Allowed to push dropdown
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.PUSH, accessLevel: ACCESS_LEVELS.PUSH,
accessLevelsData: gon.push_access_levels, accessLevelsData: gon.push_access_levels,
$dropdown: this.$allowedToPushDropdown, $dropdown: this.$allowedToPushDropdown,
...@@ -64,33 +68,38 @@ export default class ProtectedBranchEdit { ...@@ -64,33 +68,38 @@ export default class ProtectedBranchEdit {
return acc; return acc;
}, {}); }, {});
axios.patch(this.$wrap.data('url'), { axios
protected_branch: formData, .patch(this.$wrap.data('url'), {
}).then(({ data }) => { protected_branch: formData,
this.hasChanges = false; })
.then(({ data }) => {
Object.keys(ACCESS_LEVELS).forEach((level) => { this.hasChanges = false;
const accessLevelName = ACCESS_LEVELS[level];
Object.keys(ACCESS_LEVELS).forEach(level => {
// The data coming from server will be the new persisted *state* for each dropdown const accessLevelName = ACCESS_LEVELS[level];
this.setSelectedItemsToDropdown(data[accessLevelName], `${accessLevelName}_dropdown`);
// The data coming from server will be the new persisted *state* for each dropdown
this.setSelectedItemsToDropdown(data[accessLevelName], `${accessLevelName}_dropdown`);
});
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
})
.catch(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
}); });
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
}).catch(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
});
} }
setSelectedItemsToDropdown(items = [], dropdownName) { setSelectedItemsToDropdown(items = [], dropdownName) {
const itemsToAdd = items.map((currentItem) => { const itemsToAdd = items.map(currentItem => {
if (currentItem.user_id) { if (currentItem.user_id) {
// Do this only for users for now // Do this only for users for now
// get the current data for selected items // get the current data for selected items
const selectedItems = this[dropdownName].getSelectedItems(); const selectedItems = this[dropdownName].getSelectedItems();
const currentSelectedItem = _.findWhere(selectedItems, { user_id: currentItem.user_id }); const currentSelectedItem = _.findWhere(selectedItems, {
user_id: currentItem.user_id,
});
return { return {
id: currentItem.id, id: currentItem.id,
......
import $ from 'jquery'; import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash'; import createFlash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown'; import CreateItemDropdown from '~/create_item_dropdown';
import { s__ } from '~/locale';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagCreate { export default class ProtectedTagCreate {
constructor() { constructor() {
...@@ -24,7 +25,7 @@ export default class ProtectedTagCreate { ...@@ -24,7 +25,7 @@ export default class ProtectedTagCreate {
this.onSelectCallback = this.onSelect.bind(this); this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Create dropdown // Allowed to Create dropdown
this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new ProtectedTagAccessDropdown({ this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToCreateDropdown, $dropdown: $allowedToCreateDropdown,
accessLevelsData: gon.create_access_levels, accessLevelsData: gon.create_access_levels,
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
...@@ -44,7 +45,9 @@ export default class ProtectedTagCreate { ...@@ -44,7 +45,9 @@ export default class ProtectedTagCreate {
// Enable submit button after selecting an option // Enable submit button after selecting an option
onSelect() { onSelect() {
const $allowedToCreate = this[`${ACCESS_LEVELS.CREATE}_dropdown`].getSelectedItems(); const $allowedToCreate = this[`${ACCESS_LEVELS.CREATE}_dropdown`].getSelectedItems();
const toggle = !(this.$form.find('input[name="protected_tag[name]"]').val() && $allowedToCreate.length); const toggle = !(
this.$form.find('input[name="protected_tag[name]"]').val() && $allowedToCreate.length
);
this.$form.find('input[type="submit"]').attr('disabled', toggle); this.$form.find('input[type="submit"]').attr('disabled', toggle);
} }
...@@ -61,12 +64,12 @@ export default class ProtectedTagCreate { ...@@ -61,12 +64,12 @@ export default class ProtectedTagCreate {
}, },
}; };
Object.keys(ACCESS_LEVELS).forEach((level) => { Object.keys(ACCESS_LEVELS).forEach(level => {
const accessLevel = ACCESS_LEVELS[level]; const accessLevel = ACCESS_LEVELS[level];
const selectedItems = this[`${ACCESS_LEVELS.CREATE}_dropdown`].getSelectedItems(); const selectedItems = this[`${ACCESS_LEVELS.CREATE}_dropdown`].getSelectedItems();
const levelAttributes = []; const levelAttributes = [];
selectedItems.forEach((item) => { selectedItems.forEach(item => {
if (item.type === LEVEL_TYPES.USER) { if (item.type === LEVEL_TYPES.USER) {
levelAttributes.push({ levelAttributes.push({
user_id: item.user_id, user_id: item.user_id,
...@@ -94,6 +97,7 @@ export default class ProtectedTagCreate { ...@@ -94,6 +97,7 @@ export default class ProtectedTagCreate {
axios[this.$form.attr('method')](this.$form.attr('action'), this.getFormData()) axios[this.$form.attr('method')](this.$form.attr('action'), this.getFormData())
.then(() => { .then(() => {
location.reload(); location.reload();
}).catch(() => Flash('Failed to protect the tag')); })
.catch(() => createFlash(s__('ProjectSettings|Failed to protect the tag')));
} }
} }
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagEdit { export default class ProtectedTagEdit {
constructor(options) { constructor(options) {
...@@ -13,14 +14,16 @@ export default class ProtectedTagEdit { ...@@ -13,14 +14,16 @@ export default class ProtectedTagEdit {
this.$wrap = options.$wrap; this.$wrap = options.$wrap;
this.$allowedToCreateDropdownButton = this.$wrap.find('.js-allowed-to-create'); this.$allowedToCreateDropdownButton = this.$wrap.find('.js-allowed-to-create');
this.$allowedToCreateDropdownContainer = this.$allowedToCreateDropdownButton.closest('.create_access_levels-container'); this.$allowedToCreateDropdownContainer = this.$allowedToCreateDropdownButton.closest(
'.create_access_levels-container',
);
this.buildDropdowns(); this.buildDropdowns();
} }
buildDropdowns() { buildDropdowns() {
// Allowed to create dropdown // Allowed to create dropdown
this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new ProtectedTagAccessDropdown({ this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.CREATE, accessLevel: ACCESS_LEVELS.CREATE,
accessLevelsData: gon.create_access_levels, accessLevelsData: gon.create_access_levels,
$dropdown: this.$allowedToCreateDropdownButton, $dropdown: this.$allowedToCreateDropdownButton,
...@@ -52,30 +55,35 @@ export default class ProtectedTagEdit { ...@@ -52,30 +55,35 @@ export default class ProtectedTagEdit {
return acc; return acc;
}, {}); }, {});
axios.patch(this.$wrap.data('url'), { axios
protected_tag: formData, .patch(this.$wrap.data('url'), {
}).then(({ data }) => { protected_tag: formData,
this.hasChanges = false; })
.then(({ data }) => {
Object.keys(ACCESS_LEVELS).forEach((level) => { this.hasChanges = false;
const accessLevelName = ACCESS_LEVELS[level];
Object.keys(ACCESS_LEVELS).forEach(level => {
// The data coming from server will be the new persisted *state* for each dropdown const accessLevelName = ACCESS_LEVELS[level];
this.setSelectedItemsToDropdown(data[accessLevelName], `${accessLevelName}_dropdown`);
// The data coming from server will be the new persisted *state* for each dropdown
this.setSelectedItemsToDropdown(data[accessLevelName], `${accessLevelName}_dropdown`);
});
})
.catch(() => {
$.scrollTo(0);
createFlash(s__('ProjectSettings|Failed to update tag!'));
}); });
}).catch(() => {
$.scrollTo(0);
Flash('Failed to update tag!');
});
} }
setSelectedItemsToDropdown(items = [], dropdownName) { setSelectedItemsToDropdown(items = [], dropdownName) {
const itemsToAdd = items.map((currentItem) => { const itemsToAdd = items.map(currentItem => {
if (currentItem.user_id) { if (currentItem.user_id) {
// Do this only for users for now // Do this only for users for now
// get the current data for selected items // get the current data for selected items
const selectedItems = this[dropdownName].getSelectedItems(); const selectedItems = this[dropdownName].getSelectedItems();
const currentSelectedItem = _.findWhere(selectedItems, { user_id: currentItem.user_id }); const currentSelectedItem = _.findWhere(selectedItems, {
user_id: currentItem.user_id,
});
return { return {
id: currentItem.id, id: currentItem.id,
......
- default_label = 'Select'
- dropdown_label = default_label
%div{ class: "#{input_basic_name}-container" }
- if access_levels.present?
- dropdown_label = [pluralize(level_frequencies[:role], 'role'), pluralize(level_frequencies[:user], 'user'), pluralize(level_frequencies[:group], 'group')].to_sentence
= dropdown_tag(dropdown_label, options: { toggle_class: "#{toggle_class} js-multiselect", dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true,
data: { default_label: default_label, preselected_items: access_levels_data(access_levels) } })
%td %td
= render partial: 'projects/protected_branches/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.merge_access_levels, level_frequencies: access_level_frequencies(protected_branch.merge_access_levels), input_basic_name: 'merge_access_levels', toggle_class: 'js-allowed-to-merge' } = render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.merge_access_levels, level_frequencies: access_level_frequencies(protected_branch.merge_access_levels), input_basic_name: 'merge_access_levels', toggle_class: 'js-allowed-to-merge' }
%td %td
= render partial: 'projects/protected_branches/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.push_access_levels, level_frequencies: access_level_frequencies(protected_branch.push_access_levels), input_basic_name: 'push_access_levels', toggle_class: 'js-allowed-to-push' } = render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.push_access_levels, level_frequencies: access_level_frequencies(protected_branch.push_access_levels), input_basic_name: 'push_access_levels', toggle_class: 'js-allowed-to-push' }
- default_label = 'Select'
- dropdown_label = default_label
%div{ class: "#{input_basic_name}-container" }
- if access_levels.present?
- dropdown_label = [pluralize(level_frequencies[:role], 'role'), pluralize(level_frequencies[:user], 'user'), pluralize(level_frequencies[:group], 'group')].to_sentence
= dropdown_tag(dropdown_label, options: { toggle_class: "#{toggle_class} js-multiselect", dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true,
data: { default_label: default_label, preselected_items: access_levels_data(access_levels) } })
%td %td
= render partial: 'projects/protected_tags/ee/access_level_dropdown', locals: { protected_tag: protected_tag, access_levels: protected_tag.create_access_levels, level_frequencies: access_level_frequencies(protected_tag.create_access_levels), input_basic_name: 'create_access_levels', toggle_class: 'js-allowed-to-create' } = render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_tag: protected_tag, access_levels: protected_tag.create_access_levels, level_frequencies: access_level_frequencies(protected_tag.create_access_levels), input_basic_name: 'create_access_levels', toggle_class: 'js-allowed-to-create' }
- default_label = s_('RepositorySettingsAccessLevel|Select')
%div{ class: "#{input_basic_name}-container" }
= dropdown_tag(default_label, options: { toggle_class: "#{toggle_class} js-multiselect", dropdown_class: 'dropdown-menu-user dropdown-menu-selectable', filter: true, data: { default_label: default_label, preselected_items: access_levels_data(access_levels) } })
---
title: Shorten protected branch / tag access level dropdown text
merge_request: 5091
author:
type: changed
...@@ -5,7 +5,9 @@ module EE ...@@ -5,7 +5,9 @@ module EE
find(".js-allowed-to-#{operation}").click find(".js-allowed-to-#{operation}").click
wait_for_requests wait_for_requests
Array(option).each { |opt| click_on(opt) } within('.dropdown-content') do
Array(option).each { |opt| click_on(opt) }
end
find(".js-allowed-to-#{operation}").click # needed to submit form in some cases find(".js-allowed-to-#{operation}").click # needed to submit form in some cases
end end
......
...@@ -84,10 +84,10 @@ feature 'Protected Branches', :js do ...@@ -84,10 +84,10 @@ feature 'Protected Branches', :js do
within form do within form do
page.within(".js-allowed-to-merge") do page.within(".js-allowed-to-merge") do
expect(page.find(".dropdown-toggle-text")).to have_content("1 role, 0 users, 0 groups") expect(page.find(".dropdown-toggle-text")).to have_content("No one")
end end
page.within(".js-allowed-to-push") do page.within(".js-allowed-to-push") do
expect(page.find(".dropdown-toggle-text")).to have_content("1 role, 0 users, 0 groups") expect(page.find(".dropdown-toggle-text")).to have_content("Developers + Masters")
end end
end end
end end
......
...@@ -13,7 +13,9 @@ feature 'Protected Tags', :js do ...@@ -13,7 +13,9 @@ feature 'Protected Tags', :js do
find(".js-allowed-to-#{operation}").click find(".js-allowed-to-#{operation}").click
wait_for_requests wait_for_requests
Array(option).each { |opt| click_on(opt) } within('.dropdown-content') do
Array(option).each { |opt| click_on(opt) }
end
find(".js-allowed-to-#{operation}").click # needed to submit form in some cases find(".js-allowed-to-#{operation}").click # needed to submit form in some cases
end end
......
import $ from 'jquery';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { LEVEL_TYPES } from 'ee/projects/settings/constants';
describe('AccessDropdown', () => {
const defaultLabel = 'dummy default label';
let dropdown;
beforeEach(() => {
setFixtures(`
<div id="dummy-dropdown">
<span class="dropdown-toggle-text"></span>
</div>
`);
const $dropdown = $('#dummy-dropdown');
$dropdown.data('defaultLabel', defaultLabel);
const options = {
$dropdown,
accessLevelsData: {
roles: [
{
id: 42,
text: 'Dummy Role',
},
],
},
};
dropdown = new AccessDropdown(options);
});
describe('toggleLabel', () => {
let $dropdownToggleText;
const dummyItems = [
{ type: LEVEL_TYPES.ROLE, access_level: 42 },
{ type: LEVEL_TYPES.USER },
{ type: LEVEL_TYPES.USER },
{ type: LEVEL_TYPES.GROUP },
{ type: LEVEL_TYPES.GROUP },
{ type: LEVEL_TYPES.GROUP },
];
beforeEach(() => {
$dropdownToggleText = $('.dropdown-toggle-text');
});
it('displays number of items', () => {
dropdown.setSelectedItems(dummyItems);
$dropdownToggleText.addClass('is-default');
const label = dropdown.toggleLabel();
expect(label).toBe('1 role, 2 users, 3 groups');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
describe('without selected items', () => {
beforeEach(() => {
dropdown.setSelectedItems([]);
});
it('falls back to default label', () => {
const label = dropdown.toggleLabel();
expect(label).toBe(defaultLabel);
expect($dropdownToggleText).toHaveClass('is-default');
});
});
describe('with only role', () => {
beforeEach(() => {
dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.ROLE));
$dropdownToggleText.addClass('is-default');
});
it('displays the role name', () => {
const label = dropdown.toggleLabel();
expect(label).toBe('Dummy Role');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
});
describe('with only users', () => {
beforeEach(() => {
dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.USER));
$dropdownToggleText.addClass('is-default');
});
it('displays number of users', () => {
const label = dropdown.toggleLabel();
expect(label).toBe('2 users');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
});
describe('with only groups', () => {
beforeEach(() => {
dropdown.setSelectedItems(dummyItems.filter(item => item.type === LEVEL_TYPES.GROUP));
$dropdownToggleText.addClass('is-default');
});
it('displays number of groups', () => {
const label = dropdown.toggleLabel();
expect(label).toBe('3 groups');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
});
describe('with users and groups', () => {
beforeEach(() => {
const selectedTypes = [LEVEL_TYPES.GROUP, LEVEL_TYPES.USER];
dropdown.setSelectedItems(dummyItems.filter(item => selectedTypes.includes(item.type)));
$dropdownToggleText.addClass('is-default');
});
it('displays number of groups', () => {
const label = dropdown.toggleLabel();
expect(label).toBe('2 users, 3 groups');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
});
});
});
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