Make metadata sections configurable from plugin

For plugins, imported with HTML imports, and providing CSS modules, use
provided styles to control metadata sections in gr-change-view.

Related Polymer API:
https://www.polymer-project.org/1.0/docs/devguide/styling#custom-style

Includes sample integration test.

Sample code for using proposed API, in plugin.js:

Gerrit.install(function(plugin) {
  plugin.registerStyleModule('change-metadata', 'my-plugin-style');
});

Please note that `my-plugin-style` has to be unique, so plugin name
should be included.

Sample code for using proposed API, in myplugin.html:

<dom-module id="my-plugin-style">
  <template>
    <style>
      :root {
        --change-metadata-assignee: {
          display: none;
        }
        --change-metadata-label-status: {
          display: none;
        }
        --change-metadata-strategy: {
          display: none;
        }
        --change-metadata-topic: {
          display: none;
        }
      }
    </style>
  </template>
</dom-module>

Feature: Issue 5402
Change-Id: Iba2645f28d5b411df2d0310a05aa0cbec4cca26a
This commit is contained in:
Viktar Donich 2017-02-07 08:56:33 -08:00
parent 74f6261f5e
commit 9bd0ce688d
9 changed files with 481 additions and 149 deletions

View File

@ -0,0 +1,61 @@
= Gerrit Code Review - PolyGerrit Plugin Styling
CAUTION: Work in progress. Hard hat area. +
This document will be populated with details along with implementation. +
link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join the discussion.]
== Plugin styles
Plugins may provide link:https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[Polymer style modules] for UI CSS-based customization.
PolyGerrit UI implements number of styling endpoints, which apply CSS mixins link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its direct contents.
NOTE: Only items (ie CSS properties and mixin targets) documented here are guaranteed to work in the long term, since they are covered by integration tests. +
When there is a need to add new property or endpoint, please link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20Issue[file a bug] stating your usecase to track and maintain for future releases.
Plugin should be html-based and imported following PolyGerrit's link:dev-plugins-pg.html#loading[dev guide].
Plugin should provide Style Module, for example:
``` html
<dom-module id="some-style">
<style>
:root {
--css-mixin-name: {
property: value;
}
}
</style>
</dom-module>
```
Plugin should register style module with a styling endpoint using `Plugin.prototype.registerStyleModule(endpointName, styleModuleName)`, for example:
``` js
Gerrit.install(function(plugin) {
plugin.registerStyleModule('some-endpoint', 'some-style');
});
```
== Available styling endpoints
=== change-metadata
Following custom css mixins are recognized:
* `--change-metadata-assignee`
+
is applied to `gr-change-metadata section.assignee`
* `--change-metadata-label-status`
+
is applied to `gr-change-metadata section.labelStatus`
* `--change-metadata-strategy`
+
is applied to `gr-change-metadata section.strategy`
* `--change-metadata-topic`
+
is applied to `gr-change-metadata section.topic`
Following CSS properties have link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html[long-term support via integration test]:
* `display`
+
can be set to `none` to hide a section.

View File

@ -4,6 +4,7 @@ CAUTION: Work in progress. Hard hat area. +
This document will be populated with details along with implementation. +
link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join the discussion.]
[[loading]]
== Plugin loading and initialization
link:https://gerrit-review.googlesource.com/Documentation/js-api.html#_entry_point[Entry point] for the plugin and the loading method is based on link:http://w3c.github.io/webcomponents/spec/imports/[HTML Imports] spec.

View File

@ -0,0 +1,133 @@
<!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-change-metadata</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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-change-metadata.html">
<test-fixture id="plugin">
<template>
<script>
Gerrit.install(function(plugin) {
plugin.registerStyleModule('change-metadata', 'some-style');
}, '', 'http://x/plugins/foo.js');
</script>
</template>
</test-fixture>
<test-fixture id="some-style">
<template>
<dom-module id="some-style">
<style>
:root {
--change-metadata-assignee: {
display: none;
}
--change-metadata-label-status: {
display: none;
}
--change-metadata-strategy: {
display: none;
}
--change-metadata-topic: {
display: none;
}
}
</style>
</dom-module>
</template>
</test-fixture>
<test-fixture id="element">
<template>
<gr-change-metadata></gr-change-metadata>
</template>
</test-fixture>
<script>
suite('gr-change-metadata integration tests', function() {
var sandbox;
var plugin;
var element;
var sectionSelectors = [
'section.assignee',
'section.labelStatus',
'section.strategy',
'section.topic',
];
var getStyle = function(selector, name) {
return window.getComputedStyle(
Polymer.dom(element.root).querySelector(selector))[name];
};
setup(function() {
Gerrit._pluginsPending = 0;
sandbox = sinon.sandbox.create();
stub('gr-change-metadata', {
_computeShowLabelStatus: function() { return true; },
_computeShowReviewersByState: function() { return true; },
ready: function() {
this.change = {labels:[]};
this.serverConfig = {};
},
});
});
teardown(function() {
sandbox.restore();
});
suite('by default', function() {
setup(function(done) {
element = fixture('element');
flush(done);
});
sectionSelectors.forEach(function(sectionSelector) {
test(sectionSelector + ' does not have display: none', function() {
assert.notEqual(
getStyle(sectionSelector, 'display'), 'none');
});
});
});
suite('with plugin style', function() {
var styleEl;
setup(function(done) {
styleEl = fixture('some-style');
element = fixture('element');
plugin = fixture('plugin');
flush(done);
});
sectionSelectors.forEach(function(sectionSelector) {
test(sectionSelector + ' may have display: none', function() {
assert.equal(
getStyle(sectionSelector, 'display'), 'none');
});
});
});
});
</script>

View File

@ -18,10 +18,11 @@ limitations under the License.
<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="../../shared/gr-account-chip/gr-account-chip.html">
<link rel="import" href="../../shared/gr-label/gr-label.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">
<link rel="import" href="../../shared/gr-label/gr-label.html">
<link rel="import" href="../../shared/gr-linked-chip/gr-linked-chip.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-reviewer-list/gr-reviewer-list.html">
@ -71,7 +72,7 @@ limitations under the License.
.notApproved {
background-color: #ffd4d4;
}
.labelStatus {
.labelStatus .value {
max-width: 9em;
}
.webLink {
@ -113,171 +114,173 @@ limitations under the License.
}
}
</style>
<section>
<span class="title">Updated</span>
<span class="value">
<gr-date-formatter
has-tooltip
date-str="[[change.updated]]"></gr-date-formatter>
</span>
</section>
<section>
<span class="title">Owner</span>
<span class="value">
<gr-account-link account="[[change.owner]]"></gr-account-link>
</span>
</section>
<template is="dom-if" if="[[_showReviewersByState]]">
<gr-external-style name="change-metadata">
<section>
<span class="title">Assignee</span>
<span class="title">Updated</span>
<span class="value">
<gr-account-list
max-count="1"
id="assigneeValue"
placeholder="Add assignee..."
accounts="{{_assignee}}"
change="[[change]]"
readonly="[[_computeAssigneeReadOnly(mutable, change)]]"
allow-any-user></gr-account-list>
<gr-date-formatter
has-tooltip
date-str="[[change.updated]]"></gr-date-formatter>
</span>
</section>
<section>
<span class="title">Reviewers</span>
<span class="title">Owner</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"
reviewers-only></gr-reviewer-list>
<gr-account-link account="[[change.owner]]"></gr-account-link>
</span>
</section>
<template is="dom-if" if="[[_showReviewersByState]]">
<section class="assignee">
<span class="title">Assignee</span>
<span class="value">
<gr-account-list
max-count="1"
id="assigneeValue"
placeholder="Add assignee..."
accounts="{{_assignee}}"
change="[[change]]"
readonly="[[_computeAssigneeReadOnly(mutable, change)]]"
allow-any-user></gr-account-list>
</span>
</section>
<section>
<span class="title">Reviewers</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"
reviewers-only></gr-reviewer-list>
</span>
</section>
<section>
<span class="title">CC</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"
ccs-only></gr-reviewer-list>
</span>
</section>
</template>
<template is="dom-if" if="[[!_showReviewersByState]]">
<section>
<span class="title">Assignee</span>
<span class="value">
<gr-account-list
max-count="1"
id="assigneeValue"
placeholder="Add assignee..."
accounts="{{_assignee}}"
change="[[change]]"
readonly="[[!mutable]]"
allow-any-user></gr-account-list>
</span>
</section>
<section>
<span class="title">Reviewers</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"></gr-reviewer-list>
</span>
</section>
</template>
<section>
<span class="title">Project</span>
<span class="value">
<a href$="[[_computeProjectURL(change.project)]]">[[change.project]]</a>
</span>
</section>
<section>
<span class="title">CC</span>
<span class="title">Branch</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"
ccs-only></gr-reviewer-list>
<a href$="[[_computeBranchURL(change.project, change.branch)]]">[[change.branch]]</a>
</span>
</section>
</template>
<template is="dom-if" if="[[!_showReviewersByState]]">
<section>
<span class="title">Assignee</span>
<section class="topic">
<span class="title">Topic</span>
<span class="value">
<gr-account-list
max-count="1"
id="assigneeValue"
placeholder="Add assignee..."
accounts="{{_assignee}}"
change="[[change]]"
readonly="[[!mutable]]"
allow-any-user></gr-account-list>
</span>
</section>
<section>
<span class="title">Reviewers</span>
<span class="value">
<gr-reviewer-list
change="{{change}}"
mutable="[[mutable]]"></gr-reviewer-list>
</span>
</section>
</template>
<section>
<span class="title">Project</span>
<span class="value">
<a href$="[[_computeProjectURL(change.project)]]">[[change.project]]</a>
</span>
</section>
<section>
<span class="title">Branch</span>
<span class="value">
<a href$="[[_computeBranchURL(change.project, change.branch)]]">[[change.branch]]</a>
</span>
</section>
<section>
<span class="title">Topic</span>
<span class="value">
<template is="dom-if" if="[[change.topic]]">
<gr-linked-chip
text="[[change.topic]]"
href="[[_computeTopicHref(change.topic)]]"
removable="[[!_topicReadOnly]]"
on-remove="_handleTopicRemoved"></gr-linked-chip>
</template>
<template is="dom-if" if="[[!change.topic]]">
<gr-editable-label
value="{{change.topic}}"
placeholder="[[_computeTopicPlaceholder(_topicReadOnly)]]"
read-only="[[_topicReadOnly]]"
on-changed="_handleTopicChanged"></gr-editable-label>
</template>
</span>
</section>
<section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
<span class="title">Strategy</span>
<span class="value">[[_computeStrategy(change)]]</span>
</section>
<template is="dom-repeat"
items="[[_computeLabelNames(change.labels)]]" as="labelName">
<section>
<span class="title">[[labelName]]</span>
<span class="value">
<template is="dom-repeat"
items="[[_computeLabelValues(labelName, change.labels.*)]]"
as="label">
<div class="labelValueContainer">
<span class$="[[label.className]]">
<gr-label
has-tooltip
title="[[_computeValueTooltip(label.value, labelName)]]"
class="labelValue">
[[label.value]]
</gr-label>
<gr-account-chip
account="[[label.account]]"
data-account-id$="[[label.account._account_id]]"
label-name="[[labelName]]"
removable="[[_computeCanDeleteVote(label.account, mutable)]]"
transparent-background
on-remove="_onDeleteVote"></gr-account-chip>
</span>
</div>
<template is="dom-if" if="[[change.topic]]">
<gr-linked-chip
text="[[change.topic]]"
href="[[_computeTopicHref(change.topic)]]"
removable="[[!_topicReadOnly]]"
on-remove="_handleTopicRemoved"></gr-linked-chip>
</template>
<template is="dom-if" if="[[!change.topic]]">
<gr-editable-label
value="{{change.topic}}"
placeholder="[[_computeTopicPlaceholder(_topicReadOnly)]]"
read-only="[[_topicReadOnly]]"
on-changed="_handleTopicChanged"></gr-editable-label>
</template>
</span>
</section>
</template>
<template is="dom-if" if="[[_showLabelStatus]]">
<section>
<span class="title">Label Status</span>
<span class="value labelStatus">
<div hidden$="[[!_showMissingLabels(change.labels)]]">
[[_computeMissingLabelsHeader(change.labels)]]
<ul id="missingLabels">
<template
is="dom-repeat"
items="[[_computeMissingLabels(change.labels)]]">
<li>[[item]]</li>
</template>
</ul>
</div>
<div hidden$="[[_showMissingLabels(change.labels)]]">
Ready to submit
</div>
<section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
<span class="title">Strategy</span>
<span class="value">[[_computeStrategy(change)]]</span>
</section>
<template is="dom-repeat"
items="[[_computeLabelNames(change.labels)]]" as="labelName">
<section>
<span class="title">[[labelName]]</span>
<span class="value">
<template is="dom-repeat"
items="[[_computeLabelValues(labelName, change.labels.*)]]"
as="label">
<div class="labelValueContainer">
<span class$="[[label.className]]">
<gr-label
has-tooltip
title="[[_computeValueTooltip(label.value, labelName)]]"
class="labelValue">
[[label.value]]
</gr-label>
<gr-account-chip
account="[[label.account]]"
data-account-id$="[[label.account._account_id]]"
label-name="[[labelName]]"
removable="[[_computeCanDeleteVote(label.account, mutable)]]"
transparent-background
on-remove="_onDeleteVote"></gr-account-chip>
</span>
</div>
</template>
</span>
</section>
</template>
<template is="dom-if" if="[[_showLabelStatus]]">
<section class="labelStatus">
<span class="title">Label Status</span>
<span class="value">
<div hidden$="[[!_showMissingLabels(change.labels)]]">
[[_computeMissingLabelsHeader(change.labels)]]
<ul id="missingLabels">
<template
is="dom-repeat"
items="[[_computeMissingLabels(change.labels)]]">
<li>[[item]]</li>
</template>
</ul>
</div>
<div hidden$="[[_showMissingLabels(change.labels)]]">
Ready to submit
</div>
</span>
</section>
</template>
<section id="webLinks" hidden$="[[!_computeWebLinks(commitInfo)]]">
<span class="title">Links</span>
<span class="value">
<template is="dom-repeat"
items="[[_computeWebLinks(commitInfo)]]" as="link">
<a href="[[link.url]]" class="webLink" rel="noopener" target="_blank">
[[link.name]]
</a>
</template>
</span>
</section>
</template>
<section id="webLinks" hidden$="[[!_computeWebLinks(commitInfo)]]">
<span class="title">Links</span>
<span class="value">
<template is="dom-repeat"
items="[[_computeWebLinks(commitInfo)]]" as="link">
<a href="[[link.url]]" class="webLink" rel="noopener" target="_blank">
[[link.name]]
</a>
</template>
</span>
</section>
</gr-external-style>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-change-metadata.js"></script>

View File

@ -0,0 +1,25 @@
<!--
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="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-external-style">
<template>
<content></content>
</template>
<script src="gr-external-style.js"></script>
</dom-module>

View File

@ -0,0 +1,39 @@
// 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-external-style',
properties: {
name: String,
},
_applyStyle: function(name) {
var s = document.createElement('style', 'custom-style');
s.setAttribute('include', name);
Polymer.dom(this.root).appendChild(s);
},
ready: function() {
Gerrit.awaitPluginsLoaded().then(function() {
var sharedStyles = Gerrit._styleModules[this.name];
if (sharedStyles) {
sharedStyles.map(this._applyStyle.bind(this));
}
}.bind(this));
},
});
})();

View File

@ -0,0 +1,57 @@
<!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-external-style</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="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-external-style.html">
<test-fixture id="basic">
<template>
<gr-external-style name="foo"></gr-external-style>
</template>
</test-fixture>
<script>
suite('gr-change-metadata integration tests', function() {
var sandbox;
var element;
setup(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(Gerrit, 'awaitPluginsLoaded').returns(Promise.resolve());
element = fixture('basic');
});
teardown(function() {
sandbox.restore();
});
test('applies plugin-provided styles', function(done) {
Gerrit._styleModules = {'foo': ['bar']};
sandbox.stub(element, '_applyStyle');
flush(function() {
assert.isTrue(element._applyStyle.calledWith('bar'));
done();
});
});
});
</script>

View File

@ -54,6 +54,14 @@
return this._name;
};
Plugin.prototype.registerStyleModule =
function(stylingEndpointName, moduleName) {
if (!Gerrit._styleModules[stylingEndpointName]) {
Gerrit._styleModules[stylingEndpointName] = [];
}
Gerrit._styleModules[stylingEndpointName].push(moduleName);
};
Plugin.prototype.getServerInfo = function() {
return document.createElement('gr-rest-api-interface').getConfig();
};
@ -81,6 +89,9 @@
// Number of plugins to initialize, -1 means 'not yet known'.
Gerrit._pluginsPending = -1;
// Hash of style modules to be applied, insertion point to shared style name.
Gerrit._styleModules = {};
Gerrit.getPluginName = function() {
console.warn('Gerrit.getPluginName is not supported in PolyGerrit.',
'Please use self.getPluginName() instead.');

View File

@ -34,6 +34,7 @@ limitations under the License.
'change/gr-account-list/gr-account-list_test.html',
'change/gr-change-actions/gr-change-actions_test.html',
'change/gr-change-metadata/gr-change-metadata_test.html',
'change/gr-change-metadata/gr-change-metadata-it_test.html',
'change/gr-change-view/gr-change-view_test.html',
'change/gr-comment-list/gr-comment-list_test.html',
'change/gr-commit-info/gr-commit-info_test.html',
@ -72,6 +73,7 @@ limitations under the License.
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
'gr-app_test.html',
'plugins/gr-external-style/gr-external-style_test.html',
'plugins/gr-plugin-host/gr-plugin-host_test.html',
'settings/gr-account-info/gr-account-info_test.html',
'settings/gr-change-table-editor/gr-change-table-editor_test.html',