commit ad72bf9bfbba9fee8b7fafd1a32118cc6c7e71b3 Author: Dmitrii Filippov Date: Wed Oct 14 10:37:58 2020 +0200 Convert files to typescript The change converts the following files to typescript: * elements/core/gr-main-header/gr-main-header.ts Change-Id: I4836d9a65cb00c522f6fdabda26e35b3363ab224 diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts index 5a925ff..8e8eaf3 100644 --- a/polygerrit-ui/app/constants/constants.ts +++ b/polygerrit-ui/app/constants/constants.ts @@ -341,3 +341,20 @@ export enum NotifyType { OWNER_REVIEWERS = 'OWNER_REVIEWERS', ALL = 'ALL', } + +/** + * The authentication type that is configured on the server. + * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info + */ +export enum AuthType { + OPENID = 'OPENID', + OPENID_SSO = 'OPENID_SSO', + OAUTH = 'OAUTH', + HTTP = 'HTTP', + HTTP_LDAP = 'HTTP_LDAP', + CLIENT_SSL_CERT_LDAP = 'CLIENT_SSL_CERT_LDAP', + LDAP = 'LDAP', + LDAP_BIND = 'LDAP_BIND', + CUSTOM_EXTENSION = 'CUSTOM_EXTENSION', + DEVELOPMENT_BECOME_ANY_ACCOUNT = 'DEVELOPMENT_BECOME_ANY_ACCOUNT', +} diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts index 88c4cef..5ad0d92 100644 --- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts +++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts @@ -14,40 +14,65 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js'; -import '../../shared/gr-dropdown/gr-dropdown.js'; -import '../../shared/gr-icons/gr-icons.js'; -import '../../shared/gr-js-api-interface/gr-js-api-interface.js'; -import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js'; -import '../gr-account-dropdown/gr-account-dropdown.js'; -import '../gr-smart-search/gr-smart-search.js'; -import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; -import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; -import {PolymerElement} from '@polymer/polymer/polymer-element.js'; -import {htmlTemplate} from './gr-main-header_html.js'; -import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util.js'; -import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js'; -import {getAdminLinks} from '../../../utils/admin-nav-util.js'; - -const DEFAULT_LINKS = [{ - title: 'Changes', - links: [ - { - url: '/q/status:open+-is:wip', - name: 'Open', - }, - { - url: '/q/status:merged', - name: 'Merged', - }, - { - url: '/q/status:abandoned', - name: 'Abandoned', - }, - ], -}]; - -const DOCUMENTATION_LINKS = [ +import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; +import '../../shared/gr-dropdown/gr-dropdown'; +import '../../shared/gr-icons/gr-icons'; +import '../../shared/gr-js-api-interface/gr-js-api-interface'; +import '../../shared/gr-rest-api-interface/gr-rest-api-interface'; +import '../gr-account-dropdown/gr-account-dropdown'; +import '../gr-smart-search/gr-smart-search'; +import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners'; +import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin'; +import {PolymerElement} from '@polymer/polymer/polymer-element'; +import {htmlTemplate} from './gr-main-header_html'; +import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util'; +import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader'; +import {getAdminLinks, NavLink} from '../../../utils/admin-nav-util'; +import {customElement, property, observe} from '@polymer/decorators'; +import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; +import { + AccountDetailInfo, + ServerInfo, + TopMenuEntryInfo, + TopMenuItemInfo, +} from '../../../types/common'; +import {JsApiService} from '../../shared/gr-js-api-interface/gr-js-api-types'; +import {AuthType} from '../../../constants/constants'; + +interface FixedTopMenuItemInfo extends Omit { + target?: never; +} +interface MainHeaderLink { + url: string; + name: string; +} +interface MainHeaderLinkGroup { + title: string; + links: MainHeaderLink[]; + class?: string; +} + +const DEFAULT_LINKS: MainHeaderLinkGroup[] = [ + { + title: 'Changes', + links: [ + { + url: '/q/status:open+-is:wip', + name: 'Open', + }, + { + url: '/q/status:merged', + name: 'Merged', + }, + { + url: '/q/status:abandoned', + name: 'Abandoned', + }, + ], + }, +]; + +const DOCUMENTATION_LINKS: MainHeaderLink[] = [ { url: '/index.html', name: 'Table of Contents', @@ -75,90 +100,68 @@ const DOCUMENTATION_LINKS = [ ]; // Set of authentication methods that can provide custom registration page. -const AUTH_TYPES_WITH_REGISTER_URL = new Set([ - 'LDAP', - 'LDAP_BIND', - 'CUSTOM_EXTENSION', +const AUTH_TYPES_WITH_REGISTER_URL: Set = new Set([ + AuthType.LDAP, + AuthType.LDAP_BIND, + AuthType.CUSTOM_EXTENSION, ]); -/** - * @extends PolymerElement - */ -class GrMainHeader extends GestureEventListeners( - LegacyElementMixin( - PolymerElement)) { - static get template() { return htmlTemplate; } - - static get is() { return 'gr-main-header'; } - - static get properties() { - return { - searchQuery: { - type: String, - notify: true, - }, - loggedIn: { - type: Boolean, - reflectToAttribute: true, - }, - loading: { - type: Boolean, - reflectToAttribute: true, - }, +export interface GrMainHeader { + $: { + restAPI: RestApiService & Element; + jsAPI: JsApiService & Element; + }; +} - /** @type {?Object} */ - _account: Object, - _adminLinks: { - type: Array, - value() { return []; }, - }, - _defaultLinks: { - type: Array, - value() { - return DEFAULT_LINKS; - }, - }, - _docBaseUrl: { - type: String, - value: null, - }, - _links: { - type: Array, - computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' + - '_topMenus, _docBaseUrl)', - }, - loginUrl: { - type: String, - value: '/login', - }, - _userLinks: { - type: Array, - value() { return []; }, - }, - _topMenus: { - type: Array, - value() { return []; }, - }, - _registerText: { - type: String, - value: 'Sign up', - }, - _registerURL: { - type: String, - value: null, - }, - mobileSearchHidden: { - type: Boolean, - value: false, - }, - }; +@customElement('gr-main-header') +export class GrMainHeader extends GestureEventListeners( + LegacyElementMixin(PolymerElement) +) { + static get template() { + return htmlTemplate; } - static get observers() { - return [ - '_accountLoaded(_account)', - ]; - } + @property({type: String, notify: true}) + searchQuery?: string; + + @property({type: Boolean, reflectToAttribute: true}) + loggedIn?: boolean; + + @property({type: Boolean, reflectToAttribute: true}) + loading?: boolean; + + @property({type: Object}) + _account?: AccountDetailInfo; + + @property({type: Array}) + _adminLinks: NavLink[] = []; + + @property({type: String}) + _docBaseUrl: string | null = null; + + @property({ + type: Array, + computed: '_computeLinks(_userLinks, _adminLinks, _topMenus, _docBaseUrl)', + }) + _links?: MainHeaderLinkGroup[]; + + @property({type: String}) + loginUrl = '/login'; + + @property({type: Array}) + _userLinks: FixedTopMenuItemInfo[] = []; + + @property({type: Array}) + _topMenus?: TopMenuEntryInfo[] = []; + + @property({type: String}) + _registerText = 'Sign up'; + + @property({type: String}) + _registerURL?: string; + + @property({type: Boolean}) + mobileSearchHidden = false; /** @override */ ready() { @@ -182,23 +185,29 @@ class GrMainHeader extends GestureEventListeners( this._loadAccount(); } - _computeRelativeURL(path) { + _computeRelativeURL(path: string) { return '//' + window.location.host + getBaseUrl() + path; } - _computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) { + _computeLinks( + userLinks?: FixedTopMenuItemInfo[], + adminLinks?: NavLink[], + topMenus?: TopMenuEntryInfo[], + docBaseUrl?: string | null, + // defaultLinks parameter is used in tests only + defaultLinks = DEFAULT_LINKS + ) { // Polymer 2: check for undefined - if ([ - defaultLinks, - userLinks, - adminLinks, - topMenus, - docBaseUrl, - ].includes(undefined)) { + if ( + userLinks === undefined || + adminLinks === undefined || + topMenus === undefined || + docBaseUrl === undefined + ) { return undefined; } - const links = defaultLinks.map(menu => { + const links: MainHeaderLinkGroup[] = defaultLinks.map(menu => { return { title: menu.title, links: menu.links.slice(), @@ -222,15 +231,20 @@ class GrMainHeader extends GestureEventListeners( title: 'Browse', links: adminLinks.slice(), }); - const topMenuLinks = []; - links.forEach(link => { topMenuLinks[link.title] = link.links; }); + const topMenuLinks: {[name: string]: MainHeaderLink[]} = {}; + links.forEach(link => { + topMenuLinks[link.title] = link.links; + }); for (const m of topMenus) { - const items = m.items.map(this._fixCustomMenuItem).filter(link => - // Ignore GWT project links - !link.url.includes('${projectName}') + const items = m.items.map(this._fixCustomMenuItem).filter( + link => + // Ignore GWT project links + !link.url.includes('${projectName}') ); if (m.name in topMenuLinks) { - items.forEach(link => { topMenuLinks[m.name].push(link); }); + items.forEach(link => { + topMenuLinks[m.name].push(link); + }); } else { links.push({ title: m.name, @@ -241,8 +255,8 @@ class GrMainHeader extends GestureEventListeners( return links; } - _getDocLinks(docBaseUrl, docLinks) { - if (!docBaseUrl || !docLinks) { + _getDocLinks(docBaseUrl: string | null, docLinks: MainHeaderLink[]) { + if (!docBaseUrl) { return []; } return docLinks.map(link => { @@ -260,47 +274,62 @@ class GrMainHeader extends GestureEventListeners( _loadAccount() { this.loading = true; - const promises = [ + + return Promise.all([ this.$.restAPI.getAccount(), this.$.restAPI.getTopMenus(), getPluginLoader().awaitPluginsLoaded(), - ]; - - return Promise.all(promises).then(result => { + ]).then(result => { const account = result[0]; this._account = account; this.loggedIn = !!account; this.loading = false; this._topMenus = result[1]; - return getAdminLinks(account, - params => this.$.restAPI.getAccountCapabilities(params), - () => this.$.jsAPI.getAdminMenuLinks()) - .then(res => { - this._adminLinks = res.links; - }); + return getAdminLinks( + account, + () => + this.$.restAPI.getAccountCapabilities().then(capabilities => { + if (!capabilities) { + throw new Error('getAccountCapabilities returns undefined'); + } + return capabilities; + }), + () => this.$.jsAPI.getAdminMenuLinks() + ).then(res => { + this._adminLinks = res.links; + }); }); } _loadConfig() { - this.$.restAPI.getConfig() - .then(config => { - this._retrieveRegisterURL(config); - return getDocsBaseUrl(config, this.$.restAPI); - }) - .then(docBaseUrl => { this._docBaseUrl = docBaseUrl; }); + this.$.restAPI + .getConfig() + .then(config => { + if (!config) { + throw new Error('getConfig returned undefined'); + } + this._retrieveRegisterURL(config); + return getDocsBaseUrl(config, this.$.restAPI); + }) + .then(docBaseUrl => { + this._docBaseUrl = docBaseUrl; + }); } - _accountLoaded(account) { - if (!account) { return; } + @observe('_account') + _accountLoaded(account?: AccountDetailInfo) { + if (!account) { + return; + } this.$.restAPI.getPreferences().then(prefs => { - this._userLinks = prefs && prefs.my ? - prefs.my.map(this._fixCustomMenuItem) : []; + this._userLinks = + prefs && prefs.my ? prefs.my.map(this._fixCustomMenuItem) : []; }); } - _retrieveRegisterURL(config) { + _retrieveRegisterURL(config: ServerInfo) { if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) { this._registerURL = config.auth.register_url; if (config.auth.register_text) { @@ -309,11 +338,12 @@ class GrMainHeader extends GestureEventListeners( } } - _computeIsInvisible(registerURL) { + _computeIsInvisible(registerURL?: string) { return registerURL ? '' : 'invisible'; } - _fixCustomMenuItem(linkObj) { + _fixCustomMenuItem(linkObj: TopMenuItemInfo): FixedTopMenuItemInfo { + // TODO(TS): make a copy of linkObj instead of modifying the existing one // Normalize all urls to PolyGerrit style. if (linkObj.url.startsWith('#')) { linkObj.url = linkObj.url.slice(1); @@ -328,30 +358,29 @@ class GrMainHeader extends GestureEventListeners( // so we'll just disable it altogether for now. delete linkObj.target; - return linkObj; + return (linkObj as unknown) as FixedTopMenuItemInfo; } _generateSettingsLink() { return getBaseUrl() + '/settings/'; } - _onMobileSearchTap(e) { + _onMobileSearchTap(e: Event) { e.preventDefault(); e.stopPropagation(); - this.dispatchEvent(new CustomEvent('mobile-search', { - composed: true, bubbles: false, - })); + this.dispatchEvent( + new CustomEvent('mobile-search', { + composed: true, + bubbles: false, + }) + ); } - _computeLinkGroupClass(linkGroup) { - if (linkGroup && linkGroup.class) { - return linkGroup.class; - } - - return ''; + _computeLinkGroupClass(linkGroup: MainHeaderLinkGroup) { + return linkGroup.class ?? ''; } - _computeShowHideAriaLabel(mobileSearchHidden) { + _computeShowHideAriaLabel(mobileSearchHidden: boolean) { if (mobileSearchHidden) { return 'Show Searchbar'; } else { @@ -360,4 +389,8 @@ class GrMainHeader extends GestureEventListeners( } } -customElements.define(GrMainHeader.is, GrMainHeader); +declare global { + interface HTMLElementTagNameMap { + 'gr-main-header': GrMainHeader; + } +} diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.js index 48194a6..e4db65f 100644 --- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.js +++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.js @@ -103,22 +103,22 @@ suite('gr-main-header tests', () => { // When no admin links are passed, it should use the default. assert.deepEqual(element._computeLinks( - defaultLinks, /* userLinks= */[], adminLinks, /* topMenus= */[], - /* docBaseUrl= */ '' + /* docBaseUrl= */ '', + defaultLinks ), defaultLinks.concat({ title: 'Browse', links: adminLinks, })); assert.deepEqual(element._computeLinks( - defaultLinks, userLinks, adminLinks, /* topMenus= */[], - /* docBaseUrl= */ '' + /* docBaseUrl= */ '', + defaultLinks ), defaultLinks.concat([ { @@ -142,7 +142,6 @@ suite('gr-main-header tests', () => { assert.deepEqual(element._getDocLinks(null, docLinks), []); assert.deepEqual(element._getDocLinks('', docLinks), []); - assert.deepEqual(element._getDocLinks('base', null), []); assert.deepEqual(element._getDocLinks('base', []), []); assert.deepEqual(element._getDocLinks('base', docLinks), [{ @@ -172,11 +171,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - /* defaultLinks= */ [], /* userLinks= */ [], adminLinks, topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + /* defaultLinks= */ [] ), [{ title: 'Browse', links: adminLinks, @@ -208,11 +207,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - /* defaultLinks= */ [], /* userLinks= */ [], adminLinks, topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + /* defaultLinks= */ [] ), [{ title: 'Browse', links: adminLinks, @@ -247,11 +246,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - /* defaultLinks= */ [], /* userLinks= */ [], adminLinks, topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + /* defaultLinks= */ [] ), [{ title: 'Browse', links: adminLinks, @@ -284,11 +283,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - defaultLinks, /* userLinks= */ [], /* adminLinks= */ [], topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + defaultLinks ), [{ title: 'Faves', links: defaultLinks[0].links.concat([{ @@ -315,11 +314,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - /* defaultLinks= */ [], userLinks, /* adminLinks= */ [], topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + /* defaultLinks= */ [] ), [{ title: 'Your', links: userLinks.concat([{ @@ -346,11 +345,11 @@ suite('gr-main-header tests', () => { }], }]; assert.deepEqual(element._computeLinks( - /* defaultLinks= */ [], /* userLinks= */ [], adminLinks, topMenus, - /* baseDocUrl= */ '' + /* baseDocUrl= */ '', + /* defaultLinks= */ [] ), [{ title: 'Browse', links: adminLinks.concat([{ diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts index 26bf50a..8c26d4a 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface-element.ts @@ -26,7 +26,7 @@ import { RevisionInfo, } from '../../../types/common'; import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api'; -import {GrAdminApi} from '../../plugins/gr-admin-api/gr-admin-api'; +import {GrAdminApi, MenuLink} from '../../plugins/gr-admin-api/gr-admin-api'; import { JsApiService, EventCallback, @@ -294,8 +294,8 @@ export class GrJsApiInterface ); } - getAdminMenuLinks() { - const links = []; + getAdminMenuLinks(): MenuLink[] { + const links: MenuLink[] = []; for (const cb of this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) { const adminApi = (cb as unknown) as GrAdminApi; links.push(...adminApi.getMenuLinks()); diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts index 505e62e..261298b 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-types.ts @@ -18,6 +18,7 @@ import {ActionInfo, ChangeInfo, PatchSetNum} from '../../../types/common'; import {EventType, TargetElement} from '../../plugins/gr-plugin-types'; import {DiffLayer} from '../../../types/types'; import {GrAnnotationActionsInterface} from './gr-annotation-actions-js-api'; +import {MenuLink} from '../../plugins/gr-admin-api/gr-admin-api'; export interface ShowChangeDetail { change: ChangeInfo; @@ -50,5 +51,6 @@ export interface JsApiService { getDiffLayers(path: string, changeNum: number): DiffLayer[]; disposeDiffLayers(path: string): void; getCoverageAnnotationApi(): Promise; + getAdminMenuLinks(): MenuLink[]; // TODO(TS): Add more methods when needed for the TS conversion. } diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts index 70a1ace..15cdac4 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts @@ -138,6 +138,7 @@ import { RevisionId, GroupName, Hashtag, + TopMenuEntryInfo, } from '../../../types/common'; import { CancelConditionCallback, @@ -3221,12 +3222,12 @@ export class GrRestApiInterface }) as Promise; } - getTopMenus(errFn?: ErrorCallback) { + getTopMenus(errFn?: ErrorCallback): Promise { return this._fetchSharedCacheURL({ url: '/config/server/top-menus', errFn, reportUrlAsIs: true, - }); + }) as Promise; } setAssignee( diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts index bf8b25a..6b93082 100644 --- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts +++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts @@ -97,6 +97,7 @@ import { HashtagsInput, Hashtag, FileNameToFileInfoMap, + TopMenuEntryInfo, } from '../../../types/common'; import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser'; import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants'; @@ -841,4 +842,6 @@ export interface RestApiService { reviewed: boolean, errFn: ErrorCallback ): Promise; + + getTopMenus(errFn?: ErrorCallback): Promise; } diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts index 15d630f..dca8115 100644 --- a/polygerrit-ui/app/types/common.ts +++ b/polygerrit-ui/app/types/common.ts @@ -43,6 +43,7 @@ import { DraftsAction, NotifyType, EmailFormat, + AuthType, } from '../constants/constants'; import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces'; @@ -740,10 +741,10 @@ export interface AccountsConfigInfo { /** * The AuthInfo entity contains information about the authentication * configuration of the Gerrit server. - * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html + * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#auth-info */ export interface AuthInfo { - type: string; + auth_type: AuthType; // docs incorrectly names it 'type' use_contributor_agreements: boolean; contributor_agreements?: ContributorAgreementInfo; editable_account_fields: string; @@ -1121,17 +1122,17 @@ export interface ThreadSummaryInfo { /** * The TopMenuEntryInfo entity contains information about a top menu entry. - * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html + * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#top-menu-entry-info */ export interface TopMenuEntryInfo { name: string; - items: string; + items: TopMenuItemInfo[]; } /** * The TopMenuItemInfo entity contains information about a menu item ina top * menu entry. - * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html + * https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#top-menu-item-info */ export interface TopMenuItemInfo { url: string;