PolyGerrit: Show a list of projects on /admin/projects page
When going to /admin/projects/test for example it will take you back
to the old ui to view the project configs.
Bug: Issue 5966
Change-Id: I7d6fb8f17a235602bba0c535cfe4b43030f515a3
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
new file mode 100644
index 0000000..3ea1ff0
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.html
@@ -0,0 +1,109 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<link rel="import" href="../../../styles/gr-form-styles.html">
+
+<dom-module id="gr-admin-project-list">
+ <template>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ }
+ tr.project-table {
+ border-bottom: 1px solid #eee;
+ }
+ #projectList {
+ border-collapse: collapse;
+ width: 100%;
+ }
+ td {
+ flex-shrink: 0;
+ padding: .3em .5em;
+ }
+ th {
+ background-color: #ddd;
+ border-bottom: 1px solid #eee;
+ font-weight: bold;
+ padding: .3em .5em;
+ text-align: left;
+ }
+ .readOnly,
+ .repositoryBrowser,
+ .description {
+ white-space: nowrap;
+ }
+ a {
+ color: var(--default-text-color);
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ nav {
+ padding: .5em 0;
+ text-align: center;
+ }
+ nav a {
+ display: inline-block;
+ }
+ nav a:first-of-type {
+ margin-right: .5em;
+ }
+ </style>
+ <table id="projectList">
+ <tr class="headerRow">
+ <th class="name topHeader">Project Name</th>
+ <th class="description topHeader">Project Description</th>
+ <th class="repositoryBrowser topHeader">Repository Browser</th>
+ <th class="readOnly topHeader">Read only</th>
+ </tr>
+ <template is="dom-repeat" items="[[_projects]]">
+ <tr class="project-table">
+ <td class="name">
+ <a href$="[[_getUrl(item.id)]]">[[item.name]]</a>
+ </td>
+ <td class="description">[[item.description]]</td>
+ <td class="repositoryBrowser">
+ <template is="dom-repeat"
+ items="[[_computeWeblink(item)]]" as="link">
+ <a href$="[[link.url]]" class="webLink" rel="noopener" target="_blank">
+ ([[link.name]])
+ </a>
+ </template>
+ </td>
+ <td class="readOnly">[[_readOnly(item)]]</td>
+ </tr>
+ </template>
+ </table>
+ <nav>
+ <a id="prevArrow"
+ href$="[[_computeNavLink(_offset, -1, _projectsPerPage)]]"
+ hidden$="[[_hidePrevArrow(_offset)]]" hidden>← Prev</a>
+ <a id="nextArrow"
+ href$="[[_computeNavLink(_offset, 1, _projectsPerPage)]]"
+ hidden$="[[_hideNextArrow(_loading, _projects)]]" hidden>
+ Next →</a>
+ </nav>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-admin-project-list.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
new file mode 100644
index 0000000..3e06605
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list.js
@@ -0,0 +1,139 @@
+// Copyright (C) 2017 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.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-admin-project-list',
+
+ properties: {
+ /**
+ * URL params passed from the router.
+ */
+ params: {
+ type: Object,
+ observer: '_paramsChanged',
+ },
+
+ /**
+ * Offset of currently visible query results.
+ */
+ _offset: Number,
+
+ _projects: Array,
+
+ _projectsPerPage: {
+ type: Number,
+ value: 25,
+ },
+
+ _loading: {
+ type: Boolean,
+ value: true,
+ },
+ },
+
+ behaviors: [
+ Gerrit.BaseUrlBehavior,
+ Gerrit.URLEncodingBehavior,
+ ],
+
+ listeners: {
+ 'next-page': '_handleNextPage',
+ 'previous-page': '_handlePreviousPage',
+ },
+
+ _paramsChanged(value) {
+ this._loading = true;
+
+ if (value && value.offset) {
+ this._offset = value.offset;
+ } else {
+ this._offset = 0;
+ }
+
+ return this.$.restAPI.getProjects(this._projectsPerPage, this._offset)
+ .then(projects => {
+ if (!projects) {
+ this._projects = [];
+ return;
+ }
+ this._projects = Object.keys(projects)
+ .map(key => {
+ const project = projects[key];
+ project.name = key;
+ return project;
+ });
+ this._loading = false;
+ });
+ },
+
+ _readOnly(item) {
+ return item.state === 'READ_ONLY' ? 'Y' : 'N';
+ },
+
+ _getUrl(item) {
+ return this.getBaseUrl() + '/admin/projects/' +
+ this.encodeURL(item, false);
+ },
+
+ _isProjectWebLink(link) {
+ return link.name === 'gitiles' || link.name === 'gitweb';
+ },
+
+ _computeWeblink(project) {
+ if (!project.web_links) {
+ return '';
+ }
+ const webLinks = project.web_links.filter(
+ l => !this._isProjectWebLink(l));
+ return webLinks.length ? webLinks : null;
+ },
+
+ _computeNavLink(offset, direction, projectsPerPage) {
+ // Offset could be a string when passed from the router.
+ offset = +(offset || 0);
+ const newOffset = Math.max(0, offset + (projectsPerPage * direction));
+ let href = this.getBaseUrl() + '/admin/projects';
+ if (newOffset > 0) {
+ href += ',' + newOffset;
+ }
+ return href;
+ },
+
+ _hidePrevArrow(offset) {
+ return offset === 0;
+ },
+
+ _hideNextArrow(loading, projects) {
+ let lastPage = false;
+ if (projects.length < this._projectsPerPage + 1) {
+ lastPage = true;
+ }
+ return loading || lastPage || !projects || !projects.length;
+ },
+
+ _handleNextPage() {
+ if (this.$.nextArrow.hidden) { return; }
+ page.show(this._computeNavLink(
+ this._offset, 1, this._projectsPerPage));
+ },
+
+ _handlePreviousPage() {
+ if (this.$.prevArrow.hidden) { return; }
+ page.show(this._computeNavLink(
+ this._offset, -1, this._projectsPerPage));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
new file mode 100644
index 0000000..7ba231a
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-project-list/gr-admin-project-list_test.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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>gr-admin-project-list</title>
+
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+
+<link rel="import" href="gr-admin-project-list.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-admin-project-list></gr-admin-project-list>
+ </template>
+</test-fixture>
+
+<script>
+ let counter = 0;
+ const projectGenerator = () => {
+ return {
+ id: `test${++counter}`,
+ state: 'ACTIVE',
+ web_links: [
+ {
+ name: 'diffusion',
+ url: `https://phabricator.example.org/r/project/test${counter}`,
+ },
+ ],
+ };
+ };
+
+ suite('gr-admin-project-list tests', () => {
+ let element;
+ let projects;
+ let value;
+
+ suite('list with projects', () => {
+ setup(done => {
+ projects = _.times(30, projectGenerator);
+
+ stub('gr-rest-api-interface', {
+ getProjects(num, offset) {
+ return Promise.resolve(projects);
+ },
+ });
+
+ element = fixture('basic');
+ element._paramsChanged(value).then(() => { flush(done); });
+ });
+
+ test('test for test project in the list', done => {
+ flush(() => {
+ assert.equal(element._projects[1].id, 'test2');
+ done();
+ });
+ });
+
+ test('test next button', done => {
+ flush(() => {
+ let loading;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ loading = true;
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ loading = false;
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ element._projects = [];
+ assert.isTrue(element._hideNextArrow(loading, element._projects));
+ projects = _.times(4, projectGenerator);
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ done();
+ });
+ });
+
+ test('test for prev button', () => {
+ flush(() => {
+ let offset = 0;
+ assert.isTrue(element._hidePrevArrow(offset));
+ offset = 5;
+ assert.isFalse(element._hidePrevArrow(offset));
+ });
+ });
+ });
+
+ suite('test with less then 25 projects', () => {
+ setup(done => {
+ projects = _.times(25, projectGenerator);
+
+ stub('gr-rest-api-interface', {
+ getProjects(num, offset) {
+ return Promise.resolve(projects);
+ },
+ });
+
+ element = fixture('basic');
+ element._paramsChanged(value).then(() => { flush(done); });
+ });
+
+ test('test next button', done => {
+ flush(() => {
+ let loading;
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ projects = _.times(1, projectGenerator);
+ assert.isTrue(element._hideNextArrow(loading, projects));
+ projects = _.times(26, projectGenerator);
+ assert.isFalse(element._hideNextArrow(loading, projects));
+ done();
+ });
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 673a11e..fcdb209 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -108,6 +108,20 @@
});
});
+ // Matches /admin/projects[,<offset>][/].
+ page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
+ restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ app.params = {
+ view: 'gr-admin-project-list',
+ offset: data.params[1] || 0,
+ };
+ } else {
+ page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+ }
+ });
+ });
+
page('/admin/(.*)', loadUser, data => {
restAPI.getLoggedIn().then(loggedIn => {
if (loggedIn) {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 87a4687..0fe48fd 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -18,6 +18,7 @@
<link rel="import" href="../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../styles/app-theme.html">
+<link rel="import" href="./admin/gr-admin-project-list/gr-admin-project-list.html">
<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
<link rel="import" href="./change-list/gr-change-list-view/gr-change-list-view.html">
<link rel="import" href="./change-list/gr-dashboard-view/gr-dashboard-view.html">
@@ -125,6 +126,11 @@
on-account-detail-update="_handleAccountDetailUpdate">
</gr-settings-view>
</template>
+ <template is="dom-if" if="[[_showProjectListView]]" restamp="true">
+ <gr-admin-project-list
+ params="[[params]]"
+ id="projectList"></gr-admin-project-list>
+ </template>
<template is="dom-if" if="[[_showAdminView]]" restamp="true">
<gr-admin-view path="[[_path]]"></gr-admin-view>
</template>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 945e010..b19a6ea 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -127,6 +127,7 @@
this.set('_showChangeView', view === 'gr-change-view');
this.set('_showDiffView', view === 'gr-diff-view');
this.set('_showSettingsView', view === 'gr-settings-view');
+ this.set('_showProjectListView', view === 'gr-admin-project-list');
this.set('_showAdminView', view === 'gr-admin-view');
this.set('_showCLAView', view === 'gr-cla-view');
if (this.params.justRegistered) {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 43e2cf1..005124d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -559,6 +559,13 @@
});
},
+ getProjects(projectsPerPage, opt_offset) {
+ const offset = opt_offset || 0;
+ return this._fetchSharedCacheURL(
+ `/projects/?d&n=${projectsPerPage}&S=${offset}`
+ );
+ },
+
getSuggestedGroups(inputVal, opt_n, opt_errFn, opt_ctx) {
const params = {s: inputVal};
if (opt_n) { params.n = opt_n; }
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index ff0968a..6b9f655 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -30,6 +30,7 @@
// This seemed to be flakey when it was farther down the list. Keep at the
// beginning.
'gr-app_test.html',
+ 'admin/gr-admin-project-list/gr-admin-project-list_test.html',
'change-list/gr-change-list-item/gr-change-list-item_test.html',
'change-list/gr-change-list-view/gr-change-list-view_test.html',
'change-list/gr-change-list/gr-change-list_test.html',