| /** |
| * @license |
| * 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. |
| */ |
| |
| import '../../shared/gr-dialog/gr-dialog'; |
| import '../../shared/gr-list-view/gr-list-view'; |
| import '../../shared/gr-overlay/gr-overlay'; |
| import '../gr-create-group-dialog/gr-create-group-dialog'; |
| import {GerritNav} from '../../core/gr-navigation/gr-navigation'; |
| import {AppElementAdminParams} from '../../gr-app-types'; |
| import {GrOverlay} from '../../shared/gr-overlay/gr-overlay'; |
| import {GroupId, GroupInfo, GroupName} from '../../../types/common'; |
| import {GrCreateGroupDialog} from '../gr-create-group-dialog/gr-create-group-dialog'; |
| import {fireTitleChange} from '../../../utils/event-util'; |
| import {getAppContext} from '../../../services/app-context'; |
| import {SHOWN_ITEMS_COUNT} from '../../../constants/constants'; |
| import {tableStyles} from '../../../styles/gr-table-styles'; |
| import {sharedStyles} from '../../../styles/shared-styles'; |
| import {LitElement, PropertyValues, html} from 'lit'; |
| import {customElement, query, property, state} from 'lit/decorators'; |
| import {assertIsDefined} from '../../../utils/common-util'; |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-admin-group-list': GrAdminGroupList; |
| } |
| } |
| |
| @customElement('gr-admin-group-list') |
| export class GrAdminGroupList extends LitElement { |
| readonly path = '/admin/groups'; |
| |
| @query('#createOverlay') private createOverlay?: GrOverlay; |
| |
| @query('#createNewModal') private createNewModal?: GrCreateGroupDialog; |
| |
| @property({type: Object}) |
| params?: AppElementAdminParams; |
| |
| /** |
| * Offset of currently visible query results. |
| */ |
| @state() private offset = 0; |
| |
| @state() private hasNewGroupName = false; |
| |
| @state() private createNewCapability = false; |
| |
| // private but used in test |
| @state() groups: GroupInfo[] = []; |
| |
| @state() private groupsPerPage = 25; |
| |
| // private but used in test |
| @state() loading = true; |
| |
| @state() private filter = ''; |
| |
| private readonly restApiService = getAppContext().restApiService; |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| this.getCreateGroupCapability(); |
| fireTitleChange(this, 'Groups'); |
| } |
| |
| static override get styles() { |
| return [tableStyles, sharedStyles]; |
| } |
| |
| override render() { |
| return html` |
| <gr-list-view |
| .createNew=${this.createNewCapability} |
| .filter=${this.filter} |
| .items=${this.groups} |
| .itemsPerPage=${this.groupsPerPage} |
| .loading=${this.loading} |
| .offset=${this.offset} |
| .path=${this.path} |
| @create-clicked=${() => this.handleCreateClicked()} |
| > |
| <table id="list" class="genericList"> |
| <tbody> |
| <tr class="headerRow"> |
| <th class="name topHeader">Group Name</th> |
| <th class="description topHeader">Group Description</th> |
| <th class="visibleToAll topHeader">Visible To All</th> |
| </tr> |
| <tr |
| id="loading" |
| class="loadingMsg ${this.loading ? 'loading' : ''}" |
| > |
| <td>Loading...</td> |
| </tr> |
| </tbody> |
| <tbody class=${this.loading ? 'loading' : ''}> |
| ${this.groups |
| .slice(0, SHOWN_ITEMS_COUNT) |
| .map(group => this.renderGroupList(group))} |
| </tbody> |
| </table> |
| </gr-list-view> |
| <gr-overlay id="createOverlay" with-backdrop> |
| <gr-dialog |
| id="createDialog" |
| class="confirmDialog" |
| ?disabled=${!this.hasNewGroupName} |
| confirm-label="Create" |
| confirm-on-enter |
| @confirm=${() => this.handleCreateGroup()} |
| @cancel=${() => this.handleCloseCreate()} |
| > |
| <div class="header" slot="header">Create Group</div> |
| <div class="main" slot="main"> |
| <gr-create-group-dialog |
| id="createNewModal" |
| @has-new-group-name=${this.handleHasNewGroupName} |
| ></gr-create-group-dialog> |
| </div> |
| </gr-dialog> |
| </gr-overlay> |
| `; |
| } |
| |
| private renderGroupList(group: GroupInfo) { |
| return html` |
| <tr class="table"> |
| <td class="name"> |
| <a href=${this.computeGroupUrl(group.id)}>${group.name}</a> |
| </td> |
| <td class="description">${group.description}</td> |
| <td class="visibleToAll"> |
| ${group.options?.visible_to_all === true ? 'Y' : 'N'} |
| </td> |
| </tr> |
| `; |
| } |
| |
| override willUpdate(changedProperties: PropertyValues) { |
| if (changedProperties.has('params')) { |
| this.paramsChanged(); |
| } |
| } |
| |
| // private but used in test |
| paramsChanged() { |
| this.filter = this.params?.filter ?? ''; |
| this.offset = Number(this.params?.offset ?? 0); |
| this.maybeOpenCreateOverlay(this.params); |
| |
| return this.getGroups(this.filter, this.groupsPerPage, this.offset); |
| } |
| |
| /** |
| * Opens the create overlay if the route has a hash 'create' |
| * |
| * private but used in test |
| */ |
| maybeOpenCreateOverlay(params?: AppElementAdminParams) { |
| if (params?.openCreateModal) { |
| assertIsDefined(this.createOverlay, 'createOverlay'); |
| this.createOverlay.open(); |
| } |
| } |
| |
| /** |
| * Generates groups link (/admin/groups/<uuid>) |
| * |
| * private but used in test |
| */ |
| computeGroupUrl(id: string) { |
| return GerritNav.getUrlForGroup(decodeURIComponent(id) as GroupId); |
| } |
| |
| private getCreateGroupCapability() { |
| return this.restApiService.getAccount().then(account => { |
| if (!account) return; |
| return this.restApiService |
| .getAccountCapabilities(['createGroup']) |
| .then(capabilities => { |
| if (capabilities?.createGroup) { |
| this.createNewCapability = true; |
| } |
| }); |
| }); |
| } |
| |
| private getGroups(filter: string, groupsPerPage: number, offset?: number) { |
| this.groups = []; |
| this.loading = true; |
| return this.restApiService |
| .getGroups(filter, groupsPerPage, offset) |
| .then(groups => { |
| if (!groups) return; |
| this.groups = Object.keys(groups).map(key => { |
| const group = groups[key]; |
| group.name = key as GroupName; |
| return group; |
| }); |
| }) |
| .finally(() => { |
| this.loading = false; |
| }); |
| } |
| |
| private refreshGroupsList() { |
| this.restApiService.invalidateGroupsCache(); |
| return this.getGroups(this.filter, this.groupsPerPage, this.offset); |
| } |
| |
| // private but used in test |
| handleCreateGroup() { |
| assertIsDefined(this.createNewModal, 'createNewModal'); |
| this.createNewModal.handleCreateGroup().then(() => { |
| this.refreshGroupsList(); |
| }); |
| } |
| |
| // private but used in test |
| handleCloseCreate() { |
| assertIsDefined(this.createOverlay, 'createOverlay'); |
| this.createOverlay.close(); |
| } |
| |
| // private but used in test |
| handleCreateClicked() { |
| assertIsDefined(this.createOverlay, 'createOverlay'); |
| this.createOverlay.open().then(() => { |
| assertIsDefined(this.createNewModal, 'createNewModal'); |
| this.createNewModal.focus(); |
| }); |
| } |
| |
| private handleHasNewGroupName() { |
| assertIsDefined(this.createNewModal, 'createNewModal'); |
| this.hasNewGroupName = !!this.createNewModal.name; |
| } |
| } |