diff options
Diffstat (limited to 'polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts')
-rw-r--r-- | polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts | 675 |
1 files changed, 451 insertions, 224 deletions
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts index 1c9c6f3cab..154b4708d3 100644 --- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts +++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts @@ -14,9 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../../styles/gr-menu-page-styles'; -import '../../../styles/gr-page-nav-styles'; -import '../../../styles/shared-styles'; + import '../../shared/gr-dropdown-list/gr-dropdown-list'; import '../../shared/gr-icons/gr-icons'; import '../../shared/gr-page-nav/gr-page-nav'; @@ -31,8 +29,6 @@ import '../gr-repo-commands/gr-repo-commands'; import '../gr-repo-dashboards/gr-repo-dashboards'; import '../gr-repo-detail-list/gr-repo-detail-list'; import '../gr-repo-list/gr-repo-list'; -import {PolymerElement} from '@polymer/polymer/polymer-element'; -import {htmlTemplate} from './gr-admin-view_html'; import {getBaseUrl} from '../../../utils/url-util'; import { GerritNav, @@ -46,7 +42,6 @@ import { NavLink, SubsectionInterface, } from '../../../utils/admin-nav-util'; -import {customElement, observe, property} from '@polymer/decorators'; import { AppElementAdminParams, AppElementGroupParams, @@ -60,12 +55,18 @@ import { } from '../../../types/common'; import {GroupNameChangedDetail} from '../gr-group/gr-group'; import {ValueChangeDetail} from '../../shared/gr-dropdown-list/gr-dropdown-list'; -import {appContext} from '../../../services/app-context'; +import {getAppContext} from '../../../services/app-context'; import {GerritView} from '../../../services/router/router-model'; +import {menuPageStyles} from '../../../styles/gr-menu-page-styles'; +import {pageNavStyles} from '../../../styles/gr-page-nav-styles'; +import {sharedStyles} from '../../../styles/shared-styles'; +import {LitElement, PropertyValues, css, html} from 'lit'; +import {customElement, property, state} from 'lit/decorators'; +import {ifDefined} from 'lit/directives/if-defined'; const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/; -interface AdminSubsectionLink { +export interface AdminSubsectionLink { text: string; value: string; view: GerritView; @@ -90,11 +91,7 @@ function getAdminViewParamsDetail( } @customElement('gr-admin-view') -export class GrAdminView extends PolymerElement { - static get template() { - return htmlTemplate; - } - +export class GrAdminView extends LitElement { private account?: AccountDetailInfo; @property({type: Object}) @@ -106,99 +103,402 @@ export class GrAdminView extends PolymerElement { @property({type: String}) adminView?: string; - @property({type: String}) - _breadcrumbParentName?: string; + @state() private breadcrumbParentName?: string; - @property({type: String}) - _repoName?: RepoName; + // private but used in test + @state() repoName?: RepoName; - @property({type: String, observer: '_computeGroupName'}) - _groupId?: GroupId; + // private but used in test + @state() groupId?: GroupId; - @property({type: Boolean}) - _groupIsInternal?: boolean; + // private but used in test + @state() groupIsInternal?: boolean; - @property({type: String}) - _groupName?: GroupName; + // private but used in test + @state() groupName?: GroupName; - @property({type: Boolean}) - _groupOwner = false; + // private but used in test + @state() subsectionLinks?: AdminSubsectionLink[]; - @property({type: Array}) - _subsectionLinks?: AdminSubsectionLink[]; + // private but used in test + @state() filteredLinks?: NavLink[]; - @property({type: Array}) - _filteredLinks?: NavLink[]; + private reloading = false; - @property({type: Boolean}) - _showDownload = false; + // private but used in the tests + readonly jsAPI = getAppContext().jsApiService; - @property({type: Boolean}) - _isAdmin = false; + private readonly restApiService = getAppContext().restApiService; - @property({type: Boolean}) - _showGroup?: boolean; + override connectedCallback() { + super.connectedCallback(); + this.reload(); + } - @property({type: Boolean}) - _showGroupAuditLog?: boolean; + static override get styles() { + return [ + sharedStyles, + menuPageStyles, + pageNavStyles, + css` + .breadcrumbText { + /* Same as dropdown trigger so chevron spacing is consistent. */ + padding: 5px 4px; + } + iron-icon { + margin: 0 var(--spacing-xs); + } + .breadcrumb { + align-items: center; + display: flex; + } + .mainHeader { + align-items: baseline; + border-bottom: 1px solid var(--border-color); + display: flex; + } + .selectText { + display: none; + } + .selectText.show { + display: inline-block; + } + .main.breadcrumbs:not(.table) { + margin-top: var(--spacing-l); + } + `, + ]; + } - @property({type: Boolean}) - _showGroupList?: boolean; + override render() { + return html` + <gr-page-nav class="navStyles"> + <ul class="sectionContent"> + ${this.filteredLinks?.map(item => this.renderAdminNav(item))} + </ul> + </gr-page-nav> + ${this.renderSubsectionLinks()} ${this.renderRepoList()} + ${this.renderGroupList()} ${this.renderPluginList()} + ${this.renderRepoMain()} ${this.renderGroup()} + ${this.renderGroupMembers()} ${this.renderGroupAuditLog()} + ${this.renderRepoDetailList()} ${this.renderRepoCommands()} + ${this.renderRepoAccess()} ${this.renderRepoDashboards()} + `; + } - @property({type: Boolean}) - _showGroupMembers?: boolean; + private renderAdminNav(item: NavLink) { + return html` + <li class="sectionTitle ${this.computeSelectedClass(item.view)}"> + <a class="title" href=${this.computeLinkURL(item)} rel="noopener" + >${item.name}</a + > + </li> + ${item.children?.map(child => this.renderAdminNavChild(child))} + ${this.renderAdminNavSubsection(item)} + `; + } - @property({type: Boolean}) - _showRepoAccess?: boolean; + private renderAdminNavChild(child: SubsectionInterface) { + return html` + <li class=${this.computeSelectedClass(child.view)}> + <a href=${this.computeLinkURL(child)} rel="noopener">${child.name}</a> + </li> + `; + } - @property({type: Boolean}) - _showRepoCommands?: boolean; + private renderAdminNavSubsection(item: NavLink) { + if (!item.subsection) return; + + return html` + <!--If a section has a subsection, render that.--> + <li class=${this.computeSelectedClass(item.subsection.view)}> + ${this.renderAdminNavSubsectionUrl(item.subsection)} + </li> + <!--Loop through the links in the sub-section.--> + ${item.subsection?.children?.map(child => + this.renderAdminNavSubsectionChild(child) + )} + `; + } - @property({type: Boolean}) - _showRepoDashboards?: boolean; + private renderAdminNavSubsectionUrl(subsection?: SubsectionInterface) { + if (!subsection!.url) return html`${subsection!.name}`; - @property({type: Boolean}) - _showRepoDetailList?: boolean; + return html` + <a class="title" href=${this.computeLinkURL(subsection)} rel="noopener"> + ${subsection!.name}</a + > + `; + } - @property({type: Boolean}) - _showRepoMain?: boolean; + private renderAdminNavSubsectionChild(child: SubsectionInterface) { + return html` + <li + class="subsectionItem ${this.computeSelectedClass( + child.view, + child.detailType + )}" + > + <a href=${this.computeLinkURL(child)}>${child.name}</a> + </li> + `; + } - @property({type: Boolean}) - _showRepoList?: boolean; + private renderSubsectionLinks() { + if (!this.subsectionLinks?.length) return; + + return html` + <section class="mainHeader"> + <span class="breadcrumb"> + <span class="breadcrumbText">${this.breadcrumbParentName}</span> + <iron-icon icon="gr-icons:chevron-right"></iron-icon> + </span> + <gr-dropdown-list + id="pageSelect" + value=${ifDefined(this.computeSelectValue())} + .items=${this.subsectionLinks} + @value-change=${this.handleSubsectionChange} + > + </gr-dropdown-list> + </section> + `; + } - @property({type: Boolean}) - _showPluginList?: boolean; + private renderRepoList() { + const params = this.params as AppElementAdminParams; + if ( + !( + params?.view === GerritView.ADMIN && + params?.adminView === 'gr-repo-list' + ) + ) + return; - private readonly restApiService = appContext.restApiService; + return html` + <div class="main table"> + <gr-repo-list class="table" .params=${params}></gr-repo-list> + </div> + `; + } - private readonly jsAPI = appContext.jsApiService; + private renderGroupList() { + const params = this.params as AppElementAdminParams; + if ( + !( + params?.view === GerritView.ADMIN && + params?.adminView === 'gr-admin-group-list' + ) + ) + return; - override connectedCallback() { - super.connectedCallback(); - this.reload(); + return html` + <div class="main table"> + <gr-admin-group-list class="table" .params=${params}> + </gr-admin-group-list> + </div> + `; } - reload() { - const promises: [Promise<AccountDetailInfo | undefined>, Promise<void>] = [ - this.restApiService.getAccount(), - getPluginLoader().awaitPluginsLoaded(), - ]; - return Promise.all(promises).then(result => { + private renderPluginList() { + const params = this.params as AppElementAdminParams; + if ( + !( + params?.view === GerritView.ADMIN && + params?.adminView === 'gr-plugin-list' + ) + ) + return; + + return html` + <div class="main table"> + <gr-plugin-list class="table" .params=${params}></gr-plugin-list> + </div> + `; + } + + private renderRepoMain() { + const params = this.params as AppElementRepoParams; + if ( + !( + params?.view === GerritView.REPO && + (!params?.detail || params?.detail === RepoDetailView.GENERAL) + ) + ) + return; + + return html` + <div class="main breadcrumbs"> + <gr-repo .repo=${params.repo}></gr-repo> + </div> + `; + } + + private renderGroup() { + const params = this.params as AppElementGroupParams; + if (!(params?.view === GerritView.GROUP && !params?.detail)) return; + + return html` + <div class="main breadcrumbs"> + <gr-group + .groupId=${params.groupId} + @name-changed=${(e: CustomEvent<GroupNameChangedDetail>) => { + this.updateGroupName(e); + }} + ></gr-group> + </div> + `; + } + + private renderGroupMembers() { + const params = this.params as AppElementGroupParams; + if ( + !( + params?.view === GerritView.GROUP && + params?.detail === GroupDetailView.MEMBERS + ) + ) + return; + + return html` + <div class="main breadcrumbs"> + <gr-group-members .groupId=${params.groupId}></gr-group-members> + </div> + `; + } + + private renderGroupAuditLog() { + const params = this.params as AppElementGroupParams; + if ( + !( + params?.view === GerritView.GROUP && + params?.detail === GroupDetailView.LOG + ) + ) + return; + + return html` + <div class="main table breadcrumbs"> + <gr-group-audit-log + class="table" + .groupId=${params.groupId} + ></gr-group-audit-log> + </div> + `; + } + + private renderRepoDetailList() { + const params = this.params as AppElementRepoParams; + if ( + !( + params?.view === GerritView.REPO && + (params?.detail === RepoDetailView.BRANCHES || + params?.detail === RepoDetailView.TAGS) + ) + ) + return; + + return html` + <div class="main table breadcrumbs"> + <gr-repo-detail-list + class="table" + .params=${params} + ></gr-repo-detail-list> + </div> + `; + } + + private renderRepoCommands() { + const params = this.params as AppElementRepoParams; + if ( + !( + params?.view === GerritView.REPO && + params?.detail === RepoDetailView.COMMANDS + ) + ) + return; + + return html` + <div class="main breadcrumbs"> + <gr-repo-commands .repo=${params.repo}></gr-repo-commands> + </div> + `; + } + + private renderRepoAccess() { + const params = this.params as AppElementRepoParams; + if ( + !( + params?.view === GerritView.REPO && + params?.detail === RepoDetailView.ACCESS + ) + ) + return; + + return html` + <div class="main breadcrumbs"> + <gr-repo-access + .path=${this.path} + .repo=${params.repo} + ></gr-repo-access> + </div> + `; + } + + private renderRepoDashboards() { + const params = this.params as AppElementRepoParams; + if ( + !( + params?.view === GerritView.REPO && + params?.detail === RepoDetailView.DASHBOARDS + ) + ) + return; + + return html` + <div class="main table breadcrumbs"> + <gr-repo-dashboards .repo=${params.repo}></gr-repo-dashboards> + </div> + `; + } + + override willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('params')) { + this.paramsChanged(); + } + + if (changedProperties.has('groupId')) { + this.computeGroupName(); + } + } + + async reload() { + try { + this.reloading = true; + const promises: [Promise<AccountDetailInfo | undefined>, Promise<void>] = + [ + this.restApiService.getAccount(), + getPluginLoader().awaitPluginsLoaded(), + ]; + const result = await Promise.all(promises); this.account = result[0]; let options: AdminNavLinksOption | undefined = undefined; - if (this._repoName) { - options = {repoName: this._repoName}; - } else if (this._groupId) { + if (this.repoName) { + options = {repoName: this.repoName}; + } else if (this.groupId) { + const isAdmin = await this.restApiService.getIsAdmin(); + const isOwner = await this.restApiService.getIsGroupOwner( + this.groupName + ); options = { - groupId: this._groupId, - groupName: this._groupName, - groupIsInternal: this._groupIsInternal, - isAdmin: this._isAdmin, - groupOwner: this._groupOwner, + groupId: this.groupId, + groupName: this.groupName, + groupIsInternal: this.groupIsInternal, + isAdmin, + groupOwner: isOwner, }; } - return getAdminLinks( + const res = await getAdminLinks( this.account, () => this.restApiService.getAccountCapabilities().then(capabilities => { @@ -209,170 +509,114 @@ export class GrAdminView extends PolymerElement { }), () => this.jsAPI.getAdminMenuLinks(), options - ).then(res => { - this._filteredLinks = res.links; - this._breadcrumbParentName = res.expandedSection - ? res.expandedSection.name - : ''; - - if (!res.expandedSection) { - this._subsectionLinks = []; - return; - } - this._subsectionLinks = [res.expandedSection] - .concat(res.expandedSection.children ?? []) - .map(section => { - return { - text: !section.detailType ? 'Home' : section.name, - value: section.view + (section.detailType ?? ''), - view: section.view, - url: section.url, - detailType: section.detailType, - parent: this._groupId ?? this._repoName, - }; - }); - }); - }); - } - - _computeSelectValue(params: AdminViewParams) { - if (!params || !params.view) return; - return `${params.view}${getAdminViewParamsDetail(params) ?? ''}`; - } - - _selectedIsCurrentPage(selected: AdminSubsectionLink) { + ); + this.filteredLinks = res.links; + this.breadcrumbParentName = res.expandedSection + ? res.expandedSection.name + : ''; + + if (!res.expandedSection) { + this.subsectionLinks = []; + return; + } + this.subsectionLinks = [res.expandedSection] + .concat(res.expandedSection.children ?? []) + .map(section => { + return { + text: !section.detailType ? 'Home' : section.name, + value: section.view + (section.detailType ?? ''), + view: section.view, + url: section.url, + detailType: section.detailType, + parent: this.groupId ?? this.repoName, + }; + }); + } finally { + this.reloading = false; + } + } + + private computeSelectValue() { + if (!this.params?.view) return; + return `${this.params.view}${getAdminViewParamsDetail(this.params) ?? ''}`; + } + + // private but used in test + selectedIsCurrentPage(selected: AdminSubsectionLink) { if (!this.params) return false; return ( - selected.parent === (this._repoName ?? this._groupId) && + selected.parent === (this.repoName ?? this.groupId) && selected.view === this.params.view && selected.detailType === getAdminViewParamsDetail(this.params) ); } - _handleSubsectionChange(e: CustomEvent<ValueChangeDetail>) { - if (!this._subsectionLinks) return; + // private but used in test + handleSubsectionChange(e: CustomEvent<ValueChangeDetail>) { + if (!this.subsectionLinks) return; - // The GrDropdownList items are _subsectionLinks, so find(...) always return - // an item _subsectionLinks and never returns undefined - const selected = this._subsectionLinks.find( + // The GrDropdownList items are subsectionLinks, so find(...) always return + // an item subsectionLinks and never returns undefined + const selected = this.subsectionLinks.find( section => section.value === e.detail.value )!; // This is when it gets set initially. - if (this._selectedIsCurrentPage(selected)) return; + if (this.selectedIsCurrentPage(selected)) return; if (selected.url === undefined) return; + if (this.reloading) return; GerritNav.navigateToRelativeUrl(selected.url); } - @observe('params') - _paramsChanged(params: AdminViewParams) { - this.set('_showGroup', params.view === GerritView.GROUP && !params.detail); - this.set( - '_showGroupAuditLog', - params.view === GerritView.GROUP && params.detail === GroupDetailView.LOG - ); - this.set( - '_showGroupMembers', - params.view === GerritView.GROUP && - params.detail === GroupDetailView.MEMBERS - ); - - this.set( - '_showGroupList', - params.view === GerritView.ADMIN && - params.adminView === 'gr-admin-group-list' - ); - - this.set( - '_showRepoAccess', - params.view === GerritView.REPO && params.detail === RepoDetailView.ACCESS - ); - this.set( - '_showRepoCommands', - params.view === GerritView.REPO && - params.detail === RepoDetailView.COMMANDS - ); - this.set( - '_showRepoDetailList', - params.view === GerritView.REPO && - (params.detail === RepoDetailView.BRANCHES || - params.detail === RepoDetailView.TAGS) - ); - this.set( - '_showRepoDashboards', - params.view === GerritView.REPO && - params.detail === RepoDetailView.DASHBOARDS - ); - this.set( - '_showRepoMain', - params.view === GerritView.REPO && - (!params.detail || params.detail === RepoDetailView.GENERAL) - ); - this.set( - '_showRepoList', - params.view === GerritView.ADMIN && params.adminView === 'gr-repo-list' - ); + private async paramsChanged() { + if (this.needsReload()) await this.reload(); + } - this.set( - '_showPluginList', - params.view === GerritView.ADMIN && params.adminView === 'gr-plugin-list' - ); + needsReload(): boolean { + if (!this.params) return false; let needsReload = false; const newRepoName = - params.view === GerritView.REPO ? params.repo : undefined; - if (newRepoName !== this._repoName) { - this._repoName = newRepoName; + this.params.view === GerritView.REPO ? this.params.repo : undefined; + if (newRepoName !== this.repoName) { + this.repoName = newRepoName; // Reloads the admin menu. needsReload = true; } const newGroupId = - params.view === GerritView.GROUP ? params.groupId : undefined; - if (newGroupId !== this._groupId) { - this._groupId = newGroupId; + this.params.view === GerritView.GROUP ? this.params.groupId : undefined; + if (newGroupId !== this.groupId) { + this.groupId = newGroupId; // Reloads the admin menu. needsReload = true; } if ( - this._breadcrumbParentName && - (params.view !== GerritView.GROUP || !params.groupId) && - (params.view !== GerritView.REPO || !params.repo) + this.breadcrumbParentName && + (this.params.view !== GerritView.GROUP || !this.params.groupId) && + (this.params.view !== GerritView.REPO || !this.params.repo) ) { needsReload = true; } - if (!needsReload) { - return; - } - this.reload(); - } - - // TODO (beckysiegel): Update these functions after router abstraction is - // updated. They are currently copied from gr-dropdown (and should be - // updated there as well once complete). - _computeURLHelper(host: string, path: string) { - return '//' + host + getBaseUrl() + path; - } - _computeRelativeURL(path: string) { - const host = window.location.host; - return this._computeURLHelper(host, path); + return needsReload; } - _computeLinkURL(link: NavLink | SubsectionInterface) { + // private but used in test + computeLinkURL(link?: NavLink | SubsectionInterface) { if (!link || typeof link.url === 'undefined') return ''; if ((link as NavLink).target || !(link as NavLink).noBaseUrl) { return link.url; } - return this._computeRelativeURL(link.url); + return `//${window.location.host}${getBaseUrl()}${link.url}`; } - _computeSelectedClass( + private computeSelectedClass( itemView?: GerritView, - params?: AdminViewParams, detailType?: GroupDetailView | RepoDetailView ) { + const params = this.params; if (!params) return ''; // Group params are structured differently from admin params. Compute // selected differently for groups. @@ -409,40 +653,23 @@ export class GrAdminView extends PolymerElement { : ''; } - _computeGroupName(groupId?: GroupId) { - if (!groupId) return; - - const promises: Array<Promise<void>> = []; - this.restApiService.getGroupConfig(groupId).then(group => { - if (!group || !group.name) { - return; - } + // private but used in test + async computeGroupName() { + if (!this.groupId) return; - this._groupName = group.name; - this._groupIsInternal = !!group.id.match(INTERNAL_GROUP_REGEX); - this.reload(); - - promises.push( - this.restApiService.getIsAdmin().then(isAdmin => { - this._isAdmin = !!isAdmin; - }) - ); - - promises.push( - this.restApiService.getIsGroupOwner(group.name).then(isOwner => { - this._groupOwner = isOwner; - }) - ); + const group = await this.restApiService.getGroupConfig(this.groupId); + if (!group || !group.name) { + return; + } - return Promise.all(promises).then(() => { - this.reload(); - }); - }); + this.groupName = group.name; + this.groupIsInternal = !!group.id.match(INTERNAL_GROUP_REGEX); + await this.reload(); } - _updateGroupName(e: CustomEvent<GroupNameChangedDetail>) { - this._groupName = e.detail.name; - this.reload(); + private async updateGroupName(e: CustomEvent<GroupNameChangedDetail>) { + this.groupName = e.detail.name; + await this.reload(); } } |