Move admin-nav-util into models/views/admin.ts

We don't want utilities to depends on `models/`, so model specific
utilities must be moved into `models` directly.

Release-Notes: skip
Change-Id: I73aa8f18c1488539b55bdd282cc3ecc911dd94c1
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 1be5fa3..eef2a44 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
@@ -20,12 +20,6 @@
 import {getBaseUrl} from '../../../utils/url-util';
 import {navigationToken} from '../../core/gr-navigation/gr-navigation';
 import {
-  AdminNavLinksOption,
-  getAdminLinks,
-  NavLink,
-  SubsectionInterface,
-} from '../../../utils/admin-nav-util';
-import {
   AccountDetailInfo,
   GroupId,
   GroupName,
@@ -48,6 +42,10 @@
   AdminChildView,
   adminViewModelToken,
   AdminViewState,
+  AdminNavLinksOption,
+  getAdminLinks,
+  NavLink,
+  SubsectionInterface,
 } from '../../../models/views/admin';
 import {
   GroupDetailView,
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 46fa062..53826d5 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
@@ -11,7 +11,7 @@
 import '../gr-account-dropdown/gr-account-dropdown';
 import '../gr-smart-search/gr-smart-search';
 import {getBaseUrl, getDocsBaseUrl} from '../../../utils/url-util';
-import {getAdminLinks, NavLink} from '../../../utils/admin-nav-util';
+import {getAdminLinks, NavLink} from '../../../models/views/admin';
 import {
   AccountDetailInfo,
   DropdownLink,
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
index 7eb19f0..eced28c 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
@@ -17,7 +17,7 @@
   createGerritInfo,
   createServerInfo,
 } from '../../../test/test-data-generators';
-import {NavLink} from '../../../utils/admin-nav-util';
+import {NavLink} from '../../../models/views/admin';
 import {ServerInfo, TopMenuItemInfo} from '../../../types/common';
 import {AuthType} from '../../../constants/constants';
 import {fixture, html, assert} from '@open-wc/testing';
diff --git a/polygerrit-ui/app/models/views/admin.ts b/polygerrit-ui/app/models/views/admin.ts
index de9ec5d..3456e8f 100644
--- a/polygerrit-ui/app/models/views/admin.ts
+++ b/polygerrit-ui/app/models/views/admin.ts
@@ -8,6 +8,46 @@
 import {define} from '../dependency';
 import {Model} from '../model';
 import {Route, ViewState} from './base';
+import {
+  RepoName,
+  GroupId,
+  AccountDetailInfo,
+  AccountCapabilityInfo,
+} from '../../types/common';
+import {hasOwnProperty} from '../../utils/common-util';
+import {MenuLink} from '../../api/admin';
+import {createGroupUrl, GroupDetailView} from './group';
+import {createRepoUrl, RepoDetailView} from './repo';
+
+export interface SubsectionInterface {
+  name: string;
+  view: GerritView;
+  detailType?: RepoDetailView | GroupDetailView;
+  url?: string;
+  children?: SubsectionInterface[];
+}
+
+export interface AdminNavLinksOption {
+  repoName?: RepoName;
+  groupId?: GroupId;
+  groupName?: string;
+  groupIsInternal?: boolean;
+  isAdmin?: boolean;
+  groupOwner?: boolean;
+}
+
+export interface NavLink {
+  name: string;
+  noBaseUrl: boolean;
+  url: string;
+  view?: GerritView | AdminChildView;
+  viewableToAll?: boolean;
+  section?: string;
+  capability?: string;
+  target?: string | null;
+  subsection?: SubsectionInterface;
+  children?: SubsectionInterface[];
+}
 
 export const PLUGIN_LIST_ROUTE: Route<AdminViewState> = {
   urlPattern: /^\/admin\/plugins(\/)?$/,
@@ -25,6 +65,208 @@
   GROUPS = 'gr-admin-group-list',
   PLUGINS = 'gr-plugin-list',
 }
+const ADMIN_LINKS: NavLink[] = [
+  {
+    name: 'Repositories',
+    noBaseUrl: true,
+    url: createAdminUrl({adminView: AdminChildView.REPOS}),
+    view: 'gr-repo-list' as GerritView,
+    viewableToAll: true,
+  },
+  {
+    name: 'Groups',
+    section: 'Groups',
+    noBaseUrl: true,
+    url: createAdminUrl({adminView: AdminChildView.GROUPS}),
+    view: 'gr-admin-group-list' as GerritView,
+  },
+  {
+    name: 'Plugins',
+    capability: 'viewPlugins',
+    section: 'Plugins',
+    noBaseUrl: true,
+    url: createAdminUrl({adminView: AdminChildView.PLUGINS}),
+    view: 'gr-plugin-list' as GerritView,
+  },
+];
+
+export interface AdminLink {
+  url: string;
+  text: string;
+  capability: string | null;
+  noBaseUrl: boolean;
+  view: null;
+  viewableToAll: boolean;
+  target: '_blank' | null;
+}
+
+export interface AdminLinks {
+  links: NavLink[];
+  expandedSection?: SubsectionInterface;
+}
+
+export function getAdminLinks(
+  account: AccountDetailInfo | undefined,
+  getAccountCapabilities: () => Promise<AccountCapabilityInfo>,
+  getAdminMenuLinks: () => MenuLink[],
+  options?: AdminNavLinksOption
+): Promise<AdminLinks> {
+  if (!account) {
+    return Promise.resolve(
+      filterLinks(link => !!link.viewableToAll, getAdminMenuLinks, options)
+    );
+  }
+  return getAccountCapabilities().then(capabilities =>
+    filterLinks(
+      link => !link.capability || hasOwnProperty(capabilities, link.capability),
+      getAdminMenuLinks,
+      options
+    )
+  );
+}
+
+function filterLinks(
+  filterFn: (link: NavLink) => boolean,
+  getAdminMenuLinks: () => MenuLink[],
+  options?: AdminNavLinksOption
+): AdminLinks {
+  let links: NavLink[] = ADMIN_LINKS.slice(0);
+  let expandedSection: SubsectionInterface | undefined = undefined;
+
+  const isExternalLink = (link: MenuLink) => link.url[0] !== '/';
+
+  // Append top-level links that are defined by plugins.
+  links.push(
+    ...getAdminMenuLinks().map((link: MenuLink) => {
+      return {
+        url: link.url,
+        name: link.text,
+        capability: link.capability || undefined,
+        noBaseUrl: !isExternalLink(link),
+        view: undefined,
+        viewableToAll: !link.capability,
+        target: isExternalLink(link) ? '_blank' : null,
+      };
+    })
+  );
+
+  links = links.filter(filterFn);
+
+  const filteredLinks: NavLink[] = [];
+  const repoName = options && options.repoName;
+  const groupId = options && options.groupId;
+  const groupName = options && options.groupName;
+  const groupIsInternal = options && options.groupIsInternal;
+  const isAdmin = options && options.isAdmin;
+  const groupOwner = options && options.groupOwner;
+
+  // Don't bother to get sub-navigation items if only the top level links
+  // are needed. This is used by the main header dropdown.
+  if (!repoName && !groupId) {
+    return {links, expandedSection};
+  }
+
+  // Otherwise determine the full set of links and return both the full
+  // set in addition to the subsection that should be displayed if it
+  // exists.
+  for (const link of links) {
+    const linkCopy = {...link};
+    if (linkCopy.name === 'Repositories' && repoName) {
+      linkCopy.subsection = getRepoSubsections(repoName);
+      expandedSection = linkCopy.subsection;
+    } else if (linkCopy.name === 'Groups' && groupId && groupName) {
+      linkCopy.subsection = getGroupSubsections(
+        groupId,
+        groupName,
+        groupIsInternal,
+        isAdmin,
+        groupOwner
+      );
+      expandedSection = linkCopy.subsection;
+    }
+    filteredLinks.push(linkCopy);
+  }
+  return {links: filteredLinks, expandedSection};
+}
+
+export function getGroupSubsections(
+  groupId: GroupId,
+  groupName: string,
+  groupIsInternal?: boolean,
+  isAdmin?: boolean,
+  groupOwner?: boolean
+) {
+  const children: SubsectionInterface[] = [];
+  const subsection: SubsectionInterface = {
+    name: groupName,
+    view: GerritView.GROUP,
+    url: createGroupUrl({groupId}),
+    children,
+  };
+  if (groupIsInternal) {
+    children.push({
+      name: 'Members',
+      detailType: GroupDetailView.MEMBERS,
+      view: GerritView.GROUP,
+      url: createGroupUrl({groupId, detail: GroupDetailView.MEMBERS}),
+    });
+  }
+  if (groupIsInternal && (isAdmin || groupOwner)) {
+    children.push({
+      name: 'Audit Log',
+      detailType: GroupDetailView.LOG,
+      view: GerritView.GROUP,
+      url: createGroupUrl({groupId, detail: GroupDetailView.LOG}),
+    });
+  }
+  return subsection;
+}
+
+export function getRepoSubsections(repo: RepoName) {
+  return {
+    name: repo,
+    view: GerritView.REPO,
+    children: [
+      {
+        name: 'General',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.GENERAL,
+        url: createRepoUrl({repo, detail: RepoDetailView.GENERAL}),
+      },
+      {
+        name: 'Access',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.ACCESS,
+        url: createRepoUrl({repo, detail: RepoDetailView.ACCESS}),
+      },
+      {
+        name: 'Commands',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.COMMANDS,
+        url: createRepoUrl({repo, detail: RepoDetailView.COMMANDS}),
+      },
+      {
+        name: 'Branches',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.BRANCHES,
+        url: createRepoUrl({repo, detail: RepoDetailView.BRANCHES}),
+      },
+      {
+        name: 'Tags',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.TAGS,
+        url: createRepoUrl({repo, detail: RepoDetailView.TAGS}),
+      },
+      {
+        name: 'Dashboards',
+        view: GerritView.REPO,
+        detailType: RepoDetailView.DASHBOARDS,
+        url: createRepoUrl({repo, detail: RepoDetailView.DASHBOARDS}),
+      },
+    ],
+  };
+}
+
 export interface AdminViewState extends ViewState {
   view: GerritView.ADMIN;
   adminView: AdminChildView;
diff --git a/polygerrit-ui/app/models/views/admin_test.ts b/polygerrit-ui/app/models/views/admin_test.ts
index 0881018..5d142bf 100644
--- a/polygerrit-ui/app/models/views/admin_test.ts
+++ b/polygerrit-ui/app/models/views/admin_test.ts
@@ -3,15 +3,349 @@
  * Copyright 2022 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {GerritView} from '../../services/router/router-model';
+import {assert} from '@open-wc/testing';
 import '../../test/common-test-setup';
 import {assertRouteFalse, assertRouteState} from '../../test/test-utils';
+import {GerritView} from '../../services/router/router-model';
 import {
   AdminChildView,
   AdminViewState,
   createAdminUrl,
   PLUGIN_LIST_ROUTE,
+  AdminNavLinksOption,
+  getAdminLinks,
 } from './admin';
+import {
+  AccountDetailInfo,
+  GroupId,
+  RepoName,
+  Timestamp,
+} from '../../api/rest-api';
+
+suite('admin links', () => {
+  let capabilityStub: sinon.SinonStub;
+  let menuLinkStub: sinon.SinonStub;
+
+  setup(() => {
+    capabilityStub = sinon.stub();
+    menuLinkStub = sinon.stub().returns([]);
+  });
+
+  const testAdminLinks = async (
+    account: AccountDetailInfo | undefined,
+    options: AdminNavLinksOption | undefined,
+    expected: any
+  ) => {
+    const res = await getAdminLinks(
+      account,
+      capabilityStub,
+      menuLinkStub,
+      options
+    );
+
+    assert.equal(expected.totalLength, res.links.length);
+    assert.equal(res.links[0].name, 'Repositories');
+    // Repos
+    if (expected.groupListShown) {
+      assert.equal(res.links[1].name, 'Groups');
+    }
+
+    if (expected.pluginListShown) {
+      assert.equal(res.links[2].name, 'Plugins');
+      assert.isNotOk(res.links[2].subsection);
+    }
+
+    if (expected.projectPageShown) {
+      assert.isOk(res.links[0].subsection);
+      assert.equal(res.links[0].subsection!.children!.length, 6);
+    } else {
+      assert.isNotOk(res.links[0].subsection);
+    }
+    // Groups
+    if (expected.groupPageShown) {
+      assert.isOk(res.links[1].subsection);
+      assert.equal(
+        res.links[1].subsection!.children!.length,
+        expected.groupSubpageLength
+      );
+    } else if (expected.totalLength > 1) {
+      assert.isNotOk(res.links[1].subsection);
+    }
+
+    if (expected.pluginGeneratedLinks) {
+      for (const link of expected.pluginGeneratedLinks) {
+        const linkMatch = res.links.find(
+          l => l.url === link.url && l.name === link.text
+        );
+        assert.isOk(linkMatch);
+
+        // External links should open in new tab.
+        if (link.url[0] !== '/') {
+          assert.equal(linkMatch!.target, '_blank');
+        } else {
+          assert.isNotOk(linkMatch!.target);
+        }
+      }
+    }
+
+    // Current section
+    if (expected.projectPageShown || expected.groupPageShown) {
+      assert.isOk(res.expandedSection);
+      assert.isOk(res.expandedSection!.children);
+    } else {
+      assert.isNotOk(res.expandedSection);
+    }
+    if (expected.projectPageShown) {
+      assert.equal(res.expandedSection!.name, 'my-repo');
+      assert.equal(res.expandedSection!.children!.length, 6);
+    } else if (expected.groupPageShown) {
+      assert.equal(res.expandedSection!.name, 'my-group');
+      assert.equal(
+        res.expandedSection!.children!.length,
+        expected.groupSubpageLength
+      );
+    }
+  };
+
+  suite('logged out', () => {
+    let account: AccountDetailInfo;
+    let expected: any;
+
+    setup(() => {
+      expected = {
+        groupListShown: false,
+        groupPageShown: false,
+        pluginListShown: false,
+      };
+    });
+
+    test('without a specific repo or group', async () => {
+      let options;
+      expected = Object.assign(expected, {
+        totalLength: 1,
+        projectPageShown: false,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('with a repo', async () => {
+      const options = {repoName: 'my-repo' as RepoName};
+      expected = Object.assign(expected, {
+        totalLength: 1,
+        projectPageShown: true,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('with plugin generated links', async () => {
+      let options;
+      const generatedLinks = [
+        {text: 'internal link text', url: '/internal/link/url'},
+        {text: 'external link text', url: 'http://external/link/url'},
+      ];
+      menuLinkStub.returns(generatedLinks);
+      expected = Object.assign(expected, {
+        totalLength: 3,
+        projectPageShown: false,
+        pluginGeneratedLinks: generatedLinks,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+  });
+
+  suite('no plugin capability logged in', () => {
+    const account = {
+      name: 'test-user',
+      registered_on: '' as Timestamp,
+    };
+    let expected: any;
+
+    setup(() => {
+      expected = {
+        totalLength: 2,
+        pluginListShown: false,
+      };
+      capabilityStub.returns(Promise.resolve({}));
+    });
+
+    test('without a specific project or group', async () => {
+      let options;
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupListShown: true,
+        groupPageShown: false,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('with a repo', async () => {
+      const account = {
+        name: 'test-user',
+        registered_on: '' as Timestamp,
+      };
+      const options = {repoName: 'my-repo' as RepoName};
+      expected = Object.assign(expected, {
+        projectPageShown: true,
+        groupListShown: true,
+        groupPageShown: false,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+  });
+
+  suite('view plugin capability logged in', () => {
+    const account = {
+      name: 'test-user',
+      registered_on: '' as Timestamp,
+    };
+    let expected: any;
+
+    setup(() => {
+      capabilityStub.returns(Promise.resolve({viewPlugins: true}));
+      expected = {
+        totalLength: 3,
+        groupListShown: true,
+        pluginListShown: true,
+      };
+    });
+
+    test('without a specific repo or group', async () => {
+      let options;
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupPageShown: false,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('with a repo', async () => {
+      const options = {repoName: 'my-repo' as RepoName};
+      expected = Object.assign(expected, {
+        projectPageShown: true,
+        groupPageShown: false,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('admin with internal group', async () => {
+      const options = {
+        groupId: 'a15262' as GroupId,
+        groupName: 'my-group',
+        groupIsInternal: true,
+        isAdmin: true,
+        groupOwner: false,
+      };
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupPageShown: true,
+        groupSubpageLength: 2,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('group owner with internal group', async () => {
+      const options = {
+        groupId: 'a15262' as GroupId,
+        groupName: 'my-group',
+        groupIsInternal: true,
+        isAdmin: false,
+        groupOwner: true,
+      };
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupPageShown: true,
+        groupSubpageLength: 2,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('non owner or admin with internal group', async () => {
+      const options = {
+        groupId: 'a15262' as GroupId,
+        groupName: 'my-group',
+        groupIsInternal: true,
+        isAdmin: false,
+        groupOwner: false,
+      };
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupPageShown: true,
+        groupSubpageLength: 1,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+
+    test('admin with external group', async () => {
+      const options = {
+        groupId: 'a15262' as GroupId,
+        groupName: 'my-group',
+        groupIsInternal: false,
+        isAdmin: true,
+        groupOwner: true,
+      };
+      expected = Object.assign(expected, {
+        projectPageShown: false,
+        groupPageShown: true,
+        groupSubpageLength: 0,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+  });
+
+  suite('view plugin screen with plugin capability', () => {
+    const account = {
+      name: 'test-user',
+      registered_on: '' as Timestamp,
+    };
+    let expected: any;
+
+    setup(() => {
+      capabilityStub.returns(Promise.resolve({pluginCapability: true}));
+      expected = {};
+    });
+
+    test('with plugin with capabilities', async () => {
+      let options;
+      const generatedLinks = [
+        {text: 'without capability', url: '/without'},
+        {text: 'with capability', url: '/with', capability: 'pluginCapability'},
+      ];
+      menuLinkStub.returns(generatedLinks);
+      expected = Object.assign(expected, {
+        totalLength: 4,
+        pluginGeneratedLinks: generatedLinks,
+      });
+      await testAdminLinks(account, options, expected);
+    });
+  });
+
+  suite('view plugin screen without plugin capability', () => {
+    const account = {
+      name: 'test-user',
+      registered_on: '' as Timestamp,
+    };
+    let expected: any;
+
+    setup(() => {
+      capabilityStub.returns(Promise.resolve({}));
+      expected = {};
+    });
+
+    test('with plugin with capabilities', async () => {
+      let options;
+      const generatedLinks = [
+        {text: 'without capability', url: '/without'},
+        {text: 'with capability', url: '/with', capability: 'pluginCapability'},
+      ];
+      menuLinkStub.returns(generatedLinks);
+      expected = Object.assign(expected, {
+        totalLength: 3,
+        pluginGeneratedLinks: [generatedLinks[0]],
+      });
+      await testAdminLinks(account, options, expected);
+    });
+  });
+});
 
 suite('admin view model', () => {
   suite('routes', () => {
diff --git a/polygerrit-ui/app/utils/admin-nav-util.ts b/polygerrit-ui/app/utils/admin-nav-util.ts
deleted file mode 100644
index 7916799..0000000
--- a/polygerrit-ui/app/utils/admin-nav-util.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * @license
- * Copyright 2018 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
-  RepoName,
-  GroupId,
-  AccountDetailInfo,
-  AccountCapabilityInfo,
-} from '../types/common';
-import {hasOwnProperty} from './common-util';
-import {GerritView} from '../services/router/router-model';
-import {MenuLink} from '../api/admin';
-import {AdminChildView, createAdminUrl} from '../models/views/admin';
-import {createGroupUrl, GroupDetailView} from '../models/views/group';
-import {createRepoUrl, RepoDetailView} from '../models/views/repo';
-
-const ADMIN_LINKS: NavLink[] = [
-  {
-    name: 'Repositories',
-    noBaseUrl: true,
-    url: createAdminUrl({adminView: AdminChildView.REPOS}),
-    view: 'gr-repo-list' as GerritView,
-    viewableToAll: true,
-  },
-  {
-    name: 'Groups',
-    section: 'Groups',
-    noBaseUrl: true,
-    url: createAdminUrl({adminView: AdminChildView.GROUPS}),
-    view: 'gr-admin-group-list' as GerritView,
-  },
-  {
-    name: 'Plugins',
-    capability: 'viewPlugins',
-    section: 'Plugins',
-    noBaseUrl: true,
-    url: createAdminUrl({adminView: AdminChildView.PLUGINS}),
-    view: 'gr-plugin-list' as GerritView,
-  },
-];
-
-export interface AdminLink {
-  url: string;
-  text: string;
-  capability: string | null;
-  noBaseUrl: boolean;
-  view: null;
-  viewableToAll: boolean;
-  target: '_blank' | null;
-}
-
-export interface AdminLinks {
-  links: NavLink[];
-  expandedSection?: SubsectionInterface;
-}
-
-export function getAdminLinks(
-  account: AccountDetailInfo | undefined,
-  getAccountCapabilities: () => Promise<AccountCapabilityInfo>,
-  getAdminMenuLinks: () => MenuLink[],
-  options?: AdminNavLinksOption
-): Promise<AdminLinks> {
-  if (!account) {
-    return Promise.resolve(
-      filterLinks(link => !!link.viewableToAll, getAdminMenuLinks, options)
-    );
-  }
-  return getAccountCapabilities().then(capabilities =>
-    filterLinks(
-      link => !link.capability || hasOwnProperty(capabilities, link.capability),
-      getAdminMenuLinks,
-      options
-    )
-  );
-}
-
-function filterLinks(
-  filterFn: (link: NavLink) => boolean,
-  getAdminMenuLinks: () => MenuLink[],
-  options?: AdminNavLinksOption
-): AdminLinks {
-  let links: NavLink[] = ADMIN_LINKS.slice(0);
-  let expandedSection: SubsectionInterface | undefined = undefined;
-
-  const isExternalLink = (link: MenuLink) => link.url[0] !== '/';
-
-  // Append top-level links that are defined by plugins.
-  links.push(
-    ...getAdminMenuLinks().map((link: MenuLink) => {
-      return {
-        url: link.url,
-        name: link.text,
-        capability: link.capability || undefined,
-        noBaseUrl: !isExternalLink(link),
-        view: undefined,
-        viewableToAll: !link.capability,
-        target: isExternalLink(link) ? '_blank' : null,
-      };
-    })
-  );
-
-  links = links.filter(filterFn);
-
-  const filteredLinks: NavLink[] = [];
-  const repoName = options && options.repoName;
-  const groupId = options && options.groupId;
-  const groupName = options && options.groupName;
-  const groupIsInternal = options && options.groupIsInternal;
-  const isAdmin = options && options.isAdmin;
-  const groupOwner = options && options.groupOwner;
-
-  // Don't bother to get sub-navigation items if only the top level links
-  // are needed. This is used by the main header dropdown.
-  if (!repoName && !groupId) {
-    return {links, expandedSection};
-  }
-
-  // Otherwise determine the full set of links and return both the full
-  // set in addition to the subsection that should be displayed if it
-  // exists.
-  for (const link of links) {
-    const linkCopy = {...link};
-    if (linkCopy.name === 'Repositories' && repoName) {
-      linkCopy.subsection = getRepoSubsections(repoName);
-      expandedSection = linkCopy.subsection;
-    } else if (linkCopy.name === 'Groups' && groupId && groupName) {
-      linkCopy.subsection = getGroupSubsections(
-        groupId,
-        groupName,
-        groupIsInternal,
-        isAdmin,
-        groupOwner
-      );
-      expandedSection = linkCopy.subsection;
-    }
-    filteredLinks.push(linkCopy);
-  }
-  return {links: filteredLinks, expandedSection};
-}
-
-export function getGroupSubsections(
-  groupId: GroupId,
-  groupName: string,
-  groupIsInternal?: boolean,
-  isAdmin?: boolean,
-  groupOwner?: boolean
-) {
-  const children: SubsectionInterface[] = [];
-  const subsection: SubsectionInterface = {
-    name: groupName,
-    view: GerritView.GROUP,
-    url: createGroupUrl({groupId}),
-    children,
-  };
-  if (groupIsInternal) {
-    children.push({
-      name: 'Members',
-      detailType: GroupDetailView.MEMBERS,
-      view: GerritView.GROUP,
-      url: createGroupUrl({groupId, detail: GroupDetailView.MEMBERS}),
-    });
-  }
-  if (groupIsInternal && (isAdmin || groupOwner)) {
-    children.push({
-      name: 'Audit Log',
-      detailType: GroupDetailView.LOG,
-      view: GerritView.GROUP,
-      url: createGroupUrl({groupId, detail: GroupDetailView.LOG}),
-    });
-  }
-  return subsection;
-}
-
-export function getRepoSubsections(repo: RepoName) {
-  return {
-    name: repo,
-    view: GerritView.REPO,
-    children: [
-      {
-        name: 'General',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.GENERAL,
-        url: createRepoUrl({repo, detail: RepoDetailView.GENERAL}),
-      },
-      {
-        name: 'Access',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.ACCESS,
-        url: createRepoUrl({repo, detail: RepoDetailView.ACCESS}),
-      },
-      {
-        name: 'Commands',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.COMMANDS,
-        url: createRepoUrl({repo, detail: RepoDetailView.COMMANDS}),
-      },
-      {
-        name: 'Branches',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.BRANCHES,
-        url: createRepoUrl({repo, detail: RepoDetailView.BRANCHES}),
-      },
-      {
-        name: 'Tags',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.TAGS,
-        url: createRepoUrl({repo, detail: RepoDetailView.TAGS}),
-      },
-      {
-        name: 'Dashboards',
-        view: GerritView.REPO,
-        detailType: RepoDetailView.DASHBOARDS,
-        url: createRepoUrl({repo, detail: RepoDetailView.DASHBOARDS}),
-      },
-    ],
-  };
-}
-
-export interface SubsectionInterface {
-  name: string;
-  view: GerritView;
-  detailType?: RepoDetailView | GroupDetailView;
-  url?: string;
-  children?: SubsectionInterface[];
-}
-
-export interface AdminNavLinksOption {
-  repoName?: RepoName;
-  groupId?: GroupId;
-  groupName?: string;
-  groupIsInternal?: boolean;
-  isAdmin?: boolean;
-  groupOwner?: boolean;
-}
-
-export interface NavLink {
-  name: string;
-  noBaseUrl: boolean;
-  url: string;
-  view?: GerritView | AdminChildView;
-  viewableToAll?: boolean;
-  section?: string;
-  capability?: string;
-  target?: string | null;
-  subsection?: SubsectionInterface;
-  children?: SubsectionInterface[];
-}
diff --git a/polygerrit-ui/app/utils/admin-nav-util_test.ts b/polygerrit-ui/app/utils/admin-nav-util_test.ts
deleted file mode 100644
index a8600c7..0000000
--- a/polygerrit-ui/app/utils/admin-nav-util_test.ts
+++ /dev/null
@@ -1,334 +0,0 @@
-/**
- * @license
- * Copyright 2018 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {assert} from '@open-wc/testing';
-import {AccountDetailInfo, GroupId, RepoName, Timestamp} from '../api/rest-api';
-import '../test/common-test-setup';
-import {AdminNavLinksOption, getAdminLinks} from './admin-nav-util';
-
-suite('gr-admin-nav-behavior tests', () => {
-  let capabilityStub: sinon.SinonStub;
-  let menuLinkStub: sinon.SinonStub;
-
-  setup(() => {
-    capabilityStub = sinon.stub();
-    menuLinkStub = sinon.stub().returns([]);
-  });
-
-  const testAdminLinks = async (
-    account: AccountDetailInfo | undefined,
-    options: AdminNavLinksOption | undefined,
-    expected: any
-  ) => {
-    const res = await getAdminLinks(
-      account,
-      capabilityStub,
-      menuLinkStub,
-      options
-    );
-
-    assert.equal(expected.totalLength, res.links.length);
-    assert.equal(res.links[0].name, 'Repositories');
-    // Repos
-    if (expected.groupListShown) {
-      assert.equal(res.links[1].name, 'Groups');
-    }
-
-    if (expected.pluginListShown) {
-      assert.equal(res.links[2].name, 'Plugins');
-      assert.isNotOk(res.links[2].subsection);
-    }
-
-    if (expected.projectPageShown) {
-      assert.isOk(res.links[0].subsection);
-      assert.equal(res.links[0].subsection!.children!.length, 6);
-    } else {
-      assert.isNotOk(res.links[0].subsection);
-    }
-    // Groups
-    if (expected.groupPageShown) {
-      assert.isOk(res.links[1].subsection);
-      assert.equal(
-        res.links[1].subsection!.children!.length,
-        expected.groupSubpageLength
-      );
-    } else if (expected.totalLength > 1) {
-      assert.isNotOk(res.links[1].subsection);
-    }
-
-    if (expected.pluginGeneratedLinks) {
-      for (const link of expected.pluginGeneratedLinks) {
-        const linkMatch = res.links.find(
-          l => l.url === link.url && l.name === link.text
-        );
-        assert.isTrue(!!linkMatch);
-
-        // External links should open in new tab.
-        if (link.url[0] !== '/') {
-          assert.equal(linkMatch!.target, '_blank');
-        } else {
-          assert.isNotOk(linkMatch!.target);
-        }
-      }
-    }
-
-    // Current section
-    if (expected.projectPageShown || expected.groupPageShown) {
-      assert.isOk(res.expandedSection);
-      assert.isOk(res.expandedSection!.children);
-    } else {
-      assert.isNotOk(res.expandedSection);
-    }
-    if (expected.projectPageShown) {
-      assert.equal(res.expandedSection!.name, 'my-repo');
-      assert.equal(res.expandedSection!.children!.length, 6);
-    } else if (expected.groupPageShown) {
-      assert.equal(res.expandedSection!.name, 'my-group');
-      assert.equal(
-        res.expandedSection!.children!.length,
-        expected.groupSubpageLength
-      );
-    }
-  };
-
-  suite('logged out', () => {
-    let account: AccountDetailInfo;
-    let expected: any;
-
-    setup(() => {
-      expected = {
-        groupListShown: false,
-        groupPageShown: false,
-        pluginListShown: false,
-      };
-    });
-
-    test('without a specific repo or group', async () => {
-      let options;
-      expected = Object.assign(expected, {
-        totalLength: 1,
-        projectPageShown: false,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('with a repo', async () => {
-      const options = {repoName: 'my-repo' as RepoName};
-      expected = Object.assign(expected, {
-        totalLength: 1,
-        projectPageShown: true,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('with plugin generated links', async () => {
-      let options;
-      const generatedLinks = [
-        {text: 'internal link text', url: '/internal/link/url'},
-        {text: 'external link text', url: 'http://external/link/url'},
-      ];
-      menuLinkStub.returns(generatedLinks);
-      expected = Object.assign(expected, {
-        totalLength: 3,
-        projectPageShown: false,
-        pluginGeneratedLinks: generatedLinks,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-  });
-
-  suite('no plugin capability logged in', () => {
-    const account = {
-      name: 'test-user',
-      registered_on: '' as Timestamp,
-    };
-    let expected: any;
-
-    setup(() => {
-      expected = {
-        totalLength: 2,
-        pluginListShown: false,
-      };
-      capabilityStub.returns(Promise.resolve({}));
-    });
-
-    test('without a specific project or group', async () => {
-      let options;
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupListShown: true,
-        groupPageShown: false,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('with a repo', async () => {
-      const account = {
-        name: 'test-user',
-        registered_on: '' as Timestamp,
-      };
-      const options = {repoName: 'my-repo' as RepoName};
-      expected = Object.assign(expected, {
-        projectPageShown: true,
-        groupListShown: true,
-        groupPageShown: false,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-  });
-
-  suite('view plugin capability logged in', () => {
-    const account = {
-      name: 'test-user',
-      registered_on: '' as Timestamp,
-    };
-    let expected: any;
-
-    setup(() => {
-      capabilityStub.returns(Promise.resolve({viewPlugins: true}));
-      expected = {
-        totalLength: 3,
-        groupListShown: true,
-        pluginListShown: true,
-      };
-    });
-
-    test('without a specific repo or group', async () => {
-      let options;
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupPageShown: false,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('with a repo', async () => {
-      const options = {repoName: 'my-repo' as RepoName};
-      expected = Object.assign(expected, {
-        projectPageShown: true,
-        groupPageShown: false,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('admin with internal group', async () => {
-      const options = {
-        groupId: 'a15262' as GroupId,
-        groupName: 'my-group',
-        groupIsInternal: true,
-        isAdmin: true,
-        groupOwner: false,
-      };
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupPageShown: true,
-        groupSubpageLength: 2,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('group owner with internal group', async () => {
-      const options = {
-        groupId: 'a15262' as GroupId,
-        groupName: 'my-group',
-        groupIsInternal: true,
-        isAdmin: false,
-        groupOwner: true,
-      };
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupPageShown: true,
-        groupSubpageLength: 2,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('non owner or admin with internal group', async () => {
-      const options = {
-        groupId: 'a15262' as GroupId,
-        groupName: 'my-group',
-        groupIsInternal: true,
-        isAdmin: false,
-        groupOwner: false,
-      };
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupPageShown: true,
-        groupSubpageLength: 1,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-
-    test('admin with external group', async () => {
-      const options = {
-        groupId: 'a15262' as GroupId,
-        groupName: 'my-group',
-        groupIsInternal: false,
-        isAdmin: true,
-        groupOwner: true,
-      };
-      expected = Object.assign(expected, {
-        projectPageShown: false,
-        groupPageShown: true,
-        groupSubpageLength: 0,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-  });
-
-  suite('view plugin screen with plugin capability', () => {
-    const account = {
-      name: 'test-user',
-      registered_on: '' as Timestamp,
-    };
-    let expected: any;
-
-    setup(() => {
-      capabilityStub.returns(Promise.resolve({pluginCapability: true}));
-      expected = {};
-    });
-
-    test('with plugin with capabilities', async () => {
-      let options;
-      const generatedLinks = [
-        {text: 'without capability', url: '/without'},
-        {text: 'with capability', url: '/with', capability: 'pluginCapability'},
-      ];
-      menuLinkStub.returns(generatedLinks);
-      expected = Object.assign(expected, {
-        totalLength: 4,
-        pluginGeneratedLinks: generatedLinks,
-      });
-      await testAdminLinks(account, options, expected);
-    });
-  });
-
-  suite('view plugin screen without plugin capability', () => {
-    const account = {
-      name: 'test-user',
-      registered_on: '' as Timestamp,
-    };
-    let expected: any;
-
-    setup(() => {
-      capabilityStub.returns(Promise.resolve({}));
-      expected = {};
-    });
-
-    test('with plugin with capabilities', async () => {
-      let options;
-      const generatedLinks = [
-        {text: 'without capability', url: '/without'},
-        {text: 'with capability', url: '/with', capability: 'pluginCapability'},
-      ];
-      menuLinkStub.returns(generatedLinks);
-      expected = Object.assign(expected, {
-        totalLength: 3,
-        pluginGeneratedLinks: [generatedLinks[0]],
-      });
-      await testAdminLinks(account, options, expected);
-    });
-  });
-});