
Previously, when removing an added item (of any type) nested inside of another added type, the removal has no effect. This is because, in the case of an add, there is no corresponding remove object, as there is in the case of a modified section. Example: Modified access rules with a new ref and rule-2 was removed before saving. { ..., refs/for/new: { added: true, permissions: { permission-1: { added: true, rules { rule-1: { added: true, action: 'ALLOW" }, rule-2: { added: true, removed: true, action: 'ALLOW" } } } } } } This change fixes the problem by actually removing the item completely if it was added in the first place, as opposed to keeping a placeholder that can be undone, which makes sense as both ways allow restoration of the initial state of the access object. The new access JSON for this example is as follows: There is no reason for rule-2 to be in here at all because the server does not have to remove it (it did not exist before). { ..., refs/for/new: { added: true, permissions: { permission-1: { added: true, rules { rule-1: { added: true, action: 'ALLOW" }, } } } } } Bug: Issue 8795 Change-Id: Ief7ebd18f0ad207358b68f504ffe2b0b322340f9
269 lines
7.9 KiB
JavaScript
269 lines
7.9 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Fired when the section has been modified or removed.
|
|
*
|
|
* @event access-modified
|
|
*/
|
|
|
|
/**
|
|
* Fired when a section that was previously added was removed.
|
|
* @event added-section-removed
|
|
*/
|
|
|
|
const GLOBAL_NAME = 'GLOBAL_CAPABILITIES';
|
|
|
|
// The name that gets automatically input when a new reference is added.
|
|
const NEW_NAME = 'refs/heads/*';
|
|
const REFS_NAME = 'refs/';
|
|
const ON_BEHALF_OF = '(On Behalf Of)';
|
|
const LABEL = 'Label';
|
|
|
|
Polymer({
|
|
is: 'gr-access-section',
|
|
|
|
properties: {
|
|
capabilities: Object,
|
|
/** @type {?} */
|
|
section: {
|
|
type: Object,
|
|
notify: true,
|
|
observer: '_updateSection',
|
|
},
|
|
groups: Object,
|
|
labels: Object,
|
|
editing: {
|
|
type: Boolean,
|
|
value: false,
|
|
observer: '_handleEditingChanged',
|
|
},
|
|
_originalId: String,
|
|
_editingRef: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_deleted: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_permissions: Array,
|
|
},
|
|
|
|
behaviors: [
|
|
Gerrit.AccessBehavior,
|
|
],
|
|
|
|
listeners: {
|
|
'access-saved': '_handleAccessSaved',
|
|
},
|
|
|
|
_updateSection(section) {
|
|
this._permissions = this.toSortedArray(section.value.permissions);
|
|
this._originalId = section.id;
|
|
},
|
|
|
|
_handleAccessSaved() {
|
|
// Set a new 'original' value to keep track of after the value has been
|
|
// saved.
|
|
this._updateSection(this.section);
|
|
},
|
|
|
|
_handleValueChange() {
|
|
if (!this.section.value.added) {
|
|
this.section.value.modified = this.section.id !== this._originalId;
|
|
// Allows overall access page to know a change has been made.
|
|
// For a new section, this is not fired because new permissions and
|
|
// rules have to be added in order to save, modifying the ref is not
|
|
// enough.
|
|
this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
|
|
}
|
|
this.section.value.updatedId = this.section.id;
|
|
},
|
|
|
|
_handleEditingChanged(editing, editingOld) {
|
|
// Ignore when editing gets set initially.
|
|
if (!editingOld) { return; }
|
|
// Restore original values if no longer editing.
|
|
if (!editing) {
|
|
this._editingRef = false;
|
|
this._deleted = false;
|
|
delete this.section.value.deleted;
|
|
// Restore section ref.
|
|
this.set(['section', 'id'], this._originalId);
|
|
// Remove any unsaved but added permissions.
|
|
this._permissions = this._permissions.filter(p => !p.value.added);
|
|
for (const key of Object.keys(this.section.value.permissions)) {
|
|
if (this.section.value.permissions[key].added) {
|
|
delete this.section.value.permissions[key];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_computePermissions(name, capabilities, labels) {
|
|
let allPermissions;
|
|
if (name === GLOBAL_NAME) {
|
|
allPermissions = this.toSortedArray(capabilities);
|
|
} else {
|
|
const labelOptions = this._computeLabelOptions(labels);
|
|
allPermissions = labelOptions.concat(
|
|
this.toSortedArray(this.permissionValues));
|
|
}
|
|
return allPermissions.filter(permission => {
|
|
return !this.section.value.permissions[permission.id];
|
|
});
|
|
},
|
|
|
|
_computeHideEditClass(section) {
|
|
return section.id === 'GLOBAL_CAPABILITIES' ? 'hide' : '';
|
|
},
|
|
|
|
_handleAddedPermissionRemoved(e) {
|
|
const index = e.model.index;
|
|
this._permissions = this._permissions.slice(0, index).concat(
|
|
this._permissions.slice(index + 1, this._permissions.length));
|
|
},
|
|
|
|
_computeLabelOptions(labels) {
|
|
const labelOptions = [];
|
|
for (const labelName of Object.keys(labels)) {
|
|
labelOptions.push({
|
|
id: 'label-' + labelName,
|
|
value: {
|
|
name: `${LABEL} ${labelName}`,
|
|
id: 'label-' + labelName,
|
|
},
|
|
});
|
|
labelOptions.push({
|
|
id: 'labelAs-' + labelName,
|
|
value: {
|
|
name: `${LABEL} ${labelName} ${ON_BEHALF_OF}`,
|
|
id: 'labelAs-' + labelName,
|
|
},
|
|
});
|
|
}
|
|
return labelOptions;
|
|
},
|
|
|
|
_computePermissionName(name, permission, permissionValues, capabilities) {
|
|
if (name === GLOBAL_NAME) {
|
|
return capabilities[permission.id].name;
|
|
} else if (permissionValues[permission.id]) {
|
|
return permissionValues[permission.id].name;
|
|
} else if (permission.value.label) {
|
|
let behalfOf = '';
|
|
if (permission.id.startsWith('labelAs-')) {
|
|
behalfOf = ON_BEHALF_OF;
|
|
}
|
|
return `${LABEL} ${permission.value.label}${behalfOf}`;
|
|
}
|
|
},
|
|
|
|
_computeSectionName(name) {
|
|
// When a new section is created, it doesn't yet have a ref. Set into
|
|
// edit mode so that the user can input one.
|
|
if (!name) {
|
|
this._editingRef = true;
|
|
// Needed for the title value. This is the same default as GWT.
|
|
name = NEW_NAME;
|
|
// Needed for the input field value.
|
|
this.set('section.id', name);
|
|
}
|
|
if (name === GLOBAL_NAME) {
|
|
return 'Global Capabilities';
|
|
} else if (name.startsWith(REFS_NAME)) {
|
|
return `Reference: ${name}`;
|
|
}
|
|
return name;
|
|
},
|
|
|
|
_handleRemoveReference() {
|
|
if (this.section.value.added) {
|
|
this.dispatchEvent(new CustomEvent('added-section-removed',
|
|
{bubbles: true}));
|
|
}
|
|
this._deleted = true;
|
|
this.section.value.deleted = true;
|
|
this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
|
|
},
|
|
|
|
_handleUndoRemove() {
|
|
this._deleted = false;
|
|
delete this.section.value.deleted;
|
|
},
|
|
|
|
editReference() {
|
|
this._editingRef = true;
|
|
this.$.editRefInput.focus();
|
|
},
|
|
|
|
_computeSectionClass(editing, editingRef, deleted) {
|
|
const classList = [];
|
|
if (editing) {
|
|
classList.push('editing');
|
|
}
|
|
if (editingRef) {
|
|
classList.push('editingRef');
|
|
}
|
|
if (deleted) {
|
|
classList.push('deleted');
|
|
}
|
|
return classList.join(' ');
|
|
},
|
|
|
|
_computeEditBtnClass(name) {
|
|
return name === GLOBAL_NAME ? 'global' : '';
|
|
},
|
|
|
|
_handleAddPermission() {
|
|
const value = this.$.permissionSelect.value;
|
|
const permission = {
|
|
id: value,
|
|
value: {rules: {}, added: true},
|
|
};
|
|
|
|
// This is needed to update the 'label' property of the
|
|
// 'label-<label-name>' permission.
|
|
//
|
|
// The value from the add permission dropdown will either be
|
|
// label-<label-name> or labelAs-<labelName>.
|
|
// But, the format of the API response is as such:
|
|
// "permissions": {
|
|
// "label-Code-Review": {
|
|
// "label": "Code-Review",
|
|
// "rules": {...}
|
|
// }
|
|
// }
|
|
// }
|
|
// When we add a new item, we have to push the new permission in the same
|
|
// format as the ones that have been returned by the API.
|
|
if (value.startsWith('label')) {
|
|
permission.value.label =
|
|
value.replace('label-', '').replace('labelAs-', '');
|
|
}
|
|
// Add to the end of the array (used in dom-repeat) and also to the
|
|
// section object that is two way bound with its parent element.
|
|
this.push('_permissions', permission);
|
|
this.set(['section.value.permissions', permission.id],
|
|
permission.value);
|
|
},
|
|
});
|
|
})();
|