blob: 9330f080f9f7a1d1a8ea9be0e30421ffa2afdab1 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
3 * Copyright (C) 2017 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010017
Tao Zhou7d334b42020-08-31 10:56:33 +020018import '../../shared/gr-dialog/gr-dialog';
19import '../../shared/gr-list-view/gr-list-view';
20import '../../shared/gr-overlay/gr-overlay';
Tao Zhou7d334b42020-08-31 10:56:33 +020021import '../gr-create-group-dialog/gr-create-group-dialog';
Tao Zhou7d334b42020-08-31 10:56:33 +020022import {GerritNav} from '../../core/gr-navigation/gr-navigation';
Tao Zhou7d334b42020-08-31 10:56:33 +020023import {AppElementAdminParams} from '../../gr-app-types';
24import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
Dmitrii Filippov55616542020-10-05 19:07:27 +020025import {GroupId, GroupInfo, GroupName} from '../../../types/common';
Tao Zhou7d334b42020-08-31 10:56:33 +020026import {GrCreateGroupDialog} from '../gr-create-group-dialog/gr-create-group-dialog';
Milutin Kristofic60150132020-11-23 20:15:23 +010027import {fireTitleChange} from '../../../utils/event-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +010028import {getAppContext} from '../../../services/app-context';
Frank Borden949a17d2021-09-28 11:30:41 +000029import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
Paladox nonea6629992021-11-14 22:05:24 +000030import {tableStyles} from '../../../styles/gr-table-styles';
31import {sharedStyles} from '../../../styles/shared-styles';
32import {LitElement, PropertyValues, html} from 'lit';
33import {customElement, query, property, state} from 'lit/decorators';
34import {assertIsDefined} from '../../../utils/common-util';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010035
Tao Zhou7d334b42020-08-31 10:56:33 +020036declare global {
37 interface HTMLElementTagNameMap {
38 'gr-admin-group-list': GrAdminGroupList;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010039 }
Tao Zhou7d334b42020-08-31 10:56:33 +020040}
41
Tao Zhou7d334b42020-08-31 10:56:33 +020042@customElement('gr-admin-group-list')
Paladox nonea6629992021-11-14 22:05:24 +000043export class GrAdminGroupList extends LitElement {
44 readonly path = '/admin/groups';
45
46 @query('#createOverlay') private createOverlay?: GrOverlay;
47
48 @query('#createNewModal') private createNewModal?: GrCreateGroupDialog;
Tao Zhou7d334b42020-08-31 10:56:33 +020049
50 @property({type: Object})
51 params?: AppElementAdminParams;
52
53 /**
54 * Offset of currently visible query results.
55 */
Paladox nonea6629992021-11-14 22:05:24 +000056 @state() private offset = 0;
Tao Zhou7d334b42020-08-31 10:56:33 +020057
Paladox nonea6629992021-11-14 22:05:24 +000058 @state() private hasNewGroupName = false;
Tao Zhou7d334b42020-08-31 10:56:33 +020059
Paladox nonea6629992021-11-14 22:05:24 +000060 @state() private createNewCapability = false;
Tao Zhou7d334b42020-08-31 10:56:33 +020061
Paladox nonea6629992021-11-14 22:05:24 +000062 // private but used in test
63 @state() groups: GroupInfo[] = [];
Tao Zhou7d334b42020-08-31 10:56:33 +020064
Paladox nonea6629992021-11-14 22:05:24 +000065 @state() private groupsPerPage = 25;
Tao Zhou7d334b42020-08-31 10:56:33 +020066
Paladox nonea6629992021-11-14 22:05:24 +000067 // private but used in test
68 @state() loading = true;
Tao Zhou7d334b42020-08-31 10:56:33 +020069
Paladox nonea6629992021-11-14 22:05:24 +000070 @state() private filter = '';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010071
Chris Poucetc6e880b2021-11-15 19:57:06 +010072 private readonly restApiService = getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +010073
Chris Poucet59dad572021-08-20 15:25:36 +000074 override connectedCallback() {
Ben Rohlfs5f520da2021-03-10 14:58:43 +010075 super.connectedCallback();
Paladox nonea6629992021-11-14 22:05:24 +000076 this.getCreateGroupCapability();
Milutin Kristofic60150132020-11-23 20:15:23 +010077 fireTitleChange(this, 'Groups');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010078 }
79
Paladox nonea6629992021-11-14 22:05:24 +000080 static override get styles() {
81 return [tableStyles, sharedStyles];
82 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010083
Paladox nonea6629992021-11-14 22:05:24 +000084 override render() {
85 return html`
86 <gr-list-view
87 .createNew=${this.createNewCapability}
88 .filter=${this.filter}
89 .items=${this.groups}
90 .itemsPerPage=${this.groupsPerPage}
91 .loading=${this.loading}
92 .offset=${this.offset}
93 .path=${this.path}
94 @create-clicked=${() => this.handleCreateClicked()}
95 >
96 <table id="list" class="genericList">
97 <tbody>
98 <tr class="headerRow">
99 <th class="name topHeader">Group Name</th>
100 <th class="description topHeader">Group Description</th>
101 <th class="visibleToAll topHeader">Visible To All</th>
102 </tr>
103 <tr
104 id="loading"
105 class="loadingMsg ${this.loading ? 'loading' : ''}"
106 >
107 <td>Loading...</td>
108 </tr>
109 </tbody>
110 <tbody class=${this.loading ? 'loading' : ''}>
111 ${this.groups
112 .slice(0, SHOWN_ITEMS_COUNT)
113 .map(group => this.renderGroupList(group))}
114 </tbody>
115 </table>
116 </gr-list-view>
117 <gr-overlay id="createOverlay" with-backdrop>
118 <gr-dialog
119 id="createDialog"
120 class="confirmDialog"
121 ?disabled=${!this.hasNewGroupName}
122 confirm-label="Create"
123 confirm-on-enter
124 @confirm=${() => this.handleCreateGroup()}
125 @cancel=${() => this.handleCloseCreate()}
126 >
127 <div class="header" slot="header">Create Group</div>
128 <div class="main" slot="main">
129 <gr-create-group-dialog
130 id="createNewModal"
131 @has-new-group-name=${this.handleHasNewGroupName}
132 ></gr-create-group-dialog>
133 </div>
134 </gr-dialog>
135 </gr-overlay>
136 `;
137 }
138
139 private renderGroupList(group: GroupInfo) {
140 return html`
141 <tr class="table">
142 <td class="name">
143 <a href=${this.computeGroupUrl(group.id)}>${group.name}</a>
144 </td>
145 <td class="description">${group.description}</td>
146 <td class="visibleToAll">
147 ${group.options?.visible_to_all === true ? 'Y' : 'N'}
148 </td>
149 </tr>
150 `;
151 }
152
153 override willUpdate(changedProperties: PropertyValues) {
154 if (changedProperties.has('params')) {
155 this.paramsChanged();
156 }
157 }
158
159 // private but used in test
160 paramsChanged() {
161 this.filter = this.params?.filter ?? '';
162 this.offset = Number(this.params?.offset ?? 0);
163 this.maybeOpenCreateOverlay(this.params);
164
165 return this.getGroups(this.filter, this.groupsPerPage, this.offset);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100166 }
Paladox nonefea8d952017-05-28 15:48:40 +0000167
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100168 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100169 * Opens the create overlay if the route has a hash 'create'
Paladox nonea6629992021-11-14 22:05:24 +0000170 *
171 * private but used in test
Tao Zhou9a076812019-12-17 09:59:28 +0100172 */
Paladox nonea6629992021-11-14 22:05:24 +0000173 maybeOpenCreateOverlay(params?: AppElementAdminParams) {
Tao Zhou7d334b42020-08-31 10:56:33 +0200174 if (params?.openCreateModal) {
Paladox nonea6629992021-11-14 22:05:24 +0000175 assertIsDefined(this.createOverlay, 'createOverlay');
176 this.createOverlay.open();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100177 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100178 }
179
David Pursehousef7d7df42020-05-21 14:28:11 +0900180 /**
181 * Generates groups link (/admin/groups/<uuid>)
Paladox nonea6629992021-11-14 22:05:24 +0000182 *
183 * private but used in test
David Pursehousef7d7df42020-05-21 14:28:11 +0900184 */
Paladox nonea6629992021-11-14 22:05:24 +0000185 computeGroupUrl(id: string) {
Tao Zhou7d334b42020-08-31 10:56:33 +0200186 return GerritNav.getUrlForGroup(decodeURIComponent(id) as GroupId);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100187 }
188
Paladox nonea6629992021-11-14 22:05:24 +0000189 private getCreateGroupCapability() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100190 return this.restApiService.getAccount().then(account => {
Paladox nonea6629992021-11-14 22:05:24 +0000191 if (!account) return;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100192 return this.restApiService
Tao Zhou7d334b42020-08-31 10:56:33 +0200193 .getAccountCapabilities(['createGroup'])
194 .then(capabilities => {
195 if (capabilities?.createGroup) {
Paladox nonea6629992021-11-14 22:05:24 +0000196 this.createNewCapability = true;
Tao Zhou7d334b42020-08-31 10:56:33 +0200197 }
198 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100199 });
200 }
201
Paladox nonea6629992021-11-14 22:05:24 +0000202 private getGroups(filter: string, groupsPerPage: number, offset?: number) {
203 this.groups = [];
204 this.loading = true;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100205 return this.restApiService
Tao Zhou7d334b42020-08-31 10:56:33 +0200206 .getGroups(filter, groupsPerPage, offset)
207 .then(groups => {
Paladox nonea6629992021-11-14 22:05:24 +0000208 if (!groups) return;
209 this.groups = Object.keys(groups).map(key => {
Tao Zhou7d334b42020-08-31 10:56:33 +0200210 const group = groups[key];
Dmitrii Filippov55616542020-10-05 19:07:27 +0200211 group.name = key as GroupName;
Tao Zhou7d334b42020-08-31 10:56:33 +0200212 return group;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100213 });
Paladox nonea6629992021-11-14 22:05:24 +0000214 })
215 .finally(() => {
216 this.loading = false;
Tao Zhou7d334b42020-08-31 10:56:33 +0200217 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100218 }
219
Paladox nonea6629992021-11-14 22:05:24 +0000220 private refreshGroupsList() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100221 this.restApiService.invalidateGroupsCache();
Paladox nonea6629992021-11-14 22:05:24 +0000222 return this.getGroups(this.filter, this.groupsPerPage, this.offset);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100223 }
224
Paladox nonea6629992021-11-14 22:05:24 +0000225 // private but used in test
226 handleCreateGroup() {
227 assertIsDefined(this.createNewModal, 'createNewModal');
228 this.createNewModal.handleCreateGroup().then(() => {
229 this.refreshGroupsList();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100230 });
231 }
232
Paladox nonea6629992021-11-14 22:05:24 +0000233 // private but used in test
234 handleCloseCreate() {
235 assertIsDefined(this.createOverlay, 'createOverlay');
236 this.createOverlay.close();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100237 }
238
Paladox nonea6629992021-11-14 22:05:24 +0000239 // private but used in test
240 handleCreateClicked() {
241 assertIsDefined(this.createOverlay, 'createOverlay');
242 this.createOverlay.open().then(() => {
243 assertIsDefined(this.createNewModal, 'createNewModal');
244 this.createNewModal.focus();
Dhruv Srivastava26320752021-02-02 12:43:27 +0100245 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100246 }
247
Paladox nonea6629992021-11-14 22:05:24 +0000248 private handleHasNewGroupName() {
249 assertIsDefined(this.createNewModal, 'createNewModal');
250 this.hasNewGroupName = !!this.createNewModal.name;
Paladox none65d320c2021-11-14 15:20:09 +0000251 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100252}