Show and allow modification of exclusive bit in gr-permission

Bug: Issue 8034
Change-Id: Ia66c8308c242e7f5a09dc796cbd02944a3b02aa6
This commit is contained in:
Becky Siegel
2018-01-12 13:48:07 -08:00
parent 8d4403eb28
commit f44c80e33a
6 changed files with 187 additions and 68 deletions

View File

@@ -16,6 +16,7 @@ limitations under the License.
<link rel="import" href="../../../bower_components/polymer/polymer.html"> <link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html"> <link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../styles/gr-form-styles.html"> <link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html"> <link rel="import" href="../../../styles/gr-menu-page-styles.html">
<link rel="import" href="../../../styles/shared-styles.html"> <link rel="import" href="../../../styles/shared-styles.html">
@@ -52,8 +53,13 @@ limitations under the License.
#removeBtn { #removeBtn {
display: none; display: none;
} }
.right {
display: flex;
align-items: center;
}
.editing #removeBtn { .editing #removeBtn {
display: block; display: block;
margin-left: 1.5em;
} }
.editing #addRule { .editing #addRule {
display: block; display: block;
@@ -82,10 +88,17 @@ limitations under the License.
<div id="mainContainer"> <div id="mainContainer">
<div class="header"> <div class="header">
<span class="title">[[name]]</span> <span class="title">[[name]]</span>
<div class="right">
<paper-toggle-button
id="exclusiveToggle"
checked="{{permission.value.exclusive}}"
on-change="_handleValueChange"
disabled$="[[!editing]]"></paper-toggle-button>Exclusive
<gr-button <gr-button
link link
id="removeBtn" id="removeBtn"
on-tap="_handleRemovePermission">Remove</gr-button> on-tap="_handleRemovePermission">Remove</gr-button>
</div>
</div><!-- end header --> </div><!-- end header -->
<div class="rules"> <div class="rules">
<template <template

View File

@@ -58,6 +58,7 @@
type: Boolean, type: Boolean,
value: false, value: false,
}, },
_originalExclusiveValue: Boolean,
}, },
behaviors: [ behaviors: [
@@ -68,6 +69,26 @@
'_handleRulesChanged(_rules.splices)', '_handleRulesChanged(_rules.splices)',
], ],
listeners: {
'access-saved': '_handleAccessSaved',
},
ready() {
this._setupValues();
},
_setupValues() {
if (!this.permission) { return; }
this._originalExclusiveValue = !!this.permission.value.exclusive;
Polymer.dom.flush();
},
_handleAccessSaved() {
// Set a new 'original' value to keep track of after the value has been
// saved.
this._setupValues();
},
_handleEditingChanged(editing, editingOld) { _handleEditingChanged(editing, editingOld) {
// Ignore when editing gets set initially. // Ignore when editing gets set initially.
if (!editingOld) { return; } if (!editingOld) { return; }
@@ -76,9 +97,19 @@
this._deleted = false; this._deleted = false;
this._groupFilter = ''; this._groupFilter = '';
this._rules = this._rules.filter(rule => !rule.value.added); this._rules = this._rules.filter(rule => !rule.value.added);
// Restore exclusive bit to original.
this.set(['permission', 'value', 'exclusive'],
this._originalExclusiveValue);
} }
}, },
_handleValueChange() {
this.permission.value.modified = true;
// Allows overall access page to know a change has been made.
this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
},
_handleRemovePermission() { _handleRemovePermission() {
this._deleted = true; this._deleted = true;
this.permission.value.deleted = true; this.permission.value.deleted = true;

View File

@@ -288,6 +288,7 @@ limitations under the License.
}, },
}, },
}; };
element._setupValues();
flushAsynchronousOperations(); flushAsynchronousOperations();
}); });
@@ -326,6 +327,32 @@ limitations under the License.
assert.isFalse(element.$.permission.classList.contains('deleted')); assert.isFalse(element.$.permission.classList.contains('deleted'));
assert.isFalse(element._deleted); assert.isFalse(element._deleted);
}); });
test('modify a permission', () => {
element.editing = true;
element.name = 'Priority';
element.section = 'refs/*';
assert.isFalse(element._originalExclusiveValue);
assert.isNotOk(element.permission.value.modified);
MockInteractions.tap(element.$.exclusiveToggle);
flushAsynchronousOperations();
assert.isTrue(element.permission.value.exclusive);
assert.isTrue(element.permission.value.modified);
assert.isFalse(element._originalExclusiveValue);
element.editing = false;
assert.isFalse(element.permission.value.exclusive);
});
test('_handleValueChange', () => {
const modifiedHandler = sandbox.stub();
element.permission = {value: {rules: {}}};
element.addEventListener('access-modified', modifiedHandler);
assert.isNotOk(element.permission.value.modified);
element._handleValueChange();
assert.isTrue(element.permission.value.modified);
assert.isTrue(modifiedHandler.called);
});
}); });
}); });
</script> </script>

View File

@@ -76,30 +76,6 @@ limitations under the License.
'Code-Review': {}, 'Code-Review': {},
}, },
}; };
const repoAccessInput = {
add: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {action: 'DENY', modified: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: null,
},
},
},
},
},
};
setup(() => { setup(() => {
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
element = fixture('basic'); element = fixture('basic');
@@ -126,14 +102,13 @@ limitations under the License.
name: 'Create Account', name: 'Create Account',
}, },
}; };
const accessStub = sandbox.stub(element.$.restAPI, const accessStub = sandbox.stub(element.$.restAPI,
'getRepoAccessRights'); 'getRepoAccessRights');
accessStub.withArgs('New Repo').returns(
accessStub.withArgs('New Repo').returns(Promise.resolve(accessRes)); Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
accessStub.withArgs('Another New Repo') accessStub.withArgs('Another New Repo')
.returns(Promise.resolve(accessRes2)); .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = sandbox.stub(element.$.restAPI, const capabilitiesStub = sandbox.stub(element.$.restAPI,
'getCapabilities'); 'getCapabilities');
capabilitiesStub.returns(Promise.resolve(capabilitiesRes)); capabilitiesStub.returns(Promise.resolve(capabilitiesRes));
@@ -168,26 +143,13 @@ limitations under the License.
name: 'Access Database', name: 'Access Database',
}, },
}; };
const accessRes = {
local: {
GLOBAL_CAPABILITIES: {
permissions: {
accessDatabase: {
rules: {
123: {},
},
},
},
},
},
};
const repoRes = { const repoRes = {
labels: { labels: {
'Code-Review': {}, 'Code-Review': {},
}, },
}; };
const accessStub = sandbox.stub(element.$.restAPI, const accessStub = sandbox.stub(element.$.restAPI, 'getRepoAccessRights')
'getRepoAccessRights').returns(Promise.resolve(accessRes)); .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = sandbox.stub(element.$.restAPI, const capabilitiesStub = sandbox.stub(element.$.restAPI,
'getCapabilities').returns(Promise.resolve(capabilitiesRes)); 'getCapabilities').returns(Promise.resolve(capabilitiesRes));
const repoStub = sandbox.stub(element.$.restAPI, 'getRepo').returns( const repoStub = sandbox.stub(element.$.restAPI, 'getRepo').returns(
@@ -228,7 +190,8 @@ limitations under the License.
suite('with defined sections', () => { suite('with defined sections', () => {
setup(() => { setup(() => {
element._sections = element.toSortedArray(accessRes.local); element._sections =
element.toSortedArray(JSON.parse(JSON.stringify(accessRes.local)));
flushAsynchronousOperations(); flushAsynchronousOperations();
}); });
@@ -268,7 +231,7 @@ limitations under the License.
element._local = JSON.parse(JSON.stringify(accessRes.local)); element._local = JSON.parse(JSON.stringify(accessRes.local));
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}}); assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
element._local['refs/*'].permissions.owner.rules[123].deleted = true; element._local['refs/*'].permissions.owner.rules[123].deleted = true;
const expectedInput = { let expectedInput = {
add: {}, add: {},
remove: { remove: {
'refs/*': { 'refs/*': {
@@ -285,19 +248,26 @@ limitations under the License.
assert.deepEqual(element._computeAddAndRemove(), expectedInput); assert.deepEqual(element._computeAddAndRemove(), expectedInput);
delete element._local['refs/*'].permissions.owner.rules[123].deleted; delete element._local['refs/*'].permissions.owner.rules[123].deleted;
element._local['refs/*'].permissions.owner.rules[123].modified = true; element._local['refs/*'].permissions.owner.rules[123].modified = true;
assert.deepEqual(element._computeAddAndRemove(), repoAccessInput); expectedInput = {
}); add: {
'refs/*': {
test('_computeAddAndRemove permissions', () => { permissions: {
element._local = JSON.parse(JSON.stringify(accessRes.local)); owner: {
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}}); rules: {
element._local['refs/*'].permissions.owner.deleted = true; 123: {action: 'DENY', modified: true},
const expectedInput = { },
add: {}, },
},
},
},
remove: { remove: {
'refs/*': { 'refs/*': {
permissions: { permissions: {
owner: {rules: {}}, owner: {
rules: {
123: null,
},
},
}, },
}, },
}, },
@@ -309,7 +279,7 @@ limitations under the License.
element._local = JSON.parse(JSON.stringify(accessRes.local)); element._local = JSON.parse(JSON.stringify(accessRes.local));
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}}); assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
element._local['refs/*'].permissions.owner.deleted = true; element._local['refs/*'].permissions.owner.deleted = true;
const expectedInput = { let expectedInput = {
add: {}, add: {},
remove: { remove: {
'refs/*': { 'refs/*': {
@@ -320,6 +290,31 @@ limitations under the License.
}, },
}; };
assert.deepEqual(element._computeAddAndRemove(), expectedInput); assert.deepEqual(element._computeAddAndRemove(), expectedInput);
delete element._local['refs/*'].permissions.owner.deleted;
element._local['refs/*'].permissions.owner.modified = true;
expectedInput = {
add: {
'refs/*': {
permissions: {
owner: {
modified: true,
rules: {
234: {action: 'ALLOW'},
123: {action: 'DENY'},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
}); });
test('_computeAddAndRemove combinations', () => { test('_computeAddAndRemove combinations', () => {
@@ -368,11 +363,65 @@ limitations under the License.
}, },
}; };
assert.deepEqual(element._computeAddAndRemove(), expectedInput); assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify both permissions with an exclusive bit. Owner is still
// deleted.
element._local['refs/*'].permissions.owner.exclusive = true;
element._local['refs/*'].permissions.owner.modified = true;
element._local['refs/*'].permissions.read.exclusive = true;
element._local['refs/*'].permissions.read.modified = true;
expectedInput = {
add: {
'refs/*': {
permissions: {
read: {
exclusive: true,
modified: true,
rules: {
234: {action: 'ALLOW'},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {rules: {}},
read: {rules: {}},
},
},
},
};
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
}); });
test('_handleSaveForReview', done => { test('_handleSaveForReview', done => {
sandbox.stub(element.$.restAPI, 'getRepoAccessRights') const repoAccessInput = {
.returns(Promise.resolve(accessRes)); add: {
'refs/*': {
permissions: {
owner: {
rules: {
123: {action: 'DENY', modified: true},
},
},
},
},
},
remove: {
'refs/*': {
permissions: {
owner: {
rules: {
123: null,
},
},
},
},
},
};
sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sandbox.stub(element.$.restAPI, 'getRepo') sandbox.stub(element.$.restAPI, 'getRepo')
.returns(Promise.resolve({})); .returns(Promise.resolve({}));
sandbox.stub(Gerrit.Nav, 'navigateToChange'); sandbox.stub(Gerrit.Nav, 'navigateToChange');
@@ -381,8 +430,7 @@ limitations under the License.
.returns(Promise.resolve({_number: 1})); .returns(Promise.resolve({_number: 1}));
element.repo = 'test-repo'; element.repo = 'test-repo';
sandbox.stub(element, '_computeAddAndRemove') sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
.returns(repoAccessInput);
element._handleSaveForReview().then(() => { element._handleSaveForReview().then(() => {
assert.isTrue(saveForReviewStub.called); assert.isTrue(saveForReviewStub.called);

View File

@@ -62,10 +62,6 @@ limitations under the License.
align-items: center; align-items: center;
display: flex; display: flex;
} }
paper-toggle-button {
--paper-toggle-button-checked-bar-color: var(--color-link);
--paper-toggle-button-checked-button-color: var(--color-link);
}
</style> </style>
<div class="header"> <div class="header">
<h3>Messages</h3> <h3>Messages</h3>

View File

@@ -96,6 +96,10 @@ limitations under the License.
.separator.transparent { .separator.transparent {
background-color: transparent; background-color: transparent;
} }
paper-toggle-button {
--paper-toggle-button-checked-bar-color: var(--color-link);
--paper-toggle-button-checked-button-color: var(--color-link);
}
</style> </style>
</template> </template>
</dom-module> </dom-module>