Add gr-plugin-config-array-editor
Component used for editing array-based plugin config options.
Uses/will use unidirectional data flow and Polymer's notifyPath
functionality to update the config at the gr-repo level.
Bug: Issue 8535
Change-Id: I7754c80dd4327fbd65e53b6194bbe1ce9d6cb169
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html
new file mode 100644
index 0000000..ca98c50
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.html
@@ -0,0 +1,100 @@
+<!--
+@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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+
+<dom-module id="gr-plugin-config-array-editor">
+ <template>
+ <style include="shared-styles"></style>
+ <style include="gr-form-styles">
+ .wrapper {
+ width: 30em;
+ }
+ .existingItems {
+ background: var(--table-header-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: 2px;
+ }
+ gr-button {
+ float: right;
+ margin-left: .5em;
+ width: 4.5em;
+ }
+ .row {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ padding: .5em 0;
+ width: 100%;
+ }
+ .existingItems .row {
+ padding: .5em;
+ }
+ .existingItems .row:not(:first-of-type) {
+ border-top: 1px solid var(--border-color);
+ }
+ input {
+ flex-grow: 1;
+ }
+ .hide {
+ display: none;
+ }
+ .placeholder {
+ color: var(--deemphasized-text-color);
+ padding-top: .75em;
+ }
+ </style>
+ <div class="wrapper gr-form-styles">
+ <template is="dom-if" if="[[pluginOption.info.values.length]]">
+ <div class="existingItems">
+ <template is="dom-repeat" items="[[pluginOption.info.values]]">
+ <div class="row">
+ <span>[[item]]</span>
+ <gr-button
+ link
+ disabled$="[[disabled]]"
+ data-item="[[item]]"
+ on-tap="_handleDelete">Delete</gr-button>
+ </div>
+ </template>
+ </div>
+ </template>
+ <template is="dom-if" if="[[!pluginOption.info.values.length]]">
+ <div class="row placeholder">None configured.</div>
+ </template>
+ <div class$="row [[_computeShowInputRow(disabled)]]">
+ <input
+ is="iron-input"
+ id="input"
+ on-keydown="_handleInputKeydown"
+ bind-value="{{_newValue}}"/>
+ <gr-button
+ id="addButton"
+ disabled$="[[!_newValue.length]]"
+ link
+ on-tap="_handleAddTap">Add</gr-button>
+ </div>
+ </div>
+ </template>
+ <script src="gr-plugin-config-array-editor.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
new file mode 100644
index 0000000..ab4d286
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor.js
@@ -0,0 +1,90 @@
+/**
+ * @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.
+ */
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-plugin-config-array-editor',
+
+ /**
+ * Fired when the plugin config option changes.
+ *
+ * @event plugin-config-option-changed
+ */
+
+ properties: {
+ /** @type {?} */
+ pluginOption: Object,
+ /** @type {Boolean} */
+ disabled: {
+ type: Boolean,
+ computed: '_computeDisabled(pluginOption.*)',
+ },
+ /** @type {?} */
+ _newValue: {
+ type: String,
+ value: '',
+ },
+ },
+
+ _computeDisabled(record) {
+ return !(record && record.base && record.base.info &&
+ record.base.info.editable);
+ },
+
+ _handleAddTap(e) {
+ e.preventDefault();
+ this._handleAdd();
+ },
+
+ _handleInputKeydown(e) {
+ // Enter.
+ if (e.keyCode === 13) {
+ e.preventDefault();
+ this._handleAdd();
+ }
+ },
+
+ _handleAdd() {
+ if (!this._newValue.length) { return; }
+ this._dispatchChanged(
+ this.pluginOption.info.values.concat([this._newValue]));
+ this._newValue = '';
+ },
+
+ _handleDelete(e) {
+ const value = Polymer.dom(e).localTarget.dataItem;
+ this._dispatchChanged(
+ this.pluginOption.info.values.filter(str => str !== value));
+ },
+
+ _dispatchChanged(values) {
+ const {_key, info} = this.pluginOption;
+ const detail = {
+ _key,
+ info: Object.assign(info, {values}, {}),
+ notifyPath: `${_key}.values`,
+ };
+ this.dispatchEvent(
+ new CustomEvent('plugin-config-option-changed', {detail}));
+ },
+
+ _computeShowInputRow(disabled) {
+ return disabled ? 'hide' : '';
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
new file mode 100644
index 0000000..dc3f67e
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
@@ -0,0 +1,142 @@
+<!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>gr-plugin-config-array-editor</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-plugin-config-array-editor.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-plugin-config-array-editor></gr-plugin-config-array-editor>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-plugin-config-array-editor tests', () => {
+ let element;
+ let sandbox;
+ let dispatchStub;
+
+ const getAll = str => Polymer.dom(element.root).querySelectorAll(str);
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ element.pluginOption = {
+ _key: 'test-key',
+ info: {
+ values: [],
+ },
+ };
+ });
+
+ teardown(() => sandbox.restore());
+
+ test('_computeShowInputRow', () => {
+ assert.equal(element._computeShowInputRow(true), 'hide');
+ assert.equal(element._computeShowInputRow(false), '');
+ });
+
+ test('_computeDisabled', () => {
+ assert.isTrue(element._computeDisabled({}));
+ assert.isTrue(element._computeDisabled({base: {}}));
+ assert.isTrue(element._computeDisabled({base: {info: {}}}));
+ assert.isTrue(
+ element._computeDisabled({base: {info: {editable: false}}}));
+ assert.isFalse(
+ element._computeDisabled({base: {info: {editable: true}}}));
+ });
+
+ suite('adding', () => {
+ setup(() => {
+ dispatchStub = sandbox.stub(element, '_dispatchChanged');
+ });
+
+ test('with enter', () => {
+ element._newValue = '';
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ flushAsynchronousOperations();
+
+ assert.isFalse(dispatchStub.called);
+ element._newValue = 'test';
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
+ flushAsynchronousOperations();
+
+ assert.isTrue(dispatchStub.called);
+ assert.equal(dispatchStub.lastCall.args[0], 'test');
+ assert.equal(element._newValue, '');
+ });
+
+ test('with add btn', () => {
+ element._newValue = '';
+ MockInteractions.tap(element.$.addButton);
+ flushAsynchronousOperations();
+
+ assert.isFalse(dispatchStub.called);
+ element._newValue = 'test';
+ MockInteractions.tap(element.$.addButton);
+ flushAsynchronousOperations();
+
+ assert.isTrue(dispatchStub.called);
+ assert.equal(dispatchStub.lastCall.args[0], 'test');
+ assert.equal(element._newValue, '');
+ });
+ });
+
+ test('deleting', () => {
+ dispatchStub = sandbox.stub(element, '_dispatchChanged');
+ element.pluginOption = {info: {values: ['test', 'test2']}};
+ flushAsynchronousOperations();
+
+ const rows = getAll('.existingItems .row');
+ assert.equal(rows.length, 2);
+ const button = rows[0].querySelector('gr-button');
+
+ MockInteractions.tap(button);
+ flushAsynchronousOperations();
+
+ assert.isFalse(dispatchStub.called);
+ element.pluginOption.info.editable = true;
+ element.notifyPath('pluginOption.info.editable');
+ flushAsynchronousOperations();
+
+ MockInteractions.tap(button);
+ flushAsynchronousOperations();
+
+ assert.isTrue(dispatchStub.called);
+ assert.deepEqual(dispatchStub.lastCall.args[0], ['test2']);
+ });
+
+ test('_dispatchChanged', () => {
+ const eventStub = sandbox.stub(element, 'dispatchEvent');
+ element._dispatchChanged(['new-test-value']);
+
+ assert.isTrue(eventStub.called);
+ const {detail} = eventStub.lastCall.args[0];
+ assert.equal(detail._key, 'test-key');
+ assert.deepEqual(detail.info, {values: ['new-test-value']});
+ assert.equal(detail.notifyPath, 'test-key.values');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index d9e1238..395df6d 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -44,6 +44,7 @@
'admin/gr-group-members/gr-group-members_test.html',
'admin/gr-group/gr-group_test.html',
'admin/gr-permission/gr-permission_test.html',
+ 'admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html',
'admin/gr-plugin-list/gr-plugin-list_test.html',
'admin/gr-repo-access/gr-repo-access_test.html',
'admin/gr-repo-command/gr-repo-command_test.html',