Dave Borowitz | 8cdc76b | 2018-03-26 10:04:27 -0400 | [diff] [blame] | 1 | /** |
| 2 | * @license |
Ben Rohlfs | 94fcbbc | 2022-05-27 10:45:03 +0200 | [diff] [blame^] | 3 | * Copyright 2017 Google LLC |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
Dave Borowitz | 8cdc76b | 2018-03-26 10:04:27 -0400 | [diff] [blame] | 5 | */ |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 6 | |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 7 | import '../../shared/gr-dialog/gr-dialog'; |
| 8 | import '../../shared/gr-list-view/gr-list-view'; |
| 9 | import '../../shared/gr-overlay/gr-overlay'; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 10 | import '../gr-create-group-dialog/gr-create-group-dialog'; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 11 | import {GerritNav} from '../../core/gr-navigation/gr-navigation'; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 12 | import {AppElementAdminParams} from '../../gr-app-types'; |
| 13 | import {GrOverlay} from '../../shared/gr-overlay/gr-overlay'; |
Dmitrii Filippov | 5561654 | 2020-10-05 19:07:27 +0200 | [diff] [blame] | 14 | import {GroupId, GroupInfo, GroupName} from '../../../types/common'; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 15 | import {GrCreateGroupDialog} from '../gr-create-group-dialog/gr-create-group-dialog'; |
Milutin Kristofic | 6015013 | 2020-11-23 20:15:23 +0100 | [diff] [blame] | 16 | import {fireTitleChange} from '../../../utils/event-util'; |
Chris Poucet | c6e880b | 2021-11-15 19:57:06 +0100 | [diff] [blame] | 17 | import {getAppContext} from '../../../services/app-context'; |
Frank Borden | 949a17d | 2021-09-28 11:30:41 +0000 | [diff] [blame] | 18 | import {SHOWN_ITEMS_COUNT} from '../../../constants/constants'; |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 19 | import {tableStyles} from '../../../styles/gr-table-styles'; |
| 20 | import {sharedStyles} from '../../../styles/shared-styles'; |
| 21 | import {LitElement, PropertyValues, html} from 'lit'; |
| 22 | import {customElement, query, property, state} from 'lit/decorators'; |
| 23 | import {assertIsDefined} from '../../../utils/common-util'; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 24 | |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 25 | declare global { |
| 26 | interface HTMLElementTagNameMap { |
| 27 | 'gr-admin-group-list': GrAdminGroupList; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 28 | } |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 29 | } |
| 30 | |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 31 | @customElement('gr-admin-group-list') |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 32 | export class GrAdminGroupList extends LitElement { |
| 33 | readonly path = '/admin/groups'; |
| 34 | |
| 35 | @query('#createOverlay') private createOverlay?: GrOverlay; |
| 36 | |
| 37 | @query('#createNewModal') private createNewModal?: GrCreateGroupDialog; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 38 | |
| 39 | @property({type: Object}) |
| 40 | params?: AppElementAdminParams; |
| 41 | |
| 42 | /** |
| 43 | * Offset of currently visible query results. |
| 44 | */ |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 45 | @state() private offset = 0; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 46 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 47 | @state() private hasNewGroupName = false; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 48 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 49 | @state() private createNewCapability = false; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 50 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 51 | // private but used in test |
| 52 | @state() groups: GroupInfo[] = []; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 53 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 54 | @state() private groupsPerPage = 25; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 55 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 56 | // private but used in test |
| 57 | @state() loading = true; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 58 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 59 | @state() private filter = ''; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 60 | |
Chris Poucet | c6e880b | 2021-11-15 19:57:06 +0100 | [diff] [blame] | 61 | private readonly restApiService = getAppContext().restApiService; |
Ben Rohlfs | 43935a4 | 2020-12-01 19:14:09 +0100 | [diff] [blame] | 62 | |
Chris Poucet | 59dad57 | 2021-08-20 15:25:36 +0000 | [diff] [blame] | 63 | override connectedCallback() { |
Ben Rohlfs | 5f520da | 2021-03-10 14:58:43 +0100 | [diff] [blame] | 64 | super.connectedCallback(); |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 65 | this.getCreateGroupCapability(); |
Milutin Kristofic | 6015013 | 2020-11-23 20:15:23 +0100 | [diff] [blame] | 66 | fireTitleChange(this, 'Groups'); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 67 | } |
| 68 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 69 | static override get styles() { |
| 70 | return [tableStyles, sharedStyles]; |
| 71 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 72 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 73 | override render() { |
| 74 | return html` |
| 75 | <gr-list-view |
| 76 | .createNew=${this.createNewCapability} |
| 77 | .filter=${this.filter} |
| 78 | .items=${this.groups} |
| 79 | .itemsPerPage=${this.groupsPerPage} |
| 80 | .loading=${this.loading} |
| 81 | .offset=${this.offset} |
| 82 | .path=${this.path} |
| 83 | @create-clicked=${() => this.handleCreateClicked()} |
| 84 | > |
| 85 | <table id="list" class="genericList"> |
| 86 | <tbody> |
| 87 | <tr class="headerRow"> |
| 88 | <th class="name topHeader">Group Name</th> |
| 89 | <th class="description topHeader">Group Description</th> |
| 90 | <th class="visibleToAll topHeader">Visible To All</th> |
| 91 | </tr> |
| 92 | <tr |
| 93 | id="loading" |
| 94 | class="loadingMsg ${this.loading ? 'loading' : ''}" |
| 95 | > |
| 96 | <td>Loading...</td> |
| 97 | </tr> |
| 98 | </tbody> |
| 99 | <tbody class=${this.loading ? 'loading' : ''}> |
| 100 | ${this.groups |
| 101 | .slice(0, SHOWN_ITEMS_COUNT) |
| 102 | .map(group => this.renderGroupList(group))} |
| 103 | </tbody> |
| 104 | </table> |
| 105 | </gr-list-view> |
| 106 | <gr-overlay id="createOverlay" with-backdrop> |
| 107 | <gr-dialog |
| 108 | id="createDialog" |
| 109 | class="confirmDialog" |
| 110 | ?disabled=${!this.hasNewGroupName} |
| 111 | confirm-label="Create" |
| 112 | confirm-on-enter |
| 113 | @confirm=${() => this.handleCreateGroup()} |
| 114 | @cancel=${() => this.handleCloseCreate()} |
| 115 | > |
| 116 | <div class="header" slot="header">Create Group</div> |
| 117 | <div class="main" slot="main"> |
| 118 | <gr-create-group-dialog |
| 119 | id="createNewModal" |
| 120 | @has-new-group-name=${this.handleHasNewGroupName} |
| 121 | ></gr-create-group-dialog> |
| 122 | </div> |
| 123 | </gr-dialog> |
| 124 | </gr-overlay> |
| 125 | `; |
| 126 | } |
| 127 | |
| 128 | private renderGroupList(group: GroupInfo) { |
| 129 | return html` |
| 130 | <tr class="table"> |
| 131 | <td class="name"> |
| 132 | <a href=${this.computeGroupUrl(group.id)}>${group.name}</a> |
| 133 | </td> |
| 134 | <td class="description">${group.description}</td> |
| 135 | <td class="visibleToAll"> |
| 136 | ${group.options?.visible_to_all === true ? 'Y' : 'N'} |
| 137 | </td> |
| 138 | </tr> |
| 139 | `; |
| 140 | } |
| 141 | |
| 142 | override willUpdate(changedProperties: PropertyValues) { |
| 143 | if (changedProperties.has('params')) { |
| 144 | this.paramsChanged(); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // private but used in test |
| 149 | paramsChanged() { |
| 150 | this.filter = this.params?.filter ?? ''; |
| 151 | this.offset = Number(this.params?.offset ?? 0); |
| 152 | this.maybeOpenCreateOverlay(this.params); |
| 153 | |
| 154 | return this.getGroups(this.filter, this.groupsPerPage, this.offset); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 155 | } |
Paladox none | fea8d95 | 2017-05-28 15:48:40 +0000 | [diff] [blame] | 156 | |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 157 | /** |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 158 | * Opens the create overlay if the route has a hash 'create' |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 159 | * |
| 160 | * private but used in test |
Tao Zhou | 9a07681 | 2019-12-17 09:59:28 +0100 | [diff] [blame] | 161 | */ |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 162 | maybeOpenCreateOverlay(params?: AppElementAdminParams) { |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 163 | if (params?.openCreateModal) { |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 164 | assertIsDefined(this.createOverlay, 'createOverlay'); |
| 165 | this.createOverlay.open(); |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 166 | } |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 167 | } |
| 168 | |
David Pursehouse | f7d7df4 | 2020-05-21 14:28:11 +0900 | [diff] [blame] | 169 | /** |
| 170 | * Generates groups link (/admin/groups/<uuid>) |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 171 | * |
| 172 | * private but used in test |
David Pursehouse | f7d7df4 | 2020-05-21 14:28:11 +0900 | [diff] [blame] | 173 | */ |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 174 | computeGroupUrl(id: string) { |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 175 | return GerritNav.getUrlForGroup(decodeURIComponent(id) as GroupId); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 176 | } |
| 177 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 178 | private getCreateGroupCapability() { |
Ben Rohlfs | 43935a4 | 2020-12-01 19:14:09 +0100 | [diff] [blame] | 179 | return this.restApiService.getAccount().then(account => { |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 180 | if (!account) return; |
Ben Rohlfs | 43935a4 | 2020-12-01 19:14:09 +0100 | [diff] [blame] | 181 | return this.restApiService |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 182 | .getAccountCapabilities(['createGroup']) |
| 183 | .then(capabilities => { |
| 184 | if (capabilities?.createGroup) { |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 185 | this.createNewCapability = true; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 186 | } |
| 187 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 188 | }); |
| 189 | } |
| 190 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 191 | private getGroups(filter: string, groupsPerPage: number, offset?: number) { |
| 192 | this.groups = []; |
| 193 | this.loading = true; |
Ben Rohlfs | 43935a4 | 2020-12-01 19:14:09 +0100 | [diff] [blame] | 194 | return this.restApiService |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 195 | .getGroups(filter, groupsPerPage, offset) |
| 196 | .then(groups => { |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 197 | if (!groups) return; |
| 198 | this.groups = Object.keys(groups).map(key => { |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 199 | const group = groups[key]; |
Dmitrii Filippov | 5561654 | 2020-10-05 19:07:27 +0200 | [diff] [blame] | 200 | group.name = key as GroupName; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 201 | return group; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 202 | }); |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 203 | }) |
| 204 | .finally(() => { |
| 205 | this.loading = false; |
Tao Zhou | 7d334b4 | 2020-08-31 10:56:33 +0200 | [diff] [blame] | 206 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 207 | } |
| 208 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 209 | private refreshGroupsList() { |
Ben Rohlfs | 43935a4 | 2020-12-01 19:14:09 +0100 | [diff] [blame] | 210 | this.restApiService.invalidateGroupsCache(); |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 211 | return this.getGroups(this.filter, this.groupsPerPage, this.offset); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 212 | } |
| 213 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 214 | // private but used in test |
| 215 | handleCreateGroup() { |
| 216 | assertIsDefined(this.createNewModal, 'createNewModal'); |
| 217 | this.createNewModal.handleCreateGroup().then(() => { |
| 218 | this.refreshGroupsList(); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 219 | }); |
| 220 | } |
| 221 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 222 | // private but used in test |
| 223 | handleCloseCreate() { |
| 224 | assertIsDefined(this.createOverlay, 'createOverlay'); |
| 225 | this.createOverlay.close(); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 226 | } |
| 227 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 228 | // private but used in test |
| 229 | handleCreateClicked() { |
| 230 | assertIsDefined(this.createOverlay, 'createOverlay'); |
| 231 | this.createOverlay.open().then(() => { |
| 232 | assertIsDefined(this.createNewModal, 'createNewModal'); |
| 233 | this.createNewModal.focus(); |
Dhruv Srivastava | 2632075 | 2021-02-02 12:43:27 +0100 | [diff] [blame] | 234 | }); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 235 | } |
| 236 | |
Paladox none | a662999 | 2021-11-14 22:05:24 +0000 | [diff] [blame] | 237 | private handleHasNewGroupName() { |
| 238 | assertIsDefined(this.createNewModal, 'createNewModal'); |
| 239 | this.hasNewGroupName = !!this.createNewModal.name; |
Paladox none | 65d320c | 2021-11-14 15:20:09 +0000 | [diff] [blame] | 240 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 241 | } |