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
This commit is contained in:
Paladox none
2017-04-07 12:40:21 +00:00
committed by Becky Siegel
parent 5fe963e635
commit 2719063a1e
8 changed files with 408 additions and 0 deletions

View File

@@ -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>&larr; Prev</a>
<a id="nextArrow"
href$="[[_computeNavLink(_offset, 1, _projectsPerPage)]]"
hidden$="[[_hideNextArrow(_loading, _projects)]]" hidden>
Next &rarr;</a>
</nav>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-admin-project-list.js"></script>
</dom-module>

View File

@@ -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));
},
});
})();

View File

@@ -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>

View File

@@ -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) {

View File

@@ -18,6 +18,7 @@ limitations under the License.
<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 @@ limitations under the License.
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>

View File

@@ -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) {

View File

@@ -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; }

View File

@@ -30,6 +30,7 @@ limitations under the License.
// 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',