Convert gr-admin-group-list to lit
Change-Id: I0c2cd217a04383ac862ebc862d258dd02eaf4c05
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index 83063a1..f3f1139 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -15,16 +15,11 @@
* limitations under the License.
*/
-import '../../../styles/gr-table-styles';
-import '../../../styles/shared-styles';
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 {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-admin-group-list_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property, observe, computed} from '@polymer/decorators';
import {AppElementAdminParams} from '../../gr-app-types';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {GroupId, GroupInfo, GroupName} from '../../../types/common';
@@ -32,6 +27,11 @@
import {fireTitleChange} from '../../../utils/event-util';
import {appContext} 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 {
@@ -39,18 +39,13 @@
}
}
-export interface GrAdminGroupList {
- $: {
- createOverlay: GrOverlay;
- createNewModal: GrCreateGroupDialog;
- };
-}
-
@customElement('gr-admin-group-list')
-export class GrAdminGroupList extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrAdminGroupList extends LitElement {
+ readonly path = '/admin/groups';
+
+ @query('#createOverlay') private createOverlay?: GrOverlay;
+
+ @query('#createNewModal') private createNewModal?: GrCreateGroupDialog;
@property({type: Object})
params?: AppElementAdminParams;
@@ -58,135 +53,200 @@
/**
* Offset of currently visible query results.
*/
- @property({type: Number})
- _offset = 0;
+ @state() private offset = 0;
- @property({type: String})
- readonly _path = '/admin/groups';
+ @state() private hasNewGroupName = false;
- @property({type: Boolean})
- _hasNewGroupName = false;
+ @state() private createNewCapability = false;
- @property({type: Boolean})
- _createNewCapability = false;
+ // private but used in test
+ @state() groups: GroupInfo[] = [];
- @property({type: Array})
- _groups: GroupInfo[] = [];
+ @state() private groupsPerPage = 25;
- /**
- * Because we request one more than the groupsPerPage, _shownGroups
- * may be one less than _groups.
- * */
- @computed('_groups')
- get _shownGroups() {
- return this._groups.slice(0, SHOWN_ITEMS_COUNT);
- }
+ // private but used in test
+ @state() loading = true;
- @property({type: Number})
- _groupsPerPage = 25;
-
- @property({type: Boolean})
- _loading = true;
-
- @property({type: String})
- _filter = '';
+ @state() private filter = '';
private readonly restApiService = appContext.restApiService;
override connectedCallback() {
super.connectedCallback();
- this._getCreateGroupCapability();
+ this.getCreateGroupCapability();
fireTitleChange(this, 'Groups');
- this._maybeOpenCreateOverlay(this.params);
}
- @observe('params')
- _paramsChanged(params: AppElementAdminParams) {
- this._loading = true;
- this._filter = params?.filter ?? '';
- this._offset = Number(params?.offset ?? 0);
+ static override get styles() {
+ return [tableStyles, sharedStyles];
+ }
- return this._getGroups(this._filter, this._groupsPerPage, this._offset);
+ 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) {
+ maybeOpenCreateOverlay(params?: AppElementAdminParams) {
if (params?.openCreateModal) {
- this.$.createOverlay.open();
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.createOverlay.open();
}
}
/**
* Generates groups link (/admin/groups/<uuid>)
+ *
+ * private but used in test
*/
- _computeGroupUrl(id: string) {
+ computeGroupUrl(id: string) {
return GerritNav.getUrlForGroup(decodeURIComponent(id) as GroupId);
}
- _getCreateGroupCapability() {
+ private getCreateGroupCapability() {
return this.restApiService.getAccount().then(account => {
- if (!account) {
- return;
- }
+ if (!account) return;
return this.restApiService
.getAccountCapabilities(['createGroup'])
.then(capabilities => {
if (capabilities?.createGroup) {
- this._createNewCapability = true;
+ this.createNewCapability = true;
}
});
});
}
- _getGroups(filter: string, groupsPerPage: number, offset?: number) {
- this._groups = [];
+ 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 => {
+ if (!groups) return;
+ this.groups = Object.keys(groups).map(key => {
const group = groups[key];
group.name = key as GroupName;
return group;
});
- this._loading = false;
+ })
+ .finally(() => {
+ this.loading = false;
});
}
- _refreshGroupsList() {
+ private refreshGroupsList() {
this.restApiService.invalidateGroupsCache();
- return this._getGroups(this._filter, this._groupsPerPage, this._offset);
+ return this.getGroups(this.filter, this.groupsPerPage, this.offset);
}
- _handleCreateGroup() {
- this.$.createNewModal.handleCreateGroup().then(() => {
- this._refreshGroupsList();
+ // private but used in test
+ handleCreateGroup() {
+ assertIsDefined(this.createNewModal, 'createNewModal');
+ this.createNewModal.handleCreateGroup().then(() => {
+ this.refreshGroupsList();
});
}
- _handleCloseCreate() {
- this.$.createOverlay.close();
+ // private but used in test
+ handleCloseCreate() {
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.createOverlay.close();
}
- _handleCreateClicked() {
- this.$.createOverlay.open().then(() => {
- this.$.createNewModal.focus();
+ // private but used in test
+ handleCreateClicked() {
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.createOverlay.open().then(() => {
+ assertIsDefined(this.createNewModal, 'createNewModal');
+ this.createNewModal.focus();
});
}
- _visibleToAll(item: GroupInfo) {
- return item.options?.visible_to_all === true ? 'Y' : 'N';
- }
-
- computeLoadingClass(loading: boolean) {
- return loading ? 'loading' : '';
- }
-
- _handleHasNewGroupName() {
- this._hasNewGroupName = !!this.$.createNewModal.name;
+ private handleHasNewGroupName() {
+ assertIsDefined(this.createNewModal, 'createNewModal');
+ this.hasNewGroupName = !!this.createNewModal.name;
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_html.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_html.ts
deleted file mode 100644
index fdde399..0000000
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_html.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 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 {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-table-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <gr-list-view
- create-new="[[_createNewCapability]]"
- filter="[[_filter]]"
- items="[[_groups]]"
- items-per-page="[[_groupsPerPage]]"
- loading="[[_loading]]"
- offset="[[_offset]]"
- on-create-clicked="_handleCreateClicked"
- path="[[_path]]"
- >
- <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 [[computeLoadingClass(_loading)]]">
- <td>Loading...</td>
- </tr>
- </tbody>
- <tbody class$="[[computeLoadingClass(_loading)]]">
- <template is="dom-repeat" items="[[_shownGroups]]">
- <tr class="table">
- <td class="name">
- <a href$="[[_computeGroupUrl(item.id)]]">[[item.name]]</a>
- </td>
- <td class="description">[[item.description]]</td>
- <td class="visibleToAll">[[_visibleToAll(item)]]</td>
- </tr>
- </template>
- </tbody>
- </table>
- </gr-list-view>
- <gr-overlay id="createOverlay" with-backdrop="">
- <gr-dialog
- id="createDialog"
- class="confirmDialog"
- disabled="[[!_hasNewGroupName]]"
- confirm-label="Create"
- confirm-on-enter=""
- on-confirm="_handleCreateGroup"
- on-cancel="_handleCloseCreate"
- >
- <div class="header" slot="header">Create Group</div>
- <div class="main" slot="main">
- <gr-create-group-dialog
- id="createNewModal"
- on-has-new-group-name="_handleHasNewGroupName"
- ></gr-create-group-dialog>
- </div>
- </gr-dialog>
- </gr-overlay>
-`;
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
index 83669c9..709a0b7 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
@@ -30,6 +30,7 @@
import {GrListView} from '../../shared/gr-list-view/gr-list-view';
import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
+import {SHOWN_ITEMS_COUNT} from '../../../constants/constants';
const basicFixture = fixtureFromElement('gr-admin-group-list');
@@ -70,11 +71,12 @@
const value: AppElementAdminParams = {view: GerritView.ADMIN, adminView: ''};
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- test('_computeGroupUrl', () => {
+ test('computeGroupUrl', () => {
let urlStub = sinon
.stub(GerritNav, 'getUrlForGroup')
.callsFake(
@@ -83,7 +85,7 @@
let group = 'e2cd66f88a2db4d391ac068a92d987effbe872f5';
assert.equal(
- element._computeGroupUrl(group),
+ element.computeGroupUrl(group),
'/admin/groups/e2cd66f88a2db4d391ac068a92d987effbe872f5'
);
@@ -94,7 +96,7 @@
.callsFake(() => '/admin/groups/user/test');
group = 'user%2Ftest';
- assert.equal(element._computeGroupUrl(group), '/admin/groups/user/test');
+ assert.equal(element.computeGroupUrl(group), '/admin/groups/user/test');
urlStub.restore();
});
@@ -103,30 +105,31 @@
setup(async () => {
groups = createGroupObjectList('test', 26);
stubRestApi('getGroups').returns(Promise.resolve(groups));
- element._paramsChanged(value);
- await flush();
+ element.params = value;
+ element.paramsChanged();
+ await element.updateComplete;
});
test('test for test group in the list', () => {
- assert.equal(element._groups[1].name, 'test1' as GroupName);
- assert.equal(element._groups[1].options!.visible_to_all, false);
+ assert.equal(element.groups[1].name, 'test1' as GroupName);
+ assert.equal(element.groups[1].options!.visible_to_all, false);
});
- test('_shownGroups', () => {
- assert.equal(element._shownGroups.length, 25);
+ test('groups', () => {
+ assert.equal(element.groups.slice(0, SHOWN_ITEMS_COUNT).length, 25);
});
- test('_maybeOpenCreateOverlay', () => {
+ test('maybeOpenCreateOverlay', () => {
const overlayOpen = sinon.stub(
queryAndAssert<GrOverlay>(element, '#createOverlay'),
'open'
);
- element._maybeOpenCreateOverlay();
+ element.maybeOpenCreateOverlay();
assert.isFalse(overlayOpen.called);
- element._maybeOpenCreateOverlay(undefined);
+ element.maybeOpenCreateOverlay(undefined);
assert.isFalse(overlayOpen.called);
value.openCreateModal = true;
- element._maybeOpenCreateOverlay(value);
+ element.maybeOpenCreateOverlay(value);
assert.isTrue(overlayOpen.called);
});
});
@@ -135,41 +138,41 @@
setup(async () => {
groups = createGroupObjectList('test', 25);
stubRestApi('getGroups').returns(Promise.resolve(groups));
- await element._paramsChanged(value);
- await flush();
+ element.params = value;
+ await element.paramsChanged();
+ await element.updateComplete;
});
- test('_shownGroups', () => {
- assert.equal(element._shownGroups.length, 25);
+ test('groups', () => {
+ assert.equal(element.groups.slice(0, SHOWN_ITEMS_COUNT).length, 25);
});
});
suite('filter', () => {
- test('_paramsChanged', async () => {
+ test('paramsChanged', async () => {
const getGroupsStub = stubRestApi('getGroups');
getGroupsStub.returns(Promise.resolve(groups));
value.filter = 'test';
value.offset = 25;
- await element._paramsChanged(value);
+ element.params = value;
+ await element.paramsChanged();
assert.isTrue(getGroupsStub.lastCall.calledWithExactly('test', 25, 25));
});
});
suite('loading', async () => {
test('correct contents are displayed', async () => {
- assert.isTrue(element._loading);
- assert.equal(element.computeLoadingClass(element._loading), 'loading');
+ assert.isTrue(element.loading);
assert.equal(
getComputedStyle(queryAndAssert<HTMLTableElement>(element, '#loading'))
.display,
'block'
);
- element._loading = false;
- element._groups = createGroupList('test', 25);
+ element.loading = false;
+ element.groups = createGroupList('test', 25);
- await flush();
- assert.equal(element.computeLoadingClass(element._loading), '');
+ await element.updateComplete;
assert.equal(
getComputedStyle(queryAndAssert<HTMLTableElement>(element, '#loading'))
.display,
@@ -179,10 +182,10 @@
});
suite('create new', () => {
- test('_handleCreateClicked called when create-click fired', () => {
+ test('handleCreateClicked called when create-click fired', () => {
const handleCreateClickedStub = sinon.stub(
element,
- '_handleCreateClicked'
+ 'handleCreateClicked'
);
queryAndAssert<GrListView>(element, 'gr-list-view').dispatchEvent(
new CustomEvent('create-clicked', {
@@ -193,16 +196,16 @@
assert.isTrue(handleCreateClickedStub.called);
});
- test('_handleCreateClicked opens modal', () => {
+ test('handleCreateClicked opens modal', () => {
const openStub = sinon
.stub(queryAndAssert<GrOverlay>(element, '#createOverlay'), 'open')
.returns(Promise.resolve());
- element._handleCreateClicked();
+ element.handleCreateClicked();
assert.isTrue(openStub.called);
});
- test('_handleCreateGroup called when confirm fired', () => {
- const handleCreateGroupStub = sinon.stub(element, '_handleCreateGroup');
+ test('handleCreateGroup called when confirm fired', () => {
+ const handleCreateGroupStub = sinon.stub(element, 'handleCreateGroup');
queryAndAssert<GrDialog>(element, '#createDialog').dispatchEvent(
new CustomEvent('confirm', {
composed: true,
@@ -212,8 +215,8 @@
assert.isTrue(handleCreateGroupStub.called);
});
- test('_handleCloseCreate called when cancel fired', () => {
- const handleCloseCreateStub = sinon.stub(element, '_handleCloseCreate');
+ test('handleCloseCreate called when cancel fired', () => {
+ const handleCloseCreateStub = sinon.stub(element, 'handleCloseCreate');
queryAndAssert<GrDialog>(element, '#createDialog').dispatchEvent(
new CustomEvent('cancel', {
composed: true,