PolyGerrit: Implement support for members tab in groups
Change-Id: I928dbeaa8ee1e652442e618380a4015a6b1230a1
This commit is contained in:
@@ -29,6 +29,7 @@ limitations under the License.
|
|||||||
<link rel="import" href="../gr-admin-group-list/gr-admin-group-list.html">
|
<link rel="import" href="../gr-admin-group-list/gr-admin-group-list.html">
|
||||||
<link rel="import" href="../gr-admin-project-list/gr-admin-project-list.html">
|
<link rel="import" href="../gr-admin-project-list/gr-admin-project-list.html">
|
||||||
<link rel="import" href="../gr-group/gr-group.html">
|
<link rel="import" href="../gr-group/gr-group.html">
|
||||||
|
<link rel="import" href="../gr-group-members/gr-group-members.html">
|
||||||
<link rel="import" href="../gr-plugin-list/gr-plugin-list.html">
|
<link rel="import" href="../gr-plugin-list/gr-plugin-list.html">
|
||||||
<link rel="import" href="../gr-project/gr-project.html">
|
<link rel="import" href="../gr-project/gr-project.html">
|
||||||
<link rel="import" href="../gr-group-audit-log/gr-group-audit-log.html">
|
<link rel="import" href="../gr-group-audit-log/gr-group-audit-log.html">
|
||||||
@@ -88,6 +89,12 @@ limitations under the License.
|
|||||||
on-name-changed="_updateGroupName"></gr-group>
|
on-name-changed="_updateGroupName"></gr-group>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
<template is="dom-if" if="[[_showGroupMembers]]" restamp="true">
|
||||||
|
<main>
|
||||||
|
<gr-group-members
|
||||||
|
group-id="[[params.groupId]]"></gr-group-members>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
<template is="dom-if" if="[[_showGroupList]]" restamp="true">
|
<template is="dom-if" if="[[_showGroupList]]" restamp="true">
|
||||||
<main class="table">
|
<main class="table">
|
||||||
<gr-admin-group-list class="table" params="[[params]]">
|
<gr-admin-group-list class="table" params="[[params]]">
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
_showGroup: Boolean,
|
_showGroup: Boolean,
|
||||||
_showGroupAuditLog: Boolean,
|
_showGroupAuditLog: Boolean,
|
||||||
_showGroupList: Boolean,
|
_showGroupList: Boolean,
|
||||||
|
_showGroupMembers: Boolean,
|
||||||
_showProjectMain: Boolean,
|
_showProjectMain: Boolean,
|
||||||
_showProjectList: Boolean,
|
_showProjectList: Boolean,
|
||||||
_showProjectDetailList: Boolean,
|
_showProjectDetailList: Boolean,
|
||||||
@@ -128,7 +129,15 @@
|
|||||||
name: this._groupName,
|
name: this._groupName,
|
||||||
view: 'gr-group',
|
view: 'gr-group',
|
||||||
url: `/admin/groups/${this.encodeURL(this._groupId + '', true)}`,
|
url: `/admin/groups/${this.encodeURL(this._groupId + '', true)}`,
|
||||||
children: [],
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Members',
|
||||||
|
detailType: 'members',
|
||||||
|
view: 'gr-group-members',
|
||||||
|
url: `/admin/groups/${this.encodeURL(this._groupId, true)}` +
|
||||||
|
',members',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
if (this._groupOwner) {
|
if (this._groupOwner) {
|
||||||
linkCopy.subsection.children.push(
|
linkCopy.subsection.children.push(
|
||||||
@@ -161,6 +170,7 @@
|
|||||||
this.set('_showGroup', params.adminView === 'gr-group');
|
this.set('_showGroup', params.adminView === 'gr-group');
|
||||||
this.set('_showGroupAuditLog', params.adminView === 'gr-group-audit-log');
|
this.set('_showGroupAuditLog', params.adminView === 'gr-group-audit-log');
|
||||||
this.set('_showGroupList', params.adminView === 'gr-admin-group-list');
|
this.set('_showGroupList', params.adminView === 'gr-admin-group-list');
|
||||||
|
this.set('_showGroupMembers', params.adminView === 'gr-group-members');
|
||||||
this.set('_showProjectMain', params.adminView === 'gr-project');
|
this.set('_showProjectMain', params.adminView === 'gr-project');
|
||||||
this.set('_showProjectList',
|
this.set('_showProjectList',
|
||||||
params.adminView === 'gr-admin-project-list');
|
params.adminView === 'gr-admin-project-list');
|
||||||
@@ -217,8 +227,8 @@
|
|||||||
this._groupName = group.name;
|
this._groupName = group.name;
|
||||||
this.reload();
|
this.reload();
|
||||||
this.$.restAPI.getIsGroupOwner(group.name).then(
|
this.$.restAPI.getIsGroupOwner(group.name).then(
|
||||||
configs => {
|
isOwner => {
|
||||||
if (configs.hasOwnProperty(group.name)) {
|
if (isOwner) {
|
||||||
this._groupOwner = true;
|
this._groupOwner = true;
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
Gerrit.ListViewBehavior,
|
Gerrit.ListViewBehavior,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
attached() {
|
||||||
|
this.fire('title-change', {title: 'Audit Log'});
|
||||||
|
},
|
||||||
|
|
||||||
ready() {
|
ready() {
|
||||||
this._getAuditLogs();
|
this._getAuditLogs();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||||
|
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
|
||||||
|
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||||
|
<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
||||||
|
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
|
||||||
|
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||||
|
<link rel="import" href="../../../styles/shared-styles.html">
|
||||||
|
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||||
|
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||||
|
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||||
|
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||||
|
<link rel="import" href="../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html">
|
||||||
|
|
||||||
|
<dom-module id="gr-group-members">
|
||||||
|
<template>
|
||||||
|
<style include="gr-form-styles"></style>
|
||||||
|
<style include="shared-styles">
|
||||||
|
main {
|
||||||
|
margin: 2em 1em;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#loading.loading {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#loading:not(.loading) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
|
gr-autocomplete {
|
||||||
|
width: 20em;
|
||||||
|
--gr-autocomplete: {
|
||||||
|
font-size: 1em;
|
||||||
|
height: 2em;
|
||||||
|
width: 20em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style include="gr-form-styles"></style>
|
||||||
|
<main class="gr-form-styles">
|
||||||
|
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||||
|
<h1 id="Title">[[_groupName]]</h1>
|
||||||
|
<div id="form">
|
||||||
|
<fieldset>
|
||||||
|
<h3 id="members">Members</h3>
|
||||||
|
<fieldset>
|
||||||
|
<span class="value">
|
||||||
|
<gr-autocomplete
|
||||||
|
id="groupMemberSearchInput"
|
||||||
|
text="{{_groupMemberSearch}}"
|
||||||
|
query="[[_queryMembers]]"
|
||||||
|
placeholder="Name Or Email"
|
||||||
|
hidden$="[[!_groupOwner]]">
|
||||||
|
</gr-autocomplete>
|
||||||
|
</span>
|
||||||
|
<gr-button
|
||||||
|
id="saveGroupMember"
|
||||||
|
on-tap="_handleSavingGroupMember"
|
||||||
|
disabled="[[!_groupMemberSearch]]"
|
||||||
|
hidden$="[[!_groupOwner]]">
|
||||||
|
Add
|
||||||
|
</gr-button>
|
||||||
|
<div class="gr-form-styles">
|
||||||
|
<table id="groupMembers" class="gr-form-styles">
|
||||||
|
<tr class="headerRow">
|
||||||
|
<th class="nameHeader">Name</th>
|
||||||
|
<th class="emailAddressHeader">Email Address</th>
|
||||||
|
<th class="deleteHeader" hidden$="[[!_groupOwner]]">
|
||||||
|
Delete Member
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tbody>
|
||||||
|
<template is="dom-repeat" items="[[_groupMembers]]">
|
||||||
|
<tr>
|
||||||
|
<td class="nameColumn">
|
||||||
|
<a href$="[[_memberUrl(item)]]">[[item.name]]</a>
|
||||||
|
</td>
|
||||||
|
<td>[[item.email]]</td>
|
||||||
|
<td hidden$="[[!_groupOwner]]">
|
||||||
|
<gr-button
|
||||||
|
class="deleteButton"
|
||||||
|
on-tap="_handleDeleteMember">
|
||||||
|
Delete
|
||||||
|
</gr-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<h3 id="includedGroups">Included Groups</h3>
|
||||||
|
<fieldset>
|
||||||
|
<span class="value">
|
||||||
|
<gr-autocomplete
|
||||||
|
id="includedGroupSearchInput"
|
||||||
|
text="{{_includedGroupSearch}}"
|
||||||
|
query="[[_queryIncludedGroup]]"
|
||||||
|
placeholder="Group Name"
|
||||||
|
hidden$="[[!_groupOwner]]">
|
||||||
|
</gr-autocomplete>
|
||||||
|
</span>
|
||||||
|
<gr-button
|
||||||
|
id="saveIncludedGroups"
|
||||||
|
on-tap="_handleSavingIncludedGroups"
|
||||||
|
disabled="[[!_includedGroupSearch]]"
|
||||||
|
hidden$="[[!_groupOwner]]">
|
||||||
|
Add
|
||||||
|
</gr-button>
|
||||||
|
<div class="gr-form-styles">
|
||||||
|
<table id="includedGroups" class="gr-form-styles">
|
||||||
|
<tr class="headerRow">
|
||||||
|
<th class="groupNameHeader">Group Name</th>
|
||||||
|
<th class="descriptionHeader">Description</th>
|
||||||
|
<th class="deleteIncludedHeader" hidden$="[[!_groupOwner]]">
|
||||||
|
Delete Included Group
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tbody>
|
||||||
|
<template is="dom-repeat" items="[[_includedGroups]]">
|
||||||
|
<tr>
|
||||||
|
<td class="nameColumn">
|
||||||
|
<a href$="[[_groupUrl(item.group_id)]]">
|
||||||
|
[[item.name]]
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>[[item.description]]</td>
|
||||||
|
<td hidden$="[[!_groupOwner]]">
|
||||||
|
<gr-button
|
||||||
|
class="deleteIncludedGroupButton"
|
||||||
|
on-tap="_handleDeleteIncludedGroup">
|
||||||
|
Delete
|
||||||
|
</gr-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<gr-overlay id="overlay" with-backdrop>
|
||||||
|
<gr-confirm-delete-item-dialog
|
||||||
|
class="confirmDialog"
|
||||||
|
on-confirm="_handleDeleteConfirm"
|
||||||
|
on-cancel="_handleConfirmDialogCancel"
|
||||||
|
item="[[_itemName]]"
|
||||||
|
item-type="[[_itemType]]"></gr-confirm-delete-item-dialog>
|
||||||
|
</gr-overlay>
|
||||||
|
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||||
|
</template>
|
||||||
|
<script src="gr-group-members.js"></script>
|
||||||
|
</dom-module>
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
const SUGGESTIONS_LIMIT = 15;
|
||||||
|
|
||||||
|
Polymer({
|
||||||
|
is: 'gr-group-members',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
groupId: Number,
|
||||||
|
_groupMemberSearch: String,
|
||||||
|
_includedGroupSearch: String,
|
||||||
|
_loading: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
_groupName: String,
|
||||||
|
_groupMembers: Object,
|
||||||
|
_includedGroups: Object,
|
||||||
|
_itemName: String,
|
||||||
|
_itemType: String,
|
||||||
|
_queryMembers: {
|
||||||
|
type: Function,
|
||||||
|
value() {
|
||||||
|
return this._getAccountSuggestions.bind(this);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_queryIncludedGroup: {
|
||||||
|
type: Function,
|
||||||
|
value() {
|
||||||
|
return this._getGroupSuggestions.bind(this);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_groupOwner: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
behaviors: [
|
||||||
|
Gerrit.BaseUrlBehavior,
|
||||||
|
Gerrit.URLEncodingBehavior,
|
||||||
|
],
|
||||||
|
|
||||||
|
attached() {
|
||||||
|
this._loadGroupDetails();
|
||||||
|
|
||||||
|
this.fire('title-change', {title: 'Members'});
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadGroupDetails() {
|
||||||
|
if (!this.groupId) { return; }
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
return this.$.restAPI.getGroupConfig(this.groupId).then(
|
||||||
|
config => {
|
||||||
|
this._groupName = config.name;
|
||||||
|
promises.push(this.$.restAPI.getIsGroupOwner(config.name)
|
||||||
|
.then(isOwner => { this._groupOwner = isOwner; }));
|
||||||
|
promises.push(this.$.restAPI.getGroupMembers(config.name).then(
|
||||||
|
members => {
|
||||||
|
this._groupMembers = members;
|
||||||
|
}));
|
||||||
|
promises.push(this.$.restAPI.getIncludedGroup(config.name)
|
||||||
|
.then(includedGroup => {
|
||||||
|
this._includedGroups = includedGroup;
|
||||||
|
}));
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
this._loading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_computeLoadingClass(loading) {
|
||||||
|
return loading ? 'loading' : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
_isLoading() {
|
||||||
|
return this._loading || this._loading === undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
_memberUrl(item) {
|
||||||
|
if (item.email) {
|
||||||
|
item = item.email;
|
||||||
|
} else if (item.username) {
|
||||||
|
item = item.username;
|
||||||
|
} else {
|
||||||
|
item = item.name;
|
||||||
|
}
|
||||||
|
return this.getBaseUrl() + '/q/owner:' + this.encodeURL(item, true) +
|
||||||
|
' status:open';
|
||||||
|
},
|
||||||
|
|
||||||
|
_groupUrl(item) {
|
||||||
|
return this.getBaseUrl() + '/admin/groups/' + this.encodeURL(item, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleSavingGroupMember() {
|
||||||
|
return this.$.restAPI.saveGroupMembers(this._groupName,
|
||||||
|
this._groupMemberSearch).then(config => {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$.restAPI.getGroupMembers(this._groupName).then(members => {
|
||||||
|
this._groupMembers = members;
|
||||||
|
});
|
||||||
|
this._groupMemberSearch = '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleDeleteConfirm() {
|
||||||
|
this.$.overlay.close();
|
||||||
|
if (this._itemType === 'member') {
|
||||||
|
return this.$.restAPI.deleteGroupMembers(this._groupName,
|
||||||
|
this._itemName)
|
||||||
|
.then(itemDeleted => {
|
||||||
|
if (itemDeleted.status === 204) {
|
||||||
|
this.$.restAPI.getGroupMembers(this._groupName)
|
||||||
|
.then(members => {
|
||||||
|
this._groupMembers = members;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this._itemType === 'includedGroup') {
|
||||||
|
return this.$.restAPI.deleteIncludedGroup(this._groupName,
|
||||||
|
this._itemName)
|
||||||
|
.then(itemDeleted => {
|
||||||
|
if (itemDeleted.status === 204) {
|
||||||
|
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||||
|
.then(includedGroup => {
|
||||||
|
this._includedGroups = includedGroup;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleConfirmDialogCancel() {
|
||||||
|
this.$.overlay.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleDeleteMember(e) {
|
||||||
|
let item;
|
||||||
|
const name = e.model.get('item.name');
|
||||||
|
const username = e.model.get('item.username');
|
||||||
|
const email = e.model.get('item.email');
|
||||||
|
if (username) {
|
||||||
|
item = username;
|
||||||
|
} else if (name) {
|
||||||
|
item = name;
|
||||||
|
} else if (email) {
|
||||||
|
item = email;
|
||||||
|
}
|
||||||
|
if (!item) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
this._itemName = item;
|
||||||
|
this._itemType = 'member';
|
||||||
|
this.$.overlay.open();
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleSavingIncludedGroups() {
|
||||||
|
return this.$.restAPI.saveIncludedGroup(this._groupName,
|
||||||
|
this._includedGroupSearch)
|
||||||
|
.then(config => {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||||
|
.then(includedGroup => {
|
||||||
|
this._includedGroups = includedGroup;
|
||||||
|
});
|
||||||
|
this._includedGroupSearch = '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleDeleteIncludedGroup(e) {
|
||||||
|
const name = e.model.get('item.name');
|
||||||
|
if (!name) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
this._itemName = name;
|
||||||
|
this._itemType = 'includedGroup';
|
||||||
|
this.$.overlay.open();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getAccountSuggestions(input) {
|
||||||
|
if (input.length === 0) { return Promise.resolve([]); }
|
||||||
|
return this.$.restAPI.getSuggestedAccounts(
|
||||||
|
input, SUGGESTIONS_LIMIT).then(accounts => {
|
||||||
|
const accountSuggestions = [];
|
||||||
|
let nameAndEmail;
|
||||||
|
if (!accounts) { return []; }
|
||||||
|
for (const key in accounts) {
|
||||||
|
if (!accounts.hasOwnProperty(key)) { continue; }
|
||||||
|
if (accounts[key].email !== undefined) {
|
||||||
|
nameAndEmail = accounts[key].name +
|
||||||
|
' <' + accounts[key].email + '>';
|
||||||
|
} else {
|
||||||
|
nameAndEmail = accounts[key].name;
|
||||||
|
}
|
||||||
|
accountSuggestions.push({
|
||||||
|
name: nameAndEmail,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return accountSuggestions;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getGroupSuggestions(input) {
|
||||||
|
return this.$.restAPI.getSuggestedGroups(input)
|
||||||
|
.then(response => {
|
||||||
|
const groups = [];
|
||||||
|
for (const key in response) {
|
||||||
|
if (!response.hasOwnProperty(key)) { continue; }
|
||||||
|
groups.push({
|
||||||
|
name: key,
|
||||||
|
value: response[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<title>gr-group-members</title>
|
||||||
|
|
||||||
|
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||||
|
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||||
|
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||||
|
<link rel="import" href="gr-group-members.html">
|
||||||
|
|
||||||
|
<script>void(0);</script>
|
||||||
|
|
||||||
|
<test-fixture id="basic">
|
||||||
|
<template>
|
||||||
|
<gr-group-members></gr-group-members>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
suite('gr-group-members tests', () => {
|
||||||
|
let element;
|
||||||
|
let sandbox;
|
||||||
|
let groups;
|
||||||
|
let groupMembers;
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
groups = {
|
||||||
|
name: 'Administrators',
|
||||||
|
owner: 'Administrators',
|
||||||
|
group_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
groupMembers = [
|
||||||
|
{
|
||||||
|
_account_id: 1000097,
|
||||||
|
name: 'Jane Roe',
|
||||||
|
email: 'jane.roe@example.com',
|
||||||
|
username: 'jane',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_account_id: 1000096,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'john.doe@example.com',
|
||||||
|
username: 'john',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_account_id: 1000095,
|
||||||
|
name: 'Gerrit',
|
||||||
|
email: 'gerrit@example.com',
|
||||||
|
username: 'git',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
stub('gr-rest-api-interface', {
|
||||||
|
getSuggestedAccounts(input) {
|
||||||
|
if (input.startsWith('test')) {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
_account_id: 1000096,
|
||||||
|
name: 'test-account',
|
||||||
|
email: 'test.account@example.com',
|
||||||
|
username: 'test123',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_account_id: 1001439,
|
||||||
|
name: 'test-admin',
|
||||||
|
email: 'test.admin@example.com',
|
||||||
|
username: 'test_admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_account_id: 1001439,
|
||||||
|
name: 'test-git',
|
||||||
|
username: 'test_git',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getLoggedIn() { return Promise.resolve(true); },
|
||||||
|
getGroupConfig() {
|
||||||
|
return Promise.resolve(groups);
|
||||||
|
},
|
||||||
|
getGroupMembers() {
|
||||||
|
return Promise.resolve(groupMembers);
|
||||||
|
},
|
||||||
|
getIsGroupOwner() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
element = fixture('basic');
|
||||||
|
});
|
||||||
|
|
||||||
|
teardown(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('save correctly', () => {
|
||||||
|
element._groupOwner = true;
|
||||||
|
|
||||||
|
const memberName = 'test-admin';
|
||||||
|
|
||||||
|
sandbox.stub(element.$.restAPI, 'saveGroupMembers', () => {
|
||||||
|
return Promise.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = Polymer.dom(element.root).querySelector('gr-button');
|
||||||
|
|
||||||
|
assert.isTrue(button.hasAttribute('disabled'));
|
||||||
|
|
||||||
|
element.$.groupMemberSearchInput.text = memberName;
|
||||||
|
|
||||||
|
assert.isFalse(button.hasAttribute('disabled'));
|
||||||
|
|
||||||
|
element._handleSavingGroupMember().then(() => {
|
||||||
|
assert.isTrue(button.hasAttribute('disabled'));
|
||||||
|
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_getAccountSuggestions empty', done => {
|
||||||
|
element._getAccountSuggestions('nonexistent').then(accounts => {
|
||||||
|
assert.equal(accounts.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_getAccountSuggestions non-empty', done => {
|
||||||
|
element._getAccountSuggestions('test-').then(accounts => {
|
||||||
|
assert.equal(accounts.length, 3);
|
||||||
|
assert.equal(accounts[0].name,
|
||||||
|
'test-account <test.account@example.com>');
|
||||||
|
assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
|
||||||
|
assert.equal(accounts[2].name, 'test-git');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -71,12 +71,12 @@ limitations under the License.
|
|||||||
<gr-autocomplete
|
<gr-autocomplete
|
||||||
id="groupNameInput"
|
id="groupNameInput"
|
||||||
text="{{_groupConfig.name}}"
|
text="{{_groupConfig.name}}"
|
||||||
disabled$="[[_groupOwner]]"></gr-autocomplete>
|
disabled="[[!_groupOwner]]"></gr-autocomplete>
|
||||||
</span>
|
</span>
|
||||||
<gr-button
|
<gr-button
|
||||||
id="inputUpdateNameBtn"
|
id="inputUpdateNameBtn"
|
||||||
on-tap="_handleSaveName"
|
on-tap="_handleSaveName"
|
||||||
disabled$="[[_computeButtonDisabled(_groupOwner, _rename)]]">
|
disabled="[[_computeButtonDisabled(_groupOwner, _rename)]]">
|
||||||
Rename Group</gr-button>
|
Rename Group</gr-button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<h3 class$="[[_computeHeaderClass(_owner)]]">
|
<h3 class$="[[_computeHeaderClass(_owner)]]">
|
||||||
@@ -87,12 +87,12 @@ limitations under the License.
|
|||||||
<gr-autocomplete
|
<gr-autocomplete
|
||||||
text="{{_groupConfig.owner}}"
|
text="{{_groupConfig.owner}}"
|
||||||
query="[[_query]]"
|
query="[[_query]]"
|
||||||
disabled$="[[_groupOwner]]">
|
disabled$="[[!_groupOwner]]">
|
||||||
</gr-autocomplete>
|
</gr-autocomplete>
|
||||||
</span>
|
</span>
|
||||||
<gr-button
|
<gr-button
|
||||||
on-tap="_handleSaveOwner"
|
on-tap="_handleSaveOwner"
|
||||||
disabled$="[[_computeButtonDisabled(_groupOwner, _owner)]]">
|
disabled="[[_computeButtonDisabled(_groupOwner, _owner)]]">
|
||||||
Change Owners</gr-button>
|
Change Owners</gr-button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<h3 class$="[[_computeHeaderClass(_description)]]">
|
<h3 class$="[[_computeHeaderClass(_description)]]">
|
||||||
@@ -104,11 +104,11 @@ limitations under the License.
|
|||||||
class="description"
|
class="description"
|
||||||
autocomplete="on"
|
autocomplete="on"
|
||||||
bind-value="{{_groupConfig.description}}"
|
bind-value="{{_groupConfig.description}}"
|
||||||
disabled$="[[_groupOwner]]"></iron-autogrow-textarea>
|
disabled="[[!_groupOwner]]"></iron-autogrow-textarea>
|
||||||
</div>
|
</div>
|
||||||
<gr-button
|
<gr-button
|
||||||
on-tap="_handleSaveDescription"
|
on-tap="_handleSaveDescription"
|
||||||
disabled$=
|
disabled=
|
||||||
"[[_computeButtonDisabled(_groupOwner, _description)]]">
|
"[[_computeButtonDisabled(_groupOwner, _description)]]">
|
||||||
Save Description
|
Save Description
|
||||||
</gr-button>
|
</gr-button>
|
||||||
@@ -124,7 +124,7 @@ limitations under the License.
|
|||||||
<span class="value">
|
<span class="value">
|
||||||
<gr-select
|
<gr-select
|
||||||
bind-value="{{_groupConfig.options.visible_to_all}}">
|
bind-value="{{_groupConfig.options.visible_to_all}}">
|
||||||
<select disabled$="[[_groupOwner]]">
|
<select disabled$="[[!_groupOwner]]">
|
||||||
<template is="dom-repeat" items="[[_submitTypes]]">
|
<template is="dom-repeat" items="[[_submitTypes]]">
|
||||||
<option value="[[item.value]]">[[item.label]]</option>
|
<option value="[[item.value]]">[[item.label]]</option>
|
||||||
</template>
|
</template>
|
||||||
@@ -134,7 +134,7 @@ limitations under the License.
|
|||||||
</section>
|
</section>
|
||||||
<gr-button
|
<gr-button
|
||||||
on-tap="_handleSaveOptions"
|
on-tap="_handleSaveOptions"
|
||||||
disabled$=
|
disabled=
|
||||||
"[[_computeButtonDisabled(_groupOwner, _options)]]">
|
"[[_computeButtonDisabled(_groupOwner, _options)]]">
|
||||||
Save Group Options
|
Save Group Options
|
||||||
</gr-button>
|
</gr-button>
|
||||||
|
|||||||
@@ -100,14 +100,10 @@
|
|||||||
config => {
|
config => {
|
||||||
this._groupConfig = config;
|
this._groupConfig = config;
|
||||||
this._groupName = config.name;
|
this._groupName = config.name;
|
||||||
|
this.fire('title-change', {title: config.name});
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
this.$.restAPI.getIsGroupOwner(config.name).then(
|
this.$.restAPI.getIsGroupOwner(config.name)
|
||||||
configs => {
|
.then(isOwner => { this._groupOwner = isOwner; });
|
||||||
if (Object.keys(configs).length === 0 &&
|
|
||||||
configs.constructor === Object) {
|
|
||||||
this._groupOwner = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -187,7 +183,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_computeButtonDisabled(options, option) {
|
_computeButtonDisabled(options, option) {
|
||||||
return options || !option;
|
return !options || !option;
|
||||||
},
|
},
|
||||||
|
|
||||||
_computeHeaderClass(configChanged) {
|
_computeHeaderClass(configChanged) {
|
||||||
|
|||||||
@@ -76,9 +76,10 @@ limitations under the License.
|
|||||||
name: groupName,
|
name: groupName,
|
||||||
};
|
};
|
||||||
element._groupName = groupName;
|
element._groupName = groupName;
|
||||||
|
element._groupOwner = true;
|
||||||
|
|
||||||
sandbox.stub(element.$.restAPI, 'getIsGroupOwner', () => {
|
sandbox.stub(element.$.restAPI, 'getIsGroupOwner', () => {
|
||||||
return Promise.resolve({is_owner: true});
|
return Promise.resolve(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
sandbox.stub(element.$.restAPI, 'saveGroupName', () => {
|
sandbox.stub(element.$.restAPI, 'saveGroupName', () => {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Matches /admin/groups/<group>,audit-log[/]
|
// Matches /admin/groups/<group>,audit-log
|
||||||
page(/^\/admin\/groups\/(.+),audit-log$/, loadUser, data => {
|
page(/^\/admin\/groups\/(.+),audit-log$/, loadUser, data => {
|
||||||
restAPI.getLoggedIn().then(loggedIn => {
|
restAPI.getLoggedIn().then(loggedIn => {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
@@ -168,6 +168,16 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Matches /admin/groups/<group>,members
|
||||||
|
page(/^\/admin\/groups\/(.+),members$/, loadUser, data => {
|
||||||
|
app.params = {
|
||||||
|
view: Gerrit.Nav.View.ADMIN,
|
||||||
|
adminView: 'gr-group-members',
|
||||||
|
detailType: 'members',
|
||||||
|
groupId: data.params[0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Matches /admin/groups[,<offset>][/].
|
// Matches /admin/groups[,<offset>][/].
|
||||||
page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
|
page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
|
||||||
restAPI.getLoggedIn().then(loggedIn => {
|
restAPI.getLoggedIn().then(loggedIn => {
|
||||||
|
|||||||
@@ -311,9 +311,26 @@
|
|||||||
revision, opt_errFn, opt_ctx);
|
revision, opt_errFn, opt_ctx);
|
||||||
},
|
},
|
||||||
|
|
||||||
getIsGroupOwner(groupId) {
|
/**
|
||||||
const encodeId = encodeURIComponent(groupId);
|
* @param {!string} groupName
|
||||||
return this._fetchSharedCacheURL('/groups/?owned&q=' + encodeId);
|
* @returns {!Promise<boolean>}
|
||||||
|
*/
|
||||||
|
getIsGroupOwner(groupName) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
return this._fetchSharedCacheURL('/groups/?owned&q=' + encodeName)
|
||||||
|
.then(configs => configs.hasOwnProperty(encodeName));
|
||||||
|
},
|
||||||
|
|
||||||
|
getGroupMembers(groupName) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
return this.send('GET', `/groups/${encodeName}/members/`)
|
||||||
|
.then(response => this.getResponseObject(response));
|
||||||
|
},
|
||||||
|
|
||||||
|
getIncludedGroup(groupName) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
return this.send('GET', `/groups/${encodeName}/groups/`)
|
||||||
|
.then(response => this.getResponseObject(response));
|
||||||
},
|
},
|
||||||
|
|
||||||
saveGroupName(groupId, name) {
|
saveGroupName(groupId, name) {
|
||||||
@@ -341,6 +358,35 @@
|
|||||||
return this._fetchSharedCacheURL('/groups/' + group + '/log.audit');
|
return this._fetchSharedCacheURL('/groups/' + group + '/log.audit');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveGroupMembers(groupName, groupMembers) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
const encodeMember = encodeURIComponent(groupMembers);
|
||||||
|
return this.send('PUT', `/groups/${encodeName}/members/${encodeMember}`)
|
||||||
|
.then(response => this.getResponseObject(response));
|
||||||
|
},
|
||||||
|
|
||||||
|
saveIncludedGroup(groupName, includedGroup) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
const encodeIncludedGroup = encodeURIComponent(includedGroup);
|
||||||
|
return this.send('PUT',
|
||||||
|
`/groups/${encodeName}/groups/${encodeIncludedGroup}`)
|
||||||
|
.then(response => this.getResponseObject(response));
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteGroupMembers(groupName, groupMembers) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
const encodeMember = encodeURIComponent(groupMembers);
|
||||||
|
return this.send('DELETE',
|
||||||
|
`/groups/${encodeName}/members/${encodeMember}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteIncludedGroup(groupName, includedGroup) {
|
||||||
|
const encodeName = encodeURIComponent(groupName);
|
||||||
|
const encodeIncludedGroup = encodeURIComponent(includedGroup);
|
||||||
|
return this.send('DELETE',
|
||||||
|
`/groups/${encodeName}/groups/${encodeIncludedGroup}`);
|
||||||
|
},
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
return this._fetchSharedCacheURL('/config/server/version');
|
return this._fetchSharedCacheURL('/config/server/version');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ limitations under the License.
|
|||||||
'admin/gr-create-project-dialog/gr-create-project-dialog_test.html',
|
'admin/gr-create-project-dialog/gr-create-project-dialog_test.html',
|
||||||
'admin/gr-group/gr-group_test.html',
|
'admin/gr-group/gr-group_test.html',
|
||||||
'admin/gr-group-audit-log/gr-group-audit-log_test.html',
|
'admin/gr-group-audit-log/gr-group-audit-log_test.html',
|
||||||
|
'admin/gr-group-members/gr-group-members_test.html',
|
||||||
'admin/gr-plugin-list/gr-plugin-list_test.html',
|
'admin/gr-plugin-list/gr-plugin-list_test.html',
|
||||||
'admin/gr-project/gr-project_test.html',
|
'admin/gr-project/gr-project_test.html',
|
||||||
'admin/gr-project-detail-list/gr-project-detail-list_test.html',
|
'admin/gr-project-detail-list/gr-project-detail-list_test.html',
|
||||||
|
|||||||
Reference in New Issue
Block a user