blob: 5d32d32a9319b9ab63be50e66cc03b970e695acf [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2017 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04005 */
Tao Zhou7d334b42020-08-31 10:56:33 +02006import '../../shared/gr-dialog/gr-dialog';
7import '../../shared/gr-list-view/gr-list-view';
8import '../../shared/gr-overlay/gr-overlay';
Tao Zhou7d334b42020-08-31 10:56:33 +02009import '../gr-create-group-dialog/gr-create-group-dialog';
Tao Zhou7d334b42020-08-31 10:56:33 +020010import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
Dmitrii Filippov55616542020-10-05 19:07:27 +020011import {GroupId, GroupInfo, GroupName} from '../../../types/common';
Tao Zhou7d334b42020-08-31 10:56:33 +020012import {GrCreateGroupDialog} from '../gr-create-group-dialog/gr-create-group-dialog';
Milutin Kristofic60150132020-11-23 20:15:23 +010013import {fireTitleChange} from '../../../utils/event-util';
Chris Poucetc6e880b2021-11-15 19:57:06 +010014import {getAppContext} from '../../../services/app-context';
Frank Borden949a17d2021-09-28 11:30:41 +000015import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
Paladox nonea6629992021-11-14 22:05:24 +000016import {tableStyles} from '../../../styles/gr-table-styles';
17import {sharedStyles} from '../../../styles/shared-styles';
Chris Poucet0b2addc2022-08-08 16:12:35 +020018import {LitElement, PropertyValues, css, html} from 'lit';
Frank Borden42c1a452022-08-11 16:27:20 +020019import {customElement, query, property, state} from 'lit/decorators.js';
Paladox nonea6629992021-11-14 22:05:24 +000020import {assertIsDefined} from '../../../utils/common-util';
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +020021import {AdminViewState} from '../../../models/views/admin';
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +020022import {createGroupUrl} from '../../../models/views/group';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010023
Tao Zhou7d334b42020-08-31 10:56:33 +020024declare global {
25 interface HTMLElementTagNameMap {
26 'gr-admin-group-list': GrAdminGroupList;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010027 }
Tao Zhou7d334b42020-08-31 10:56:33 +020028}
29
Tao Zhou7d334b42020-08-31 10:56:33 +020030@customElement('gr-admin-group-list')
Paladox nonea6629992021-11-14 22:05:24 +000031export class GrAdminGroupList extends LitElement {
32 readonly path = '/admin/groups';
33
34 @query('#createOverlay') private createOverlay?: GrOverlay;
35
36 @query('#createNewModal') private createNewModal?: GrCreateGroupDialog;
Tao Zhou7d334b42020-08-31 10:56:33 +020037
38 @property({type: Object})
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +020039 params?: AdminViewState;
Tao Zhou7d334b42020-08-31 10:56:33 +020040
41 /**
42 * Offset of currently visible query results.
43 */
Paladox nonea6629992021-11-14 22:05:24 +000044 @state() private offset = 0;
Tao Zhou7d334b42020-08-31 10:56:33 +020045
Paladox nonea6629992021-11-14 22:05:24 +000046 @state() private hasNewGroupName = false;
Tao Zhou7d334b42020-08-31 10:56:33 +020047
Paladox nonea6629992021-11-14 22:05:24 +000048 @state() private createNewCapability = false;
Tao Zhou7d334b42020-08-31 10:56:33 +020049
Paladox nonea6629992021-11-14 22:05:24 +000050 // private but used in test
51 @state() groups: GroupInfo[] = [];
Tao Zhou7d334b42020-08-31 10:56:33 +020052
Paladox nonea6629992021-11-14 22:05:24 +000053 @state() private groupsPerPage = 25;
Tao Zhou7d334b42020-08-31 10:56:33 +020054
Paladox nonea6629992021-11-14 22:05:24 +000055 // private but used in test
56 @state() loading = true;
Tao Zhou7d334b42020-08-31 10:56:33 +020057
Paladox nonea6629992021-11-14 22:05:24 +000058 @state() private filter = '';
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010059
Chris Poucetc6e880b2021-11-15 19:57:06 +010060 private readonly restApiService = getAppContext().restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +010061
Chris Poucet59dad572021-08-20 15:25:36 +000062 override connectedCallback() {
Ben Rohlfs5f520da2021-03-10 14:58:43 +010063 super.connectedCallback();
Paladox nonea6629992021-11-14 22:05:24 +000064 this.getCreateGroupCapability();
Milutin Kristofic60150132020-11-23 20:15:23 +010065 fireTitleChange(this, 'Groups');
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010066 }
67
Paladox nonea6629992021-11-14 22:05:24 +000068 static override get styles() {
Chris Poucet0b2addc2022-08-08 16:12:35 +020069 return [
70 tableStyles,
71 sharedStyles,
72 css`
73 gr-list-view {
74 --generic-list-description-width: 70%;
75 }
76 `,
77 ];
Paladox nonea6629992021-11-14 22:05:24 +000078 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010079
Paladox nonea6629992021-11-14 22:05:24 +000080 override render() {
81 return html`
82 <gr-list-view
83 .createNew=${this.createNewCapability}
84 .filter=${this.filter}
85 .items=${this.groups}
86 .itemsPerPage=${this.groupsPerPage}
87 .loading=${this.loading}
88 .offset=${this.offset}
89 .path=${this.path}
90 @create-clicked=${() => this.handleCreateClicked()}
91 >
92 <table id="list" class="genericList">
93 <tbody>
94 <tr class="headerRow">
95 <th class="name topHeader">Group Name</th>
96 <th class="description topHeader">Group Description</th>
97 <th class="visibleToAll topHeader">Visible To All</th>
98 </tr>
99 <tr
100 id="loading"
101 class="loadingMsg ${this.loading ? 'loading' : ''}"
102 >
103 <td>Loading...</td>
104 </tr>
105 </tbody>
106 <tbody class=${this.loading ? 'loading' : ''}>
107 ${this.groups
108 .slice(0, SHOWN_ITEMS_COUNT)
109 .map(group => this.renderGroupList(group))}
110 </tbody>
111 </table>
112 </gr-list-view>
113 <gr-overlay id="createOverlay" with-backdrop>
114 <gr-dialog
115 id="createDialog"
116 class="confirmDialog"
117 ?disabled=${!this.hasNewGroupName}
118 confirm-label="Create"
119 confirm-on-enter
120 @confirm=${() => this.handleCreateGroup()}
121 @cancel=${() => this.handleCloseCreate()}
122 >
123 <div class="header" slot="header">Create Group</div>
124 <div class="main" slot="main">
125 <gr-create-group-dialog
126 id="createNewModal"
127 @has-new-group-name=${this.handleHasNewGroupName}
128 ></gr-create-group-dialog>
129 </div>
130 </gr-dialog>
131 </gr-overlay>
132 `;
133 }
134
135 private renderGroupList(group: GroupInfo) {
136 return html`
137 <tr class="table">
138 <td class="name">
139 <a href=${this.computeGroupUrl(group.id)}>${group.name}</a>
140 </td>
141 <td class="description">${group.description}</td>
142 <td class="visibleToAll">
143 ${group.options?.visible_to_all === true ? 'Y' : 'N'}
144 </td>
145 </tr>
146 `;
147 }
148
149 override willUpdate(changedProperties: PropertyValues) {
150 if (changedProperties.has('params')) {
151 this.paramsChanged();
152 }
153 }
154
155 // private but used in test
156 paramsChanged() {
157 this.filter = this.params?.filter ?? '';
158 this.offset = Number(this.params?.offset ?? 0);
159 this.maybeOpenCreateOverlay(this.params);
160
161 return this.getGroups(this.filter, this.groupsPerPage, this.offset);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100162 }
Paladox nonefea8d952017-05-28 15:48:40 +0000163
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100164 /**
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100165 * Opens the create overlay if the route has a hash 'create'
Paladox nonea6629992021-11-14 22:05:24 +0000166 *
167 * private but used in test
Tao Zhou9a076812019-12-17 09:59:28 +0100168 */
Ben Rohlfsdcb88ac2022-09-12 11:50:26 +0200169 maybeOpenCreateOverlay(params?: AdminViewState) {
Tao Zhou7d334b42020-08-31 10:56:33 +0200170 if (params?.openCreateModal) {
Paladox nonea6629992021-11-14 22:05:24 +0000171 assertIsDefined(this.createOverlay, 'createOverlay');
172 this.createOverlay.open();
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100173 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100174 }
175
David Pursehousef7d7df42020-05-21 14:28:11 +0900176 /**
177 * Generates groups link (/admin/groups/<uuid>)
Paladox nonea6629992021-11-14 22:05:24 +0000178 *
179 * private but used in test
David Pursehousef7d7df42020-05-21 14:28:11 +0900180 */
Ben Rohlfsbd8dbcf2022-09-16 09:01:14 +0200181 computeGroupUrl(encodedId: string) {
182 const groupId = decodeURIComponent(encodedId) as GroupId;
183 return createGroupUrl({groupId});
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100184 }
185
Paladox nonea6629992021-11-14 22:05:24 +0000186 private getCreateGroupCapability() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100187 return this.restApiService.getAccount().then(account => {
Paladox nonea6629992021-11-14 22:05:24 +0000188 if (!account) return;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100189 return this.restApiService
Tao Zhou7d334b42020-08-31 10:56:33 +0200190 .getAccountCapabilities(['createGroup'])
191 .then(capabilities => {
192 if (capabilities?.createGroup) {
Paladox nonea6629992021-11-14 22:05:24 +0000193 this.createNewCapability = true;
Tao Zhou7d334b42020-08-31 10:56:33 +0200194 }
195 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100196 });
197 }
198
Paladox nonea6629992021-11-14 22:05:24 +0000199 private getGroups(filter: string, groupsPerPage: number, offset?: number) {
200 this.groups = [];
201 this.loading = true;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100202 return this.restApiService
Tao Zhou7d334b42020-08-31 10:56:33 +0200203 .getGroups(filter, groupsPerPage, offset)
204 .then(groups => {
Paladox nonea6629992021-11-14 22:05:24 +0000205 if (!groups) return;
206 this.groups = Object.keys(groups).map(key => {
Tao Zhou7d334b42020-08-31 10:56:33 +0200207 const group = groups[key];
Dmitrii Filippov55616542020-10-05 19:07:27 +0200208 group.name = key as GroupName;
Tao Zhou7d334b42020-08-31 10:56:33 +0200209 return group;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100210 });
Paladox nonea6629992021-11-14 22:05:24 +0000211 })
212 .finally(() => {
213 this.loading = false;
Tao Zhou7d334b42020-08-31 10:56:33 +0200214 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100215 }
216
Paladox nonea6629992021-11-14 22:05:24 +0000217 private refreshGroupsList() {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100218 this.restApiService.invalidateGroupsCache();
Paladox nonea6629992021-11-14 22:05:24 +0000219 return this.getGroups(this.filter, this.groupsPerPage, this.offset);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100220 }
221
Paladox nonea6629992021-11-14 22:05:24 +0000222 // private but used in test
223 handleCreateGroup() {
224 assertIsDefined(this.createNewModal, 'createNewModal');
225 this.createNewModal.handleCreateGroup().then(() => {
226 this.refreshGroupsList();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100227 });
228 }
229
Paladox nonea6629992021-11-14 22:05:24 +0000230 // private but used in test
231 handleCloseCreate() {
232 assertIsDefined(this.createOverlay, 'createOverlay');
233 this.createOverlay.close();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100234 }
235
Paladox nonea6629992021-11-14 22:05:24 +0000236 // private but used in test
237 handleCreateClicked() {
238 assertIsDefined(this.createOverlay, 'createOverlay');
239 this.createOverlay.open().then(() => {
240 assertIsDefined(this.createNewModal, 'createNewModal');
241 this.createNewModal.focus();
Dhruv Srivastava26320752021-02-02 12:43:27 +0100242 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100243 }
244
Paladox nonea6629992021-11-14 22:05:24 +0000245 private handleHasNewGroupName() {
246 assertIsDefined(this.createNewModal, 'createNewModal');
247 this.hasNewGroupName = !!this.createNewModal.name;
Paladox none65d320c2021-11-14 15:20:09 +0000248 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100249}