Introduce gr-admin-nav-behavior and use in gr-admin-view
gr-admin-nav-behavior will also be used by the main header. For this change, the behavior replaces work previously done in gr-admin-view. In a follow-up change, gr-admin-view will display the current section's options in a gr-dropdown-list and the gr-main-header will display top level admin links in a dropdown menu. Change-Id: Ief0ca439528929f35335309c292512579bb696c3
This commit is contained in:
		@@ -0,0 +1,203 @@
 | 
			
		||||
<!--
 | 
			
		||||
@license
 | 
			
		||||
Copyright (C) 2018 The Android Open Source Project
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
-->
 | 
			
		||||
<script>
 | 
			
		||||
(function(window) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  const ACCOUNT_CAPABILITIES = ['createProject', 'createGroup', 'viewPlugins'];
 | 
			
		||||
 | 
			
		||||
  const ADMIN_LINKS = [{
 | 
			
		||||
    name: 'Repositories',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/repos',
 | 
			
		||||
    view: 'gr-repo-list',
 | 
			
		||||
    viewableToAll: true,
 | 
			
		||||
  }, {
 | 
			
		||||
    name: 'Groups',
 | 
			
		||||
    section: 'Groups',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/groups',
 | 
			
		||||
    view: 'gr-admin-group-list',
 | 
			
		||||
  }, {
 | 
			
		||||
    name: 'Plugins',
 | 
			
		||||
    capability: 'viewPlugins',
 | 
			
		||||
    section: 'Plugins',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/plugins',
 | 
			
		||||
    view: 'gr-plugin-list',
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  window.Gerrit = window.Gerrit || {};
 | 
			
		||||
 | 
			
		||||
  /** @polymerBehavior Gerrit.AdminNavBehavior */
 | 
			
		||||
  Gerrit.AdminNavBehavior = {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {!Object} account
 | 
			
		||||
     * @param {!Function} getAccountCapabilities
 | 
			
		||||
     * @param {!Function} getAdminMenuLinks
 | 
			
		||||
     *  Possible aguments in options:
 | 
			
		||||
     *    repoName?: string
 | 
			
		||||
     *    groupId?: string,
 | 
			
		||||
     *    groupName?: string,
 | 
			
		||||
     *    groupIsInternal?: boolean,
 | 
			
		||||
     *    isAdmin?: boolean,
 | 
			
		||||
     *    groupOwner?: boolean,
 | 
			
		||||
     * @param {!Object=} opt_options
 | 
			
		||||
     * @return {Promise<!Object>}
 | 
			
		||||
     */
 | 
			
		||||
    getAdminLinks(account, getAccountCapabilities, getAdminMenuLinks,
 | 
			
		||||
        opt_options) {
 | 
			
		||||
      if (!account) {
 | 
			
		||||
        return Promise.resolve(this._filterLinks(link => link.viewableToAll,
 | 
			
		||||
            getAdminMenuLinks, opt_options));
 | 
			
		||||
      }
 | 
			
		||||
      return getAccountCapabilities(ACCOUNT_CAPABILITIES)
 | 
			
		||||
          .then(capabilities => {
 | 
			
		||||
            return this._filterLinks(link => {
 | 
			
		||||
              return !link.capability ||
 | 
			
		||||
                  capabilities.hasOwnProperty(link.capability);
 | 
			
		||||
            }, getAdminMenuLinks, opt_options);
 | 
			
		||||
          });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {!Function} filterFn
 | 
			
		||||
     * @param {!Function} getAdminMenuLinks
 | 
			
		||||
     *  Possible aguments in options:
 | 
			
		||||
     *    repoName?: string
 | 
			
		||||
     *    groupId?: string,
 | 
			
		||||
     *    groupName?: string,
 | 
			
		||||
     *    groupIsInternal?: boolean,
 | 
			
		||||
     *    isAdmin?: boolean,
 | 
			
		||||
     *    groupOwner?: boolean,
 | 
			
		||||
     * @param {!Object|undefined} opt_options
 | 
			
		||||
     * @return {Promise<!Object>}
 | 
			
		||||
     */
 | 
			
		||||
    _filterLinks(filterFn, getAdminMenuLinks, opt_options) {
 | 
			
		||||
      let links = ADMIN_LINKS.slice(0);
 | 
			
		||||
      let expandedSection;
 | 
			
		||||
 | 
			
		||||
      // Append top-level links that are defined by plugins.
 | 
			
		||||
      links.push(...getAdminMenuLinks().map(link => ({
 | 
			
		||||
        url: link.url,
 | 
			
		||||
        name: link.text,
 | 
			
		||||
        noBaseUrl: link.url[0] === '/',
 | 
			
		||||
        view: null,
 | 
			
		||||
        viewableToAll: true,
 | 
			
		||||
      })));
 | 
			
		||||
 | 
			
		||||
      links = links.filter(filterFn);
 | 
			
		||||
 | 
			
		||||
      const filteredLinks = [];
 | 
			
		||||
      const repoName = opt_options && opt_options.repoName;
 | 
			
		||||
      const groupId = opt_options && opt_options.groupId;
 | 
			
		||||
      const groupName = opt_options && opt_options.groupName;
 | 
			
		||||
      const groupIsInternal = opt_options && opt_options.groupIsInternal;
 | 
			
		||||
      const isAdmin = opt_options && opt_options.isAdmin;
 | 
			
		||||
      const groupOwner = opt_options && opt_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 = Object.assign({}, link);
 | 
			
		||||
        if (linkCopy.name === 'Repositories' && repoName) {
 | 
			
		||||
          linkCopy.subsection = this.getRepoSubsections(repoName);
 | 
			
		||||
          expandedSection = linkCopy.subsection;
 | 
			
		||||
        } else if (linkCopy.name === 'Groups' && groupId && groupName) {
 | 
			
		||||
          linkCopy.subsection = this.getGroupSubsections(groupId, groupName,
 | 
			
		||||
              groupIsInternal, isAdmin, groupOwner);
 | 
			
		||||
          expandedSection = linkCopy.subsection;
 | 
			
		||||
        }
 | 
			
		||||
        filteredLinks.push(linkCopy);
 | 
			
		||||
      }
 | 
			
		||||
      return {links: filteredLinks, expandedSection};
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getGroupSubsections(groupId, groupName, groupIsInternal, isAdmin,
 | 
			
		||||
        groupOwner) {
 | 
			
		||||
      const subsection = {
 | 
			
		||||
        name: groupName,
 | 
			
		||||
        view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
        url: Gerrit.Nav.getUrlForGroup(groupId),
 | 
			
		||||
        children: [],
 | 
			
		||||
      };
 | 
			
		||||
      if (groupIsInternal) {
 | 
			
		||||
        subsection.children.push({
 | 
			
		||||
          name: 'Members',
 | 
			
		||||
          detailType: Gerrit.Nav.GroupDetailView.MEMBERS,
 | 
			
		||||
          view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForGroupMembers(groupId),
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      if (groupIsInternal && (isAdmin || groupOwner)) {
 | 
			
		||||
        subsection.children.push(
 | 
			
		||||
            {
 | 
			
		||||
              name: 'Audit Log',
 | 
			
		||||
              detailType: Gerrit.Nav.GroupDetailView.LOG,
 | 
			
		||||
              view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForGroupLog(groupId),
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      return subsection;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getRepoSubsections(repoName) {
 | 
			
		||||
      return {
 | 
			
		||||
        name: repoName,
 | 
			
		||||
        view: Gerrit.Nav.View.REPO,
 | 
			
		||||
        url: Gerrit.Nav.getUrlForRepo(repoName),
 | 
			
		||||
        children: [{
 | 
			
		||||
          name: 'Access',
 | 
			
		||||
          view: Gerrit.Nav.View.REPO,
 | 
			
		||||
          detailType: Gerrit.Nav.RepoDetailView.ACCESS,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForRepoAccess(repoName),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Commands',
 | 
			
		||||
          view: Gerrit.Nav.View.REPO,
 | 
			
		||||
          detailType: Gerrit.Nav.RepoDetailView.COMMANDS,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForRepoCommands(repoName),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Branches',
 | 
			
		||||
          view: Gerrit.Nav.View.REPO,
 | 
			
		||||
          detailType: Gerrit.Nav.RepoDetailView.BRANCHES,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForRepoBranches(repoName),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Tags',
 | 
			
		||||
          view: Gerrit.Nav.View.REPO,
 | 
			
		||||
          detailType: Gerrit.Nav.RepoDetailView.TAGS,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForRepoTags(repoName),
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Dashboards',
 | 
			
		||||
          view: Gerrit.Nav.View.REPO,
 | 
			
		||||
          detailType: Gerrit.Nav.RepoDetailView.DASHBOARDS,
 | 
			
		||||
          url: Gerrit.Nav.getUrlForRepoDashboards(repoName),
 | 
			
		||||
        }],
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
})(window);
 | 
			
		||||
</script>
 | 
			
		||||
@@ -0,0 +1,303 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<!--
 | 
			
		||||
@license
 | 
			
		||||
Copyright (C) 2018 The Android Open Source Project
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 | 
			
		||||
<title>keyboard-shortcut-behavior</title>
 | 
			
		||||
 | 
			
		||||
<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 | 
			
		||||
<script src="../../bower_components/web-component-tester/browser.js"></script>
 | 
			
		||||
<link rel="import" href="../../test/common-test-setup.html"/>
 | 
			
		||||
<link rel="import" href="gr-admin-nav-behavior.html">
 | 
			
		||||
 | 
			
		||||
<test-fixture id="basic">
 | 
			
		||||
  <template>
 | 
			
		||||
    <test-element></test-element>
 | 
			
		||||
  </template>
 | 
			
		||||
</test-fixture>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  suite('gr-admin-nav-behavior tests', () => {
 | 
			
		||||
    let element;
 | 
			
		||||
    let sandbox;
 | 
			
		||||
    let capabilityStub;
 | 
			
		||||
    let menuLinkStub;
 | 
			
		||||
 | 
			
		||||
    suiteSetup(() => {
 | 
			
		||||
      // Define a Polymer element that uses this behavior.
 | 
			
		||||
      Polymer({
 | 
			
		||||
        is: 'test-element',
 | 
			
		||||
        behaviors: [
 | 
			
		||||
          Gerrit.AdminNavBehavior,
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setup(() => {
 | 
			
		||||
      element = fixture('basic');
 | 
			
		||||
      sandbox = sinon.sandbox.create();
 | 
			
		||||
      capabilityStub = sinon.stub();
 | 
			
		||||
      menuLinkStub = sinon.stub().returns([]);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    teardown(() => {
 | 
			
		||||
      sandbox.restore();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const testAdminLinks = (account, options, expected, done) => {
 | 
			
		||||
      element.getAdminLinks(account,
 | 
			
		||||
          capabilityStub,
 | 
			
		||||
          menuLinkStub,
 | 
			
		||||
          options)
 | 
			
		||||
          .then(res => {
 | 
			
		||||
            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, 5);
 | 
			
		||||
            } 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) {
 | 
			
		||||
                assert.isTrue(!!res.links.find(l => {
 | 
			
		||||
                  return (l.url === link.url && l.name === link.text);
 | 
			
		||||
                }));
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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, 5);
 | 
			
		||||
            } else if (expected.groupPageShown) {
 | 
			
		||||
              assert.equal(res.expandedSection.name, 'my-group');
 | 
			
		||||
              assert.equal(res.expandedSection.children.length,
 | 
			
		||||
                  expected.groupSubpageLength);
 | 
			
		||||
            }
 | 
			
		||||
            done();
 | 
			
		||||
          });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    suite('logged out', () => {
 | 
			
		||||
      let account;
 | 
			
		||||
      let expected;
 | 
			
		||||
 | 
			
		||||
      setup(() => {
 | 
			
		||||
        expected = {
 | 
			
		||||
          groupListShown: false,
 | 
			
		||||
          groupPageShown: false,
 | 
			
		||||
          pluginListShown: false,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('without a specific repo or group', done => {
 | 
			
		||||
        let options;
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          totalLength: 1,
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('with a repo', done => {
 | 
			
		||||
        const options = {repoName: 'my-repo'};
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          totalLength: 1,
 | 
			
		||||
          projectPageShown: true,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('with plugin generated links', done => {
 | 
			
		||||
        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,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    suite('no plugin capability logged in', () => {
 | 
			
		||||
      const account = {
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      };
 | 
			
		||||
      let expected;
 | 
			
		||||
 | 
			
		||||
      setup(() => {
 | 
			
		||||
        expected = {
 | 
			
		||||
          totalLength: 2,
 | 
			
		||||
          pluginListShown: false,
 | 
			
		||||
        };
 | 
			
		||||
        capabilityStub.returns(Promise.resolve({}));
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('without a specific project or group', done => {
 | 
			
		||||
        let options;
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupListShown: true,
 | 
			
		||||
          groupPageShown: false,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('with a repo', done => {
 | 
			
		||||
        const account = {
 | 
			
		||||
          name: 'test-user',
 | 
			
		||||
        };
 | 
			
		||||
        const options = {repoName: 'my-repo'};
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: true,
 | 
			
		||||
          groupListShown: true,
 | 
			
		||||
          groupPageShown: false,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    suite('view plugin capability logged in', () => {
 | 
			
		||||
      const account = {
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      };
 | 
			
		||||
      let expected;
 | 
			
		||||
 | 
			
		||||
      setup(() => {
 | 
			
		||||
        capabilityStub.returns(Promise.resolve({viewPlugins: true}));
 | 
			
		||||
        expected = {
 | 
			
		||||
          totalLength: 3,
 | 
			
		||||
          groupListShown: true,
 | 
			
		||||
          pluginListShown: true,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('without a specific repo or group', done => {
 | 
			
		||||
        let options;
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupPageShown: false,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('with a repo', done => {
 | 
			
		||||
        const options = {repoName: 'my-repo'};
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: true,
 | 
			
		||||
          groupPageShown: false,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('admin with internal group', done => {
 | 
			
		||||
        const options = {
 | 
			
		||||
          groupId: 'a15262',
 | 
			
		||||
          groupName: 'my-group',
 | 
			
		||||
          groupIsInternal: true,
 | 
			
		||||
          isAdmin: true,
 | 
			
		||||
          groupOwner: false,
 | 
			
		||||
        };
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupPageShown: true,
 | 
			
		||||
          groupSubpageLength: 2,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('group owner with internal group', done => {
 | 
			
		||||
        const options = {
 | 
			
		||||
          groupId: 'a15262',
 | 
			
		||||
          groupName: 'my-group',
 | 
			
		||||
          groupIsInternal: true,
 | 
			
		||||
          isAdmin: false,
 | 
			
		||||
          groupOwner: true,
 | 
			
		||||
        };
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupPageShown: true,
 | 
			
		||||
          groupSubpageLength: 2,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('non owner or admin with internal group', done => {
 | 
			
		||||
        const options = {
 | 
			
		||||
          groupId: 'a15262',
 | 
			
		||||
          groupName: 'my-group',
 | 
			
		||||
          groupIsInternal: true,
 | 
			
		||||
          isAdmin: false,
 | 
			
		||||
          groupOwner: false,
 | 
			
		||||
        };
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupPageShown: true,
 | 
			
		||||
          groupSubpageLength: 1,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      test('admin with external group', done => {
 | 
			
		||||
        const options = {
 | 
			
		||||
          groupId: 'a15262',
 | 
			
		||||
          groupName: 'my-group',
 | 
			
		||||
          groupIsInternal: false,
 | 
			
		||||
          isAdmin: true,
 | 
			
		||||
          groupOwner: true,
 | 
			
		||||
        };
 | 
			
		||||
        expected = Object.assign(expected, {
 | 
			
		||||
          projectPageShown: false,
 | 
			
		||||
          groupPageShown: true,
 | 
			
		||||
          groupSubpageLength: 0,
 | 
			
		||||
        });
 | 
			
		||||
        testAdminLinks(account, options, expected, done);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
@@ -18,6 +18,7 @@ limitations under the License.
 | 
			
		||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
 | 
			
		||||
 | 
			
		||||
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
 | 
			
		||||
<link rel="import" href="../../../behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html">
 | 
			
		||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
 | 
			
		||||
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
 | 
			
		||||
<link rel="import" href="../../../styles/gr-page-nav-styles.html">
 | 
			
		||||
 
 | 
			
		||||
@@ -17,35 +17,9 @@
 | 
			
		||||
(function() {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  // Note: noBaseUrl: true is set on entries where the URL is not yet supported
 | 
			
		||||
  // by router abstraction.
 | 
			
		||||
  const ADMIN_LINKS = [{
 | 
			
		||||
    name: 'Repositories',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/repos',
 | 
			
		||||
    view: 'gr-repo-list',
 | 
			
		||||
    viewableToAll: true,
 | 
			
		||||
    children: [],
 | 
			
		||||
  }, {
 | 
			
		||||
    name: 'Groups',
 | 
			
		||||
    section: 'Groups',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/groups',
 | 
			
		||||
    view: 'gr-admin-group-list',
 | 
			
		||||
    children: [],
 | 
			
		||||
  }, {
 | 
			
		||||
    name: 'Plugins',
 | 
			
		||||
    capability: 'viewPlugins',
 | 
			
		||||
    section: 'Plugins',
 | 
			
		||||
    noBaseUrl: true,
 | 
			
		||||
    url: '/admin/plugins',
 | 
			
		||||
    view: 'gr-plugin-list',
 | 
			
		||||
  }];
 | 
			
		||||
 | 
			
		||||
  const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
 | 
			
		||||
 | 
			
		||||
  const ACCOUNT_CAPABILITIES = ['createProject', 'createGroup', 'viewPlugins'];
 | 
			
		||||
 | 
			
		||||
  Polymer({
 | 
			
		||||
    is: 'gr-admin-view',
 | 
			
		||||
 | 
			
		||||
@@ -66,6 +40,7 @@
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        value: false,
 | 
			
		||||
      },
 | 
			
		||||
      _subsectionLinks: Array,
 | 
			
		||||
      _filteredLinks: Array,
 | 
			
		||||
      _showDownload: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
@@ -89,6 +64,7 @@
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    behaviors: [
 | 
			
		||||
      Gerrit.AdminNavBehavior,
 | 
			
		||||
      Gerrit.BaseUrlBehavior,
 | 
			
		||||
      Gerrit.URLEncodingBehavior,
 | 
			
		||||
    ],
 | 
			
		||||
@@ -108,113 +84,54 @@
 | 
			
		||||
      ];
 | 
			
		||||
      return Promise.all(promises).then(result => {
 | 
			
		||||
        this._account = result[0];
 | 
			
		||||
        if (!this._account) {
 | 
			
		||||
          // Return so that  account capabilities don't load with no account.
 | 
			
		||||
          return this._filteredLinks = this._filterLinks(link => {
 | 
			
		||||
            return link.viewableToAll;
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        this._loadAccountCapabilities();
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _filterLinks(filterFn) {
 | 
			
		||||
      let links = ADMIN_LINKS.slice(0);
 | 
			
		||||
 | 
			
		||||
      // Append top-level links that are defined by plugins.
 | 
			
		||||
      links.push(...this.$.jsAPI.getAdminMenuLinks().map(link => ({
 | 
			
		||||
        url: link.url,
 | 
			
		||||
        name: link.text,
 | 
			
		||||
        children: [],
 | 
			
		||||
        noBaseUrl: link.url[0] === '/',
 | 
			
		||||
        view: null,
 | 
			
		||||
        viewableToAll: true,
 | 
			
		||||
      })));
 | 
			
		||||
 | 
			
		||||
      links = links.filter(filterFn);
 | 
			
		||||
 | 
			
		||||
      const filteredLinks = [];
 | 
			
		||||
      for (const link of links) {
 | 
			
		||||
        const linkCopy = Object.assign({}, link);
 | 
			
		||||
        linkCopy.children = linkCopy.children ?
 | 
			
		||||
            linkCopy.children.filter(filterFn) : [];
 | 
			
		||||
        if (linkCopy.name === 'Repositories' && this._repoName) {
 | 
			
		||||
          linkCopy.subsection = {
 | 
			
		||||
            name: this._repoName,
 | 
			
		||||
            view: Gerrit.Nav.View.REPO,
 | 
			
		||||
            url: Gerrit.Nav.getUrlForRepo(this._repoName),
 | 
			
		||||
            children: [{
 | 
			
		||||
              name: 'Access',
 | 
			
		||||
              view: Gerrit.Nav.View.REPO,
 | 
			
		||||
              detailType: Gerrit.Nav.RepoDetailView.ACCESS,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForRepoAccess(this._repoName),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              name: 'Commands',
 | 
			
		||||
              view: Gerrit.Nav.View.REPO,
 | 
			
		||||
              detailType: Gerrit.Nav.RepoDetailView.COMMANDS,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForRepoCommands(this._repoName),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              name: 'Branches',
 | 
			
		||||
              view: Gerrit.Nav.View.REPO,
 | 
			
		||||
              detailType: Gerrit.Nav.RepoDetailView.BRANCHES,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForRepoBranches(this._repoName),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              name: 'Tags',
 | 
			
		||||
              view: Gerrit.Nav.View.REPO,
 | 
			
		||||
              detailType: Gerrit.Nav.RepoDetailView.TAGS,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForRepoTags(this._repoName),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              name: 'Dashboards',
 | 
			
		||||
              view: Gerrit.Nav.View.REPO,
 | 
			
		||||
              detailType: Gerrit.Nav.RepoDetailView.DASHBOARDS,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForRepoDashboards(this._repoName),
 | 
			
		||||
            }],
 | 
			
		||||
        let options;
 | 
			
		||||
        if (this._repoName) {
 | 
			
		||||
          options = {repoName: this._repoName};
 | 
			
		||||
        } else if (this._groupId) {
 | 
			
		||||
          options = {
 | 
			
		||||
            groupId: this._groupId,
 | 
			
		||||
            groupName: this._groupName,
 | 
			
		||||
            groupIsInternal: this._groupIsInternal,
 | 
			
		||||
            isAdmin: this._isAdmin,
 | 
			
		||||
            groupOwner: this._groupOwner,
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        if (linkCopy.name === 'Groups' && this._groupId && this._groupName) {
 | 
			
		||||
          linkCopy.subsection = {
 | 
			
		||||
            name: this._groupName,
 | 
			
		||||
            view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
            url: Gerrit.Nav.getUrlForGroup(this._groupId),
 | 
			
		||||
            children: [],
 | 
			
		||||
          };
 | 
			
		||||
          if (this._groupIsInternal) {
 | 
			
		||||
            linkCopy.subsection.children.push({
 | 
			
		||||
              name: 'Members',
 | 
			
		||||
              detailType: Gerrit.Nav.GroupDetailView.MEMBERS,
 | 
			
		||||
              view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
              url: Gerrit.Nav.getUrlForGroupMembers(this._groupId),
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          if (this._groupIsInternal && (this._isAdmin || this._groupOwner)) {
 | 
			
		||||
            linkCopy.subsection.children.push(
 | 
			
		||||
                {
 | 
			
		||||
                  name: 'Audit Log',
 | 
			
		||||
                  detailType: Gerrit.Nav.GroupDetailView.LOG,
 | 
			
		||||
                  view: Gerrit.Nav.View.GROUP,
 | 
			
		||||
                  url: Gerrit.Nav.getUrlForGroupLog(this._groupId),
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        filteredLinks.push(linkCopy);
 | 
			
		||||
        return this.getAdminLinks(this._account,
 | 
			
		||||
            this.$.restAPI.getAccountCapabilities.bind(this.$.restAPI),
 | 
			
		||||
            this.$.jsAPI.getAdminMenuLinks.bind(this.$.jsAPI),
 | 
			
		||||
            options)
 | 
			
		||||
            .then(res => {
 | 
			
		||||
              this._filteredLinks = res.links;
 | 
			
		||||
 | 
			
		||||
              if (!res.expandedSection) {
 | 
			
		||||
                this._subsectionLinks = [];
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
      return filteredLinks;
 | 
			
		||||
              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 || '',
 | 
			
		||||
                };
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _loadAccountCapabilities() {
 | 
			
		||||
      return this.$.restAPI.getAccountCapabilities(ACCOUNT_CAPABILITIES)
 | 
			
		||||
          .then(capabilities => {
 | 
			
		||||
            this._filteredLinks = this._filterLinks(link => {
 | 
			
		||||
              return !link.capability ||
 | 
			
		||||
                  capabilities.hasOwnProperty(link.capability);
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
    _handleSubsectionChange(e) {
 | 
			
		||||
      const selected = this._subsectionLinks
 | 
			
		||||
          .find(section => section.value === e.detail.value);
 | 
			
		||||
 | 
			
		||||
      // This is when it gets set initially.
 | 
			
		||||
      if (this._selectedIsCurrentPage(selected)) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      Gerrit.Nav.navigateToRelativeUrl(selected.url);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _paramsChanged(params) {
 | 
			
		||||
@@ -260,6 +177,10 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _computeSelectedTitle(params) {
 | 
			
		||||
      return this.getSelectedTitle(params.view);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 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).
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,9 @@ limitations under the License.
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_filteredLinks admin', done => {
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccount').returns(Promise.resolve({
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      }));
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
 | 
			
		||||
        return Promise.resolve({
 | 
			
		||||
          createGroup: true,
 | 
			
		||||
@@ -102,35 +105,36 @@ limitations under the License.
 | 
			
		||||
          viewPlugins: true,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      element._loadAccountCapabilities().then(() => {
 | 
			
		||||
      element.reload().then(() => {
 | 
			
		||||
        assert.equal(element._filteredLinks.length, 3);
 | 
			
		||||
 | 
			
		||||
        // Repos
 | 
			
		||||
        assert.equal(element._filteredLinks[0].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
 | 
			
		||||
        // Groups
 | 
			
		||||
        assert.equal(element._filteredLinks[1].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
 | 
			
		||||
        // Plugins
 | 
			
		||||
        assert.equal(element._filteredLinks[2].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('_filteredLinks non admin authenticated', done => {
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccount').returns(Promise.resolve({
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      }));
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
 | 
			
		||||
        return Promise.resolve({});
 | 
			
		||||
      });
 | 
			
		||||
      element._loadAccountCapabilities().then(() => {
 | 
			
		||||
      element.reload().then(() => {
 | 
			
		||||
        assert.equal(element._filteredLinks.length, 2);
 | 
			
		||||
 | 
			
		||||
        // Repos
 | 
			
		||||
        assert.equal(element._filteredLinks[0].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
 | 
			
		||||
        // Groups
 | 
			
		||||
        assert.equal(element._filteredLinks[1].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
@@ -140,7 +144,6 @@ limitations under the License.
 | 
			
		||||
        assert.equal(element._filteredLinks.length, 1);
 | 
			
		||||
 | 
			
		||||
        // Repos
 | 
			
		||||
        assert.equal(element._filteredLinks[0].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
@@ -156,7 +159,6 @@ limitations under the License.
 | 
			
		||||
        assert.deepEqual(element._filteredLinks[1], {
 | 
			
		||||
          url: '/internal/link/url',
 | 
			
		||||
          name: 'internal link text',
 | 
			
		||||
          children: [],
 | 
			
		||||
          noBaseUrl: true,
 | 
			
		||||
          view: null,
 | 
			
		||||
          viewableToAll: true,
 | 
			
		||||
@@ -164,7 +166,6 @@ limitations under the License.
 | 
			
		||||
        assert.deepEqual(element._filteredLinks[2], {
 | 
			
		||||
          url: 'http://external/link/url',
 | 
			
		||||
          name: 'external link text',
 | 
			
		||||
          children: [],
 | 
			
		||||
          noBaseUrl: false,
 | 
			
		||||
          view: null,
 | 
			
		||||
          viewableToAll: true,
 | 
			
		||||
@@ -174,6 +175,9 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
    test('Repo shows up in nav', done => {
 | 
			
		||||
      element._repoName = 'Test Repo';
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccount').returns(Promise.resolve({
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      }));
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
 | 
			
		||||
        return Promise.resolve({
 | 
			
		||||
          createGroup: true,
 | 
			
		||||
@@ -181,18 +185,52 @@ limitations under the License.
 | 
			
		||||
          viewPlugins: true,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      element._loadAccountCapabilities().then(() => {
 | 
			
		||||
      element.reload().then(() => {
 | 
			
		||||
        flushAsynchronousOperations();
 | 
			
		||||
        assert.equal(element._filteredLinks.length, 3);
 | 
			
		||||
 | 
			
		||||
        // Repos
 | 
			
		||||
        assert.equal(element._filteredLinks[0].children.length, 0);
 | 
			
		||||
        assert.equal(element._filteredLinks[0].subsection.children.length, 5);
 | 
			
		||||
        assert.equal(element._filteredLinks[0].subsection.name, 'Test Repo');
 | 
			
		||||
 | 
			
		||||
        // Groups
 | 
			
		||||
        assert.equal(element._filteredLinks[1].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[1].subsection);
 | 
			
		||||
 | 
			
		||||
        // Plugins
 | 
			
		||||
        assert.equal(element._filteredLinks[2].children.length, 0);
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[2].subsection);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Group shows up in nav', done => {
 | 
			
		||||
      element._groupId = 'a15262';
 | 
			
		||||
      element._groupName = 'my-group';
 | 
			
		||||
      element._groupIsInternal = true;
 | 
			
		||||
      element._isAdmin = true;
 | 
			
		||||
      element._groupOwner = false;
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccount').returns(Promise.resolve({
 | 
			
		||||
        name: 'test-user',
 | 
			
		||||
      }));
 | 
			
		||||
      sandbox.stub(element.$.restAPI, 'getAccountCapabilities', () => {
 | 
			
		||||
        return Promise.resolve({
 | 
			
		||||
          createGroup: true,
 | 
			
		||||
          createProject: true,
 | 
			
		||||
          viewPlugins: true,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      element.reload().then(() => {
 | 
			
		||||
        flushAsynchronousOperations();
 | 
			
		||||
        assert.equal(element._filteredLinks.length, 3);
 | 
			
		||||
 | 
			
		||||
        // Repos
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[0].subsection);
 | 
			
		||||
 | 
			
		||||
        // Groups
 | 
			
		||||
        assert.equal(element._filteredLinks[1].subsection.children.length, 2);
 | 
			
		||||
        assert.equal(element._filteredLinks[1].subsection.name, 'my-group');
 | 
			
		||||
 | 
			
		||||
        // Plugins
 | 
			
		||||
        assert.isNotOk(element._filteredLinks[2].subsection);
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -196,6 +196,7 @@ limitations under the License.
 | 
			
		||||
    'keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html',
 | 
			
		||||
    'rest-client-behavior/rest-client-behavior_test.html',
 | 
			
		||||
    'gr-access-behavior/gr-access-behavior_test.html',
 | 
			
		||||
    'gr-admin-nav-behavior/gr-admin-nav-behavior_test.html',
 | 
			
		||||
    'gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html',
 | 
			
		||||
    'gr-change-table-behavior/gr-change-table-behavior_test.html',
 | 
			
		||||
    'gr-patch-set-behavior/gr-patch-set-behavior_test.html',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user