summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
diff options
context:
space:
mode:
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.ts675
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();
}
}