Abstract PolyGerrit router

Set up the mechanism for generating URLs and triggering navigation in PG
components in a way that is decoupled from page.js and gr-router.
Upgrade some components to use this system.

Feature: Issue 6446
Change-Id: Idc18cbd87b8e4e05d24ae6a5feb0a0a43f47fd7f
This commit is contained in:
Wyatt Allen
2017-06-08 09:20:46 -07:00
parent a29772693b
commit 797480f4fc
17 changed files with 315 additions and 53 deletions

View File

@@ -19,6 +19,8 @@ limitations under the License.
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">

View File

@@ -842,9 +842,8 @@
'uploaded to this change.',
action: 'Reload',
callback: () => {
// Load the current change without any patch range.
location.href = `${this.getBaseUrl()}/c/${
this.change._number}`;
// Load the current change without any patch range.
Gerrit.Nav.navigateToChange(this.change);
},
});
cleanupFn();

View File

@@ -14,11 +14,10 @@ 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="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../plugins/gr-external-style/gr-external-style.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">

View File

@@ -57,9 +57,7 @@
},
behaviors: [
Gerrit.BaseUrlBehavior,
Gerrit.RESTClientBehavior,
Gerrit.URLEncodingBehavior,
],
observers: [
@@ -266,27 +264,17 @@
},
_computeProjectURL(project) {
return this.getBaseUrl() + '/q/project:' +
this.encodeURL(project, false);
return Gerrit.Nav.getUrlForProject(project);
},
_computeBranchURL(project, branch) {
let status;
if (this.change.status == this.ChangeStatus.NEW) {
status = 'open';
} else {
status = this.change.status.toLowerCase();
}
return this.getBaseUrl() + '/q/project:' +
this.encodeURL(project, false) +
' branch:' + this.encodeURL(branch, false) +
' status:' + this.encodeURL(status, false);
return Gerrit.Nav.getUrlForBranch(branch, project,
this.change.status == this.ChangeStatus.NEW ? 'open' :
this.change.status.toLowerCase());
},
_computeTopicURL(topic) {
return this.getBaseUrl() + '/q/topic:' +
this.encodeURL('"' + topic + '"', false) +
'+(status:open OR status:merged)';
return Gerrit.Nav.getUrlForTopic(topic);
},
_handleTopicRemoved() {

View File

@@ -300,12 +300,6 @@ limitations under the License.
});
});
test('topic href has quotes', () => {
const hrefArr = element._computeTopicURL('test')
.split('%2522'); // Double-escaped quote.
assert.equal(hrefArr[1], 'test');
});
test('topic removal', () => {
sandbox.stub(element.$.restAPI, 'setChangeTopic').returns(
Promise.resolve());

View File

@@ -14,11 +14,11 @@ 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-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
@@ -316,7 +316,7 @@ limitations under the License.
change="{{_change}}" hidden$="[[!_loggedIn]]"></gr-change-star>
<a
aria-label$="[[_computeChangePermalinkAriaLabel(_change._number)]]"
href$="[[_computeChangePermalink(_change._number)]]">[[_change._number]]</a><!--
href$="[[_computeChangeUrl(_change)]]">[[_change._number]]</a><!--
--><template is="dom-if" if="[[_changeStatus]]"><!--
--> (<!--
--><span
@@ -471,7 +471,7 @@ limitations under the License.
commit-info="[[_commitInfo]]"></gr-commit-info>
<span class="latestPatchContainer">
/
<a href$="[[getBaseUrl()]]/c/[[_change._number]]">Go to latest patch set</a>
<a href$="[[_computeChangeUrl(_change)]]">Go to latest patch set</a>
</span>
<span class="downloadContainer desktop">
/

View File

@@ -183,7 +183,6 @@
},
behaviors: [
Gerrit.BaseUrlBehavior,
Gerrit.KeyboardShortcutBehavior,
Gerrit.PatchSetBehavior,
Gerrit.RESTClientBehavior,
@@ -632,8 +631,8 @@
page.show(this.changePath(this._changeNum) + '/' + patchExpr);
},
_computeChangePermalink(changeNum) {
return this.getBaseUrl() + '/' + changeNum;
_computeChangeUrl(change) {
return Gerrit.Nav.getUrlForChange(change);
},
_computeChangeStatus(change, patchNum) {
@@ -1288,8 +1287,7 @@
action: 'Reload',
callback: function() {
// Load the current change without any patch range.
location.href = this.getBaseUrl() + '/c/' +
this._change._number;
Gerrit.Nav.navigateToChange(this._change);
}.bind(this),
});
}

View File

@@ -0,0 +1,152 @@
<!--
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.
-->
<script>
(function(window) {
'use strict';
// Navigation parameters object format:
//
// Each object has a `view` property with a value from Gerrit.Nav.View. The
// remaining properties depend on the value used for view.
//
// - Gerrit.Nav.View.CHANGE:
// - `id`, required, String: the numeric ID of the change.
//
// - Gerrit.Nav.View.SEARCH:
// - `owner`, optional, String: the owner name.
// - `project`, optional, String: the project name.
// - `branch`, optional, String: the branch name.
// - `topic`, optional, String: the topic name.
// - `statuses`, optional, Array<String>: the list of change statuses to
// search for. If more than one is provided, the search will OR them
// together.
window.Gerrit = window.Gerrit || {};
// Prevent redefinition.
if (window.Gerrit.hasOwnProperty('Nav')) { return; }
const uninitialized = () => {
console.warn('Use of uninitialized routing');
};
window.Gerrit.Nav = {
View: {
CHANGE: 'change',
SEARCH: 'search',
},
/** @type {Function} */
_navigate: uninitialized,
/** @type {Function} */
_generateUrl: uninitialized,
/**
* Setup router implementation.
* @param {Function} handleNavigate
* @param {Function} generateUrl
*/
setup(navigate, generateUrl) {
this._navigate = navigate;
this._generateUrl = generateUrl;
},
destroy() {
this._navigate = uninitialized;
this._generateUrl = uninitialized;
},
/**
* Generate a URL for the given route parameters.
* @param {Object} params
* @return {String}
*/
_getUrlFor(params) {
return this._generateUrl(params);
},
/**
* @param {String} project The name of the project.
* @return {String}
*/
getUrlForProject(project) {
return this._getUrlFor({
view: Gerrit.Nav.View.SEARCH,
project,
});
},
/**
* @param {String} branch The name of the branch.
* @param {String} project The name of the project.
* @param {String} status The status to search.
* @return {String}
*/
getUrlForBranch(branch, project, status) {
return this._getUrlFor({
view: Gerrit.Nav.View.SEARCH,
branch,
project,
statuses: [status],
});
},
/**
* @param {String} topic The name of the topic.
* @return {String}
*/
getUrlForTopic(topic) {
return this._getUrlFor({
view: Gerrit.Nav.View.SEARCH,
topic,
statuses: ['open', 'merged'],
});
},
/**
* @param {Object} change The change object.
* @return {String}
*/
getUrlForChange(change) {
return this._getUrlFor({
view: Gerrit.Nav.View.CHANGE,
id: change._number,
});
},
/**
* @param {Object} change The change object.
* @return {String}
*/
navigateToChange(change) {
this._navigate(this.getUrlForChange(change));
},
/**
* @param {String} owner The name of the owner.
* @return {String}
*/
getUrlForOwner(owner) {
return this._getUrlFor({
view: Gerrit.Nav.View.SEARCH,
owner,
});
},
};
})(window);
</script>

View File

@@ -14,7 +14,9 @@ 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="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-reporting/gr-reporting.html">

View File

@@ -37,7 +37,11 @@
getReporting().timeEnd('WebComponentsReady');
});
function startRouter() {
function encode(s) {
return window.Gerrit.URLEncodingBehavior.encodeURL(s, false);
}
function startRouter(generateUrl) {
const base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
if (base) {
page.base(base);
@@ -46,6 +50,8 @@
const restAPI = document.createElement('gr-rest-api-interface');
const reporting = getReporting();
Gerrit.Nav.setup(url => { page.show(url); }, generateUrl);
// Middleware
page((ctx, next) => {
document.body.scrollTop = 0;
@@ -352,7 +358,45 @@
is: 'gr-router',
start() {
if (!app) { return; }
startRouter();
startRouter(this._generateUrl);
},
_generateUrl(params) {
const base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
let url = '';
if (params.view === Gerrit.Nav.View.SEARCH) {
const operators = [];
if (params.owner) {
operators.push('owner:' + encode(params.owner));
}
if (params.project) {
operators.push('project:' + encode(params.project));
}
if (params.branch) {
operators.push('branch:' + encode(params.branch));
}
if (params.topic) {
operators.push('topic:"' + encode(params.topic) + '"');
}
if (params.statuses) {
if (params.statuses.length === 1) {
operators.push('status:' + encode(params.statuses[0]));
} else if (params.statuses.length > 1) {
operators.push(
'(' +
params.statuses.map(s => `status:${encode(s)}`).join(' OR ') +
')');
}
}
url = '/q/' + operators.join('+');
} else if (params.view === Gerrit.Nav.View.CHANGE) {
url = '/c/' + params.id;
} else {
throw new Error('Can\'t generate');
}
return base + url;
},
});
})();

View File

@@ -0,0 +1,73 @@
<!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-router</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.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-router.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-router></gr-router>
</template>
</test-fixture>
<script>
suite('gr-router tests', () => {
suite('generateUrl', () => {
let element;
setup(() => {
element = fixture('basic');
});
test('search', () => {
let params = {
view: Gerrit.Nav.View.SEARCH,
owner: 'a%b',
project: 'c%d',
branch: 'e%f',
topic: 'g%h',
statuses: ['op%en'],
};
assert.equal(element._generateUrl(params),
'/q/owner:a%2525b+project:c%2525d+branch:e%2525f+' +
'topic:"g%2525h"+status:op%2525en');
params = {
view: Gerrit.Nav.View.SEARCH,
statuses: ['a', 'b', 'c'],
};
assert.equal(element._generateUrl(params),
'/q/(status:a OR status:b OR status:c)');
});
test('change', () => {
const params = {
view: Gerrit.Nav.View.CHANGE,
id: '1234',
};
assert.equal(element._generateUrl(params), '/c/1234');
});
});
});
</script>

View File

@@ -16,6 +16,7 @@ limitations under the License.
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../gr-account-label/gr-account-label.html">
<link rel="import" href="../../../styles/shared-styles.html">

View File

@@ -31,8 +31,7 @@
_computeOwnerLink(account) {
if (!account) { return; }
const accountID = account.email || account._account_id;
return this.getBaseUrl() + '/q/owner:' + encodeURIComponent(accountID);
return Gerrit.Nav.getUrlForOwner(account.email || account._account_id);
},
_computeShowEmail(account) {

View File

@@ -43,18 +43,7 @@ limitations under the License.
});
test('computed fields', () => {
assert.equal(element._computeOwnerLink(
{
_account_id: 123,
email: 'andybons+gerrit@gmail.com',
}),
'/q/owner:andybons%2Bgerrit%40gmail.com');
assert.equal(element._computeOwnerLink({_account_id: 42}),
'/q/owner:42');
assert.equal(element._computeShowEmail({name: 'asd'}), false);
assert.equal(element._computeShowEmail({}), true);
});
});

View File

@@ -15,5 +15,5 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import"
href="../bower_components/iron-test-helpers/iron-test-helpers.html" />
<link rel="import" href="../bower_components/iron-test-helpers/iron-test-helpers.html" />
<link rel="import" href="test-router.html" />

View File

@@ -62,6 +62,7 @@ limitations under the License.
'core/gr-account-dropdown/gr-account-dropdown_test.html',
'core/gr-error-manager/gr-error-manager_test.html',
'core/gr-main-header/gr-main-header_test.html',
'core/gr-router/gr-router_test.html',
'core/gr-reporting/gr-reporting_test.html',
'core/gr-search-bar/gr-search-bar_test.html',
'diff/gr-diff-builder/gr-diff-builder_test.html',

View File

@@ -0,0 +1,21 @@
<!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.
-->
<link rel="import" href="../elements/core/gr-navigation/gr-navigation.html">
<script>
Gerrit.Nav.setup(url => { /* noop */ }, params => '');
</script>