blob: 9330f080f9f7a1d1a8ea9be0e30421ffa2afdab1 [file] [log] [blame]
/**
* @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;
}
}