Convert polygerrit to es6-modules
This change replace all HTML imports with es6-modules. The only exceptions are: * gr-app.html file, which can be deleted only after updating the gerrit/httpd/raw/PolyGerritIndexHtml.soy file. * dark-theme.html which is loaded via importHref. Must be updated manually later in a separate change. This change was produced automatically by ./es6-modules-converter.sh script. No manual changes were made. Change-Id: I0c447dd8c05757741e2c940720652d01d9fb7d67
This commit is contained in:
parent
dbe954539d
commit
daf0ec9543
@ -1,21 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
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>
|
||||
/**
|
||||
* @license
|
||||
* 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -63,4 +61,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,40 +19,42 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>async-foreach-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="async-foreach-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./async-foreach-behavior.js"></script>
|
||||
|
||||
<script>
|
||||
suite('async-foreach-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
test('loops over each item', () => {
|
||||
const fn = sinon.stub().returns(Promise.resolve());
|
||||
return Gerrit.AsyncForeachBehavior.asyncForeach([1, 2, 3], fn)
|
||||
.then(() => {
|
||||
assert.isTrue(fn.calledThrice);
|
||||
assert.equal(fn.getCall(0).args[0], 1);
|
||||
assert.equal(fn.getCall(1).args[0], 2);
|
||||
assert.equal(fn.getCall(2).args[0], 3);
|
||||
});
|
||||
});
|
||||
|
||||
test('halts on stop condition', () => {
|
||||
const stub = sinon.stub();
|
||||
const fn = (e, stop) => {
|
||||
stub(e);
|
||||
stop();
|
||||
return Promise.resolve();
|
||||
};
|
||||
return Gerrit.AsyncForeachBehavior.asyncForeach([1, 2, 3], fn)
|
||||
.then(() => {
|
||||
assert.isTrue(stub.calledOnce);
|
||||
assert.equal(stub.lastCall.args[0], 1);
|
||||
});
|
||||
});
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './async-foreach-behavior.js';
|
||||
suite('async-foreach-behavior tests', () => {
|
||||
test('loops over each item', () => {
|
||||
const fn = sinon.stub().returns(Promise.resolve());
|
||||
return Gerrit.AsyncForeachBehavior.asyncForeach([1, 2, 3], fn)
|
||||
.then(() => {
|
||||
assert.isTrue(fn.calledThrice);
|
||||
assert.equal(fn.getCall(0).args[0], 1);
|
||||
assert.equal(fn.getCall(1).args[0], 2);
|
||||
assert.equal(fn.getCall(2).args[0], 3);
|
||||
});
|
||||
});
|
||||
|
||||
test('halts on stop condition', () => {
|
||||
const stub = sinon.stub();
|
||||
const fn = (e, stop) => {
|
||||
stub(e);
|
||||
stop();
|
||||
return Promise.resolve();
|
||||
};
|
||||
return Gerrit.AsyncForeachBehavior.asyncForeach([1, 2, 3], fn)
|
||||
.then(() => {
|
||||
assert.isTrue(stub.calledOnce);
|
||||
assert.equal(stub.lastCall.args[0], 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,21 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
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>
|
||||
/**
|
||||
* @license
|
||||
* 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -46,4 +44,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,17 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>base-url-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<script>
|
||||
/** @type {string} */
|
||||
window.CANONICAL_PATH = '/r';
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './base-url-behavior.js';
|
||||
/** @type {string} */
|
||||
window.CANONICAL_PATH = '/r';
|
||||
</script>
|
||||
<link rel="import" href="base-url-behavior.html">
|
||||
<script type="module" src="./base-url-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -45,30 +48,33 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('base-url-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './base-url-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('base-url-behavior tests', () => {
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('getBaseUrl', () => {
|
||||
assert.deepEqual(element.getBaseUrl(), '/r');
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('getBaseUrl', () => {
|
||||
assert.deepEqual(element.getBaseUrl(), '/r');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,21 +1,21 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* 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.
|
||||
*/
|
||||
import '../base-url-behavior/base-url-behavior.js';
|
||||
|
||||
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="../base-url-behavior/base-url-behavior.html">
|
||||
<script>
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -77,4 +77,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -15,17 +15,22 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Polymer included for the html import polyfill. -->
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<title>docs-url-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<link rel="import" href="docs-url-behavior.html">
|
||||
<script type="module" src="./docs-url-behavior.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './docs-url-behavior.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -33,71 +38,74 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('docs-url-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './docs-url-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('docs-url-behavior tests', () => {
|
||||
let element;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'docs-url-behavior-element',
|
||||
behaviors: [Gerrit.DocsUrlBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
element._clearDocsBaseUrlCache();
|
||||
});
|
||||
|
||||
test('null config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
return element.getDocsBaseUrl(null, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.equal(docsBaseUrl, '/Documentation');
|
||||
});
|
||||
});
|
||||
|
||||
test('no doc config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
const config = {gerrit: {}};
|
||||
return element.getDocsBaseUrl(config, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.equal(docsBaseUrl, '/Documentation');
|
||||
});
|
||||
});
|
||||
|
||||
test('has doc config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
const config = {gerrit: {doc_url: 'foobar'}};
|
||||
return element.getDocsBaseUrl(config, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isFalse(mockRestApi.probePath.called);
|
||||
assert.equal(docsBaseUrl, 'foobar');
|
||||
});
|
||||
});
|
||||
|
||||
test('no probe', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(false)),
|
||||
};
|
||||
return element.getDocsBaseUrl(null, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.isNotOk(docsBaseUrl);
|
||||
});
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'docs-url-behavior-element',
|
||||
behaviors: [Gerrit.DocsUrlBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
element._clearDocsBaseUrlCache();
|
||||
});
|
||||
|
||||
test('null config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
return element.getDocsBaseUrl(null, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.equal(docsBaseUrl, '/Documentation');
|
||||
});
|
||||
});
|
||||
|
||||
test('no doc config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
const config = {gerrit: {}};
|
||||
return element.getDocsBaseUrl(config, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.equal(docsBaseUrl, '/Documentation');
|
||||
});
|
||||
});
|
||||
|
||||
test('has doc config', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(true)),
|
||||
};
|
||||
const config = {gerrit: {doc_url: 'foobar'}};
|
||||
return element.getDocsBaseUrl(config, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isFalse(mockRestApi.probePath.called);
|
||||
assert.equal(docsBaseUrl, 'foobar');
|
||||
});
|
||||
});
|
||||
|
||||
test('no probe', () => {
|
||||
const mockRestApi = {
|
||||
probePath: sinon.stub().returns(Promise.resolve(false)),
|
||||
};
|
||||
return element.getDocsBaseUrl(null, mockRestApi)
|
||||
.then(docsBaseUrl => {
|
||||
assert.isTrue(
|
||||
mockRestApi.probePath.calledWith('/Documentation/index.html'));
|
||||
assert.isNotOk(docsBaseUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@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.
|
||||
-->
|
||||
<script>
|
||||
/**
|
||||
* @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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -61,4 +60,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>dom-util-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="dom-util-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./dom-util-behavior.js"></script>
|
||||
|
||||
<test-fixture id="nested-structure">
|
||||
<template>
|
||||
@ -40,34 +40,37 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('dom-util-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let divs;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './dom-util-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('dom-util-behavior tests', () => {
|
||||
let element;
|
||||
let divs;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.DomUtilBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
const testDom = fixture('nested-structure');
|
||||
element = testDom[0];
|
||||
divs = testDom[1];
|
||||
});
|
||||
|
||||
test('descendedFromClass', () => {
|
||||
// .c is a child of .a and not vice versa.
|
||||
assert.isTrue(element.descendedFromClass(divs.querySelector('.c'), 'a'));
|
||||
assert.isFalse(element.descendedFromClass(divs.querySelector('.a'), 'c'));
|
||||
|
||||
// Stops at stop element.
|
||||
assert.isFalse(element.descendedFromClass(divs.querySelector('.c'), 'a',
|
||||
divs.querySelector('.b')));
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.DomUtilBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
const testDom = fixture('nested-structure');
|
||||
element = testDom[0];
|
||||
divs = testDom[1];
|
||||
});
|
||||
|
||||
test('descendedFromClass', () => {
|
||||
// .c is a child of .a and not vice versa.
|
||||
assert.isTrue(element.descendedFromClass(divs.querySelector('.c'), 'a'));
|
||||
assert.isFalse(element.descendedFromClass(divs.querySelector('.a'), 'c'));
|
||||
|
||||
// Stops at stop element.
|
||||
assert.isFalse(element.descendedFromClass(divs.querySelector('.c'), 'a',
|
||||
divs.querySelector('.b')));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,21 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2019 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>
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2019 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -69,4 +67,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -1,21 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
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>
|
||||
/**
|
||||
* @license
|
||||
* 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -177,4 +175,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-access-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-access-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -33,41 +33,44 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-access-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-access-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-access-behavior tests', () => {
|
||||
let element;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.AccessBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('toSortedArray', () => {
|
||||
const rules = {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW', force: false,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW', force: false,
|
||||
},
|
||||
};
|
||||
const expectedResult = [
|
||||
{id: '4c97682e6ce6b7247f3381b6f1789356666de7f', value: {
|
||||
action: 'ALLOW', force: false,
|
||||
}},
|
||||
{id: 'global:Project-Owners', value: {
|
||||
action: 'ALLOW', force: false,
|
||||
}},
|
||||
];
|
||||
assert.deepEqual(element.toSortedArray(rules), expectedResult);
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.AccessBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('toSortedArray', () => {
|
||||
const rules = {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW', force: false,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW', force: false,
|
||||
},
|
||||
};
|
||||
const expectedResult = [
|
||||
{id: '4c97682e6ce6b7247f3381b6f1789356666de7f', value: {
|
||||
action: 'ALLOW', force: false,
|
||||
}},
|
||||
{id: 'global:Project-Owners', value: {
|
||||
action: 'ALLOW', force: false,
|
||||
}},
|
||||
];
|
||||
assert.deepEqual(element.toSortedArray(rules), expectedResult);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@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.
|
||||
-->
|
||||
<script>
|
||||
/**
|
||||
* @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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -220,4 +219,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-admin-nav-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-admin-nav-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -33,338 +33,341 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-admin-nav-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
let capabilityStub;
|
||||
let menuLinkStub;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-admin-nav-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-admin-nav-behavior tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
let capabilityStub;
|
||||
let menuLinkStub;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.AdminNavBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
sandbox = sinon.sandbox.create();
|
||||
capabilityStub = sinon.stub();
|
||||
menuLinkStub = sinon.stub().returns([]);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
const testAdminLinks = (account, options, expected, done) => {
|
||||
element.getAdminLinks(account,
|
||||
capabilityStub,
|
||||
menuLinkStub,
|
||||
options)
|
||||
.then(res => {
|
||||
assert.equal(expected.totalLength, res.links.length);
|
||||
assert.equal(res.links[0].name, 'Repositories');
|
||||
// Repos
|
||||
if (expected.groupListShown) {
|
||||
assert.equal(res.links[1].name, 'Groups');
|
||||
}
|
||||
|
||||
if (expected.pluginListShown) {
|
||||
assert.equal(res.links[2].name, 'Plugins');
|
||||
assert.isNotOk(res.links[2].subsection);
|
||||
}
|
||||
|
||||
if (expected.projectPageShown) {
|
||||
assert.isOk(res.links[0].subsection);
|
||||
assert.equal(res.links[0].subsection.children.length, 5);
|
||||
} else {
|
||||
assert.isNotOk(res.links[0].subsection);
|
||||
}
|
||||
// Groups
|
||||
if (expected.groupPageShown) {
|
||||
assert.isOk(res.links[1].subsection);
|
||||
assert.equal(res.links[1].subsection.children.length,
|
||||
expected.groupSubpageLength);
|
||||
} else if ( expected.totalLength > 1) {
|
||||
assert.isNotOk(res.links[1].subsection);
|
||||
}
|
||||
|
||||
if (expected.pluginGeneratedLinks) {
|
||||
for (const link of expected.pluginGeneratedLinks) {
|
||||
const linkMatch = res.links
|
||||
.find(l => (l.url === link.url && l.name === link.text));
|
||||
assert.isTrue(!!linkMatch);
|
||||
|
||||
// External links should open in new tab.
|
||||
if (link.url[0] !== '/') {
|
||||
assert.equal(linkMatch.target, '_blank');
|
||||
} else {
|
||||
assert.isNotOk(linkMatch.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current section
|
||||
if (expected.projectPageShown || expected.groupPageShown) {
|
||||
assert.isOk(res.expandedSection);
|
||||
assert.isOk(res.expandedSection.children);
|
||||
} else {
|
||||
assert.isNotOk(res.expandedSection);
|
||||
}
|
||||
if (expected.projectPageShown) {
|
||||
assert.equal(res.expandedSection.name, 'my-repo');
|
||||
assert.equal(res.expandedSection.children.length, 5);
|
||||
} else if (expected.groupPageShown) {
|
||||
assert.equal(res.expandedSection.name, 'my-group');
|
||||
assert.equal(res.expandedSection.children.length,
|
||||
expected.groupSubpageLength);
|
||||
}
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
suite('logged out', () => {
|
||||
let account;
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
expected = {
|
||||
groupListShown: false,
|
||||
groupPageShown: false,
|
||||
pluginListShown: false,
|
||||
};
|
||||
});
|
||||
|
||||
test('without a specific repo or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 1,
|
||||
projectPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 1,
|
||||
projectPageShown: true,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with plugin generated links', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'internal link text', url: '/internal/link/url'},
|
||||
{text: 'external link text', url: 'http://external/link/url'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 3,
|
||||
projectPageShown: false,
|
||||
pluginGeneratedLinks: generatedLinks,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('no plugin capability logged in', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
expected = {
|
||||
totalLength: 2,
|
||||
pluginListShown: false,
|
||||
};
|
||||
capabilityStub.returns(Promise.resolve({}));
|
||||
});
|
||||
|
||||
test('without a specific project or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupListShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: true,
|
||||
groupListShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin capability logged in', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({viewPlugins: true}));
|
||||
expected = {
|
||||
totalLength: 3,
|
||||
groupListShown: true,
|
||||
pluginListShown: true,
|
||||
};
|
||||
});
|
||||
|
||||
test('without a specific repo or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('admin with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: true,
|
||||
groupOwner: false,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 2,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('group owner with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: false,
|
||||
groupOwner: true,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 2,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('non owner or admin with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: false,
|
||||
groupOwner: false,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 1,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('admin with external group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: false,
|
||||
isAdmin: true,
|
||||
groupOwner: true,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 0,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin screen with plugin capability', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({pluginCapability: true}));
|
||||
expected = {};
|
||||
});
|
||||
|
||||
test('with plugin with capabilities', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'without capability', url: '/without'},
|
||||
{text: 'with capability',
|
||||
url: '/with',
|
||||
capability: 'pluginCapability'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 4,
|
||||
pluginGeneratedLinks: generatedLinks,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin screen without plugin capability', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({}));
|
||||
expected = {};
|
||||
});
|
||||
|
||||
test('with plugin with capabilities', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'without capability', url: '/without'},
|
||||
{text: 'with capability',
|
||||
url: '/with',
|
||||
capability: 'pluginCapability'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 3,
|
||||
pluginGeneratedLinks: [generatedLinks[0]],
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.AdminNavBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
sandbox = sinon.sandbox.create();
|
||||
capabilityStub = sinon.stub();
|
||||
menuLinkStub = sinon.stub().returns([]);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
const testAdminLinks = (account, options, expected, done) => {
|
||||
element.getAdminLinks(account,
|
||||
capabilityStub,
|
||||
menuLinkStub,
|
||||
options)
|
||||
.then(res => {
|
||||
assert.equal(expected.totalLength, res.links.length);
|
||||
assert.equal(res.links[0].name, 'Repositories');
|
||||
// Repos
|
||||
if (expected.groupListShown) {
|
||||
assert.equal(res.links[1].name, 'Groups');
|
||||
}
|
||||
|
||||
if (expected.pluginListShown) {
|
||||
assert.equal(res.links[2].name, 'Plugins');
|
||||
assert.isNotOk(res.links[2].subsection);
|
||||
}
|
||||
|
||||
if (expected.projectPageShown) {
|
||||
assert.isOk(res.links[0].subsection);
|
||||
assert.equal(res.links[0].subsection.children.length, 5);
|
||||
} else {
|
||||
assert.isNotOk(res.links[0].subsection);
|
||||
}
|
||||
// Groups
|
||||
if (expected.groupPageShown) {
|
||||
assert.isOk(res.links[1].subsection);
|
||||
assert.equal(res.links[1].subsection.children.length,
|
||||
expected.groupSubpageLength);
|
||||
} else if ( expected.totalLength > 1) {
|
||||
assert.isNotOk(res.links[1].subsection);
|
||||
}
|
||||
|
||||
if (expected.pluginGeneratedLinks) {
|
||||
for (const link of expected.pluginGeneratedLinks) {
|
||||
const linkMatch = res.links
|
||||
.find(l => (l.url === link.url && l.name === link.text));
|
||||
assert.isTrue(!!linkMatch);
|
||||
|
||||
// External links should open in new tab.
|
||||
if (link.url[0] !== '/') {
|
||||
assert.equal(linkMatch.target, '_blank');
|
||||
} else {
|
||||
assert.isNotOk(linkMatch.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current section
|
||||
if (expected.projectPageShown || expected.groupPageShown) {
|
||||
assert.isOk(res.expandedSection);
|
||||
assert.isOk(res.expandedSection.children);
|
||||
} else {
|
||||
assert.isNotOk(res.expandedSection);
|
||||
}
|
||||
if (expected.projectPageShown) {
|
||||
assert.equal(res.expandedSection.name, 'my-repo');
|
||||
assert.equal(res.expandedSection.children.length, 5);
|
||||
} else if (expected.groupPageShown) {
|
||||
assert.equal(res.expandedSection.name, 'my-group');
|
||||
assert.equal(res.expandedSection.children.length,
|
||||
expected.groupSubpageLength);
|
||||
}
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
suite('logged out', () => {
|
||||
let account;
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
expected = {
|
||||
groupListShown: false,
|
||||
groupPageShown: false,
|
||||
pluginListShown: false,
|
||||
};
|
||||
});
|
||||
|
||||
test('without a specific repo or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 1,
|
||||
projectPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 1,
|
||||
projectPageShown: true,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with plugin generated links', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'internal link text', url: '/internal/link/url'},
|
||||
{text: 'external link text', url: 'http://external/link/url'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 3,
|
||||
projectPageShown: false,
|
||||
pluginGeneratedLinks: generatedLinks,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('no plugin capability logged in', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
expected = {
|
||||
totalLength: 2,
|
||||
pluginListShown: false,
|
||||
};
|
||||
capabilityStub.returns(Promise.resolve({}));
|
||||
});
|
||||
|
||||
test('without a specific project or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupListShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: true,
|
||||
groupListShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin capability logged in', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({viewPlugins: true}));
|
||||
expected = {
|
||||
totalLength: 3,
|
||||
groupListShown: true,
|
||||
pluginListShown: true,
|
||||
};
|
||||
});
|
||||
|
||||
test('without a specific repo or group', done => {
|
||||
let options;
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('with a repo', done => {
|
||||
const options = {repoName: 'my-repo'};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: true,
|
||||
groupPageShown: false,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('admin with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: true,
|
||||
groupOwner: false,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 2,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('group owner with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: false,
|
||||
groupOwner: true,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 2,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('non owner or admin with internal group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: true,
|
||||
isAdmin: false,
|
||||
groupOwner: false,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 1,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
|
||||
test('admin with external group', done => {
|
||||
const options = {
|
||||
groupId: 'a15262',
|
||||
groupName: 'my-group',
|
||||
groupIsInternal: false,
|
||||
isAdmin: true,
|
||||
groupOwner: true,
|
||||
};
|
||||
expected = Object.assign(expected, {
|
||||
projectPageShown: false,
|
||||
groupPageShown: true,
|
||||
groupSubpageLength: 0,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin screen with plugin capability', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({pluginCapability: true}));
|
||||
expected = {};
|
||||
});
|
||||
|
||||
test('with plugin with capabilities', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'without capability', url: '/without'},
|
||||
{text: 'with capability',
|
||||
url: '/with',
|
||||
capability: 'pluginCapability'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 4,
|
||||
pluginGeneratedLinks: generatedLinks,
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view plugin screen without plugin capability', () => {
|
||||
const account = {
|
||||
name: 'test-user',
|
||||
};
|
||||
let expected;
|
||||
|
||||
setup(() => {
|
||||
capabilityStub.returns(Promise.resolve({}));
|
||||
expected = {};
|
||||
});
|
||||
|
||||
test('with plugin with capabilities', done => {
|
||||
let options;
|
||||
const generatedLinks = [
|
||||
{text: 'without capability', url: '/without'},
|
||||
{text: 'with capability',
|
||||
url: '/with',
|
||||
capability: 'pluginCapability'},
|
||||
];
|
||||
menuLinkStub.returns(generatedLinks);
|
||||
expected = Object.assign(expected, {
|
||||
totalLength: 3,
|
||||
pluginGeneratedLinks: [generatedLinks[0]],
|
||||
});
|
||||
testAdminLinks(account, options, expected, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 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>
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -105,4 +104,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-change-table-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-change-table-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -41,87 +41,90 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-change-table-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-change-table-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-change-table-behavior tests', () => {
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.ChangeTableBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('getComplementColumns', () => {
|
||||
let columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.deepEqual(element.getComplementColumns(columns), []);
|
||||
|
||||
columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Size',
|
||||
];
|
||||
assert.deepEqual(element.getComplementColumns(columns),
|
||||
['Owner', 'Updated']);
|
||||
});
|
||||
|
||||
test('isColumnHidden', () => {
|
||||
const columnToCheck = 'Repo';
|
||||
let columnsToDisplay = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.isFalse(element.isColumnHidden(columnToCheck, columnsToDisplay));
|
||||
|
||||
columnsToDisplay = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.isTrue(element.isColumnHidden(columnToCheck, columnsToDisplay));
|
||||
});
|
||||
|
||||
test('getVisibleColumns maps Project to Repo', () => {
|
||||
const columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
];
|
||||
assert.deepEqual(element.getVisibleColumns(columns), columns.slice(0));
|
||||
assert.deepEqual(
|
||||
element.getVisibleColumns(columns.concat(['Project'])),
|
||||
columns.slice(0).concat(['Repo']));
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.ChangeTableBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('getComplementColumns', () => {
|
||||
let columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.deepEqual(element.getComplementColumns(columns), []);
|
||||
|
||||
columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Size',
|
||||
];
|
||||
assert.deepEqual(element.getComplementColumns(columns),
|
||||
['Owner', 'Updated']);
|
||||
});
|
||||
|
||||
test('isColumnHidden', () => {
|
||||
const columnToCheck = 'Repo';
|
||||
let columnsToDisplay = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Repo',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.isFalse(element.isColumnHidden(columnToCheck, columnsToDisplay));
|
||||
|
||||
columnsToDisplay = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
'Assignee',
|
||||
'Branch',
|
||||
'Updated',
|
||||
'Size',
|
||||
];
|
||||
assert.isTrue(element.isColumnHidden(columnToCheck, columnsToDisplay));
|
||||
});
|
||||
|
||||
test('getVisibleColumns maps Project to Repo', () => {
|
||||
const columns = [
|
||||
'Subject',
|
||||
'Status',
|
||||
'Owner',
|
||||
];
|
||||
assert.deepEqual(element.getVisibleColumns(columns), columns.slice(0));
|
||||
assert.deepEqual(
|
||||
element.getVisibleColumns(columns.concat(['Project'])),
|
||||
columns.slice(0).concat(['Repo']));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,23 +1,21 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* 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.
|
||||
*/
|
||||
import '../../scripts/gr-display-name-utils/gr-display-name-utils.js';
|
||||
|
||||
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 src="../../scripts/gr-display-name-utils/gr-display-name-utils.js"></script>
|
||||
|
||||
<script>
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -57,4 +55,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-display-name-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-display-name-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-display-name-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -33,69 +33,72 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-display-name-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const config = {
|
||||
user: {
|
||||
anonymous_coward_name: 'Anonymous Coward',
|
||||
},
|
||||
};
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-display-name-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-display-name-behavior tests', () => {
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const config = {
|
||||
user: {
|
||||
anonymous_coward_name: 'Anonymous Coward',
|
||||
},
|
||||
};
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element-anon',
|
||||
behaviors: [
|
||||
Gerrit.DisplayNameBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('getUserName name only', () => {
|
||||
const account = {
|
||||
name: 'test-name',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true), 'test-name');
|
||||
});
|
||||
|
||||
test('getUserName username only', () => {
|
||||
const account = {
|
||||
username: 'test-user',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true), 'test-user');
|
||||
});
|
||||
|
||||
test('getUserName email only', () => {
|
||||
const account = {
|
||||
email: 'test-user@test-url.com',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true),
|
||||
'test-user@test-url.com');
|
||||
});
|
||||
|
||||
test('getUserName returns not Anonymous Coward as the anon name', () => {
|
||||
assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
|
||||
});
|
||||
|
||||
test('getUserName for the config returning the anon name', () => {
|
||||
const config = {
|
||||
user: {
|
||||
anonymous_coward_name: 'Test Anon',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
|
||||
});
|
||||
|
||||
test('getGroupDisplayName', () => {
|
||||
assert.equal(element.getGroupDisplayName({name: 'Some user name'}),
|
||||
'Some user name (group)');
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element-anon',
|
||||
behaviors: [
|
||||
Gerrit.DisplayNameBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('getUserName name only', () => {
|
||||
const account = {
|
||||
name: 'test-name',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true), 'test-name');
|
||||
});
|
||||
|
||||
test('getUserName username only', () => {
|
||||
const account = {
|
||||
username: 'test-user',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true), 'test-user');
|
||||
});
|
||||
|
||||
test('getUserName email only', () => {
|
||||
const account = {
|
||||
email: 'test-user@test-url.com',
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, account, true),
|
||||
'test-user@test-url.com');
|
||||
});
|
||||
|
||||
test('getUserName returns not Anonymous Coward as the anon name', () => {
|
||||
assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
|
||||
});
|
||||
|
||||
test('getUserName for the config returning the anon name', () => {
|
||||
const config = {
|
||||
user: {
|
||||
anonymous_coward_name: 'Test Anon',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
|
||||
});
|
||||
|
||||
test('getGroupDisplayName', () => {
|
||||
assert.equal(element.getGroupDisplayName({name: 'Some user name'}),
|
||||
'Some user name (group)');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* 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.
|
||||
*/
|
||||
import '../base-url-behavior/base-url-behavior.js';
|
||||
|
||||
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="../base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<script>
|
||||
import '../gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -80,5 +80,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-list-view-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-list-view-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -33,62 +33,65 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-list-view-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-list-view-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-list-view-behavior tests', () => {
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.ListViewBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('computeLoadingClass', () => {
|
||||
assert.equal(element.computeLoadingClass(true), 'loading');
|
||||
assert.equal(element.computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('computeShownItems', () => {
|
||||
const myArr = new Array(26);
|
||||
assert.equal(element.computeShownItems(myArr).length, 25);
|
||||
});
|
||||
|
||||
test('getUrl', () => {
|
||||
assert.equal(element.getUrl('/path/to/something/', 'item'),
|
||||
'/path/to/something/item');
|
||||
assert.equal(element.getUrl('/path/to/something/', 'item%test'),
|
||||
'/path/to/something/item%2525test');
|
||||
});
|
||||
|
||||
test('getFilterValue', () => {
|
||||
let params;
|
||||
assert.equal(element.getFilterValue(params), '');
|
||||
|
||||
params = {filter: null};
|
||||
assert.equal(element.getFilterValue(params), '');
|
||||
|
||||
params = {filter: 'test'};
|
||||
assert.equal(element.getFilterValue(params), 'test');
|
||||
});
|
||||
|
||||
test('getOffsetValue', () => {
|
||||
let params;
|
||||
assert.equal(element.getOffsetValue(params), 0);
|
||||
|
||||
params = {offset: null};
|
||||
assert.equal(element.getOffsetValue(params), 0);
|
||||
|
||||
params = {offset: 1};
|
||||
assert.equal(element.getOffsetValue(params), 1);
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.ListViewBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('computeLoadingClass', () => {
|
||||
assert.equal(element.computeLoadingClass(true), 'loading');
|
||||
assert.equal(element.computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('computeShownItems', () => {
|
||||
const myArr = new Array(26);
|
||||
assert.equal(element.computeShownItems(myArr).length, 25);
|
||||
});
|
||||
|
||||
test('getUrl', () => {
|
||||
assert.equal(element.getUrl('/path/to/something/', 'item'),
|
||||
'/path/to/something/item');
|
||||
assert.equal(element.getUrl('/path/to/something/', 'item%test'),
|
||||
'/path/to/something/item%2525test');
|
||||
});
|
||||
|
||||
test('getFilterValue', () => {
|
||||
let params;
|
||||
assert.equal(element.getFilterValue(params), '');
|
||||
|
||||
params = {filter: null};
|
||||
assert.equal(element.getFilterValue(params), '');
|
||||
|
||||
params = {filter: 'test'};
|
||||
assert.equal(element.getFilterValue(params), 'test');
|
||||
});
|
||||
|
||||
test('getOffsetValue', () => {
|
||||
let params;
|
||||
assert.equal(element.getOffsetValue(params), 0);
|
||||
|
||||
params = {offset: null};
|
||||
assert.equal(element.getOffsetValue(params), 0);
|
||||
|
||||
params = {offset: 1};
|
||||
assert.equal(element.getOffsetValue(params), 1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 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>
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -298,4 +297,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -15,313 +15,315 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Polymer included for the html import polyfill. -->
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<title>gr-patch-set-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<link rel="import" href="gr-patch-set-behavior.html">
|
||||
<script type="module" src="./gr-patch-set-behavior.js"></script>
|
||||
|
||||
<script>
|
||||
suite('gr-patch-set-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
test('getRevisionByPatchNum', () => {
|
||||
const get = Gerrit.PatchSetBehavior.getRevisionByPatchNum;
|
||||
const revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.deepEqual(get(revisions, '1'), revisions[1]);
|
||||
assert.deepEqual(get(revisions, 2), revisions[2]);
|
||||
assert.equal(get(revisions, '3'), undefined);
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates on latest', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(knownChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates not on latest', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
sha3: {description: 'patch 3', _number: 3},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isFalse(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates new status', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'MERGED',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.equal(result.newStatus, 'MERGED');
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates new messages', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [{message: 'blah blah'}],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isTrue(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeWipForPatchSets', () => {
|
||||
// Compute patch sets for a given timeline on a change. The initial WIP
|
||||
// property of the change can be true or false. The map of tags by
|
||||
// revision is keyed by patch set number. Each value is a list of change
|
||||
// message tags in the order that they occurred in the timeline. These
|
||||
// indicate actions that modify the WIP property of the change and/or
|
||||
// create new patch sets.
|
||||
//
|
||||
// Returns the actual results with an assertWip method that can be used
|
||||
// to compare against an expected value for a particular patch set.
|
||||
const compute = (initialWip, tagsByRevision) => {
|
||||
const change = {
|
||||
messages: [],
|
||||
work_in_progress: initialWip,
|
||||
};
|
||||
const revs = Object.keys(tagsByRevision).sort((a, b) => a - b);
|
||||
for (const rev of revs) {
|
||||
for (const tag of tagsByRevision[rev]) {
|
||||
change.messages.push({
|
||||
tag,
|
||||
_revision_number: rev,
|
||||
});
|
||||
}
|
||||
}
|
||||
let patchNums = revs.map(rev => { return {num: rev}; });
|
||||
patchNums = Gerrit.PatchSetBehavior._computeWipForPatchSets(
|
||||
change, patchNums);
|
||||
const actualWipsByRevision = {};
|
||||
for (const patchNum of patchNums) {
|
||||
actualWipsByRevision[patchNum.num] = patchNum.wip;
|
||||
}
|
||||
const verifier = {
|
||||
assertWip(revision, expectedWip) {
|
||||
const patchNum = patchNums.find(patchNum => patchNum.num == revision);
|
||||
if (!patchNum) {
|
||||
assert.fail('revision ' + revision + ' not found');
|
||||
}
|
||||
assert.equal(patchNum.wip, expectedWip,
|
||||
'wip state for ' + revision + ' is ' +
|
||||
patchNum.wip + '; expected ' + expectedWip);
|
||||
return verifier;
|
||||
},
|
||||
};
|
||||
return verifier;
|
||||
};
|
||||
|
||||
compute(false, {1: ['upload']}).assertWip(1, false);
|
||||
compute(true, {1: ['upload']}).assertWip(1, true);
|
||||
|
||||
const setWip = 'autogenerated:gerrit:setWorkInProgress';
|
||||
const uploadInWip = 'autogenerated:gerrit:newWipPatchSet';
|
||||
const clearWip = 'autogenerated:gerrit:setReadyForReview';
|
||||
|
||||
compute(false, {
|
||||
1: ['upload', setWip],
|
||||
2: ['upload'],
|
||||
3: ['upload', clearWip],
|
||||
4: ['upload', setWip],
|
||||
}).assertWip(1, false) // Change was created with PS1 ready for review
|
||||
.assertWip(2, true) // PS2 was uploaded during WIP
|
||||
.assertWip(3, false) // PS3 was marked ready for review after upload
|
||||
.assertWip(4, false); // PS4 was uploaded ready for review
|
||||
|
||||
compute(false, {
|
||||
1: [uploadInWip, null, 'addReviewer'],
|
||||
2: ['upload'],
|
||||
3: ['upload', clearWip, setWip],
|
||||
4: ['upload'],
|
||||
5: ['upload', clearWip],
|
||||
6: [uploadInWip],
|
||||
}).assertWip(1, true) // Change was created in WIP
|
||||
.assertWip(2, true) // PS2 was uploaded during WIP
|
||||
.assertWip(3, false) // PS3 was marked ready for review
|
||||
.assertWip(4, true) // PS4 was uploaded during WIP
|
||||
.assertWip(5, false) // PS5 was marked ready for review
|
||||
.assertWip(6, true); // PS6 was uploaded with WIP option
|
||||
});
|
||||
|
||||
test('patchNumEquals', () => {
|
||||
const equals = Gerrit.PatchSetBehavior.patchNumEquals;
|
||||
assert.isFalse(equals('edit', 'PARENT'));
|
||||
assert.isFalse(equals('edit', NaN));
|
||||
assert.isFalse(equals(1, '2'));
|
||||
|
||||
assert.isTrue(equals(1, '1'));
|
||||
assert.isTrue(equals(1, 1));
|
||||
assert.isTrue(equals('edit', 'edit'));
|
||||
assert.isTrue(equals('PARENT', 'PARENT'));
|
||||
});
|
||||
|
||||
test('isMergeParent', () => {
|
||||
const isParent = Gerrit.PatchSetBehavior.isMergeParent;
|
||||
assert.isFalse(isParent(1));
|
||||
assert.isFalse(isParent(4321));
|
||||
assert.isFalse(isParent('52'));
|
||||
assert.isFalse(isParent('edit'));
|
||||
assert.isFalse(isParent('PARENT'));
|
||||
assert.isFalse(isParent(0));
|
||||
|
||||
assert.isTrue(isParent(-23));
|
||||
assert.isTrue(isParent(-1));
|
||||
assert.isTrue(isParent('-42'));
|
||||
});
|
||||
|
||||
test('findEditParentRevision', () => {
|
||||
const findParent = Gerrit.PatchSetBehavior.findEditParentRevision;
|
||||
let revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.strictEqual(findParent(revisions), null);
|
||||
|
||||
revisions = [...revisions, {_number: 'edit', basePatchNum: 3}];
|
||||
assert.strictEqual(findParent(revisions), null);
|
||||
|
||||
revisions = [...revisions, {_number: 3}];
|
||||
assert.deepEqual(findParent(revisions), {_number: 3});
|
||||
});
|
||||
|
||||
test('findEditParentPatchNum', () => {
|
||||
const findNum = Gerrit.PatchSetBehavior.findEditParentPatchNum;
|
||||
let revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.equal(findNum(revisions), -1);
|
||||
|
||||
revisions =
|
||||
[...revisions, {_number: 'edit', basePatchNum: 3}, {_number: 3}];
|
||||
assert.deepEqual(findNum(revisions), 3);
|
||||
});
|
||||
|
||||
test('sortRevisions', () => {
|
||||
const sort = Gerrit.PatchSetBehavior.sortRevisions;
|
||||
const revisions = [
|
||||
{_number: 0},
|
||||
{_number: 2},
|
||||
{_number: 1},
|
||||
];
|
||||
const sorted = [
|
||||
{_number: 2},
|
||||
{_number: 1},
|
||||
{_number: 0},
|
||||
];
|
||||
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
|
||||
// Edit patchset should follow directly after its basePatchNum.
|
||||
revisions.push({_number: 'edit', basePatchNum: 2});
|
||||
sorted.unshift({_number: 'edit', basePatchNum: 2});
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
|
||||
revisions[0].basePatchNum = 0;
|
||||
const edit = sorted.shift();
|
||||
edit.basePatchNum = 0;
|
||||
// Edit patchset should be at index 2.
|
||||
sorted.splice(2, 0, edit);
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
});
|
||||
|
||||
test('getParentIndex', () => {
|
||||
assert.equal(Gerrit.PatchSetBehavior.getParentIndex('-13'), 13);
|
||||
assert.equal(Gerrit.PatchSetBehavior.getParentIndex(-4), 4);
|
||||
});
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-patch-set-behavior.js';
|
||||
suite('gr-patch-set-behavior tests', () => {
|
||||
test('getRevisionByPatchNum', () => {
|
||||
const get = Gerrit.PatchSetBehavior.getRevisionByPatchNum;
|
||||
const revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.deepEqual(get(revisions, '1'), revisions[1]);
|
||||
assert.deepEqual(get(revisions, 2), revisions[2]);
|
||||
assert.equal(get(revisions, '3'), undefined);
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates on latest', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(knownChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates not on latest', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
sha3: {description: 'patch 3', _number: 3},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isFalse(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates new status', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'MERGED',
|
||||
messages: [],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.equal(result.newStatus, 'MERGED');
|
||||
assert.isFalse(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchChangeUpdates new messages', done => {
|
||||
const knownChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [],
|
||||
};
|
||||
const actualChange = {
|
||||
revisions: {
|
||||
sha1: {description: 'patch 1', _number: 1},
|
||||
sha2: {description: 'patch 2', _number: 2},
|
||||
},
|
||||
status: 'NEW',
|
||||
messages: [{message: 'blah blah'}],
|
||||
};
|
||||
const mockRestApi = {
|
||||
getChangeDetail() {
|
||||
return Promise.resolve(actualChange);
|
||||
},
|
||||
};
|
||||
Gerrit.PatchSetBehavior.fetchChangeUpdates(knownChange, mockRestApi)
|
||||
.then(result => {
|
||||
assert.isTrue(result.isLatest);
|
||||
assert.isNotOk(result.newStatus);
|
||||
assert.isTrue(result.newMessages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeWipForPatchSets', () => {
|
||||
// Compute patch sets for a given timeline on a change. The initial WIP
|
||||
// property of the change can be true or false. The map of tags by
|
||||
// revision is keyed by patch set number. Each value is a list of change
|
||||
// message tags in the order that they occurred in the timeline. These
|
||||
// indicate actions that modify the WIP property of the change and/or
|
||||
// create new patch sets.
|
||||
//
|
||||
// Returns the actual results with an assertWip method that can be used
|
||||
// to compare against an expected value for a particular patch set.
|
||||
const compute = (initialWip, tagsByRevision) => {
|
||||
const change = {
|
||||
messages: [],
|
||||
work_in_progress: initialWip,
|
||||
};
|
||||
const revs = Object.keys(tagsByRevision).sort((a, b) => a - b);
|
||||
for (const rev of revs) {
|
||||
for (const tag of tagsByRevision[rev]) {
|
||||
change.messages.push({
|
||||
tag,
|
||||
_revision_number: rev,
|
||||
});
|
||||
}
|
||||
}
|
||||
let patchNums = revs.map(rev => { return {num: rev}; });
|
||||
patchNums = Gerrit.PatchSetBehavior._computeWipForPatchSets(
|
||||
change, patchNums);
|
||||
const actualWipsByRevision = {};
|
||||
for (const patchNum of patchNums) {
|
||||
actualWipsByRevision[patchNum.num] = patchNum.wip;
|
||||
}
|
||||
const verifier = {
|
||||
assertWip(revision, expectedWip) {
|
||||
const patchNum = patchNums.find(patchNum => patchNum.num == revision);
|
||||
if (!patchNum) {
|
||||
assert.fail('revision ' + revision + ' not found');
|
||||
}
|
||||
assert.equal(patchNum.wip, expectedWip,
|
||||
'wip state for ' + revision + ' is ' +
|
||||
patchNum.wip + '; expected ' + expectedWip);
|
||||
return verifier;
|
||||
},
|
||||
};
|
||||
return verifier;
|
||||
};
|
||||
|
||||
compute(false, {1: ['upload']}).assertWip(1, false);
|
||||
compute(true, {1: ['upload']}).assertWip(1, true);
|
||||
|
||||
const setWip = 'autogenerated:gerrit:setWorkInProgress';
|
||||
const uploadInWip = 'autogenerated:gerrit:newWipPatchSet';
|
||||
const clearWip = 'autogenerated:gerrit:setReadyForReview';
|
||||
|
||||
compute(false, {
|
||||
1: ['upload', setWip],
|
||||
2: ['upload'],
|
||||
3: ['upload', clearWip],
|
||||
4: ['upload', setWip],
|
||||
}).assertWip(1, false) // Change was created with PS1 ready for review
|
||||
.assertWip(2, true) // PS2 was uploaded during WIP
|
||||
.assertWip(3, false) // PS3 was marked ready for review after upload
|
||||
.assertWip(4, false); // PS4 was uploaded ready for review
|
||||
|
||||
compute(false, {
|
||||
1: [uploadInWip, null, 'addReviewer'],
|
||||
2: ['upload'],
|
||||
3: ['upload', clearWip, setWip],
|
||||
4: ['upload'],
|
||||
5: ['upload', clearWip],
|
||||
6: [uploadInWip],
|
||||
}).assertWip(1, true) // Change was created in WIP
|
||||
.assertWip(2, true) // PS2 was uploaded during WIP
|
||||
.assertWip(3, false) // PS3 was marked ready for review
|
||||
.assertWip(4, true) // PS4 was uploaded during WIP
|
||||
.assertWip(5, false) // PS5 was marked ready for review
|
||||
.assertWip(6, true); // PS6 was uploaded with WIP option
|
||||
});
|
||||
|
||||
test('patchNumEquals', () => {
|
||||
const equals = Gerrit.PatchSetBehavior.patchNumEquals;
|
||||
assert.isFalse(equals('edit', 'PARENT'));
|
||||
assert.isFalse(equals('edit', NaN));
|
||||
assert.isFalse(equals(1, '2'));
|
||||
|
||||
assert.isTrue(equals(1, '1'));
|
||||
assert.isTrue(equals(1, 1));
|
||||
assert.isTrue(equals('edit', 'edit'));
|
||||
assert.isTrue(equals('PARENT', 'PARENT'));
|
||||
});
|
||||
|
||||
test('isMergeParent', () => {
|
||||
const isParent = Gerrit.PatchSetBehavior.isMergeParent;
|
||||
assert.isFalse(isParent(1));
|
||||
assert.isFalse(isParent(4321));
|
||||
assert.isFalse(isParent('52'));
|
||||
assert.isFalse(isParent('edit'));
|
||||
assert.isFalse(isParent('PARENT'));
|
||||
assert.isFalse(isParent(0));
|
||||
|
||||
assert.isTrue(isParent(-23));
|
||||
assert.isTrue(isParent(-1));
|
||||
assert.isTrue(isParent('-42'));
|
||||
});
|
||||
|
||||
test('findEditParentRevision', () => {
|
||||
const findParent = Gerrit.PatchSetBehavior.findEditParentRevision;
|
||||
let revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.strictEqual(findParent(revisions), null);
|
||||
|
||||
revisions = [...revisions, {_number: 'edit', basePatchNum: 3}];
|
||||
assert.strictEqual(findParent(revisions), null);
|
||||
|
||||
revisions = [...revisions, {_number: 3}];
|
||||
assert.deepEqual(findParent(revisions), {_number: 3});
|
||||
});
|
||||
|
||||
test('findEditParentPatchNum', () => {
|
||||
const findNum = Gerrit.PatchSetBehavior.findEditParentPatchNum;
|
||||
let revisions = [
|
||||
{_number: 0},
|
||||
{_number: 1},
|
||||
{_number: 2},
|
||||
];
|
||||
assert.equal(findNum(revisions), -1);
|
||||
|
||||
revisions =
|
||||
[...revisions, {_number: 'edit', basePatchNum: 3}, {_number: 3}];
|
||||
assert.deepEqual(findNum(revisions), 3);
|
||||
});
|
||||
|
||||
test('sortRevisions', () => {
|
||||
const sort = Gerrit.PatchSetBehavior.sortRevisions;
|
||||
const revisions = [
|
||||
{_number: 0},
|
||||
{_number: 2},
|
||||
{_number: 1},
|
||||
];
|
||||
const sorted = [
|
||||
{_number: 2},
|
||||
{_number: 1},
|
||||
{_number: 0},
|
||||
];
|
||||
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
|
||||
// Edit patchset should follow directly after its basePatchNum.
|
||||
revisions.push({_number: 'edit', basePatchNum: 2});
|
||||
sorted.unshift({_number: 'edit', basePatchNum: 2});
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
|
||||
revisions[0].basePatchNum = 0;
|
||||
const edit = sorted.shift();
|
||||
edit.basePatchNum = 0;
|
||||
// Edit patchset should be at index 2.
|
||||
sorted.splice(2, 0, edit);
|
||||
assert.deepEqual(sort(revisions), sorted);
|
||||
});
|
||||
|
||||
test('getParentIndex', () => {
|
||||
assert.equal(Gerrit.PatchSetBehavior.getParentIndex('-13'), 13);
|
||||
assert.equal(Gerrit.PatchSetBehavior.getParentIndex(-4), 4);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,21 +1,21 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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.
|
||||
*/
|
||||
import '../../scripts/util.js';
|
||||
|
||||
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 src="../../scripts/util.js"></script>
|
||||
<script>
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -137,4 +137,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -15,89 +15,91 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Polymer included for the html import polyfill. -->
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<title>gr-path-list-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<link rel="import" href="gr-path-list-behavior.html">
|
||||
<script type="module" src="./gr-path-list-behavior.js"></script>
|
||||
|
||||
<script>
|
||||
suite('gr-path-list-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
test('special sort', () => {
|
||||
const sort = Gerrit.PathListBehavior.specialFilePathCompare;
|
||||
const testFiles = [
|
||||
'/a.h',
|
||||
'/MERGE_LIST',
|
||||
'/a.cpp',
|
||||
'/COMMIT_MSG',
|
||||
'/asdasd',
|
||||
'/mrPeanutbutter.py',
|
||||
];
|
||||
assert.deepEqual(
|
||||
testFiles.sort(sort),
|
||||
[
|
||||
'/COMMIT_MSG',
|
||||
'/MERGE_LIST',
|
||||
'/a.h',
|
||||
'/a.cpp',
|
||||
'/asdasd',
|
||||
'/mrPeanutbutter.py',
|
||||
]);
|
||||
});
|
||||
|
||||
test('file display name', () => {
|
||||
const name = Gerrit.PathListBehavior.computeDisplayPath;
|
||||
assert.equal(name('/foo/bar/baz'), '/foo/bar/baz');
|
||||
assert.equal(name('/foobarbaz'), '/foobarbaz');
|
||||
assert.equal(name('/COMMIT_MSG'), 'Commit message');
|
||||
assert.equal(name('/MERGE_LIST'), 'Merge list');
|
||||
});
|
||||
|
||||
test('isMagicPath', () => {
|
||||
const isMagic = Gerrit.PathListBehavior.isMagicPath;
|
||||
assert.isFalse(isMagic(undefined));
|
||||
assert.isFalse(isMagic('/foo.cc'));
|
||||
assert.isTrue(isMagic('/COMMIT_MSG'));
|
||||
assert.isTrue(isMagic('/MERGE_LIST'));
|
||||
});
|
||||
|
||||
test('truncatePath with long path should add ellipsis', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
let path = 'level1/level2/level3/level4/file.js';
|
||||
let shortenedPath = truncatePath(path);
|
||||
// The expected path is truncated with an ellipsis.
|
||||
const expectedPath = '\u2026/file.js';
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
|
||||
path = 'level2/file.js';
|
||||
shortenedPath = truncatePath(path);
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
});
|
||||
|
||||
test('truncatePath with opt_threshold', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
let path = 'level1/level2/level3/level4/file.js';
|
||||
let shortenedPath = truncatePath(path, 2);
|
||||
// The expected path is truncated with an ellipsis.
|
||||
const expectedPath = '\u2026/level4/file.js';
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
|
||||
path = 'level2/file.js';
|
||||
shortenedPath = truncatePath(path, 2);
|
||||
assert.equal(shortenedPath, path);
|
||||
});
|
||||
|
||||
test('truncatePath with short path should not add ellipsis', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
const path = 'file.js';
|
||||
const expectedPath = 'file.js';
|
||||
const shortenedPath = truncatePath(path);
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
});
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-path-list-behavior.js';
|
||||
suite('gr-path-list-behavior tests', () => {
|
||||
test('special sort', () => {
|
||||
const sort = Gerrit.PathListBehavior.specialFilePathCompare;
|
||||
const testFiles = [
|
||||
'/a.h',
|
||||
'/MERGE_LIST',
|
||||
'/a.cpp',
|
||||
'/COMMIT_MSG',
|
||||
'/asdasd',
|
||||
'/mrPeanutbutter.py',
|
||||
];
|
||||
assert.deepEqual(
|
||||
testFiles.sort(sort),
|
||||
[
|
||||
'/COMMIT_MSG',
|
||||
'/MERGE_LIST',
|
||||
'/a.h',
|
||||
'/a.cpp',
|
||||
'/asdasd',
|
||||
'/mrPeanutbutter.py',
|
||||
]);
|
||||
});
|
||||
|
||||
test('file display name', () => {
|
||||
const name = Gerrit.PathListBehavior.computeDisplayPath;
|
||||
assert.equal(name('/foo/bar/baz'), '/foo/bar/baz');
|
||||
assert.equal(name('/foobarbaz'), '/foobarbaz');
|
||||
assert.equal(name('/COMMIT_MSG'), 'Commit message');
|
||||
assert.equal(name('/MERGE_LIST'), 'Merge list');
|
||||
});
|
||||
|
||||
test('isMagicPath', () => {
|
||||
const isMagic = Gerrit.PathListBehavior.isMagicPath;
|
||||
assert.isFalse(isMagic(undefined));
|
||||
assert.isFalse(isMagic('/foo.cc'));
|
||||
assert.isTrue(isMagic('/COMMIT_MSG'));
|
||||
assert.isTrue(isMagic('/MERGE_LIST'));
|
||||
});
|
||||
|
||||
test('truncatePath with long path should add ellipsis', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
let path = 'level1/level2/level3/level4/file.js';
|
||||
let shortenedPath = truncatePath(path);
|
||||
// The expected path is truncated with an ellipsis.
|
||||
const expectedPath = '\u2026/file.js';
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
|
||||
path = 'level2/file.js';
|
||||
shortenedPath = truncatePath(path);
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
});
|
||||
|
||||
test('truncatePath with opt_threshold', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
let path = 'level1/level2/level3/level4/file.js';
|
||||
let shortenedPath = truncatePath(path, 2);
|
||||
// The expected path is truncated with an ellipsis.
|
||||
const expectedPath = '\u2026/level4/file.js';
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
|
||||
path = 'level2/file.js';
|
||||
shortenedPath = truncatePath(path, 2);
|
||||
assert.equal(shortenedPath, path);
|
||||
});
|
||||
|
||||
test('truncatePath with short path should not add ellipsis', () => {
|
||||
const truncatePath = Gerrit.PathListBehavior.truncatePath;
|
||||
const path = 'file.js';
|
||||
const expectedPath = 'file.js';
|
||||
const shortenedPath = truncatePath(path);
|
||||
assert.equal(shortenedPath, expectedPath);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2019 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>
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2019 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -52,4 +51,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 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="../../elements/shared/gr-tooltip/gr-tooltip.html">
|
||||
<script src="../../scripts/rootElement.js"></script>
|
||||
|
||||
<script src="gr-tooltip-behavior.js"></script>
|
@ -14,6 +14,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import '../../scripts/bundled-polymer.js';
|
||||
|
||||
import '../../elements/shared/gr-tooltip/gr-tooltip.js';
|
||||
import '../../scripts/rootElement.js';
|
||||
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -135,7 +141,7 @@
|
||||
_positionTooltip(tooltip) {
|
||||
// This flush is needed for tooltips to be positioned correctly in Firefox
|
||||
// and Safari.
|
||||
Polymer.dom.flush();
|
||||
flush();
|
||||
const rect = this.getBoundingClientRect();
|
||||
const boxRect = tooltip.getBoundingClientRect();
|
||||
const parentRect = tooltip.parentElement.getBoundingClientRect();
|
||||
|
@ -18,15 +18,20 @@ limitations under the License.
|
||||
|
||||
<title>tooltip-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-tooltip-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-tooltip-behavior.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-tooltip-behavior.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -34,124 +39,127 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-tooltip-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-tooltip-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-tooltip-behavior tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
function makeTooltip(tooltipRect, parentRect) {
|
||||
return {
|
||||
getBoundingClientRect() { return tooltipRect; },
|
||||
updateStyles: sinon.stub(),
|
||||
style: {left: 0, top: 0},
|
||||
parentElement: {
|
||||
getBoundingClientRect() { return parentRect; },
|
||||
},
|
||||
};
|
||||
}
|
||||
function makeTooltip(tooltipRect, parentRect) {
|
||||
return {
|
||||
getBoundingClientRect() { return tooltipRect; },
|
||||
updateStyles: sinon.stub(),
|
||||
style: {left: 0, top: 0},
|
||||
parentElement: {
|
||||
getBoundingClientRect() { return parentRect; },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'tooltip-behavior-element',
|
||||
behaviors: [Gerrit.TooltipBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('normal position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 100, width: 200};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 50},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isFalse(tooltip.updateStyles.called);
|
||||
assert.equal(tooltip.style.left, '175px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('left side position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 10, width: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isBelow(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '0px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('right side position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 950, width: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '915px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('position to bottom', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 950, width: 50, height: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element.positionBelow = true;
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '915px');
|
||||
assert.equal(tooltip.style.top, '157.2px');
|
||||
});
|
||||
|
||||
test('hides tooltip when detached', () => {
|
||||
sandbox.stub(element, '_handleHideTooltip');
|
||||
element.remove();
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element._handleHideTooltip.called);
|
||||
});
|
||||
|
||||
test('sets up listeners when has-tooltip is changed', () => {
|
||||
const addListenerStub = sandbox.stub(element, 'addEventListener');
|
||||
element.hasTooltip = true;
|
||||
assert.isTrue(addListenerStub.called);
|
||||
});
|
||||
|
||||
test('clean up listeners when has-tooltip changed to false', () => {
|
||||
const removeListenerStub = sandbox.stub(element, 'removeEventListener');
|
||||
element.hasTooltip = true;
|
||||
element.hasTooltip = false;
|
||||
assert.isTrue(removeListenerStub.called);
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'tooltip-behavior-element',
|
||||
behaviors: [Gerrit.TooltipBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('normal position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 100, width: 200};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 50},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isFalse(tooltip.updateStyles.called);
|
||||
assert.equal(tooltip.style.left, '175px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('left side position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 10, width: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isBelow(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '0px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('right side position', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 950, width: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '915px');
|
||||
assert.equal(tooltip.style.top, '100px');
|
||||
});
|
||||
|
||||
test('position to bottom', () => {
|
||||
sandbox.stub(element, 'getBoundingClientRect', () => {
|
||||
return {top: 100, left: 950, width: 50, height: 50};
|
||||
});
|
||||
const tooltip = makeTooltip(
|
||||
{height: 30, width: 120},
|
||||
{top: 0, left: 0, width: 1000});
|
||||
|
||||
element.positionBelow = true;
|
||||
element._positionTooltip(tooltip);
|
||||
assert.isTrue(tooltip.updateStyles.called);
|
||||
const offset = tooltip.updateStyles
|
||||
.lastCall.args[0]['--gr-tooltip-arrow-center-offset'];
|
||||
assert.isAbove(parseFloat(offset.replace(/px$/, '')), 0);
|
||||
assert.equal(tooltip.style.left, '915px');
|
||||
assert.equal(tooltip.style.top, '157.2px');
|
||||
});
|
||||
|
||||
test('hides tooltip when detached', () => {
|
||||
sandbox.stub(element, '_handleHideTooltip');
|
||||
element.remove();
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element._handleHideTooltip.called);
|
||||
});
|
||||
|
||||
test('sets up listeners when has-tooltip is changed', () => {
|
||||
const addListenerStub = sandbox.stub(element, 'addEventListener');
|
||||
element.hasTooltip = true;
|
||||
assert.isTrue(addListenerStub.called);
|
||||
});
|
||||
|
||||
test('clean up listeners when has-tooltip changed to false', () => {
|
||||
const removeListenerStub = sandbox.stub(element, 'removeEventListener');
|
||||
element.hasTooltip = true;
|
||||
element.hasTooltip = false;
|
||||
assert.isTrue(removeListenerStub.called);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 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>
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -73,4 +72,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -18,15 +18,20 @@ limitations under the License.
|
||||
|
||||
<title>gr-url-encoding-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-url-encoding-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-url-encoding-behavior.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-url-encoding-behavior.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -34,62 +39,65 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-url-encoding-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './gr-url-encoding-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-url-encoding-behavior tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.URLEncodingBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('encodeURL', () => {
|
||||
test('double encodes', () => {
|
||||
assert.equal(element.encodeURL('abc?123'), 'abc%253F123');
|
||||
assert.equal(element.encodeURL('def/ghi'), 'def%252Fghi');
|
||||
assert.equal(element.encodeURL('jkl'), 'jkl');
|
||||
assert.equal(element.encodeURL(''), '');
|
||||
});
|
||||
|
||||
test('does not convert colons', () => {
|
||||
assert.equal(element.encodeURL('mno:pqr'), 'mno:pqr');
|
||||
});
|
||||
|
||||
test('converts spaces to +', () => {
|
||||
assert.equal(element.encodeURL('words with spaces'), 'words+with+spaces');
|
||||
});
|
||||
|
||||
test('does not convert slashes when configured', () => {
|
||||
assert.equal(element.encodeURL('stu/vwx', true), 'stu/vwx');
|
||||
});
|
||||
|
||||
test('does not convert slashes when configured', () => {
|
||||
assert.equal(element.encodeURL('stu/vwx', true), 'stu/vwx');
|
||||
});
|
||||
});
|
||||
|
||||
suite('singleDecodeUrl', () => {
|
||||
test('single decodes', () => {
|
||||
assert.equal(element.singleDecodeURL('abc%3Fdef'), 'abc?def');
|
||||
});
|
||||
|
||||
test('converts + to space', () => {
|
||||
assert.equal(element.singleDecodeURL('ghi+jkl'), 'ghi jkl');
|
||||
});
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.URLEncodingBehavior],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('encodeURL', () => {
|
||||
test('double encodes', () => {
|
||||
assert.equal(element.encodeURL('abc?123'), 'abc%253F123');
|
||||
assert.equal(element.encodeURL('def/ghi'), 'def%252Fghi');
|
||||
assert.equal(element.encodeURL('jkl'), 'jkl');
|
||||
assert.equal(element.encodeURL(''), '');
|
||||
});
|
||||
|
||||
test('does not convert colons', () => {
|
||||
assert.equal(element.encodeURL('mno:pqr'), 'mno:pqr');
|
||||
});
|
||||
|
||||
test('converts spaces to +', () => {
|
||||
assert.equal(element.encodeURL('words with spaces'), 'words+with+spaces');
|
||||
});
|
||||
|
||||
test('does not convert slashes when configured', () => {
|
||||
assert.equal(element.encodeURL('stu/vwx', true), 'stu/vwx');
|
||||
});
|
||||
|
||||
test('does not convert slashes when configured', () => {
|
||||
assert.equal(element.encodeURL('stu/vwx', true), 'stu/vwx');
|
||||
});
|
||||
});
|
||||
|
||||
suite('singleDecodeUrl', () => {
|
||||
test('single decodes', () => {
|
||||
assert.equal(element.singleDecodeURL('abc%3Fdef'), 'abc?def');
|
||||
});
|
||||
|
||||
test('converts + to space', () => {
|
||||
assert.equal(element.singleDecodeURL('ghi+jkl'), 'ghi jkl');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,21 +1,20 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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.
|
||||
*/
|
||||
/*
|
||||
|
||||
How to Add a Keyboard Shortcut
|
||||
==============================
|
||||
@ -95,12 +94,17 @@ by gr-app, and actually implemented by gr-comment-thread.
|
||||
|
||||
NOTE: doc-only shortcuts will not be customizable in the same way that other
|
||||
shortcuts are.
|
||||
-->
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="/bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
|
||||
<script src="../../types/polymer-behaviors.js"></script>
|
||||
*/
|
||||
/*
|
||||
FIXME(polymer-modulizer): the above comments were extracted
|
||||
from HTML and may be out of place here. Review them and
|
||||
then delete this comment!
|
||||
*/
|
||||
import '../../scripts/bundled-polymer.js';
|
||||
|
||||
<script>
|
||||
import {IronA11yKeysBehavior} from '@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
|
||||
import '../../types/polymer-behaviors.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -307,7 +311,7 @@ shortcuts are.
|
||||
|
||||
/** @return {!(Event|PolymerDomApi|PolymerEventApi)} */
|
||||
const getKeyboardEvent = function(e) {
|
||||
e = Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
|
||||
e = dom(e.detail ? e.detail.keyboardEvent : e);
|
||||
// When e is a keyboardEvent, e.event is not null.
|
||||
if (e.event) { e = e.event; }
|
||||
return e;
|
||||
@ -482,7 +486,7 @@ shortcuts are.
|
||||
|
||||
/** @polymerBehavior Gerrit.KeyboardShortcutBehavior*/
|
||||
Gerrit.KeyboardShortcutBehavior = [
|
||||
Polymer.IronA11yKeysBehavior,
|
||||
IronA11yKeysBehavior,
|
||||
{
|
||||
// Exports for convenience. Note: Closure compiler crashes when
|
||||
// object-shorthand syntax is used here.
|
||||
@ -518,7 +522,7 @@ shortcuts are.
|
||||
|
||||
shouldSuppressKeyboardShortcut(e) {
|
||||
e = getKeyboardEvent(e);
|
||||
const tagName = Polymer.dom(e).rootTarget.tagName;
|
||||
const tagName = dom(e).rootTarget.tagName;
|
||||
if (tagName === 'INPUT' || tagName === 'TEXTAREA' ||
|
||||
(e.keyCode === 13 && tagName === 'A')) {
|
||||
// Suppress shortcuts if the key is 'enter' and target is an anchor.
|
||||
@ -542,7 +546,7 @@ shortcuts are.
|
||||
},
|
||||
|
||||
getRootTarget(e) {
|
||||
return Polymer.dom(getKeyboardEvent(e)).rootTarget;
|
||||
return dom(getKeyboardEvent(e)).rootTarget;
|
||||
},
|
||||
|
||||
bindShortcut(shortcut, ...bindings) {
|
||||
@ -671,4 +675,3 @@ shortcuts are.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="keyboard-shortcut-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./keyboard-shortcut-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -41,403 +41,406 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('keyboard-shortcut-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
const kb = window.Gerrit.KeyboardShortcutBinder;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './keyboard-shortcut-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('keyboard-shortcut-behavior tests', () => {
|
||||
const kb = window.Gerrit.KeyboardShortcutBinder;
|
||||
|
||||
let element;
|
||||
let overlay;
|
||||
let sandbox;
|
||||
let element;
|
||||
let overlay;
|
||||
let sandbox;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.KeyboardShortcutBehavior],
|
||||
keyBindings: {
|
||||
k: '_handleKey',
|
||||
enter: '_handleKey',
|
||||
},
|
||||
_handleKey() {},
|
||||
});
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [Gerrit.KeyboardShortcutBehavior],
|
||||
keyBindings: {
|
||||
k: '_handleKey',
|
||||
enter: '_handleKey',
|
||||
},
|
||||
_handleKey() {},
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('ShortcutManager', () => {
|
||||
test('bindings management', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
const {NEXT_FILE} = kb.Shortcut;
|
||||
|
||||
assert.isUndefined(mgr.getBindingsForShortcut(NEXT_FILE));
|
||||
mgr.bindShortcut(NEXT_FILE, ']', '}', 'right');
|
||||
assert.deepEqual(
|
||||
mgr.getBindingsForShortcut(NEXT_FILE),
|
||||
[']', '}', 'right']);
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
suite('binding descriptions', () => {
|
||||
function mapToObject(m) {
|
||||
const o = {};
|
||||
m.forEach((v, k) => o[k] = v);
|
||||
return o;
|
||||
}
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('ShortcutManager', () => {
|
||||
test('bindings management', () => {
|
||||
test('single combo description', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
const {NEXT_FILE} = kb.Shortcut;
|
||||
|
||||
assert.isUndefined(mgr.getBindingsForShortcut(NEXT_FILE));
|
||||
mgr.bindShortcut(NEXT_FILE, ']', '}', 'right');
|
||||
assert.deepEqual(mgr.describeBinding('a'), ['a']);
|
||||
assert.deepEqual(mgr.describeBinding('a:keyup'), ['a']);
|
||||
assert.deepEqual(mgr.describeBinding('ctrl+a'), ['Ctrl', 'a']);
|
||||
assert.deepEqual(
|
||||
mgr.getBindingsForShortcut(NEXT_FILE),
|
||||
[']', '}', 'right']);
|
||||
mgr.describeBinding('ctrl+shift+up:keyup'),
|
||||
['Ctrl', 'Shift', '↑']);
|
||||
});
|
||||
|
||||
suite('binding descriptions', () => {
|
||||
function mapToObject(m) {
|
||||
const o = {};
|
||||
m.forEach((v, k) => o[k] = v);
|
||||
return o;
|
||||
}
|
||||
test('combo set description', () => {
|
||||
const {GO_KEY, DOC_ONLY, ShortcutManager} = kb;
|
||||
const {GO_TO_OPENED_CHANGES, NEXT_FILE, PREV_FILE} = kb.Shortcut;
|
||||
|
||||
test('single combo description', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
assert.deepEqual(mgr.describeBinding('a'), ['a']);
|
||||
assert.deepEqual(mgr.describeBinding('a:keyup'), ['a']);
|
||||
assert.deepEqual(mgr.describeBinding('ctrl+a'), ['Ctrl', 'a']);
|
||||
assert.deepEqual(
|
||||
mgr.describeBinding('ctrl+shift+up:keyup'),
|
||||
['Ctrl', 'Shift', '↑']);
|
||||
const mgr = new ShortcutManager();
|
||||
assert.isNull(mgr.describeBindings(NEXT_FILE));
|
||||
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, GO_KEY, 'o');
|
||||
assert.deepEqual(
|
||||
mgr.describeBindings(GO_TO_OPENED_CHANGES),
|
||||
[['g', 'o']]);
|
||||
|
||||
mgr.bindShortcut(NEXT_FILE, DOC_ONLY, ']', 'ctrl+shift+right:keyup');
|
||||
assert.deepEqual(
|
||||
mgr.describeBindings(NEXT_FILE),
|
||||
[[']'], ['Ctrl', 'Shift', '→']]);
|
||||
|
||||
mgr.bindShortcut(PREV_FILE, '[');
|
||||
assert.deepEqual(mgr.describeBindings(PREV_FILE), [['[']]);
|
||||
});
|
||||
|
||||
test('combo set description width', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['u']]), 1);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['g', 'o']]), 2);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['Shift', 'r']]), 6);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['x'], ['y']]), 4);
|
||||
assert.strictEqual(
|
||||
mgr.comboSetDisplayWidth([['x'], ['y'], ['Shift', 'z']]),
|
||||
12);
|
||||
});
|
||||
|
||||
test('distribute shortcut help', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
assert.deepEqual(mgr.distributeBindingDesc([['o']]), [[['o']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([['g', 'o']]),
|
||||
[[['g', 'o']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([['ctrl', 'shift', 'meta', 'enter']]),
|
||||
[[['ctrl', 'shift', 'meta', 'enter']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([
|
||||
['ctrl', 'shift', 'meta', 'enter'],
|
||||
['o'],
|
||||
]),
|
||||
[
|
||||
[['ctrl', 'shift', 'meta', 'enter']],
|
||||
[['o']],
|
||||
]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([
|
||||
['ctrl', 'enter'],
|
||||
['meta', 'enter'],
|
||||
['ctrl', 's'],
|
||||
['meta', 's'],
|
||||
]),
|
||||
[
|
||||
[['ctrl', 'enter'], ['meta', 'enter']],
|
||||
[['ctrl', 's'], ['meta', 's']],
|
||||
]);
|
||||
});
|
||||
|
||||
test('active shortcuts by section', () => {
|
||||
const {NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH} =
|
||||
kb.Shortcut;
|
||||
const {DIFFS, EVERYWHERE, NAVIGATION} = kb.ShortcutSection;
|
||||
|
||||
const mgr = new kb.ShortcutManager();
|
||||
mgr.bindShortcut(NEXT_FILE, ']');
|
||||
mgr.bindShortcut(NEXT_LINE, 'j');
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, 'g+o');
|
||||
mgr.bindShortcut(SEARCH, '/');
|
||||
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[NEXT_FILE]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
|
||||
test('combo set description', () => {
|
||||
const {GO_KEY, DOC_ONLY, ShortcutManager} = kb;
|
||||
const {GO_TO_OPENED_CHANGES, NEXT_FILE, PREV_FILE} = kb.Shortcut;
|
||||
|
||||
const mgr = new ShortcutManager();
|
||||
assert.isNull(mgr.describeBindings(NEXT_FILE));
|
||||
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, GO_KEY, 'o');
|
||||
assert.deepEqual(
|
||||
mgr.describeBindings(GO_TO_OPENED_CHANGES),
|
||||
[['g', 'o']]);
|
||||
|
||||
mgr.bindShortcut(NEXT_FILE, DOC_ONLY, ']', 'ctrl+shift+right:keyup');
|
||||
assert.deepEqual(
|
||||
mgr.describeBindings(NEXT_FILE),
|
||||
[[']'], ['Ctrl', 'Shift', '→']]);
|
||||
|
||||
mgr.bindShortcut(PREV_FILE, '[');
|
||||
assert.deepEqual(mgr.describeBindings(PREV_FILE), [['[']]);
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[NEXT_LINE]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{shortcut: NEXT_LINE, text: 'Go to next line'},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
|
||||
test('combo set description width', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['u']]), 1);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['g', 'o']]), 2);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['Shift', 'r']]), 6);
|
||||
assert.strictEqual(mgr.comboSetDisplayWidth([['x'], ['y']]), 4);
|
||||
assert.strictEqual(
|
||||
mgr.comboSetDisplayWidth([['x'], ['y'], ['Shift', 'z']]),
|
||||
12);
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[SEARCH]: null,
|
||||
[GO_TO_OPENED_CHANGES]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{shortcut: NEXT_LINE, text: 'Go to next line'},
|
||||
],
|
||||
[EVERYWHERE]: [
|
||||
{shortcut: SEARCH, text: 'Search'},
|
||||
{
|
||||
shortcut: GO_TO_OPENED_CHANGES,
|
||||
text: 'Go to Opened Changes',
|
||||
},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('distribute shortcut help', () => {
|
||||
const mgr = new kb.ShortcutManager();
|
||||
assert.deepEqual(mgr.distributeBindingDesc([['o']]), [[['o']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([['g', 'o']]),
|
||||
[[['g', 'o']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([['ctrl', 'shift', 'meta', 'enter']]),
|
||||
[[['ctrl', 'shift', 'meta', 'enter']]]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([
|
||||
['ctrl', 'shift', 'meta', 'enter'],
|
||||
['o'],
|
||||
]),
|
||||
[
|
||||
[['ctrl', 'shift', 'meta', 'enter']],
|
||||
[['o']],
|
||||
]);
|
||||
assert.deepEqual(
|
||||
mgr.distributeBindingDesc([
|
||||
['ctrl', 'enter'],
|
||||
['meta', 'enter'],
|
||||
['ctrl', 's'],
|
||||
['meta', 's'],
|
||||
]),
|
||||
[
|
||||
[['ctrl', 'enter'], ['meta', 'enter']],
|
||||
[['ctrl', 's'], ['meta', 's']],
|
||||
]);
|
||||
test('directory view', () => {
|
||||
const {
|
||||
NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH,
|
||||
SAVE_COMMENT,
|
||||
} = kb.Shortcut;
|
||||
const {DIFFS, EVERYWHERE, NAVIGATION} = kb.ShortcutSection;
|
||||
const {GO_KEY, ShortcutManager} = kb;
|
||||
|
||||
const mgr = new ShortcutManager();
|
||||
mgr.bindShortcut(NEXT_FILE, ']');
|
||||
mgr.bindShortcut(NEXT_LINE, 'j');
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, GO_KEY, 'o');
|
||||
mgr.bindShortcut(SEARCH, '/');
|
||||
mgr.bindShortcut(
|
||||
SAVE_COMMENT, 'ctrl+enter', 'meta+enter', 'ctrl+s', 'meta+s');
|
||||
|
||||
assert.deepEqual(mapToObject(mgr.directoryView()), {});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[GO_TO_OPENED_CHANGES]: null,
|
||||
[NEXT_FILE]: null,
|
||||
[NEXT_LINE]: null,
|
||||
[SAVE_COMMENT]: null,
|
||||
[SEARCH]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
test('active shortcuts by section', () => {
|
||||
const {NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH} =
|
||||
kb.Shortcut;
|
||||
const {DIFFS, EVERYWHERE, NAVIGATION} = kb.ShortcutSection;
|
||||
|
||||
const mgr = new kb.ShortcutManager();
|
||||
mgr.bindShortcut(NEXT_FILE, ']');
|
||||
mgr.bindShortcut(NEXT_LINE, 'j');
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, 'g+o');
|
||||
mgr.bindShortcut(SEARCH, '/');
|
||||
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[NEXT_FILE]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[NEXT_LINE]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{shortcut: NEXT_LINE, text: 'Go to next line'},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[SEARCH]: null,
|
||||
[GO_TO_OPENED_CHANGES]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.activeShortcutsBySection()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{shortcut: NEXT_LINE, text: 'Go to next line'},
|
||||
],
|
||||
[EVERYWHERE]: [
|
||||
{shortcut: SEARCH, text: 'Search'},
|
||||
{
|
||||
shortcut: GO_TO_OPENED_CHANGES,
|
||||
text: 'Go to Opened Changes',
|
||||
},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{shortcut: NEXT_FILE, text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('directory view', () => {
|
||||
const {
|
||||
NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH,
|
||||
SAVE_COMMENT,
|
||||
} = kb.Shortcut;
|
||||
const {DIFFS, EVERYWHERE, NAVIGATION} = kb.ShortcutSection;
|
||||
const {GO_KEY, ShortcutManager} = kb;
|
||||
|
||||
const mgr = new ShortcutManager();
|
||||
mgr.bindShortcut(NEXT_FILE, ']');
|
||||
mgr.bindShortcut(NEXT_LINE, 'j');
|
||||
mgr.bindShortcut(GO_TO_OPENED_CHANGES, GO_KEY, 'o');
|
||||
mgr.bindShortcut(SEARCH, '/');
|
||||
mgr.bindShortcut(
|
||||
SAVE_COMMENT, 'ctrl+enter', 'meta+enter', 'ctrl+s', 'meta+s');
|
||||
|
||||
assert.deepEqual(mapToObject(mgr.directoryView()), {});
|
||||
|
||||
mgr.attachHost({
|
||||
keyboardShortcuts() {
|
||||
return {
|
||||
[GO_TO_OPENED_CHANGES]: null,
|
||||
[NEXT_FILE]: null,
|
||||
[NEXT_LINE]: null,
|
||||
[SAVE_COMMENT]: null,
|
||||
[SEARCH]: null,
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.directoryView()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{binding: [['j']], text: 'Go to next line'},
|
||||
{
|
||||
binding: [['Ctrl', 'Enter'], ['Meta', 'Enter']],
|
||||
text: 'Save comment',
|
||||
},
|
||||
{
|
||||
binding: [['Ctrl', 's'], ['Meta', 's']],
|
||||
text: 'Save comment',
|
||||
},
|
||||
],
|
||||
[EVERYWHERE]: [
|
||||
{binding: [['/']], text: 'Search'},
|
||||
{binding: [['g', 'o']], text: 'Go to Opened Changes'},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{binding: [[']']], text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('doesn’t block kb shortcuts for non-whitelisted els', done => {
|
||||
const divEl = document.createElement('div');
|
||||
element.appendChild(divEl);
|
||||
element._handleKey = e => {
|
||||
assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(divEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for input els', done => {
|
||||
const inputEl = document.createElement('input');
|
||||
element.appendChild(inputEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(inputEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for textarea els', done => {
|
||||
const textareaEl = document.createElement('textarea');
|
||||
element.appendChild(textareaEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for anything in a gr-overlay', done => {
|
||||
const divEl = document.createElement('div');
|
||||
const element = overlay.querySelector('test-element');
|
||||
element.appendChild(divEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(divEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks enter shortcut on an anchor', done => {
|
||||
const anchorEl = document.createElement('a');
|
||||
const element = overlay.querySelector('test-element');
|
||||
element.appendChild(anchorEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(anchorEl, 13, null, 'enter');
|
||||
});
|
||||
|
||||
test('modifierPressed returns accurate values', () => {
|
||||
const spy = sandbox.spy(element, 'modifierPressed');
|
||||
element._handleKey = e => {
|
||||
element.modifierPressed(e);
|
||||
};
|
||||
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
});
|
||||
|
||||
test('isModifierPressed returns accurate value', () => {
|
||||
const spy = sandbox.spy(element, 'isModifierPressed');
|
||||
element._handleKey = e => {
|
||||
element.isModifierPressed(e, 'shiftKey');
|
||||
};
|
||||
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
});
|
||||
|
||||
suite('GO_KEY timing', () => {
|
||||
let handlerStub;
|
||||
|
||||
setup(() => {
|
||||
element._shortcut_go_table.set('a', '_handleA');
|
||||
handlerStub = element._handleA = sinon.stub();
|
||||
sandbox.stub(Date, 'now').returns(10000);
|
||||
});
|
||||
|
||||
test('success', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isTrue(handlerStub.calledOnce);
|
||||
assert.strictEqual(handlerStub.lastCall.args[0], e);
|
||||
});
|
||||
|
||||
test('go key not pressed', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = null;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('go key pressed too long ago', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 3000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('should suppress', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(true);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('unrecognized key', () => {
|
||||
const e = {detail: {key: 'f'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
assert.deepEqual(
|
||||
mapToObject(mgr.directoryView()),
|
||||
{
|
||||
[DIFFS]: [
|
||||
{binding: [['j']], text: 'Go to next line'},
|
||||
{
|
||||
binding: [['Ctrl', 'Enter'], ['Meta', 'Enter']],
|
||||
text: 'Save comment',
|
||||
},
|
||||
{
|
||||
binding: [['Ctrl', 's'], ['Meta', 's']],
|
||||
text: 'Save comment',
|
||||
},
|
||||
],
|
||||
[EVERYWHERE]: [
|
||||
{binding: [['/']], text: 'Search'},
|
||||
{binding: [['g', 'o']], text: 'Go to Opened Changes'},
|
||||
],
|
||||
[NAVIGATION]: [
|
||||
{binding: [[']']], text: 'Go to next file'},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('doesn’t block kb shortcuts for non-whitelisted els', done => {
|
||||
const divEl = document.createElement('div');
|
||||
element.appendChild(divEl);
|
||||
element._handleKey = e => {
|
||||
assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(divEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for input els', done => {
|
||||
const inputEl = document.createElement('input');
|
||||
element.appendChild(inputEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(inputEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for textarea els', done => {
|
||||
const textareaEl = document.createElement('textarea');
|
||||
element.appendChild(textareaEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks kb shortcuts for anything in a gr-overlay', done => {
|
||||
const divEl = document.createElement('div');
|
||||
const element = overlay.querySelector('test-element');
|
||||
element.appendChild(divEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(divEl, 75, null, 'k');
|
||||
});
|
||||
|
||||
test('blocks enter shortcut on an anchor', done => {
|
||||
const anchorEl = document.createElement('a');
|
||||
const element = overlay.querySelector('test-element');
|
||||
element.appendChild(anchorEl);
|
||||
element._handleKey = e => {
|
||||
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
|
||||
done();
|
||||
};
|
||||
MockInteractions.keyDownOn(anchorEl, 13, null, 'enter');
|
||||
});
|
||||
|
||||
test('modifierPressed returns accurate values', () => {
|
||||
const spy = sandbox.spy(element, 'modifierPressed');
|
||||
element._handleKey = e => {
|
||||
element.modifierPressed(e);
|
||||
};
|
||||
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
});
|
||||
|
||||
test('isModifierPressed returns accurate value', () => {
|
||||
const spy = sandbox.spy(element, 'isModifierPressed');
|
||||
element._handleKey = e => {
|
||||
element.isModifierPressed(e, 'shiftKey');
|
||||
};
|
||||
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
|
||||
assert.isTrue(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, null, 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
|
||||
assert.isFalse(spy.lastCall.returnValue);
|
||||
});
|
||||
|
||||
suite('GO_KEY timing', () => {
|
||||
let handlerStub;
|
||||
|
||||
setup(() => {
|
||||
element._shortcut_go_table.set('a', '_handleA');
|
||||
handlerStub = element._handleA = sinon.stub();
|
||||
sandbox.stub(Date, 'now').returns(10000);
|
||||
});
|
||||
|
||||
test('success', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isTrue(handlerStub.calledOnce);
|
||||
assert.strictEqual(handlerStub.lastCall.args[0], e);
|
||||
});
|
||||
|
||||
test('go key not pressed', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = null;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('go key pressed too long ago', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 3000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('should suppress', () => {
|
||||
const e = {detail: {key: 'a'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(true);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
|
||||
test('unrecognized key', () => {
|
||||
const e = {detail: {key: 'f'}, preventDefault: () => {}};
|
||||
sandbox.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
|
||||
element._shortcut_go_key_last_pressed = 9000;
|
||||
element._handleGoAction(e);
|
||||
assert.isFalse(handlerStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,22 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2016 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.
|
||||
*/
|
||||
import '../../scripts/bundled-polymer.js';
|
||||
|
||||
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="../base-url-behavior/base-url-behavior.html">
|
||||
<script>
|
||||
import '../base-url-behavior/base-url-behavior.js';
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
@ -198,4 +198,3 @@ limitations under the License.
|
||||
};
|
||||
}
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -19,19 +19,23 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>keyboard-shortcut-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<script>
|
||||
/** @type {string} */
|
||||
window.CANONICAL_PATH = '/r';
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import '../base-url-behavior/base-url-behavior.js';
|
||||
import './rest-client-behavior.js';
|
||||
/** @type {string} */
|
||||
window.CANONICAL_PATH = '/r';
|
||||
</script>
|
||||
|
||||
<link rel="import" href="../base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="rest-client-behavior.html">
|
||||
<script type="module" src="../base-url-behavior/base-url-behavior.js"></script>
|
||||
<script type="module" src="./rest-client-behavior.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -47,191 +51,195 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('rest-client-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import '../base-url-behavior/base-url-behavior.js';
|
||||
import './rest-client-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('rest-client-behavior tests', () => {
|
||||
let element;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let overlay;
|
||||
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.RESTClientBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('changeBaseURL', () => {
|
||||
assert.deepEqual(
|
||||
element.changeBaseURL('test/project', '1', '2'),
|
||||
'/r/changes/test%2Fproject~1/revisions/2'
|
||||
);
|
||||
});
|
||||
|
||||
test('changePath', () => {
|
||||
assert.deepEqual(element.changePath('1'), '/r/c/1');
|
||||
});
|
||||
|
||||
test('Open status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
mergeable: true,
|
||||
};
|
||||
let statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, []);
|
||||
assert.equal(statusString, '');
|
||||
|
||||
change.submittable = false;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Active']);
|
||||
|
||||
// With no missing labels but no submitEnabled option.
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Active']);
|
||||
|
||||
// Without missing labels and enabled submit
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, submitEnabled: true});
|
||||
assert.deepEqual(statuses, ['Ready to submit']);
|
||||
|
||||
change.mergeable = false;
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
|
||||
delete change.mergeable;
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, mergeable: true, submitEnabled: true});
|
||||
assert.deepEqual(statuses, ['Ready to submit']);
|
||||
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, mergeable: false});
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
});
|
||||
|
||||
test('Merge conflict', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
mergeable: false,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
assert.equal(statusString, 'Merge Conflict');
|
||||
});
|
||||
|
||||
test('mergeable prop undefined', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, []);
|
||||
assert.equal(statusString, '');
|
||||
});
|
||||
|
||||
test('Merged status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'MERGED',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merged']);
|
||||
assert.equal(statusString, 'Merged');
|
||||
});
|
||||
|
||||
test('Abandoned status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'ABANDONED',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Abandoned']);
|
||||
assert.equal(statusString, 'Abandoned');
|
||||
});
|
||||
|
||||
test('Open status with private and wip', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
labels: {},
|
||||
mergeable: true,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['WIP', 'Private']);
|
||||
assert.equal(statusString, 'WIP, Private');
|
||||
});
|
||||
|
||||
test('Merge conflict with private and wip', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
labels: {},
|
||||
mergeable: false,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merge Conflict', 'WIP', 'Private']);
|
||||
assert.equal(statusString, 'Merge Conflict, WIP, Private');
|
||||
suiteSetup(() => {
|
||||
// Define a Polymer element that uses this behavior.
|
||||
Polymer({
|
||||
is: 'test-element',
|
||||
behaviors: [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.RESTClientBehavior,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
overlay = fixture('within-overlay');
|
||||
});
|
||||
|
||||
test('changeBaseURL', () => {
|
||||
assert.deepEqual(
|
||||
element.changeBaseURL('test/project', '1', '2'),
|
||||
'/r/changes/test%2Fproject~1/revisions/2'
|
||||
);
|
||||
});
|
||||
|
||||
test('changePath', () => {
|
||||
assert.deepEqual(element.changePath('1'), '/r/c/1');
|
||||
});
|
||||
|
||||
test('Open status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
mergeable: true,
|
||||
};
|
||||
let statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, []);
|
||||
assert.equal(statusString, '');
|
||||
|
||||
change.submittable = false;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Active']);
|
||||
|
||||
// With no missing labels but no submitEnabled option.
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Active']);
|
||||
|
||||
// Without missing labels and enabled submit
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, submitEnabled: true});
|
||||
assert.deepEqual(statuses, ['Ready to submit']);
|
||||
|
||||
change.mergeable = false;
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true});
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
|
||||
delete change.mergeable;
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, mergeable: true, submitEnabled: true});
|
||||
assert.deepEqual(statuses, ['Ready to submit']);
|
||||
|
||||
change.submittable = true;
|
||||
statuses = element.changeStatuses(change,
|
||||
{includeDerived: true, mergeable: false});
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
});
|
||||
|
||||
test('Merge conflict', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
mergeable: false,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merge Conflict']);
|
||||
assert.equal(statusString, 'Merge Conflict');
|
||||
});
|
||||
|
||||
test('mergeable prop undefined', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, []);
|
||||
assert.equal(statusString, '');
|
||||
});
|
||||
|
||||
test('Merged status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'MERGED',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merged']);
|
||||
assert.equal(statusString, 'Merged');
|
||||
});
|
||||
|
||||
test('Abandoned status', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'ABANDONED',
|
||||
labels: {},
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Abandoned']);
|
||||
assert.equal(statusString, 'Abandoned');
|
||||
});
|
||||
|
||||
test('Open status with private and wip', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
labels: {},
|
||||
mergeable: true,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['WIP', 'Private']);
|
||||
assert.equal(statusString, 'WIP, Private');
|
||||
});
|
||||
|
||||
test('Merge conflict with private and wip', () => {
|
||||
const change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
labels: {},
|
||||
mergeable: false,
|
||||
};
|
||||
const statuses = element.changeStatuses(change);
|
||||
const statusString = element.changeStatusString(change);
|
||||
assert.deepEqual(statuses, ['Merge Conflict', 'WIP', 'Private']);
|
||||
assert.equal(statusString, 'Merge Conflict, WIP, Private');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,20 +1,19 @@
|
||||
<!--
|
||||
@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.
|
||||
-->
|
||||
<script>
|
||||
/**
|
||||
* @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(window) {
|
||||
'use strict';
|
||||
|
||||
@ -74,4 +73,3 @@ limitations under the License.
|
||||
throw new Error(`Refused to bind value as ${type}: ${value}`);
|
||||
};
|
||||
})(window);
|
||||
</script>
|
||||
|
@ -18,15 +18,20 @@ limitations under the License.
|
||||
|
||||
<title>safe-types-behavior</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="safe-types-behavior.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./safe-types-behavior.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './safe-types-behavior.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -34,92 +39,95 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-tooltip-behavior tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../test/test-pre-setup.js';
|
||||
import '../../test/common-test-setup.js';
|
||||
import './safe-types-behavior.js';
|
||||
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
|
||||
suite('gr-tooltip-behavior tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
suiteSetup(() => {
|
||||
Polymer({
|
||||
is: 'safe-types-element',
|
||||
behaviors: [Gerrit.SafeTypes],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('SafeUrl accepts valid urls', () => {
|
||||
function accepts(url) {
|
||||
const safeUrl = new element.SafeUrl(url);
|
||||
assert.isOk(safeUrl);
|
||||
assert.equal(url, safeUrl.asString());
|
||||
}
|
||||
accepts('http://www.google.com/');
|
||||
accepts('https://www.google.com/');
|
||||
accepts('HtTpS://www.google.com/');
|
||||
accepts('//www.google.com/');
|
||||
accepts('/c/1234/file/path.html@45');
|
||||
accepts('#hash-url');
|
||||
accepts('mailto:name@example.com');
|
||||
});
|
||||
|
||||
test('SafeUrl rejects invalid urls', () => {
|
||||
function rejects(url) {
|
||||
assert.throws(() => { new element.SafeUrl(url); });
|
||||
}
|
||||
rejects('javascript://alert("evil");');
|
||||
rejects('ftp:example.com');
|
||||
rejects('data:text/html,scary business');
|
||||
});
|
||||
|
||||
suite('safeTypesBridge', () => {
|
||||
function acceptsString(value, type) {
|
||||
assert.equal(Gerrit.SafeTypes.safeTypesBridge(value, type),
|
||||
value);
|
||||
}
|
||||
|
||||
function rejects(value, type) {
|
||||
assert.throws(() => { Gerrit.SafeTypes.safeTypesBridge(value, type); });
|
||||
}
|
||||
|
||||
test('accepts valid URL strings', () => {
|
||||
acceptsString('/foo/bar', 'URL');
|
||||
acceptsString('#baz', 'URL');
|
||||
});
|
||||
|
||||
test('rejects invalid URL strings', () => {
|
||||
rejects('javascript://void();', 'URL');
|
||||
});
|
||||
|
||||
test('accepts SafeUrl values', () => {
|
||||
const url = '/abc/123';
|
||||
const safeUrl = new element.SafeUrl(url);
|
||||
assert.equal(Gerrit.SafeTypes.safeTypesBridge(safeUrl, 'URL'), url);
|
||||
});
|
||||
|
||||
test('rejects non-string or non-SafeUrl types', () => {
|
||||
rejects(3.1415926, 'URL');
|
||||
});
|
||||
|
||||
test('accepts any binding to STRING or CONSTANT', () => {
|
||||
acceptsString('foo/bar/baz', 'STRING');
|
||||
acceptsString('lorem ipsum dolor', 'CONSTANT');
|
||||
});
|
||||
|
||||
test('rejects all other types', () => {
|
||||
rejects('foo', 'JAVASCRIPT');
|
||||
rejects('foo', 'HTML');
|
||||
rejects('foo', 'RESOURCE_URL');
|
||||
rejects('foo', 'STYLE');
|
||||
});
|
||||
suiteSetup(() => {
|
||||
Polymer({
|
||||
is: 'safe-types-element',
|
||||
behaviors: [Gerrit.SafeTypes],
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('SafeUrl accepts valid urls', () => {
|
||||
function accepts(url) {
|
||||
const safeUrl = new element.SafeUrl(url);
|
||||
assert.isOk(safeUrl);
|
||||
assert.equal(url, safeUrl.asString());
|
||||
}
|
||||
accepts('http://www.google.com/');
|
||||
accepts('https://www.google.com/');
|
||||
accepts('HtTpS://www.google.com/');
|
||||
accepts('//www.google.com/');
|
||||
accepts('/c/1234/file/path.html@45');
|
||||
accepts('#hash-url');
|
||||
accepts('mailto:name@example.com');
|
||||
});
|
||||
|
||||
test('SafeUrl rejects invalid urls', () => {
|
||||
function rejects(url) {
|
||||
assert.throws(() => { new element.SafeUrl(url); });
|
||||
}
|
||||
rejects('javascript://alert("evil");');
|
||||
rejects('ftp:example.com');
|
||||
rejects('data:text/html,scary business');
|
||||
});
|
||||
|
||||
suite('safeTypesBridge', () => {
|
||||
function acceptsString(value, type) {
|
||||
assert.equal(Gerrit.SafeTypes.safeTypesBridge(value, type),
|
||||
value);
|
||||
}
|
||||
|
||||
function rejects(value, type) {
|
||||
assert.throws(() => { Gerrit.SafeTypes.safeTypesBridge(value, type); });
|
||||
}
|
||||
|
||||
test('accepts valid URL strings', () => {
|
||||
acceptsString('/foo/bar', 'URL');
|
||||
acceptsString('#baz', 'URL');
|
||||
});
|
||||
|
||||
test('rejects invalid URL strings', () => {
|
||||
rejects('javascript://void();', 'URL');
|
||||
});
|
||||
|
||||
test('accepts SafeUrl values', () => {
|
||||
const url = '/abc/123';
|
||||
const safeUrl = new element.SafeUrl(url);
|
||||
assert.equal(Gerrit.SafeTypes.safeTypesBridge(safeUrl, 'URL'), url);
|
||||
});
|
||||
|
||||
test('rejects non-string or non-SafeUrl types', () => {
|
||||
rejects(3.1415926, 'URL');
|
||||
});
|
||||
|
||||
test('accepts any binding to STRING or CONSTANT', () => {
|
||||
acceptsString('foo/bar/baz', 'STRING');
|
||||
acceptsString('lorem ipsum dolor', 'CONSTANT');
|
||||
});
|
||||
|
||||
test('rejects all other types', () => {
|
||||
rejects('foo', 'JAVASCRIPT');
|
||||
rejects('foo', 'HTML');
|
||||
rejects('foo', 'RESOURCE_URL');
|
||||
rejects('foo', 'STYLE');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,291 +14,308 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-access-behavior/gr-access-behavior.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-icons/gr-icons.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-permission/gr-permission.js';
|
||||
import '../../../scripts/util.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {htmlTemplate} from './gr-access-section_html.js';
|
||||
|
||||
/**
|
||||
* Fired when the section has been modified or removed.
|
||||
*
|
||||
* @event access-modified
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when a section that was previously added was removed.
|
||||
*
|
||||
* @event added-section-removed
|
||||
*/
|
||||
|
||||
const GLOBAL_NAME = 'GLOBAL_CAPABILITIES';
|
||||
|
||||
// The name that gets automatically input when a new reference is added.
|
||||
const NEW_NAME = 'refs/heads/*';
|
||||
const REFS_NAME = 'refs/';
|
||||
const ON_BEHALF_OF = '(On Behalf Of)';
|
||||
const LABEL = 'Label';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrAccessSection extends mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
/**
|
||||
* Fired when the section has been modified or removed.
|
||||
*
|
||||
* @event access-modified
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-repo-access_test.
|
||||
*/
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
/**
|
||||
* Fired when a section that was previously added was removed.
|
||||
*
|
||||
* @event added-section-removed
|
||||
*/
|
||||
static get is() { return 'gr-access-section'; }
|
||||
|
||||
const GLOBAL_NAME = 'GLOBAL_CAPABILITIES';
|
||||
static get properties() {
|
||||
return {
|
||||
capabilities: Object,
|
||||
/** @type {?} */
|
||||
section: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
observer: '_updateSection',
|
||||
},
|
||||
groups: Object,
|
||||
labels: Object,
|
||||
editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
canUpload: Boolean,
|
||||
ownerOf: Array,
|
||||
_originalId: String,
|
||||
_editingRef: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_deleted: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_permissions: Array,
|
||||
};
|
||||
}
|
||||
|
||||
// The name that gets automatically input when a new reference is added.
|
||||
const NEW_NAME = 'refs/heads/*';
|
||||
const REFS_NAME = 'refs/';
|
||||
const ON_BEHALF_OF = '(On Behalf Of)';
|
||||
const LABEL = 'Label';
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-saved',
|
||||
() => this._handleAccessSaved());
|
||||
}
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrAccessSection extends Polymer.mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
/**
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-repo-access_test.
|
||||
*/
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-access-section'; }
|
||||
_updateSection(section) {
|
||||
this._permissions = this.toSortedArray(section.value.permissions);
|
||||
this._originalId = section.id;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
capabilities: Object,
|
||||
/** @type {?} */
|
||||
section: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
observer: '_updateSection',
|
||||
},
|
||||
groups: Object,
|
||||
labels: Object,
|
||||
editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
canUpload: Boolean,
|
||||
ownerOf: Array,
|
||||
_originalId: String,
|
||||
_editingRef: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_deleted: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_permissions: Array,
|
||||
};
|
||||
_handleAccessSaved() {
|
||||
// Set a new 'original' value to keep track of after the value has been
|
||||
// saved.
|
||||
this._updateSection(this.section);
|
||||
}
|
||||
|
||||
_handleValueChange() {
|
||||
if (!this.section.value.added) {
|
||||
this.section.value.modified = this.section.id !== this._originalId;
|
||||
// Allows overall access page to know a change has been made.
|
||||
// For a new section, this is not fired because new permissions and
|
||||
// rules have to be added in order to save, modifying the ref is not
|
||||
// enough.
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
this.section.value.updatedId = this.section.id;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-saved',
|
||||
() => this._handleAccessSaved());
|
||||
}
|
||||
|
||||
_updateSection(section) {
|
||||
this._permissions = this.toSortedArray(section.value.permissions);
|
||||
this._originalId = section.id;
|
||||
}
|
||||
|
||||
_handleAccessSaved() {
|
||||
// Set a new 'original' value to keep track of after the value has been
|
||||
// saved.
|
||||
this._updateSection(this.section);
|
||||
}
|
||||
|
||||
_handleValueChange() {
|
||||
if (!this.section.value.added) {
|
||||
this.section.value.modified = this.section.id !== this._originalId;
|
||||
// Allows overall access page to know a change has been made.
|
||||
// For a new section, this is not fired because new permissions and
|
||||
// rules have to be added in order to save, modifying the ref is not
|
||||
// enough.
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
this.section.value.updatedId = this.section.id;
|
||||
}
|
||||
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld) { return; }
|
||||
// Restore original values if no longer editing.
|
||||
if (!editing) {
|
||||
this._editingRef = false;
|
||||
this._deleted = false;
|
||||
delete this.section.value.deleted;
|
||||
// Restore section ref.
|
||||
this.set(['section', 'id'], this._originalId);
|
||||
// Remove any unsaved but added permissions.
|
||||
this._permissions = this._permissions.filter(p => !p.value.added);
|
||||
for (const key of Object.keys(this.section.value.permissions)) {
|
||||
if (this.section.value.permissions[key].added) {
|
||||
delete this.section.value.permissions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_computePermissions(name, capabilities, labels) {
|
||||
let allPermissions;
|
||||
if (!this.section || !this.section.value) {
|
||||
return [];
|
||||
}
|
||||
if (name === GLOBAL_NAME) {
|
||||
allPermissions = this.toSortedArray(capabilities);
|
||||
} else {
|
||||
const labelOptions = this._computeLabelOptions(labels);
|
||||
allPermissions = labelOptions.concat(
|
||||
this.toSortedArray(this.permissionValues));
|
||||
}
|
||||
return allPermissions
|
||||
.filter(permission => !this.section.value.permissions[permission.id]);
|
||||
}
|
||||
|
||||
_computeHideEditClass(section) {
|
||||
return section.id === 'GLOBAL_CAPABILITIES' ? 'hide' : '';
|
||||
}
|
||||
|
||||
_handleAddedPermissionRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._permissions = this._permissions.slice(0, index).concat(
|
||||
this._permissions.slice(index + 1, this._permissions.length));
|
||||
}
|
||||
|
||||
_computeLabelOptions(labels) {
|
||||
const labelOptions = [];
|
||||
if (!labels) { return []; }
|
||||
for (const labelName of Object.keys(labels)) {
|
||||
labelOptions.push({
|
||||
id: 'label-' + labelName,
|
||||
value: {
|
||||
name: `${LABEL} ${labelName}`,
|
||||
id: 'label-' + labelName,
|
||||
},
|
||||
});
|
||||
labelOptions.push({
|
||||
id: 'labelAs-' + labelName,
|
||||
value: {
|
||||
name: `${LABEL} ${labelName} ${ON_BEHALF_OF}`,
|
||||
id: 'labelAs-' + labelName,
|
||||
},
|
||||
});
|
||||
}
|
||||
return labelOptions;
|
||||
}
|
||||
|
||||
_computePermissionName(name, permission, permissionValues, capabilities) {
|
||||
if (name === GLOBAL_NAME) {
|
||||
return capabilities[permission.id].name;
|
||||
} else if (permissionValues[permission.id]) {
|
||||
return permissionValues[permission.id].name;
|
||||
} else if (permission.value.label) {
|
||||
let behalfOf = '';
|
||||
if (permission.id.startsWith('labelAs-')) {
|
||||
behalfOf = ON_BEHALF_OF;
|
||||
}
|
||||
return `${LABEL} ${permission.value.label}${behalfOf}`;
|
||||
}
|
||||
}
|
||||
|
||||
_computeSectionName(name) {
|
||||
// When a new section is created, it doesn't yet have a ref. Set into
|
||||
// edit mode so that the user can input one.
|
||||
if (!name) {
|
||||
this._editingRef = true;
|
||||
// Needed for the title value. This is the same default as GWT.
|
||||
name = NEW_NAME;
|
||||
// Needed for the input field value.
|
||||
this.set('section.id', name);
|
||||
}
|
||||
if (name === GLOBAL_NAME) {
|
||||
return 'Global Capabilities';
|
||||
} else if (name.startsWith(REFS_NAME)) {
|
||||
return `Reference: ${name}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
_handleRemoveReference() {
|
||||
if (this.section.value.added) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'added-section-removed', {bubbles: true, composed: true}));
|
||||
}
|
||||
this._deleted = true;
|
||||
this.section.value.deleted = true;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleUndoRemove() {
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld) { return; }
|
||||
// Restore original values if no longer editing.
|
||||
if (!editing) {
|
||||
this._editingRef = false;
|
||||
this._deleted = false;
|
||||
delete this.section.value.deleted;
|
||||
}
|
||||
|
||||
editRefInput() {
|
||||
return Polymer.dom(this.root).querySelector(Polymer.Element ?
|
||||
'iron-input.editRefInput' :
|
||||
'input[is=iron-input].editRefInput');
|
||||
}
|
||||
|
||||
editReference() {
|
||||
this._editingRef = true;
|
||||
this.editRefInput().focus();
|
||||
}
|
||||
|
||||
_isEditEnabled(canUpload, ownerOf, sectionId) {
|
||||
return canUpload || (ownerOf && ownerOf.indexOf(sectionId) >= 0);
|
||||
}
|
||||
|
||||
_computeSectionClass(editing, canUpload, ownerOf, editingRef, deleted) {
|
||||
const classList = [];
|
||||
if (editing
|
||||
&& this._isEditEnabled(canUpload, ownerOf, this.section.id)) {
|
||||
classList.push('editing');
|
||||
// Restore section ref.
|
||||
this.set(['section', 'id'], this._originalId);
|
||||
// Remove any unsaved but added permissions.
|
||||
this._permissions = this._permissions.filter(p => !p.value.added);
|
||||
for (const key of Object.keys(this.section.value.permissions)) {
|
||||
if (this.section.value.permissions[key].added) {
|
||||
delete this.section.value.permissions[key];
|
||||
}
|
||||
}
|
||||
if (editingRef) {
|
||||
classList.push('editingRef');
|
||||
}
|
||||
if (deleted) {
|
||||
classList.push('deleted');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_computeEditBtnClass(name) {
|
||||
return name === GLOBAL_NAME ? 'global' : '';
|
||||
}
|
||||
|
||||
_handleAddPermission() {
|
||||
const value = this.$.permissionSelect.value;
|
||||
const permission = {
|
||||
id: value,
|
||||
value: {rules: {}, added: true},
|
||||
};
|
||||
|
||||
// This is needed to update the 'label' property of the
|
||||
// 'label-<label-name>' permission.
|
||||
//
|
||||
// The value from the add permission dropdown will either be
|
||||
// label-<label-name> or labelAs-<labelName>.
|
||||
// But, the format of the API response is as such:
|
||||
// "permissions": {
|
||||
// "label-Code-Review": {
|
||||
// "label": "Code-Review",
|
||||
// "rules": {...}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// When we add a new item, we have to push the new permission in the same
|
||||
// format as the ones that have been returned by the API.
|
||||
if (value.startsWith('label')) {
|
||||
permission.value.label =
|
||||
value.replace('label-', '').replace('labelAs-', '');
|
||||
}
|
||||
// Add to the end of the array (used in dom-repeat) and also to the
|
||||
// section object that is two way bound with its parent element.
|
||||
this.push('_permissions', permission);
|
||||
this.set(['section.value.permissions', permission.id],
|
||||
permission.value);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrAccessSection.is, GrAccessSection);
|
||||
})();
|
||||
_computePermissions(name, capabilities, labels) {
|
||||
let allPermissions;
|
||||
if (!this.section || !this.section.value) {
|
||||
return [];
|
||||
}
|
||||
if (name === GLOBAL_NAME) {
|
||||
allPermissions = this.toSortedArray(capabilities);
|
||||
} else {
|
||||
const labelOptions = this._computeLabelOptions(labels);
|
||||
allPermissions = labelOptions.concat(
|
||||
this.toSortedArray(this.permissionValues));
|
||||
}
|
||||
return allPermissions
|
||||
.filter(permission => !this.section.value.permissions[permission.id]);
|
||||
}
|
||||
|
||||
_computeHideEditClass(section) {
|
||||
return section.id === 'GLOBAL_CAPABILITIES' ? 'hide' : '';
|
||||
}
|
||||
|
||||
_handleAddedPermissionRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._permissions = this._permissions.slice(0, index).concat(
|
||||
this._permissions.slice(index + 1, this._permissions.length));
|
||||
}
|
||||
|
||||
_computeLabelOptions(labels) {
|
||||
const labelOptions = [];
|
||||
if (!labels) { return []; }
|
||||
for (const labelName of Object.keys(labels)) {
|
||||
labelOptions.push({
|
||||
id: 'label-' + labelName,
|
||||
value: {
|
||||
name: `${LABEL} ${labelName}`,
|
||||
id: 'label-' + labelName,
|
||||
},
|
||||
});
|
||||
labelOptions.push({
|
||||
id: 'labelAs-' + labelName,
|
||||
value: {
|
||||
name: `${LABEL} ${labelName} ${ON_BEHALF_OF}`,
|
||||
id: 'labelAs-' + labelName,
|
||||
},
|
||||
});
|
||||
}
|
||||
return labelOptions;
|
||||
}
|
||||
|
||||
_computePermissionName(name, permission, permissionValues, capabilities) {
|
||||
if (name === GLOBAL_NAME) {
|
||||
return capabilities[permission.id].name;
|
||||
} else if (permissionValues[permission.id]) {
|
||||
return permissionValues[permission.id].name;
|
||||
} else if (permission.value.label) {
|
||||
let behalfOf = '';
|
||||
if (permission.id.startsWith('labelAs-')) {
|
||||
behalfOf = ON_BEHALF_OF;
|
||||
}
|
||||
return `${LABEL} ${permission.value.label}${behalfOf}`;
|
||||
}
|
||||
}
|
||||
|
||||
_computeSectionName(name) {
|
||||
// When a new section is created, it doesn't yet have a ref. Set into
|
||||
// edit mode so that the user can input one.
|
||||
if (!name) {
|
||||
this._editingRef = true;
|
||||
// Needed for the title value. This is the same default as GWT.
|
||||
name = NEW_NAME;
|
||||
// Needed for the input field value.
|
||||
this.set('section.id', name);
|
||||
}
|
||||
if (name === GLOBAL_NAME) {
|
||||
return 'Global Capabilities';
|
||||
} else if (name.startsWith(REFS_NAME)) {
|
||||
return `Reference: ${name}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
_handleRemoveReference() {
|
||||
if (this.section.value.added) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'added-section-removed', {bubbles: true, composed: true}));
|
||||
}
|
||||
this._deleted = true;
|
||||
this.section.value.deleted = true;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleUndoRemove() {
|
||||
this._deleted = false;
|
||||
delete this.section.value.deleted;
|
||||
}
|
||||
|
||||
editRefInput() {
|
||||
return dom(this.root).querySelector(PolymerElement ?
|
||||
'iron-input.editRefInput' :
|
||||
'input[is=iron-input].editRefInput');
|
||||
}
|
||||
|
||||
editReference() {
|
||||
this._editingRef = true;
|
||||
this.editRefInput().focus();
|
||||
}
|
||||
|
||||
_isEditEnabled(canUpload, ownerOf, sectionId) {
|
||||
return canUpload || (ownerOf && ownerOf.indexOf(sectionId) >= 0);
|
||||
}
|
||||
|
||||
_computeSectionClass(editing, canUpload, ownerOf, editingRef, deleted) {
|
||||
const classList = [];
|
||||
if (editing
|
||||
&& this._isEditEnabled(canUpload, ownerOf, this.section.id)) {
|
||||
classList.push('editing');
|
||||
}
|
||||
if (editingRef) {
|
||||
classList.push('editingRef');
|
||||
}
|
||||
if (deleted) {
|
||||
classList.push('deleted');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_computeEditBtnClass(name) {
|
||||
return name === GLOBAL_NAME ? 'global' : '';
|
||||
}
|
||||
|
||||
_handleAddPermission() {
|
||||
const value = this.$.permissionSelect.value;
|
||||
const permission = {
|
||||
id: value,
|
||||
value: {rules: {}, added: true},
|
||||
};
|
||||
|
||||
// This is needed to update the 'label' property of the
|
||||
// 'label-<label-name>' permission.
|
||||
//
|
||||
// The value from the add permission dropdown will either be
|
||||
// label-<label-name> or labelAs-<labelName>.
|
||||
// But, the format of the API response is as such:
|
||||
// "permissions": {
|
||||
// "label-Code-Review": {
|
||||
// "label": "Code-Review",
|
||||
// "rules": {...}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// When we add a new item, we have to push the new permission in the same
|
||||
// format as the ones that have been returned by the API.
|
||||
if (value.startsWith('label')) {
|
||||
permission.value.label =
|
||||
value.replace('label-', '').replace('labelAs-', '');
|
||||
}
|
||||
// Add to the end of the array (used in dom-repeat) and also to the
|
||||
// section object that is two way bound with its parent element.
|
||||
this.push('_permissions', permission);
|
||||
this.set(['section.value.permissions', permission.id],
|
||||
permission.value);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrAccessSection.is, GrAccessSection);
|
||||
|
@ -1,36 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.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">
|
||||
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-permission/gr-permission.html">
|
||||
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
|
||||
<dom-module id="gr-access-section">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
@ -89,50 +75,23 @@ limitations under the License.
|
||||
<style include="gr-form-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<fieldset id="section"
|
||||
class$="gr-form-styles [[_computeSectionClass(editing, canUpload, ownerOf, _editingRef, _deleted)]]">
|
||||
<fieldset id="section" class\$="gr-form-styles [[_computeSectionClass(editing, canUpload, ownerOf, _editingRef, _deleted)]]">
|
||||
<div id="mainContainer">
|
||||
<div class="header">
|
||||
<div class="name">
|
||||
<h3>[[_computeSectionName(section.id)]]</h3>
|
||||
<gr-button
|
||||
id="editBtn"
|
||||
link
|
||||
class$="[[_computeEditBtnClass(section.id)]]"
|
||||
on-click="editReference">
|
||||
<gr-button id="editBtn" link="" class\$="[[_computeEditBtnClass(section.id)]]" on-click="editReference">
|
||||
<iron-icon id="icon" icon="gr-icons:create"></iron-icon>
|
||||
</gr-button>
|
||||
</div>
|
||||
<iron-input
|
||||
class="editRefInput"
|
||||
bind-value="{{section.id}}"
|
||||
type="text"
|
||||
on-input="_handleValueChange">
|
||||
<input
|
||||
class="editRefInput"
|
||||
bind-value="{{section.id}}"
|
||||
is="iron-input"
|
||||
type="text"
|
||||
on-input="_handleValueChange">
|
||||
<iron-input class="editRefInput" bind-value="{{section.id}}" type="text" on-input="_handleValueChange">
|
||||
<input class="editRefInput" bind-value="{{section.id}}" is="iron-input" type="text" on-input="_handleValueChange">
|
||||
</iron-input>
|
||||
<gr-button
|
||||
link
|
||||
id="deleteBtn"
|
||||
on-click="_handleRemoveReference">Remove</gr-button>
|
||||
<gr-button link="" id="deleteBtn" on-click="_handleRemoveReference">Remove</gr-button>
|
||||
</div><!-- end header -->
|
||||
<div class="sectionContent">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="{{_permissions}}"
|
||||
as="permission">
|
||||
<gr-permission
|
||||
name="[[_computePermissionName(section.id, permission, permissionValues, capabilities)]]"
|
||||
permission="{{permission}}"
|
||||
labels="[[labels]]"
|
||||
section="[[section.id]]"
|
||||
editing="[[editing]]"
|
||||
groups="[[groups]]"
|
||||
on-added-permission-removed="_handleAddedPermissionRemoved">
|
||||
<template is="dom-repeat" items="{{_permissions}}" as="permission">
|
||||
<gr-permission name="[[_computePermissionName(section.id, permission, permissionValues, capabilities)]]" permission="{{permission}}" labels="[[labels]]" section="[[section.id]]" editing="[[editing]]" groups="[[groups]]" on-added-permission-removed="_handleAddedPermissionRemoved">
|
||||
</gr-permission>
|
||||
</template>
|
||||
<div id="addPermission">
|
||||
@ -140,29 +99,19 @@ limitations under the License.
|
||||
<select id="permissionSelect">
|
||||
<!-- called with a third parameter so that permissions update
|
||||
after a new section is added. -->
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_computePermissions(section.id, capabilities, labels, section.value.permissions.*)]]">
|
||||
<template is="dom-repeat" items="[[_computePermissions(section.id, capabilities, labels, section.value.permissions.*)]]">
|
||||
<option value="[[item.value.id]]">[[item.value.name]]</option>
|
||||
</template>
|
||||
</select>
|
||||
<gr-button
|
||||
link
|
||||
id="addBtn"
|
||||
on-click="_handleAddPermission">Add</gr-button>
|
||||
<gr-button link="" id="addBtn" on-click="_handleAddPermission">Add</gr-button>
|
||||
</div>
|
||||
<!-- end addPermission -->
|
||||
</div><!-- end sectionContent -->
|
||||
</div><!-- end mainContainer -->
|
||||
<div id="deletedContainer">
|
||||
<span>[[_computeSectionName(section.id)]] was deleted</span>
|
||||
<gr-button
|
||||
link
|
||||
id="undoRemoveBtn"
|
||||
on-click="_handleUndoRemove">Undo</gr-button>
|
||||
<gr-button link="" id="undoRemoveBtn" on-click="_handleUndoRemove">Undo</gr-button>
|
||||
</div><!-- end deletedContainer -->
|
||||
</fieldset>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-access-section.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-access-section</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-access-section.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-access-section.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-access-section.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,28 +41,310 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-access-section tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-access-section.js';
|
||||
suite('gr-access-section tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('unit tests', () => {
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
element.section = {
|
||||
id: 'refs/*',
|
||||
value: {
|
||||
permissions: {
|
||||
read: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
element.capabilities = {
|
||||
accessDatabase: {
|
||||
id: 'accessDatabase',
|
||||
name: 'Access Database',
|
||||
},
|
||||
administrateServer: {
|
||||
id: 'administrateServer',
|
||||
name: 'Administrate Server',
|
||||
},
|
||||
batchChangesLimit: {
|
||||
id: 'batchChangesLimit',
|
||||
name: 'Batch Changes Limit',
|
||||
},
|
||||
createAccount: {
|
||||
id: 'createAccount',
|
||||
name: 'Create Account',
|
||||
},
|
||||
};
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
element._updateSection(element.section);
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
test('_updateSection', () => {
|
||||
// _updateSection was called in setup, so just make assertions.
|
||||
const expectedPermissions = [
|
||||
{
|
||||
id: 'read',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.deepEqual(element._permissions, expectedPermissions);
|
||||
assert.equal(element._originalId, element.section.id);
|
||||
});
|
||||
|
||||
suite('unit tests', () => {
|
||||
test('_computeLabelOptions', () => {
|
||||
const expectedLabelOptions = [
|
||||
{
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review',
|
||||
id: 'label-Code-Review',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review (On Behalf Of)',
|
||||
id: 'labelAs-Code-Review',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(element._computeLabelOptions(element.labels),
|
||||
expectedLabelOptions);
|
||||
});
|
||||
|
||||
test('_handleAccessSaved', () => {
|
||||
assert.equal(element._originalId, 'refs/*');
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleAccessSaved();
|
||||
assert.equal(element._originalId, 'refs/for/bar');
|
||||
});
|
||||
|
||||
test('_computePermissions', () => {
|
||||
sandbox.stub(element, 'toSortedArray').returns(
|
||||
[{
|
||||
id: 'push',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'read',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const expectedPermissions = [{
|
||||
id: 'push',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
const labelOptions = [
|
||||
{
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review',
|
||||
id: 'label-Code-Review',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review (On Behalf Of)',
|
||||
id: 'labelAs-Code-Review',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// For global capabilities, just return the sorted array filtered by
|
||||
// existing permissions.
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
assert.deepEqual(element._computePermissions(name, element.capabilities,
|
||||
element.labels), expectedPermissions);
|
||||
|
||||
// Uses the capabilities array to come up with possible values.
|
||||
assert.isTrue(element.toSortedArray.lastCall.
|
||||
calledWithExactly(element.capabilities));
|
||||
|
||||
// For everything else, include possible label values before filtering.
|
||||
name = 'refs/for/*';
|
||||
assert.deepEqual(element._computePermissions(name, element.capabilities,
|
||||
element.labels), labelOptions.concat(expectedPermissions));
|
||||
|
||||
// Uses permissionValues (defined in gr-access-behavior) to come up with
|
||||
// possible values.
|
||||
assert.isTrue(element.toSortedArray.lastCall.
|
||||
calledWithExactly(element.permissionValues));
|
||||
});
|
||||
|
||||
test('_computePermissionName', () => {
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
let permission = {
|
||||
id: 'administrateServer',
|
||||
value: {},
|
||||
};
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
element.capabilities[permission.id].name);
|
||||
|
||||
name = 'refs/for/*';
|
||||
permission = {
|
||||
id: 'abandon',
|
||||
value: {},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(
|
||||
name, permission, element.permissionValues, element.capabilities),
|
||||
element.permissionValues[permission.id].name);
|
||||
|
||||
name = 'refs/for/*';
|
||||
permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
'Label Code-Review');
|
||||
|
||||
permission = {
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
'Label Code-Review(On Behalf Of)');
|
||||
});
|
||||
|
||||
test('_computeSectionName', () => {
|
||||
let name;
|
||||
// When computing the section name for an undefined name, it means a
|
||||
// new section is being added. In this case, it should defualt to
|
||||
// 'refs/heads/*'.
|
||||
element._editingRef = false;
|
||||
assert.equal(element._computeSectionName(name),
|
||||
'Reference: refs/heads/*');
|
||||
assert.isTrue(element._editingRef);
|
||||
assert.equal(element.section.id, 'refs/heads/*');
|
||||
|
||||
// Reset editing to false.
|
||||
element._editingRef = false;
|
||||
name = 'GLOBAL_CAPABILITIES';
|
||||
assert.equal(element._computeSectionName(name), 'Global Capabilities');
|
||||
assert.isFalse(element._editingRef);
|
||||
|
||||
name = 'refs/for/*';
|
||||
assert.equal(element._computeSectionName(name),
|
||||
'Reference: refs/for/*');
|
||||
assert.isFalse(element._editingRef);
|
||||
});
|
||||
|
||||
test('editReference', () => {
|
||||
element.editReference();
|
||||
assert.isTrue(element._editingRef);
|
||||
});
|
||||
|
||||
test('_computeSectionClass', () => {
|
||||
let editingRef = false;
|
||||
let canUpload = false;
|
||||
let ownerOf = [];
|
||||
let editing = false;
|
||||
let deleted = false;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), '');
|
||||
|
||||
editing = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), '');
|
||||
|
||||
ownerOf = ['refs/*'];
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing');
|
||||
|
||||
ownerOf = [];
|
||||
canUpload = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing');
|
||||
|
||||
editingRef = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing editingRef');
|
||||
|
||||
deleted = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing editingRef deleted');
|
||||
|
||||
editingRef = false;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing deleted');
|
||||
});
|
||||
|
||||
test('_computeEditBtnClass', () => {
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
assert.equal(element._computeEditBtnClass(name), 'global');
|
||||
name = 'refs/for/*';
|
||||
assert.equal(element._computeEditBtnClass(name), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('interactive tests', () => {
|
||||
setup(() => {
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
suite('Global section', () => {
|
||||
setup(() => {
|
||||
element.section = {
|
||||
id: 'refs/*',
|
||||
id: 'GLOBAL_CAPABILITIES',
|
||||
value: {
|
||||
permissions: {
|
||||
read: {
|
||||
accessDatabase: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
@ -81,476 +368,196 @@ limitations under the License.
|
||||
name: 'Create Account',
|
||||
},
|
||||
};
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
element._updateSection(element.section);
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
test('_updateSection', () => {
|
||||
// _updateSection was called in setup, so just make assertions.
|
||||
const expectedPermissions = [
|
||||
{
|
||||
id: 'read',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
assert.deepEqual(element._permissions, expectedPermissions);
|
||||
assert.equal(element._originalId, element.section.id);
|
||||
});
|
||||
|
||||
test('_computeLabelOptions', () => {
|
||||
const expectedLabelOptions = [
|
||||
{
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review',
|
||||
id: 'label-Code-Review',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review (On Behalf Of)',
|
||||
id: 'labelAs-Code-Review',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(element._computeLabelOptions(element.labels),
|
||||
expectedLabelOptions);
|
||||
});
|
||||
|
||||
test('_handleAccessSaved', () => {
|
||||
assert.equal(element._originalId, 'refs/*');
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleAccessSaved();
|
||||
assert.equal(element._originalId, 'refs/for/bar');
|
||||
});
|
||||
|
||||
test('_computePermissions', () => {
|
||||
sandbox.stub(element, 'toSortedArray').returns(
|
||||
[{
|
||||
id: 'push',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'read',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const expectedPermissions = [{
|
||||
id: 'push',
|
||||
value: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
const labelOptions = [
|
||||
{
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review',
|
||||
id: 'label-Code-Review',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
name: 'Label Code-Review (On Behalf Of)',
|
||||
id: 'labelAs-Code-Review',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// For global capabilities, just return the sorted array filtered by
|
||||
// existing permissions.
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
assert.deepEqual(element._computePermissions(name, element.capabilities,
|
||||
element.labels), expectedPermissions);
|
||||
|
||||
// Uses the capabilities array to come up with possible values.
|
||||
assert.isTrue(element.toSortedArray.lastCall.
|
||||
calledWithExactly(element.capabilities));
|
||||
|
||||
// For everything else, include possible label values before filtering.
|
||||
name = 'refs/for/*';
|
||||
assert.deepEqual(element._computePermissions(name, element.capabilities,
|
||||
element.labels), labelOptions.concat(expectedPermissions));
|
||||
|
||||
// Uses permissionValues (defined in gr-access-behavior) to come up with
|
||||
// possible values.
|
||||
assert.isTrue(element.toSortedArray.lastCall.
|
||||
calledWithExactly(element.permissionValues));
|
||||
});
|
||||
|
||||
test('_computePermissionName', () => {
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
let permission = {
|
||||
id: 'administrateServer',
|
||||
value: {},
|
||||
};
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
element.capabilities[permission.id].name);
|
||||
|
||||
name = 'refs/for/*';
|
||||
permission = {
|
||||
id: 'abandon',
|
||||
value: {},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(
|
||||
name, permission, element.permissionValues, element.capabilities),
|
||||
element.permissionValues[permission.id].name);
|
||||
|
||||
name = 'refs/for/*';
|
||||
permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
'Label Code-Review');
|
||||
|
||||
permission = {
|
||||
id: 'labelAs-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(element._computePermissionName(name, permission,
|
||||
element.permissionValues, element.capabilities),
|
||||
'Label Code-Review(On Behalf Of)');
|
||||
});
|
||||
|
||||
test('_computeSectionName', () => {
|
||||
let name;
|
||||
// When computing the section name for an undefined name, it means a
|
||||
// new section is being added. In this case, it should defualt to
|
||||
// 'refs/heads/*'.
|
||||
element._editingRef = false;
|
||||
assert.equal(element._computeSectionName(name),
|
||||
'Reference: refs/heads/*');
|
||||
assert.isTrue(element._editingRef);
|
||||
assert.equal(element.section.id, 'refs/heads/*');
|
||||
|
||||
// Reset editing to false.
|
||||
element._editingRef = false;
|
||||
name = 'GLOBAL_CAPABILITIES';
|
||||
assert.equal(element._computeSectionName(name), 'Global Capabilities');
|
||||
assert.isFalse(element._editingRef);
|
||||
|
||||
name = 'refs/for/*';
|
||||
assert.equal(element._computeSectionName(name),
|
||||
'Reference: refs/for/*');
|
||||
assert.isFalse(element._editingRef);
|
||||
});
|
||||
|
||||
test('editReference', () => {
|
||||
element.editReference();
|
||||
assert.isTrue(element._editingRef);
|
||||
});
|
||||
|
||||
test('_computeSectionClass', () => {
|
||||
let editingRef = false;
|
||||
let canUpload = false;
|
||||
let ownerOf = [];
|
||||
let editing = false;
|
||||
let deleted = false;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), '');
|
||||
|
||||
editing = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), '');
|
||||
|
||||
ownerOf = ['refs/*'];
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing');
|
||||
|
||||
ownerOf = [];
|
||||
canUpload = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing');
|
||||
|
||||
editingRef = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing editingRef');
|
||||
|
||||
deleted = true;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing editingRef deleted');
|
||||
|
||||
editingRef = false;
|
||||
assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
|
||||
editingRef, deleted), 'editing deleted');
|
||||
});
|
||||
|
||||
test('_computeEditBtnClass', () => {
|
||||
let name = 'GLOBAL_CAPABILITIES';
|
||||
assert.equal(element._computeEditBtnClass(name), 'global');
|
||||
name = 'refs/for/*';
|
||||
assert.equal(element._computeEditBtnClass(name), '');
|
||||
test('classes are assigned correctly', () => {
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element.$.section.classList.contains('deleted'));
|
||||
assert.isTrue(element.$.editBtn.classList.contains('global'));
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
suite('interactive tests', () => {
|
||||
suite('Non-global section', () => {
|
||||
setup(() => {
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
element.section = {
|
||||
id: 'refs/*',
|
||||
value: {
|
||||
permissions: {
|
||||
read: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
suite('Global section', () => {
|
||||
setup(() => {
|
||||
element.section = {
|
||||
id: 'GLOBAL_CAPABILITIES',
|
||||
value: {
|
||||
permissions: {
|
||||
accessDatabase: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
element.capabilities = {
|
||||
accessDatabase: {
|
||||
id: 'accessDatabase',
|
||||
name: 'Access Database',
|
||||
},
|
||||
administrateServer: {
|
||||
id: 'administrateServer',
|
||||
name: 'Administrate Server',
|
||||
},
|
||||
batchChangesLimit: {
|
||||
id: 'batchChangesLimit',
|
||||
name: 'Batch Changes Limit',
|
||||
},
|
||||
createAccount: {
|
||||
id: 'createAccount',
|
||||
name: 'Create Account',
|
||||
},
|
||||
};
|
||||
element._updateSection(element.section);
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
test('classes are assigned correctly', () => {
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element.$.section.classList.contains('deleted'));
|
||||
assert.isTrue(element.$.editBtn.classList.contains('global'));
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
|
||||
});
|
||||
element.capabilities = {};
|
||||
element._updateSection(element.section);
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
suite('Non-global section', () => {
|
||||
setup(() => {
|
||||
element.section = {
|
||||
id: 'refs/*',
|
||||
value: {
|
||||
permissions: {
|
||||
read: {
|
||||
rules: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
element.capabilities = {};
|
||||
element._updateSection(element.section);
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
test('classes are assigned correctly', () => {
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element.$.section.classList.contains('deleted'));
|
||||
assert.isFalse(element.$.editBtn.classList.contains('global'));
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
flushAsynchronousOperations();
|
||||
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
|
||||
});
|
||||
|
||||
test('classes are assigned correctly', () => {
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element.$.section.classList.contains('deleted'));
|
||||
assert.isFalse(element.$.editBtn.classList.contains('global'));
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
flushAsynchronousOperations();
|
||||
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
|
||||
});
|
||||
test('add permission', () => {
|
||||
element.editing = true;
|
||||
element.$.permissionSelect.value = 'label-Code-Review';
|
||||
assert.equal(element._permissions.length, 1);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
1);
|
||||
MockInteractions.tap(element.$.addBtn);
|
||||
flushAsynchronousOperations();
|
||||
|
||||
test('add permission', () => {
|
||||
element.editing = true;
|
||||
element.$.permissionSelect.value = 'label-Code-Review';
|
||||
assert.equal(element._permissions.length, 1);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
1);
|
||||
MockInteractions.tap(element.$.addBtn);
|
||||
flushAsynchronousOperations();
|
||||
// The permission is added to both the permissions array and also
|
||||
// the section's permission object.
|
||||
assert.equal(element._permissions.length, 2);
|
||||
let permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
added: true,
|
||||
label: 'Code-Review',
|
||||
rules: {},
|
||||
},
|
||||
};
|
||||
assert.equal(element._permissions.length, 2);
|
||||
assert.deepEqual(element._permissions[1], permission);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
2);
|
||||
assert.deepEqual(
|
||||
element.section.value.permissions['label-Code-Review'],
|
||||
permission.value);
|
||||
|
||||
// The permission is added to both the permissions array and also
|
||||
// the section's permission object.
|
||||
assert.equal(element._permissions.length, 2);
|
||||
let permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
added: true,
|
||||
label: 'Code-Review',
|
||||
rules: {},
|
||||
},
|
||||
};
|
||||
assert.equal(element._permissions.length, 2);
|
||||
assert.deepEqual(element._permissions[1], permission);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
2);
|
||||
assert.deepEqual(
|
||||
element.section.value.permissions['label-Code-Review'],
|
||||
permission.value);
|
||||
element.$.permissionSelect.value = 'abandon';
|
||||
MockInteractions.tap(element.$.addBtn);
|
||||
flushAsynchronousOperations();
|
||||
|
||||
element.$.permissionSelect.value = 'abandon';
|
||||
MockInteractions.tap(element.$.addBtn);
|
||||
flushAsynchronousOperations();
|
||||
permission = {
|
||||
id: 'abandon',
|
||||
value: {
|
||||
added: true,
|
||||
rules: {},
|
||||
},
|
||||
};
|
||||
|
||||
permission = {
|
||||
id: 'abandon',
|
||||
value: {
|
||||
added: true,
|
||||
rules: {},
|
||||
},
|
||||
};
|
||||
assert.equal(element._permissions.length, 3);
|
||||
assert.deepEqual(element._permissions[2], permission);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
3);
|
||||
assert.deepEqual(element.section.value.permissions['abandon'],
|
||||
permission.value);
|
||||
|
||||
assert.equal(element._permissions.length, 3);
|
||||
assert.deepEqual(element._permissions[2], permission);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
3);
|
||||
assert.deepEqual(element.section.value.permissions['abandon'],
|
||||
permission.value);
|
||||
// Unsaved changes are discarded when editing is cancelled.
|
||||
element.editing = false;
|
||||
assert.equal(element._permissions.length, 1);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
1);
|
||||
});
|
||||
|
||||
// Unsaved changes are discarded when editing is cancelled.
|
||||
test('edit section reference', done => {
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
element.editing = true;
|
||||
assert.isTrue(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element._editingRef);
|
||||
MockInteractions.tap(element.$.editBtn);
|
||||
element.editRefInput().bindValue='new/ref';
|
||||
setTimeout(() => {
|
||||
assert.equal(element.section.id, 'new/ref');
|
||||
assert.isTrue(element._editingRef);
|
||||
assert.isTrue(element.$.section.classList.contains('editingRef'));
|
||||
element.editing = false;
|
||||
assert.equal(element._permissions.length, 1);
|
||||
assert.equal(Object.keys(element.section.value.permissions).length,
|
||||
1);
|
||||
});
|
||||
|
||||
test('edit section reference', done => {
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
|
||||
assert.isFalse(element.$.section.classList.contains('editing'));
|
||||
element.editing = true;
|
||||
assert.isTrue(element.$.section.classList.contains('editing'));
|
||||
assert.isFalse(element._editingRef);
|
||||
MockInteractions.tap(element.$.editBtn);
|
||||
element.editRefInput().bindValue='new/ref';
|
||||
setTimeout(() => {
|
||||
assert.equal(element.section.id, 'new/ref');
|
||||
assert.isTrue(element._editingRef);
|
||||
assert.isTrue(element.$.section.classList.contains('editingRef'));
|
||||
element.editing = false;
|
||||
assert.isFalse(element._editingRef);
|
||||
assert.equal(element.section.id, 'refs/for/bar');
|
||||
done();
|
||||
});
|
||||
assert.equal(element.section.id, 'refs/for/bar');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_handleValueChange', () => {
|
||||
// For an exising section.
|
||||
const modifiedHandler = sandbox.stub();
|
||||
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
|
||||
assert.notOk(element.section.value.updatedId);
|
||||
element.section.id = 'refs/for/baz';
|
||||
element.addEventListener('access-modified', modifiedHandler);
|
||||
assert.isNotOk(element.section.value.modified);
|
||||
element._handleValueChange();
|
||||
assert.equal(element.section.value.updatedId, 'refs/for/baz');
|
||||
assert.isTrue(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 1);
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
test('_handleValueChange', () => {
|
||||
// For an exising section.
|
||||
const modifiedHandler = sandbox.stub();
|
||||
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
|
||||
assert.notOk(element.section.value.updatedId);
|
||||
element.section.id = 'refs/for/baz';
|
||||
element.addEventListener('access-modified', modifiedHandler);
|
||||
assert.isNotOk(element.section.value.modified);
|
||||
element._handleValueChange();
|
||||
assert.equal(element.section.value.updatedId, 'refs/for/baz');
|
||||
assert.isTrue(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 1);
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
|
||||
// For a new section.
|
||||
element.section.value.added = true;
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
});
|
||||
// For a new section.
|
||||
element.section.value.added = true;
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
element.section.id = 'refs/for/bar';
|
||||
element._handleValueChange();
|
||||
assert.isFalse(element.section.value.modified);
|
||||
assert.equal(modifiedHandler.callCount, 2);
|
||||
});
|
||||
|
||||
test('remove section', () => {
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
assert.isTrue(element.$.section.classList.contains('deleted'));
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
test('remove section', () => {
|
||||
element.editing = true;
|
||||
element.canUpload = true;
|
||||
element.ownerOf = [];
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
assert.isTrue(element.$.section.classList.contains('deleted'));
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
|
||||
MockInteractions.tap(element.$.undoRemoveBtn);
|
||||
flushAsynchronousOperations();
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
MockInteractions.tap(element.$.undoRemoveBtn);
|
||||
flushAsynchronousOperations();
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
element.editing = false;
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
});
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.section.value.deleted);
|
||||
element.editing = false;
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.section.value.deleted);
|
||||
});
|
||||
|
||||
test('removing an added permission', () => {
|
||||
element.editing = true;
|
||||
assert.equal(element._permissions.length, 1);
|
||||
element.shadowRoot
|
||||
.querySelector('gr-permission').fire('added-permission-removed');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element._permissions.length, 0);
|
||||
});
|
||||
test('removing an added permission', () => {
|
||||
element.editing = true;
|
||||
assert.equal(element._permissions.length, 1);
|
||||
element.shadowRoot
|
||||
.querySelector('gr-permission').fire('added-permission-removed');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element._permissions.length, 0);
|
||||
});
|
||||
|
||||
test('remove an added section', () => {
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-section-removed', removeStub);
|
||||
element.editing = true;
|
||||
element.section.value.added = true;
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
assert.isTrue(removeStub.called);
|
||||
});
|
||||
test('remove an added section', () => {
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-section-removed', removeStub);
|
||||
element.editing = true;
|
||||
element.section.value.added = true;
|
||||
MockInteractions.tap(element.$.deleteBtn);
|
||||
assert.isTrue(removeStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,155 +14,171 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-dialog/gr-dialog.js';
|
||||
import '../../shared/gr-list-view/gr-list-view.js';
|
||||
import '../../shared/gr-overlay/gr-overlay.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-create-group-dialog/gr-create-group-dialog.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-admin-group-list_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrAdminGroupList extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-admin-group-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/groups',
|
||||
},
|
||||
_hasNewGroupName: Boolean,
|
||||
_createNewCapability: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_groups: Array,
|
||||
|
||||
/**
|
||||
* Because we request one more than the groupsPerPage, _shownGroups
|
||||
* may be one less than _groups.
|
||||
* */
|
||||
_shownGroups: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_groups)',
|
||||
},
|
||||
|
||||
_groupsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: String,
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._getCreateGroupCapability();
|
||||
this.fire('title-change', {title: 'Groups'});
|
||||
this._maybeOpenCreateOverlay(this.params);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getGroups(this._filter, this._groupsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
* Opens the create overlay if the route has a hash 'create'
|
||||
*
|
||||
* @param {!Object} params
|
||||
*/
|
||||
class GrAdminGroupList extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-admin-group-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/groups',
|
||||
},
|
||||
_hasNewGroupName: Boolean,
|
||||
_createNewCapability: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_groups: Array,
|
||||
|
||||
/**
|
||||
* Because we request one more than the groupsPerPage, _shownGroups
|
||||
* may be one less than _groups.
|
||||
* */
|
||||
_shownGroups: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_groups)',
|
||||
},
|
||||
|
||||
_groupsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: String,
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._getCreateGroupCapability();
|
||||
this.fire('title-change', {title: 'Groups'});
|
||||
this._maybeOpenCreateOverlay(this.params);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getGroups(this._filter, this._groupsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the create overlay if the route has a hash 'create'
|
||||
*
|
||||
* @param {!Object} params
|
||||
*/
|
||||
_maybeOpenCreateOverlay(params) {
|
||||
if (params && params.openCreateModal) {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
}
|
||||
|
||||
_computeGroupUrl(id) {
|
||||
return Gerrit.Nav.getUrlForGroup(id);
|
||||
}
|
||||
|
||||
_getCreateGroupCapability() {
|
||||
return this.$.restAPI.getAccount().then(account => {
|
||||
if (!account) { return; }
|
||||
return this.$.restAPI.getAccountCapabilities(['createGroup'])
|
||||
.then(capabilities => {
|
||||
if (capabilities.createGroup) {
|
||||
this._createNewCapability = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getGroups(filter, groupsPerPage, offset) {
|
||||
this._groups = [];
|
||||
return this.$.restAPI.getGroups(filter, groupsPerPage, offset)
|
||||
.then(groups => {
|
||||
if (!groups) {
|
||||
return;
|
||||
}
|
||||
this._groups = Object.keys(groups)
|
||||
.map(key => {
|
||||
const group = groups[key];
|
||||
group.name = key;
|
||||
return group;
|
||||
});
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_refreshGroupsList() {
|
||||
this.$.restAPI.invalidateGroupsCache();
|
||||
return this._getGroups(this._filter, this._groupsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_handleCreateGroup() {
|
||||
this.$.createNewModal.handleCreateGroup().then(() => {
|
||||
this._refreshGroupsList();
|
||||
});
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
_maybeOpenCreateOverlay(params) {
|
||||
if (params && params.openCreateModal) {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_visibleToAll(item) {
|
||||
return item.options.visible_to_all === true ? 'Y' : 'N';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrAdminGroupList.is, GrAdminGroupList);
|
||||
})();
|
||||
_computeGroupUrl(id) {
|
||||
return Gerrit.Nav.getUrlForGroup(id);
|
||||
}
|
||||
|
||||
_getCreateGroupCapability() {
|
||||
return this.$.restAPI.getAccount().then(account => {
|
||||
if (!account) { return; }
|
||||
return this.$.restAPI.getAccountCapabilities(['createGroup'])
|
||||
.then(capabilities => {
|
||||
if (capabilities.createGroup) {
|
||||
this._createNewCapability = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getGroups(filter, groupsPerPage, offset) {
|
||||
this._groups = [];
|
||||
return this.$.restAPI.getGroups(filter, groupsPerPage, offset)
|
||||
.then(groups => {
|
||||
if (!groups) {
|
||||
return;
|
||||
}
|
||||
this._groups = Object.keys(groups)
|
||||
.map(key => {
|
||||
const group = groups[key];
|
||||
group.name = key;
|
||||
return group;
|
||||
});
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_refreshGroupsList() {
|
||||
this.$.restAPI.invalidateGroupsCache();
|
||||
return this._getGroups(this._filter, this._groupsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_handleCreateGroup() {
|
||||
this.$.createNewModal.handleCreateGroup().then(() => {
|
||||
this._refreshGroupsList();
|
||||
});
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_visibleToAll(item) {
|
||||
return item.options.visible_to_all === true ? 'Y' : 'N';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrAdminGroupList.is, GrAdminGroupList);
|
||||
|
@ -1,64 +1,43 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
|
||||
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
|
||||
<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-create-group-dialog/gr-create-group-dialog.html">
|
||||
|
||||
<dom-module id="gr-admin-group-list">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<style include="gr-table-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<gr-list-view
|
||||
create-new="[[_createNewCapability]]"
|
||||
filter="[[_filter]]"
|
||||
items="[[_groups]]"
|
||||
items-per-page="[[_groupsPerPage]]"
|
||||
loading="[[_loading]]"
|
||||
offset="[[_offset]]"
|
||||
on-create-clicked="_handleCreateClicked"
|
||||
path="[[_path]]">
|
||||
<gr-list-view create-new="[[_createNewCapability]]" filter="[[_filter]]" items="[[_groups]]" items-per-page="[[_groupsPerPage]]" loading="[[_loading]]" offset="[[_offset]]" on-create-clicked="_handleCreateClicked" path="[[_path]]">
|
||||
<table id="list" class="genericList">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="name topHeader">Group Name</th>
|
||||
<th class="description topHeader">Group Description</th>
|
||||
<th class="visibleToAll topHeader">Visible To All</th>
|
||||
</tr>
|
||||
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<tr id="loading" class\$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody class$="[[computeLoadingClass(_loading)]]">
|
||||
</tbody><tbody class\$="[[computeLoadingClass(_loading)]]">
|
||||
<template is="dom-repeat" items="[[_shownGroups]]">
|
||||
<tr class="table">
|
||||
<td class="name">
|
||||
<a href$="[[_computeGroupUrl(item.group_id)]]">[[item.name]]</a>
|
||||
<a href\$="[[_computeGroupUrl(item.group_id)]]">[[item.name]]</a>
|
||||
</td>
|
||||
<td class="description">[[item.description]]</td>
|
||||
<td class="visibleToAll">[[_visibleToAll(item)]]</td>
|
||||
@ -67,27 +46,15 @@ limitations under the License.
|
||||
</tbody>
|
||||
</table>
|
||||
</gr-list-view>
|
||||
<gr-overlay id="createOverlay" with-backdrop>
|
||||
<gr-dialog
|
||||
id="createDialog"
|
||||
class="confirmDialog"
|
||||
disabled="[[!_hasNewGroupName]]"
|
||||
confirm-label="Create"
|
||||
confirm-on-enter
|
||||
on-confirm="_handleCreateGroup"
|
||||
on-cancel="_handleCloseCreate">
|
||||
<gr-overlay id="createOverlay" with-backdrop="">
|
||||
<gr-dialog id="createDialog" class="confirmDialog" disabled="[[!_hasNewGroupName]]" confirm-label="Create" confirm-on-enter="" on-confirm="_handleCreateGroup" on-cancel="_handleCloseCreate">
|
||||
<div class="header" slot="header">
|
||||
Create Group
|
||||
</div>
|
||||
<div class="main" slot="main">
|
||||
<gr-create-group-dialog
|
||||
has-new-group-name="{{_hasNewGroupName}}"
|
||||
params="[[params]]"
|
||||
id="createNewModal"></gr-create-group-dialog>
|
||||
<gr-create-group-dialog has-new-group-name="{{_hasNewGroupName}}" params="[[params]]" id="createNewModal"></gr-create-group-dialog>
|
||||
</div>
|
||||
</gr-dialog>
|
||||
</gr-overlay>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-admin-group-list.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,18 +19,23 @@ 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-group-list</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
|
||||
<link rel="import" href="gr-admin-group-list.html">
|
||||
<script type="module" src="./gr-admin-group-list.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-admin-group-list.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -38,153 +43,155 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
let counter = 0;
|
||||
const groupGenerator = () => {
|
||||
return {
|
||||
name: `test${++counter}`,
|
||||
id: '59b92f35489e62c80d1ab1bf0c2d17843038df8b',
|
||||
url: '#/admin/groups/uuid-59b92f35489e62c80d1ab1bf0c2d17843038df8b',
|
||||
options: {
|
||||
visible_to_all: false,
|
||||
},
|
||||
description: 'Gerrit Site Administrators',
|
||||
group_id: 1,
|
||||
owner: 'Administrators',
|
||||
owner_id: '7ca042f4d5847936fcb90ca91057673157fd06fc',
|
||||
};
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-admin-group-list.js';
|
||||
let counter = 0;
|
||||
const groupGenerator = () => {
|
||||
return {
|
||||
name: `test${++counter}`,
|
||||
id: '59b92f35489e62c80d1ab1bf0c2d17843038df8b',
|
||||
url: '#/admin/groups/uuid-59b92f35489e62c80d1ab1bf0c2d17843038df8b',
|
||||
options: {
|
||||
visible_to_all: false,
|
||||
},
|
||||
description: 'Gerrit Site Administrators',
|
||||
group_id: 1,
|
||||
owner: 'Administrators',
|
||||
owner_id: '7ca042f4d5847936fcb90ca91057673157fd06fc',
|
||||
};
|
||||
};
|
||||
|
||||
suite('gr-admin-group-list tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let groups;
|
||||
let sandbox;
|
||||
let value;
|
||||
suite('gr-admin-group-list tests', () => {
|
||||
let element;
|
||||
let groups;
|
||||
let sandbox;
|
||||
let value;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with groups', () => {
|
||||
setup(done => {
|
||||
groups = _.times(26, groupGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getGroups(num, offset) {
|
||||
return Promise.resolve(groups);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with groups', () => {
|
||||
setup(done => {
|
||||
groups = _.times(26, groupGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getGroups(num, offset) {
|
||||
return Promise.resolve(groups);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('test for test group in the list', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._groups[1].name, '1');
|
||||
assert.equal(element._groups[1].options.visible_to_all, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_shownGroups', () => {
|
||||
assert.equal(element._shownGroups.length, 25);
|
||||
});
|
||||
|
||||
test('_maybeOpenCreateOverlay', () => {
|
||||
const overlayOpen = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._maybeOpenCreateOverlay();
|
||||
assert.isFalse(overlayOpen.called);
|
||||
const params = {};
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isFalse(overlayOpen.called);
|
||||
params.openCreateModal = true;
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isTrue(overlayOpen.called);
|
||||
test('test for test group in the list', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._groups[1].name, '1');
|
||||
assert.equal(element._groups[1].options.visible_to_all, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('test with less then 25 groups', () => {
|
||||
setup(done => {
|
||||
groups = _.times(25, groupGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getGroups(num, offset) {
|
||||
return Promise.resolve(groups);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('_shownGroups', () => {
|
||||
assert.equal(element._shownGroups.length, 25);
|
||||
});
|
||||
test('_shownGroups', () => {
|
||||
assert.equal(element._shownGroups.length, 25);
|
||||
});
|
||||
|
||||
suite('filter', () => {
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroups',
|
||||
() => Promise.resolve(groups));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.isTrue(element.$.restAPI.getGroups.lastCall
|
||||
.calledWithExactly('test', 25, 25));
|
||||
done();
|
||||
});
|
||||
test('_maybeOpenCreateOverlay', () => {
|
||||
const overlayOpen = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._maybeOpenCreateOverlay();
|
||||
assert.isFalse(overlayOpen.called);
|
||||
const params = {};
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isFalse(overlayOpen.called);
|
||||
params.openCreateModal = true;
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isTrue(overlayOpen.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('test with less then 25 groups', () => {
|
||||
setup(done => {
|
||||
groups = _.times(25, groupGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getGroups(num, offset) {
|
||||
return Promise.resolve(groups);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._groups = _.times(25, groupGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
test('_shownGroups', () => {
|
||||
assert.equal(element._shownGroups.length, 25);
|
||||
});
|
||||
});
|
||||
|
||||
suite('create new', () => {
|
||||
test('_handleCreateClicked called when create-click fired', () => {
|
||||
sandbox.stub(element, '_handleCreateClicked');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-list-view').fire('create-clicked');
|
||||
assert.isTrue(element._handleCreateClicked.called);
|
||||
});
|
||||
|
||||
test('_handleCreateClicked opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._handleCreateClicked();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateGroup called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateGroup');
|
||||
element.$.createDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateGroup.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreate called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreate');
|
||||
element.$.createDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreate.called);
|
||||
suite('filter', () => {
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroups',
|
||||
() => Promise.resolve(groups));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.isTrue(element.$.restAPI.getGroups.lastCall
|
||||
.calledWithExactly('test', 25, 25));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._groups = _.times(25, groupGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
suite('create new', () => {
|
||||
test('_handleCreateClicked called when create-click fired', () => {
|
||||
sandbox.stub(element, '_handleCreateClicked');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-list-view').fire('create-clicked');
|
||||
assert.isTrue(element._handleCreateClicked.called);
|
||||
});
|
||||
|
||||
test('_handleCreateClicked opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._handleCreateClicked();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateGroup called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateGroup');
|
||||
element.$.createDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateGroup.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreate called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreate');
|
||||
element.$.createDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreate.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,281 +14,310 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '../../../styles/gr-menu-page-styles.js';
|
||||
import '../../../styles/gr-page-nav-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-dropdown-list/gr-dropdown-list.js';
|
||||
import '../../shared/gr-icons/gr-icons.js';
|
||||
import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
|
||||
import '../../shared/gr-page-nav/gr-page-nav.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-admin-group-list/gr-admin-group-list.js';
|
||||
import '../gr-group/gr-group.js';
|
||||
import '../gr-group-audit-log/gr-group-audit-log.js';
|
||||
import '../gr-group-members/gr-group-members.js';
|
||||
import '../gr-plugin-list/gr-plugin-list.js';
|
||||
import '../gr-repo/gr-repo.js';
|
||||
import '../gr-repo-access/gr-repo-access.js';
|
||||
import '../gr-repo-commands/gr-repo-commands.js';
|
||||
import '../gr-repo-dashboards/gr-repo-dashboards.js';
|
||||
import '../gr-repo-detail-list/gr-repo-detail-list.js';
|
||||
import '../gr-repo-list/gr-repo-list.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-admin-view_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.AdminNavMixin
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrAdminView extends Polymer.mixinBehaviors( [
|
||||
Gerrit.AdminNavBehavior,
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-admin-view'; }
|
||||
const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {?} */
|
||||
params: Object,
|
||||
path: String,
|
||||
adminView: String,
|
||||
/**
|
||||
* @appliesMixin Gerrit.AdminNavMixin
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrAdminView extends mixinBehaviors( [
|
||||
Gerrit.AdminNavBehavior,
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
_breadcrumbParentName: String,
|
||||
_repoName: String,
|
||||
_groupId: {
|
||||
type: Number,
|
||||
observer: '_computeGroupName',
|
||||
},
|
||||
_groupIsInternal: Boolean,
|
||||
_groupName: String,
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_subsectionLinks: Array,
|
||||
_filteredLinks: Array,
|
||||
_showDownload: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_showGroup: Boolean,
|
||||
_showGroupAuditLog: Boolean,
|
||||
_showGroupList: Boolean,
|
||||
_showGroupMembers: Boolean,
|
||||
_showRepoAccess: Boolean,
|
||||
_showRepoCommands: Boolean,
|
||||
_showRepoDashboards: Boolean,
|
||||
_showRepoDetailList: Boolean,
|
||||
_showRepoMain: Boolean,
|
||||
_showRepoList: Boolean,
|
||||
_showPluginList: Boolean,
|
||||
};
|
||||
}
|
||||
static get is() { return 'gr-admin-view'; }
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_paramsChanged(params)',
|
||||
];
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {?} */
|
||||
params: Object,
|
||||
path: String,
|
||||
adminView: String,
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.reload();
|
||||
}
|
||||
|
||||
reload() {
|
||||
const promises = [
|
||||
this.$.restAPI.getAccount(),
|
||||
Gerrit.awaitPluginsLoaded(),
|
||||
];
|
||||
return Promise.all(promises).then(result => {
|
||||
this._account = result[0];
|
||||
let options;
|
||||
if (this._repoName) {
|
||||
options = {repoName: this._repoName};
|
||||
} else if (this._groupId) {
|
||||
options = {
|
||||
groupId: this._groupId,
|
||||
groupName: this._groupName,
|
||||
groupIsInternal: this._groupIsInternal,
|
||||
isAdmin: this._isAdmin,
|
||||
groupOwner: this._groupOwner,
|
||||
};
|
||||
}
|
||||
|
||||
return this.getAdminLinks(this._account,
|
||||
this.$.restAPI.getAccountCapabilities.bind(this.$.restAPI),
|
||||
this.$.jsAPI.getAdminMenuLinks.bind(this.$.jsAPI),
|
||||
options)
|
||||
.then(res => {
|
||||
this._filteredLinks = res.links;
|
||||
this._breadcrumbParentName = res.expandedSection ?
|
||||
res.expandedSection.name : '';
|
||||
|
||||
if (!res.expandedSection) {
|
||||
this._subsectionLinks = [];
|
||||
return;
|
||||
}
|
||||
this._subsectionLinks = [res.expandedSection]
|
||||
.concat(res.expandedSection.children).map(section => {
|
||||
return {
|
||||
text: !section.detailType ? 'Home' : section.name,
|
||||
value: section.view + (section.detailType || ''),
|
||||
view: section.view,
|
||||
url: section.url,
|
||||
detailType: section.detailType,
|
||||
parent: this._groupId || this._repoName || '',
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeSelectValue(params) {
|
||||
if (!params || !params.view) { return; }
|
||||
return params.view + (params.detail || '');
|
||||
}
|
||||
|
||||
_selectedIsCurrentPage(selected) {
|
||||
return (selected.parent === (this._repoName || this._groupId) &&
|
||||
selected.view === this.params.view &&
|
||||
selected.detailType === this.params.detail);
|
||||
}
|
||||
|
||||
_handleSubsectionChange(e) {
|
||||
const selected = this._subsectionLinks
|
||||
.find(section => section.value === e.detail.value);
|
||||
|
||||
// This is when it gets set initially.
|
||||
if (this._selectedIsCurrentPage(selected)) {
|
||||
return;
|
||||
}
|
||||
Gerrit.Nav.navigateToRelativeUrl(selected.url);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
const isGroupView = params.view === Gerrit.Nav.View.GROUP;
|
||||
const isRepoView = params.view === Gerrit.Nav.View.REPO;
|
||||
const isAdminView = params.view === Gerrit.Nav.View.ADMIN;
|
||||
|
||||
this.set('_showGroup', isGroupView && !params.detail);
|
||||
this.set('_showGroupAuditLog', isGroupView &&
|
||||
params.detail === Gerrit.Nav.GroupDetailView.LOG);
|
||||
this.set('_showGroupMembers', isGroupView &&
|
||||
params.detail === Gerrit.Nav.GroupDetailView.MEMBERS);
|
||||
|
||||
this.set('_showGroupList', isAdminView &&
|
||||
params.adminView === 'gr-admin-group-list');
|
||||
|
||||
this.set('_showRepoAccess', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.ACCESS);
|
||||
this.set('_showRepoCommands', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.COMMANDS);
|
||||
this.set('_showRepoDetailList', isRepoView &&
|
||||
(params.detail === Gerrit.Nav.RepoDetailView.BRANCHES ||
|
||||
params.detail === Gerrit.Nav.RepoDetailView.TAGS));
|
||||
this.set('_showRepoDashboards', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.DASHBOARDS);
|
||||
this.set('_showRepoMain', isRepoView && !params.detail);
|
||||
|
||||
this.set('_showRepoList', isAdminView &&
|
||||
params.adminView === 'gr-repo-list');
|
||||
|
||||
this.set('_showPluginList', isAdminView &&
|
||||
params.adminView === 'gr-plugin-list');
|
||||
|
||||
let needsReload = false;
|
||||
if (params.repo !== this._repoName) {
|
||||
this._repoName = params.repo || '';
|
||||
// Reloads the admin menu.
|
||||
needsReload = true;
|
||||
}
|
||||
if (params.groupId !== this._groupId) {
|
||||
this._groupId = params.groupId || '';
|
||||
// Reloads the admin menu.
|
||||
needsReload = true;
|
||||
}
|
||||
if (this._breadcrumbParentName && !params.groupId && !params.repo) {
|
||||
needsReload = true;
|
||||
}
|
||||
if (!needsReload) { return; }
|
||||
this.reload();
|
||||
}
|
||||
|
||||
// TODO (beckysiegel): Update these functions after router abstraction is
|
||||
// updated. They are currently copied from gr-dropdown (and should be
|
||||
// updated there as well once complete).
|
||||
_computeURLHelper(host, path) {
|
||||
return '//' + host + this.getBaseUrl() + path;
|
||||
}
|
||||
|
||||
_computeRelativeURL(path) {
|
||||
const host = window.location.host;
|
||||
return this._computeURLHelper(host, path);
|
||||
}
|
||||
|
||||
_computeLinkURL(link) {
|
||||
if (!link || typeof link.url === 'undefined') { return ''; }
|
||||
if (link.target || !link.noBaseUrl) {
|
||||
return link.url;
|
||||
}
|
||||
return this._computeRelativeURL(link.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} itemView
|
||||
* @param {Object} params
|
||||
* @param {string=} opt_detailType
|
||||
*/
|
||||
_computeSelectedClass(itemView, params, opt_detailType) {
|
||||
if (!params) return '';
|
||||
// Group params are structured differently from admin params. Compute
|
||||
// selected differently for groups.
|
||||
// TODO(wyatta): Simplify this when all routes work like group params.
|
||||
if (params.view === Gerrit.Nav.View.GROUP &&
|
||||
itemView === Gerrit.Nav.View.GROUP) {
|
||||
if (!params.detail && !opt_detailType) { return 'selected'; }
|
||||
if (params.detail === opt_detailType) { return 'selected'; }
|
||||
return '';
|
||||
}
|
||||
|
||||
if (params.view === Gerrit.Nav.View.REPO &&
|
||||
itemView === Gerrit.Nav.View.REPO) {
|
||||
if (!params.detail && !opt_detailType) { return 'selected'; }
|
||||
if (params.detail === opt_detailType) { return 'selected'; }
|
||||
return '';
|
||||
}
|
||||
|
||||
if (params.detailType && params.detailType !== opt_detailType) {
|
||||
return '';
|
||||
}
|
||||
return itemView === params.adminView ? 'selected' : '';
|
||||
}
|
||||
|
||||
_computeGroupName(groupId) {
|
||||
if (!groupId) { return ''; }
|
||||
|
||||
const promises = [];
|
||||
this.$.restAPI.getGroupConfig(groupId).then(group => {
|
||||
if (!group || !group.name) { return; }
|
||||
|
||||
this._groupName = group.name;
|
||||
this._groupIsInternal = !!group.id.match(INTERNAL_GROUP_REGEX);
|
||||
this.reload();
|
||||
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(group.name).then(
|
||||
isOwner => {
|
||||
this._groupOwner = isOwner;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_updateGroupName(e) {
|
||||
this._groupName = e.detail.name;
|
||||
this.reload();
|
||||
}
|
||||
_breadcrumbParentName: String,
|
||||
_repoName: String,
|
||||
_groupId: {
|
||||
type: Number,
|
||||
observer: '_computeGroupName',
|
||||
},
|
||||
_groupIsInternal: Boolean,
|
||||
_groupName: String,
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_subsectionLinks: Array,
|
||||
_filteredLinks: Array,
|
||||
_showDownload: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_showGroup: Boolean,
|
||||
_showGroupAuditLog: Boolean,
|
||||
_showGroupList: Boolean,
|
||||
_showGroupMembers: Boolean,
|
||||
_showRepoAccess: Boolean,
|
||||
_showRepoCommands: Boolean,
|
||||
_showRepoDashboards: Boolean,
|
||||
_showRepoDetailList: Boolean,
|
||||
_showRepoMain: Boolean,
|
||||
_showRepoList: Boolean,
|
||||
_showPluginList: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrAdminView.is, GrAdminView);
|
||||
})();
|
||||
static get observers() {
|
||||
return [
|
||||
'_paramsChanged(params)',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.reload();
|
||||
}
|
||||
|
||||
reload() {
|
||||
const promises = [
|
||||
this.$.restAPI.getAccount(),
|
||||
Gerrit.awaitPluginsLoaded(),
|
||||
];
|
||||
return Promise.all(promises).then(result => {
|
||||
this._account = result[0];
|
||||
let options;
|
||||
if (this._repoName) {
|
||||
options = {repoName: this._repoName};
|
||||
} else if (this._groupId) {
|
||||
options = {
|
||||
groupId: this._groupId,
|
||||
groupName: this._groupName,
|
||||
groupIsInternal: this._groupIsInternal,
|
||||
isAdmin: this._isAdmin,
|
||||
groupOwner: this._groupOwner,
|
||||
};
|
||||
}
|
||||
|
||||
return this.getAdminLinks(this._account,
|
||||
this.$.restAPI.getAccountCapabilities.bind(this.$.restAPI),
|
||||
this.$.jsAPI.getAdminMenuLinks.bind(this.$.jsAPI),
|
||||
options)
|
||||
.then(res => {
|
||||
this._filteredLinks = res.links;
|
||||
this._breadcrumbParentName = res.expandedSection ?
|
||||
res.expandedSection.name : '';
|
||||
|
||||
if (!res.expandedSection) {
|
||||
this._subsectionLinks = [];
|
||||
return;
|
||||
}
|
||||
this._subsectionLinks = [res.expandedSection]
|
||||
.concat(res.expandedSection.children).map(section => {
|
||||
return {
|
||||
text: !section.detailType ? 'Home' : section.name,
|
||||
value: section.view + (section.detailType || ''),
|
||||
view: section.view,
|
||||
url: section.url,
|
||||
detailType: section.detailType,
|
||||
parent: this._groupId || this._repoName || '',
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeSelectValue(params) {
|
||||
if (!params || !params.view) { return; }
|
||||
return params.view + (params.detail || '');
|
||||
}
|
||||
|
||||
_selectedIsCurrentPage(selected) {
|
||||
return (selected.parent === (this._repoName || this._groupId) &&
|
||||
selected.view === this.params.view &&
|
||||
selected.detailType === this.params.detail);
|
||||
}
|
||||
|
||||
_handleSubsectionChange(e) {
|
||||
const selected = this._subsectionLinks
|
||||
.find(section => section.value === e.detail.value);
|
||||
|
||||
// This is when it gets set initially.
|
||||
if (this._selectedIsCurrentPage(selected)) {
|
||||
return;
|
||||
}
|
||||
Gerrit.Nav.navigateToRelativeUrl(selected.url);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
const isGroupView = params.view === Gerrit.Nav.View.GROUP;
|
||||
const isRepoView = params.view === Gerrit.Nav.View.REPO;
|
||||
const isAdminView = params.view === Gerrit.Nav.View.ADMIN;
|
||||
|
||||
this.set('_showGroup', isGroupView && !params.detail);
|
||||
this.set('_showGroupAuditLog', isGroupView &&
|
||||
params.detail === Gerrit.Nav.GroupDetailView.LOG);
|
||||
this.set('_showGroupMembers', isGroupView &&
|
||||
params.detail === Gerrit.Nav.GroupDetailView.MEMBERS);
|
||||
|
||||
this.set('_showGroupList', isAdminView &&
|
||||
params.adminView === 'gr-admin-group-list');
|
||||
|
||||
this.set('_showRepoAccess', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.ACCESS);
|
||||
this.set('_showRepoCommands', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.COMMANDS);
|
||||
this.set('_showRepoDetailList', isRepoView &&
|
||||
(params.detail === Gerrit.Nav.RepoDetailView.BRANCHES ||
|
||||
params.detail === Gerrit.Nav.RepoDetailView.TAGS));
|
||||
this.set('_showRepoDashboards', isRepoView &&
|
||||
params.detail === Gerrit.Nav.RepoDetailView.DASHBOARDS);
|
||||
this.set('_showRepoMain', isRepoView && !params.detail);
|
||||
|
||||
this.set('_showRepoList', isAdminView &&
|
||||
params.adminView === 'gr-repo-list');
|
||||
|
||||
this.set('_showPluginList', isAdminView &&
|
||||
params.adminView === 'gr-plugin-list');
|
||||
|
||||
let needsReload = false;
|
||||
if (params.repo !== this._repoName) {
|
||||
this._repoName = params.repo || '';
|
||||
// Reloads the admin menu.
|
||||
needsReload = true;
|
||||
}
|
||||
if (params.groupId !== this._groupId) {
|
||||
this._groupId = params.groupId || '';
|
||||
// Reloads the admin menu.
|
||||
needsReload = true;
|
||||
}
|
||||
if (this._breadcrumbParentName && !params.groupId && !params.repo) {
|
||||
needsReload = true;
|
||||
}
|
||||
if (!needsReload) { return; }
|
||||
this.reload();
|
||||
}
|
||||
|
||||
// TODO (beckysiegel): Update these functions after router abstraction is
|
||||
// updated. They are currently copied from gr-dropdown (and should be
|
||||
// updated there as well once complete).
|
||||
_computeURLHelper(host, path) {
|
||||
return '//' + host + this.getBaseUrl() + path;
|
||||
}
|
||||
|
||||
_computeRelativeURL(path) {
|
||||
const host = window.location.host;
|
||||
return this._computeURLHelper(host, path);
|
||||
}
|
||||
|
||||
_computeLinkURL(link) {
|
||||
if (!link || typeof link.url === 'undefined') { return ''; }
|
||||
if (link.target || !link.noBaseUrl) {
|
||||
return link.url;
|
||||
}
|
||||
return this._computeRelativeURL(link.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} itemView
|
||||
* @param {Object} params
|
||||
* @param {string=} opt_detailType
|
||||
*/
|
||||
_computeSelectedClass(itemView, params, opt_detailType) {
|
||||
if (!params) return '';
|
||||
// Group params are structured differently from admin params. Compute
|
||||
// selected differently for groups.
|
||||
// TODO(wyatta): Simplify this when all routes work like group params.
|
||||
if (params.view === Gerrit.Nav.View.GROUP &&
|
||||
itemView === Gerrit.Nav.View.GROUP) {
|
||||
if (!params.detail && !opt_detailType) { return 'selected'; }
|
||||
if (params.detail === opt_detailType) { return 'selected'; }
|
||||
return '';
|
||||
}
|
||||
|
||||
if (params.view === Gerrit.Nav.View.REPO &&
|
||||
itemView === Gerrit.Nav.View.REPO) {
|
||||
if (!params.detail && !opt_detailType) { return 'selected'; }
|
||||
if (params.detail === opt_detailType) { return 'selected'; }
|
||||
return '';
|
||||
}
|
||||
|
||||
if (params.detailType && params.detailType !== opt_detailType) {
|
||||
return '';
|
||||
}
|
||||
return itemView === params.adminView ? 'selected' : '';
|
||||
}
|
||||
|
||||
_computeGroupName(groupId) {
|
||||
if (!groupId) { return ''; }
|
||||
|
||||
const promises = [];
|
||||
this.$.restAPI.getGroupConfig(groupId).then(group => {
|
||||
if (!group || !group.name) { return; }
|
||||
|
||||
this._groupName = group.name;
|
||||
this._groupIsInternal = !!group.id.match(INTERNAL_GROUP_REGEX);
|
||||
this.reload();
|
||||
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(group.name).then(
|
||||
isOwner => {
|
||||
this._groupOwner = isOwner;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_updateGroupName(e) {
|
||||
this._groupName = e.detail.name;
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrAdminView.is, GrAdminView);
|
||||
|
@ -1,48 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-page-nav-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
|
||||
<link rel="import" href="../../shared/gr-dropdown-list/gr-dropdown-list.html">
|
||||
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
|
||||
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-page-nav/gr-page-nav.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-admin-group-list/gr-admin-group-list.html">
|
||||
<link rel="import" href="../gr-group/gr-group.html">
|
||||
<link rel="import" href="../gr-group-audit-log/gr-group-audit-log.html">
|
||||
<link rel="import" href="../gr-group-members/gr-group-members.html">
|
||||
<link rel="import" href="../gr-plugin-list/gr-plugin-list.html">
|
||||
<link rel="import" href="../gr-repo/gr-repo.html">
|
||||
<link rel="import" href="../gr-repo-access/gr-repo-access.html">
|
||||
<link rel="import" href="../gr-repo-commands/gr-repo-commands.html">
|
||||
<link rel="import" href="../gr-repo-dashboards/gr-repo-dashboards.html">
|
||||
<link rel="import" href="../gr-repo-detail-list/gr-repo-detail-list.html">
|
||||
<link rel="import" href="../gr-repo-list/gr-repo-list.html">
|
||||
|
||||
<dom-module id="gr-admin-view">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -84,28 +58,24 @@ limitations under the License.
|
||||
<gr-page-nav class="navStyles">
|
||||
<ul class="sectionContent">
|
||||
<template id="adminNav" is="dom-repeat" items="[[_filteredLinks]]">
|
||||
<li class$="sectionTitle [[_computeSelectedClass(item.view, params)]]">
|
||||
<a class="title" href="[[_computeLinkURL(item)]]"
|
||||
rel="noopener">[[item.name]]</a>
|
||||
<li class\$="sectionTitle [[_computeSelectedClass(item.view, params)]]">
|
||||
<a class="title" href="[[_computeLinkURL(item)]]" rel="noopener">[[item.name]]</a>
|
||||
</li>
|
||||
<template is="dom-repeat" items="[[item.children]]" as="child">
|
||||
<li class$="[[_computeSelectedClass(child.view, params)]]">
|
||||
<a href$="[[_computeLinkURL(child)]]"
|
||||
rel="noopener">[[child.name]]</a>
|
||||
<li class\$="[[_computeSelectedClass(child.view, params)]]">
|
||||
<a href\$="[[_computeLinkURL(child)]]" rel="noopener">[[child.name]]</a>
|
||||
</li>
|
||||
</template>
|
||||
<template is="dom-if" if="[[item.subsection]]">
|
||||
<!--If a section has a subsection, render that.-->
|
||||
<li class$="[[_computeSelectedClass(item.subsection.view, params)]]">
|
||||
<a class="title" href$="[[_computeLinkURL(item.subsection)]]"
|
||||
rel="noopener">
|
||||
<li class\$="[[_computeSelectedClass(item.subsection.view, params)]]">
|
||||
<a class="title" href\$="[[_computeLinkURL(item.subsection)]]" rel="noopener">
|
||||
[[item.subsection.name]]</a>
|
||||
</li>
|
||||
<!--Loop through the links in the sub-section.-->
|
||||
<template is="dom-repeat"
|
||||
items="[[item.subsection.children]]" as="child">
|
||||
<li class$="subsectionItem [[_computeSelectedClass(child.view, params, child.detailType)]]">
|
||||
<a href$="[[_computeLinkURL(child)]]">[[child.name]]</a>
|
||||
<template is="dom-repeat" items="[[item.subsection.children]]" as="child">
|
||||
<li class\$="subsectionItem [[_computeSelectedClass(child.view, params, child.detailType)]]">
|
||||
<a href\$="[[_computeLinkURL(child)]]">[[child.name]]</a>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
@ -118,12 +88,7 @@ limitations under the License.
|
||||
<span class="breadcrumbText">[[_breadcrumbParentName]]</span>
|
||||
<iron-icon icon="gr-icons:chevron-right"></iron-icon>
|
||||
</span>
|
||||
<gr-dropdown-list
|
||||
lowercase
|
||||
id="pageSelect"
|
||||
value="[[_computeSelectValue(params)]]"
|
||||
items="[[_subsectionLinks]]"
|
||||
on-value-change="_handleSubsectionChange">
|
||||
<gr-dropdown-list lowercase="" id="pageSelect" value="[[_computeSelectValue(params)]]" items="[[_subsectionLinks]]" on-value-change="_handleSubsectionChange">
|
||||
</gr-dropdown-list>
|
||||
</section>
|
||||
</template>
|
||||
@ -150,42 +115,32 @@ limitations under the License.
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showGroup]]" restamp="true">
|
||||
<main class="breadcrumbs">
|
||||
<gr-group
|
||||
group-id="[[params.groupId]]"
|
||||
on-name-changed="_updateGroupName"></gr-group>
|
||||
<gr-group group-id="[[params.groupId]]" on-name-changed="_updateGroupName"></gr-group>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showGroupMembers]]" restamp="true">
|
||||
<main class="breadcrumbs">
|
||||
<gr-group-members
|
||||
group-id="[[params.groupId]]"></gr-group-members>
|
||||
<gr-group-members group-id="[[params.groupId]]"></gr-group-members>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showRepoDetailList]]" restamp="true">
|
||||
<main class="table breadcrumbs">
|
||||
<gr-repo-detail-list
|
||||
params="[[params]]"
|
||||
class="table"></gr-repo-detail-list>
|
||||
<gr-repo-detail-list params="[[params]]" class="table"></gr-repo-detail-list>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showGroupAuditLog]]" restamp="true">
|
||||
<main class="table breadcrumbs">
|
||||
<gr-group-audit-log
|
||||
group-id="[[params.groupId]]"
|
||||
class="table"></gr-group-audit-log>
|
||||
<gr-group-audit-log group-id="[[params.groupId]]" class="table"></gr-group-audit-log>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showRepoCommands]]" restamp="true">
|
||||
<main class="breadcrumbs">
|
||||
<gr-repo-commands
|
||||
repo="[[params.repo]]"></gr-repo-commands>
|
||||
<gr-repo-commands repo="[[params.repo]]"></gr-repo-commands>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showRepoAccess]]" restamp="true">
|
||||
<main class="breadcrumbs">
|
||||
<gr-repo-access
|
||||
path="[[path]]"
|
||||
repo="[[params.repo]]"></gr-repo-access>
|
||||
<gr-repo-access path="[[path]]" repo="[[params.repo]]"></gr-repo-access>
|
||||
</main>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_showRepoDashboards]]" restamp="true">
|
||||
@ -195,6 +150,4 @@ limitations under the License.
|
||||
</template>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
|
||||
</template>
|
||||
<script src="gr-admin-view.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,67 +14,76 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const DETAIL_TYPES = {
|
||||
BRANCHES: 'branches',
|
||||
ID: 'id',
|
||||
TAGS: 'tags',
|
||||
};
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../shared/gr-dialog/gr-dialog.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-confirm-delete-item-dialog_html.js';
|
||||
|
||||
const DETAIL_TYPES = {
|
||||
BRANCHES: 'branches',
|
||||
ID: 'id',
|
||||
TAGS: 'tags',
|
||||
};
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrConfirmDeleteItemDialog extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-confirm-delete-item-dialog'; }
|
||||
/**
|
||||
* Fired when the confirm button is pressed.
|
||||
*
|
||||
* @event confirm
|
||||
*/
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
* Fired when the cancel button is pressed.
|
||||
*
|
||||
* @event cancel
|
||||
*/
|
||||
class GrConfirmDeleteItemDialog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-confirm-delete-item-dialog'; }
|
||||
/**
|
||||
* Fired when the confirm button is pressed.
|
||||
*
|
||||
* @event confirm
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the cancel button is pressed.
|
||||
*
|
||||
* @event cancel
|
||||
*/
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
item: String,
|
||||
itemType: String,
|
||||
};
|
||||
}
|
||||
|
||||
_handleConfirmTap(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.fire('confirm', null, {bubbles: false});
|
||||
}
|
||||
|
||||
_handleCancelTap(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.fire('cancel', null, {bubbles: false});
|
||||
}
|
||||
|
||||
_computeItemName(detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return 'Branch';
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return 'Tag';
|
||||
} else if (detailType === DETAIL_TYPES.ID) {
|
||||
return 'ID';
|
||||
}
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
item: String,
|
||||
itemType: String,
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrConfirmDeleteItemDialog.is,
|
||||
GrConfirmDeleteItemDialog);
|
||||
})();
|
||||
_handleConfirmTap(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.fire('confirm', null, {bubbles: false});
|
||||
}
|
||||
|
||||
_handleCancelTap(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.fire('cancel', null, {bubbles: false});
|
||||
}
|
||||
|
||||
_computeItemName(detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return 'Branch';
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return 'Tag';
|
||||
} else if (detailType === DETAIL_TYPES.ID) {
|
||||
return 'ID';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrConfirmDeleteItemDialog.is,
|
||||
GrConfirmDeleteItemDialog);
|
||||
|
@ -1,38 +1,29 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
|
||||
<dom-module id="gr-confirm-delete-item-dialog">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
width: 30em;
|
||||
}
|
||||
</style>
|
||||
<gr-dialog
|
||||
confirm-label="Delete [[_computeItemName(itemType)]]"
|
||||
confirm-on-enter
|
||||
on-confirm="_handleConfirmTap"
|
||||
on-cancel="_handleCancelTap">
|
||||
<gr-dialog confirm-label="Delete [[_computeItemName(itemType)]]" confirm-on-enter="" on-confirm="_handleConfirmTap" on-cancel="_handleCancelTap">
|
||||
<div class="header" slot="header">[[_computeItemName(itemType)]] Deletion</div>
|
||||
<div class="main" slot="main">
|
||||
<label for="branchInput">
|
||||
@ -43,6 +34,4 @@ limitations under the License.
|
||||
</div>
|
||||
</div>
|
||||
</gr-dialog>
|
||||
</template>
|
||||
<script src="gr-confirm-delete-item-dialog.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-confirm-delete-item-dialog</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-confirm-delete-item-dialog.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-confirm-delete-item-dialog.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-confirm-delete-item-dialog.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,53 +40,55 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-confirm-delete-item-dialog tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-confirm-delete-item-dialog.js';
|
||||
suite('gr-confirm-delete-item-dialog tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('_handleConfirmTap', () => {
|
||||
const confirmHandler = sandbox.stub();
|
||||
element.addEventListener('confirm', confirmHandler);
|
||||
sandbox.spy(element, '_handleConfirmTap');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-dialog').fire('confirm');
|
||||
assert.isTrue(confirmHandler.called);
|
||||
assert.isTrue(confirmHandler.calledOnce);
|
||||
assert.isTrue(element._handleConfirmTap.called);
|
||||
assert.isTrue(element._handleConfirmTap.calledOnce);
|
||||
});
|
||||
|
||||
test('_handleCancelTap', () => {
|
||||
const cancelHandler = sandbox.stub();
|
||||
element.addEventListener('cancel', cancelHandler);
|
||||
sandbox.spy(element, '_handleCancelTap');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-dialog').fire('cancel');
|
||||
assert.isTrue(cancelHandler.called);
|
||||
assert.isTrue(cancelHandler.calledOnce);
|
||||
assert.isTrue(element._handleCancelTap.called);
|
||||
assert.isTrue(element._handleCancelTap.calledOnce);
|
||||
});
|
||||
|
||||
test('_computeItemName function for branches', () => {
|
||||
assert.deepEqual(element._computeItemName('branches'), 'Branch');
|
||||
assert.notEqual(element._computeItemName('branches'), 'Tag');
|
||||
});
|
||||
|
||||
test('_computeItemName function for tags', () => {
|
||||
assert.deepEqual(element._computeItemName('tags'), 'Tag');
|
||||
assert.notEqual(element._computeItemName('tags'), 'Branch');
|
||||
});
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('_handleConfirmTap', () => {
|
||||
const confirmHandler = sandbox.stub();
|
||||
element.addEventListener('confirm', confirmHandler);
|
||||
sandbox.spy(element, '_handleConfirmTap');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-dialog').fire('confirm');
|
||||
assert.isTrue(confirmHandler.called);
|
||||
assert.isTrue(confirmHandler.calledOnce);
|
||||
assert.isTrue(element._handleConfirmTap.called);
|
||||
assert.isTrue(element._handleConfirmTap.calledOnce);
|
||||
});
|
||||
|
||||
test('_handleCancelTap', () => {
|
||||
const cancelHandler = sandbox.stub();
|
||||
element.addEventListener('cancel', cancelHandler);
|
||||
sandbox.spy(element, '_handleCancelTap');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-dialog').fire('cancel');
|
||||
assert.isTrue(cancelHandler.called);
|
||||
assert.isTrue(cancelHandler.calledOnce);
|
||||
assert.isTrue(element._handleCancelTap.called);
|
||||
assert.isTrue(element._handleCancelTap.calledOnce);
|
||||
});
|
||||
|
||||
test('_computeItemName function for branches', () => {
|
||||
assert.deepEqual(element._computeItemName('branches'), 'Branch');
|
||||
assert.notEqual(element._computeItemName('branches'), 'Tag');
|
||||
});
|
||||
|
||||
test('_computeItemName function for tags', () => {
|
||||
assert.deepEqual(element._computeItemName('tags'), 'Tag');
|
||||
assert.notEqual(element._computeItemName('tags'), 'Branch');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,148 +14,166 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
|
||||
const SUGGESTIONS_LIMIT = 15;
|
||||
const REF_PREFIX = 'refs/heads/';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-autocomplete/gr-autocomplete.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-create-change-dialog_html.js';
|
||||
|
||||
const SUGGESTIONS_LIMIT = 15;
|
||||
const REF_PREFIX = 'refs/heads/';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreateChangeDialog extends mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-repo-commands_test.
|
||||
*/
|
||||
class GrCreateChangeDialog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
/**
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-repo-commands_test.
|
||||
*/
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-create-change-dialog'; }
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
repoName: String,
|
||||
branch: String,
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
subject: String,
|
||||
topic: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getRepoBranchesSuggestions.bind(this);
|
||||
},
|
||||
static get is() { return 'gr-create-change-dialog'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
repoName: String,
|
||||
branch: String,
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
subject: String,
|
||||
topic: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getRepoBranchesSuggestions.bind(this);
|
||||
},
|
||||
baseChange: String,
|
||||
baseCommit: String,
|
||||
privateByDefault: String,
|
||||
canCreate: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
_privateChangesEnabled: Boolean,
|
||||
};
|
||||
},
|
||||
baseChange: String,
|
||||
baseCommit: String,
|
||||
privateByDefault: String,
|
||||
canCreate: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
_privateChangesEnabled: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
if (!this.repoName) { return Promise.resolve(); }
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.$.restAPI.getProjectConfig(this.repoName)
|
||||
.then(config => {
|
||||
this.privateByDefault = config.private_by_default;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getConfig().then(config => {
|
||||
if (!config) { return; }
|
||||
|
||||
this._privateConfig = config && config.change &&
|
||||
config.change.disable_private_changes;
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_allowCreate(branch, subject)',
|
||||
];
|
||||
}
|
||||
|
||||
_computeBranchClass(baseChange) {
|
||||
return baseChange ? 'hide' : '';
|
||||
}
|
||||
|
||||
_allowCreate(branch, subject) {
|
||||
this.canCreate = !!branch && !!subject;
|
||||
}
|
||||
|
||||
handleCreateChange() {
|
||||
const isPrivate = this.$.privateChangeCheckBox.checked;
|
||||
const isWip = true;
|
||||
return this.$.restAPI.createChange(this.repoName, this.branch,
|
||||
this.subject, this.topic, isPrivate, isWip, this.baseChange,
|
||||
this.baseCommit || null)
|
||||
.then(changeCreated => {
|
||||
if (!changeCreated) { return; }
|
||||
Gerrit.Nav.navigateToChange(changeCreated);
|
||||
});
|
||||
}
|
||||
|
||||
_getRepoBranchesSuggestions(input) {
|
||||
if (input.startsWith(REF_PREFIX)) {
|
||||
input = input.substring(REF_PREFIX.length);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
if (!this.repoName) { return Promise.resolve(); }
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.$.restAPI.getProjectConfig(this.repoName)
|
||||
.then(config => {
|
||||
this.privateByDefault = config.private_by_default;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getConfig().then(config => {
|
||||
if (!config) { return; }
|
||||
|
||||
this._privateConfig = config && config.change &&
|
||||
config.change.disable_private_changes;
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_allowCreate(branch, subject)',
|
||||
];
|
||||
}
|
||||
|
||||
_computeBranchClass(baseChange) {
|
||||
return baseChange ? 'hide' : '';
|
||||
}
|
||||
|
||||
_allowCreate(branch, subject) {
|
||||
this.canCreate = !!branch && !!subject;
|
||||
}
|
||||
|
||||
handleCreateChange() {
|
||||
const isPrivate = this.$.privateChangeCheckBox.checked;
|
||||
const isWip = true;
|
||||
return this.$.restAPI.createChange(this.repoName, this.branch,
|
||||
this.subject, this.topic, isPrivate, isWip, this.baseChange,
|
||||
this.baseCommit || null)
|
||||
.then(changeCreated => {
|
||||
if (!changeCreated) { return; }
|
||||
Gerrit.Nav.navigateToChange(changeCreated);
|
||||
});
|
||||
}
|
||||
|
||||
_getRepoBranchesSuggestions(input) {
|
||||
if (input.startsWith(REF_PREFIX)) {
|
||||
input = input.substring(REF_PREFIX.length);
|
||||
}
|
||||
return this.$.restAPI.getRepoBranches(
|
||||
input, this.repoName, SUGGESTIONS_LIMIT).then(response => {
|
||||
const branches = [];
|
||||
let branch;
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
if (response[key].ref.startsWith('refs/heads/')) {
|
||||
branch = response[key].ref.substring('refs/heads/'.length);
|
||||
} else {
|
||||
branch = response[key].ref;
|
||||
}
|
||||
branches.push({
|
||||
name: branch,
|
||||
});
|
||||
}
|
||||
return branches;
|
||||
});
|
||||
}
|
||||
|
||||
_formatBooleanString(config) {
|
||||
if (config && config.configured_value === 'TRUE') {
|
||||
return true;
|
||||
} else if (config && config.configured_value === 'FALSE') {
|
||||
return false;
|
||||
} else if (config && config.configured_value === 'INHERIT') {
|
||||
if (config && config.inherited_value) {
|
||||
return true;
|
||||
return this.$.restAPI.getRepoBranches(
|
||||
input, this.repoName, SUGGESTIONS_LIMIT).then(response => {
|
||||
const branches = [];
|
||||
let branch;
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
if (response[key].ref.startsWith('refs/heads/')) {
|
||||
branch = response[key].ref.substring('refs/heads/'.length);
|
||||
} else {
|
||||
return false;
|
||||
branch = response[key].ref;
|
||||
}
|
||||
branches.push({
|
||||
name: branch,
|
||||
});
|
||||
}
|
||||
return branches;
|
||||
});
|
||||
}
|
||||
|
||||
_formatBooleanString(config) {
|
||||
if (config && config.configured_value === 'TRUE') {
|
||||
return true;
|
||||
} else if (config && config.configured_value === 'FALSE') {
|
||||
return false;
|
||||
} else if (config && config.configured_value === 'INHERIT') {
|
||||
if (config && config.inherited_value) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_computePrivateSectionClass(config) {
|
||||
return config ? 'hide' : '';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreateChangeDialog.is, GrCreateChangeDialog);
|
||||
})();
|
||||
_computePrivateSectionClass(config) {
|
||||
return config ? 'hide' : '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreateChangeDialog.is, GrCreateChangeDialog);
|
||||
|
@ -1,36 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
|
||||
<dom-module id="gr-create-change-dialog">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -53,76 +39,42 @@ limitations under the License.
|
||||
}
|
||||
</style>
|
||||
<div class="gr-form-styles">
|
||||
<section class$="[[_computeBranchClass(baseChange)]]">
|
||||
<section class\$="[[_computeBranchClass(baseChange)]]">
|
||||
<span class="title">Select branch for new change</span>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="branchInput"
|
||||
text="{{branch}}"
|
||||
query="[[_query]]"
|
||||
placeholder="Destination branch">
|
||||
<gr-autocomplete id="branchInput" text="{{branch}}" query="[[_query]]" placeholder="Destination branch">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
</section>
|
||||
<section class$="[[_computeBranchClass(baseChange)]]">
|
||||
<section class\$="[[_computeBranchClass(baseChange)]]">
|
||||
<span class="title">Provide base commit sha1 for change</span>
|
||||
<span class="value">
|
||||
<iron-input
|
||||
maxlength="40"
|
||||
placeholder="(optional)"
|
||||
bind-value="{{baseCommit}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
id="baseCommitInput"
|
||||
maxlength="40"
|
||||
placeholder="(optional)"
|
||||
bind-value="{{baseCommit}}">
|
||||
<iron-input maxlength="40" placeholder="(optional)" bind-value="{{baseCommit}}">
|
||||
<input is="iron-input" id="baseCommitInput" maxlength="40" placeholder="(optional)" bind-value="{{baseCommit}}">
|
||||
</iron-input>
|
||||
</span>
|
||||
</section>
|
||||
<section>
|
||||
<span class="title">Enter topic for new change</span>
|
||||
<span class="value">
|
||||
<iron-input
|
||||
maxlength="1024"
|
||||
placeholder="(optional)"
|
||||
bind-value="{{topic}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
id="tagNameInput"
|
||||
maxlength="1024"
|
||||
placeholder="(optional)"
|
||||
bind-value="{{topic}}">
|
||||
<iron-input maxlength="1024" placeholder="(optional)" bind-value="{{topic}}">
|
||||
<input is="iron-input" id="tagNameInput" maxlength="1024" placeholder="(optional)" bind-value="{{topic}}">
|
||||
</iron-input>
|
||||
</span>
|
||||
</section>
|
||||
<section id="description">
|
||||
<span class="title">Description</span>
|
||||
<span class="value">
|
||||
<iron-autogrow-textarea
|
||||
id="messageInput"
|
||||
class="message"
|
||||
autocomplete="on"
|
||||
rows="4"
|
||||
max-rows="15"
|
||||
bind-value="{{subject}}"
|
||||
placeholder="Insert the description of the change.">
|
||||
<iron-autogrow-textarea id="messageInput" class="message" autocomplete="on" rows="4" max-rows="15" bind-value="{{subject}}" placeholder="Insert the description of the change.">
|
||||
</iron-autogrow-textarea>
|
||||
</span>
|
||||
</section>
|
||||
<section class$="[[_computePrivateSectionClass(_privateChangesEnabled)]]">
|
||||
<label
|
||||
class="title"
|
||||
for="privateChangeCheckBox">Private change</label>
|
||||
<section class\$="[[_computePrivateSectionClass(_privateChangesEnabled)]]">
|
||||
<label class="title" for="privateChangeCheckBox">Private change</label>
|
||||
<span class="value">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="privateChangeCheckBox"
|
||||
checked$="[[_formatBooleanString(privateByDefault)]]">
|
||||
<input type="checkbox" id="privateChangeCheckBox" checked\$="[[_formatBooleanString(privateByDefault)]]">
|
||||
</span>
|
||||
</section>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-create-change-dialog.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-create-change-dialog</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-create-change-dialog.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-create-change-dialog.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-change-dialog.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,136 +40,138 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-create-change-dialog tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-change-dialog.js';
|
||||
suite('gr-create-change-dialog tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
getRepoBranches(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
ref: 'refs/heads/test-branch',
|
||||
revision: '67ebf73496383c6777035e374d2d664009e2aa5c',
|
||||
can_delete: true,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
});
|
||||
element = fixture('basic');
|
||||
element.repoName = 'test-repo',
|
||||
element._repoConfig = {
|
||||
private_by_default: {
|
||||
configured_value: 'FALSE',
|
||||
inherited_value: false,
|
||||
},
|
||||
};
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
getRepoBranches(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
ref: 'refs/heads/test-branch',
|
||||
revision: '67ebf73496383c6777035e374d2d664009e2aa5c',
|
||||
can_delete: true,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('new change created with default', done => {
|
||||
const configInputObj = {
|
||||
branch: 'test-branch',
|
||||
subject: 'first change created with polygerrit ui',
|
||||
topic: 'test-topic',
|
||||
is_private: false,
|
||||
work_in_progress: true,
|
||||
};
|
||||
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createChange', () => Promise.resolve({}));
|
||||
|
||||
element.branch = 'test-branch';
|
||||
element.topic = 'test-topic';
|
||||
element.subject = 'first change created with polygerrit ui';
|
||||
assert.isFalse(element.$.privateChangeCheckBox.checked);
|
||||
|
||||
element.$.branchInput.bindValue = configInputObj.branch;
|
||||
element.$.tagNameInput.bindValue = configInputObj.topic;
|
||||
element.$.messageInput.bindValue = configInputObj.subject;
|
||||
|
||||
element.handleCreateChange().then(() => {
|
||||
// Private change
|
||||
assert.isFalse(saveStub.lastCall.args[4]);
|
||||
// WIP Change
|
||||
assert.isTrue(saveStub.lastCall.args[5]);
|
||||
assert.isTrue(saveStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('new change created with private', done => {
|
||||
element.privateByDefault = {
|
||||
configured_value: 'TRUE',
|
||||
element = fixture('basic');
|
||||
element.repoName = 'test-repo',
|
||||
element._repoConfig = {
|
||||
private_by_default: {
|
||||
configured_value: 'FALSE',
|
||||
inherited_value: false,
|
||||
};
|
||||
sandbox.stub(element, '_formatBooleanString', () => Promise.resolve(true));
|
||||
flushAsynchronousOperations();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const configInputObj = {
|
||||
branch: 'test-branch',
|
||||
subject: 'first change created with polygerrit ui',
|
||||
topic: 'test-topic',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
};
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createChange', () => Promise.resolve({}));
|
||||
test('new change created with default', done => {
|
||||
const configInputObj = {
|
||||
branch: 'test-branch',
|
||||
subject: 'first change created with polygerrit ui',
|
||||
topic: 'test-topic',
|
||||
is_private: false,
|
||||
work_in_progress: true,
|
||||
};
|
||||
|
||||
element.branch = 'test-branch';
|
||||
element.topic = 'test-topic';
|
||||
element.subject = 'first change created with polygerrit ui';
|
||||
assert.isTrue(element.$.privateChangeCheckBox.checked);
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createChange', () => Promise.resolve({}));
|
||||
|
||||
element.$.branchInput.bindValue = configInputObj.branch;
|
||||
element.$.tagNameInput.bindValue = configInputObj.topic;
|
||||
element.$.messageInput.bindValue = configInputObj.subject;
|
||||
element.branch = 'test-branch';
|
||||
element.topic = 'test-topic';
|
||||
element.subject = 'first change created with polygerrit ui';
|
||||
assert.isFalse(element.$.privateChangeCheckBox.checked);
|
||||
|
||||
element.handleCreateChange().then(() => {
|
||||
// Private change
|
||||
assert.isTrue(saveStub.lastCall.args[4]);
|
||||
// WIP Change
|
||||
assert.isTrue(saveStub.lastCall.args[5]);
|
||||
assert.isTrue(saveStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
element.$.branchInput.bindValue = configInputObj.branch;
|
||||
element.$.tagNameInput.bindValue = configInputObj.topic;
|
||||
element.$.messageInput.bindValue = configInputObj.subject;
|
||||
|
||||
test('_getRepoBranchesSuggestions empty', done => {
|
||||
element._getRepoBranchesSuggestions('nonexistent').then(branches => {
|
||||
assert.equal(branches.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getRepoBranchesSuggestions non-empty', done => {
|
||||
element._getRepoBranchesSuggestions('test-branch').then(branches => {
|
||||
assert.equal(branches.length, 1);
|
||||
assert.equal(branches[0].name, 'test-branch');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeBranchClass', () => {
|
||||
assert.equal(element._computeBranchClass(true), 'hide');
|
||||
assert.equal(element._computeBranchClass(false), '');
|
||||
});
|
||||
|
||||
test('_computePrivateSectionClass', () => {
|
||||
assert.equal(element._computePrivateSectionClass(true), 'hide');
|
||||
assert.equal(element._computePrivateSectionClass(false), '');
|
||||
element.handleCreateChange().then(() => {
|
||||
// Private change
|
||||
assert.isFalse(saveStub.lastCall.args[4]);
|
||||
// WIP Change
|
||||
assert.isTrue(saveStub.lastCall.args[5]);
|
||||
assert.isTrue(saveStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('new change created with private', done => {
|
||||
element.privateByDefault = {
|
||||
configured_value: 'TRUE',
|
||||
inherited_value: false,
|
||||
};
|
||||
sandbox.stub(element, '_formatBooleanString', () => Promise.resolve(true));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const configInputObj = {
|
||||
branch: 'test-branch',
|
||||
subject: 'first change created with polygerrit ui',
|
||||
topic: 'test-topic',
|
||||
is_private: true,
|
||||
work_in_progress: true,
|
||||
};
|
||||
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createChange', () => Promise.resolve({}));
|
||||
|
||||
element.branch = 'test-branch';
|
||||
element.topic = 'test-topic';
|
||||
element.subject = 'first change created with polygerrit ui';
|
||||
assert.isTrue(element.$.privateChangeCheckBox.checked);
|
||||
|
||||
element.$.branchInput.bindValue = configInputObj.branch;
|
||||
element.$.tagNameInput.bindValue = configInputObj.topic;
|
||||
element.$.messageInput.bindValue = configInputObj.subject;
|
||||
|
||||
element.handleCreateChange().then(() => {
|
||||
// Private change
|
||||
assert.isTrue(saveStub.lastCall.args[4]);
|
||||
// WIP Change
|
||||
assert.isTrue(saveStub.lastCall.args[5]);
|
||||
assert.isTrue(saveStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getRepoBranchesSuggestions empty', done => {
|
||||
element._getRepoBranchesSuggestions('nonexistent').then(branches => {
|
||||
assert.equal(branches.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getRepoBranchesSuggestions non-empty', done => {
|
||||
element._getRepoBranchesSuggestions('test-branch').then(branches => {
|
||||
assert.equal(branches.length, 1);
|
||||
assert.equal(branches[0].name, 'test-branch');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeBranchClass', () => {
|
||||
assert.equal(element._computeBranchClass(true), 'hide');
|
||||
assert.equal(element._computeBranchClass(false), '');
|
||||
});
|
||||
|
||||
test('_computePrivateSectionClass', () => {
|
||||
assert.equal(element._computePrivateSectionClass(true), 'hide');
|
||||
assert.equal(element._computePrivateSectionClass(false), '');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,65 +14,77 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreateGroupDialog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-create-group-dialog'; }
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-create-group-dialog_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
hasNewGroupName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
_name: Object,
|
||||
_groupCreated: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreateGroupDialog extends mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateGroupName(_name)',
|
||||
];
|
||||
}
|
||||
static get is() { return 'gr-create-group-dialog'; }
|
||||
|
||||
_computeGroupUrl(groupId) {
|
||||
return this.getBaseUrl() + '/admin/groups/' +
|
||||
this.encodeURL(groupId, true);
|
||||
}
|
||||
|
||||
_updateGroupName(name) {
|
||||
this.hasNewGroupName = !!name;
|
||||
}
|
||||
|
||||
handleCreateGroup() {
|
||||
return this.$.restAPI.createGroup({name: this._name})
|
||||
.then(groupRegistered => {
|
||||
if (groupRegistered.status !== 201) { return; }
|
||||
this._groupCreated = true;
|
||||
return this.$.restAPI.getGroupConfig(this._name)
|
||||
.then(group => {
|
||||
page.show(this._computeGroupUrl(group.group_id));
|
||||
});
|
||||
});
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
hasNewGroupName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
_name: Object,
|
||||
_groupCreated: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrCreateGroupDialog.is, GrCreateGroupDialog);
|
||||
})();
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateGroupName(_name)',
|
||||
];
|
||||
}
|
||||
|
||||
_computeGroupUrl(groupId) {
|
||||
return this.getBaseUrl() + '/admin/groups/' +
|
||||
this.encodeURL(groupId, true);
|
||||
}
|
||||
|
||||
_updateGroupName(name) {
|
||||
this.hasNewGroupName = !!name;
|
||||
}
|
||||
|
||||
handleCreateGroup() {
|
||||
return this.$.restAPI.createGroup({name: this._name})
|
||||
.then(groupRegistered => {
|
||||
if (groupRegistered.status !== 201) { return; }
|
||||
this._groupCreated = true;
|
||||
return this.$.restAPI.getGroupConfig(this._name)
|
||||
.then(group => {
|
||||
page.show(this._computeGroupUrl(group.group_id));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreateGroupDialog.is, GrCreateGroupDialog);
|
||||
|
@ -1,31 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<dom-module id="gr-create-group-dialog">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -41,16 +32,11 @@ limitations under the License.
|
||||
<div id="form">
|
||||
<section>
|
||||
<span class="title">Group name</span>
|
||||
<iron-input
|
||||
bind-value="{{_name}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
bind-value="{{_name}}">
|
||||
<iron-input bind-value="{{_name}}">
|
||||
<input is="iron-input" bind-value="{{_name}}">
|
||||
</iron-input>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-create-group-dialog.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-create-group-dialog</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-create-group-dialog.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-create-group-dialog.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-group-dialog.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,66 +41,68 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-create-group-dialog tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
const GROUP_NAME = 'test-group';
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-group-dialog.js';
|
||||
suite('gr-create-group-dialog tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
const GROUP_NAME = 'test-group';
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('name is updated correctly', done => {
|
||||
assert.isFalse(element.hasNewGroupName);
|
||||
test('name is updated correctly', done => {
|
||||
assert.isFalse(element.hasNewGroupName);
|
||||
|
||||
const inputEl = element.root.querySelector('iron-input');
|
||||
inputEl.bindValue = GROUP_NAME;
|
||||
const inputEl = element.root.querySelector('iron-input');
|
||||
inputEl.bindValue = GROUP_NAME;
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewGroupName);
|
||||
assert.deepEqual(element._name, GROUP_NAME);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test for redirecting to group on successful creation', done => {
|
||||
sandbox.stub(element.$.restAPI, 'createGroup')
|
||||
.returns(Promise.resolve({status: 201}));
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'getGroupConfig')
|
||||
.returns(Promise.resolve({group_id: 551}));
|
||||
|
||||
const showStub = sandbox.stub(page, 'show');
|
||||
element.handleCreateGroup()
|
||||
.then(() => {
|
||||
assert.isTrue(showStub.calledWith('/admin/groups/551'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test for unsuccessful group creation', done => {
|
||||
sandbox.stub(element.$.restAPI, 'createGroup')
|
||||
.returns(Promise.resolve({status: 409}));
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'getGroupConfig')
|
||||
.returns(Promise.resolve({group_id: 551}));
|
||||
|
||||
const showStub = sandbox.stub(page, 'show');
|
||||
element.handleCreateGroup()
|
||||
.then(() => {
|
||||
assert.isFalse(showStub.called);
|
||||
done();
|
||||
});
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewGroupName);
|
||||
assert.deepEqual(element._name, GROUP_NAME);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test for redirecting to group on successful creation', done => {
|
||||
sandbox.stub(element.$.restAPI, 'createGroup')
|
||||
.returns(Promise.resolve({status: 201}));
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'getGroupConfig')
|
||||
.returns(Promise.resolve({group_id: 551}));
|
||||
|
||||
const showStub = sandbox.stub(page, 'show');
|
||||
element.handleCreateGroup()
|
||||
.then(() => {
|
||||
assert.isTrue(showStub.calledWith('/admin/groups/551'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test for unsuccessful group creation', done => {
|
||||
sandbox.stub(element.$.restAPI, 'createGroup')
|
||||
.returns(Promise.resolve({status: 409}));
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'getGroupConfig')
|
||||
.returns(Promise.resolve({group_id: 551}));
|
||||
|
||||
const showStub = sandbox.stub(page, 'show');
|
||||
element.handleCreateGroup()
|
||||
.then(() => {
|
||||
assert.isFalse(showStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,89 +14,103 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const DETAIL_TYPES = {
|
||||
branches: 'branches',
|
||||
tags: 'tags',
|
||||
};
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-create-pointer-dialog_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreatePointerDialog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-create-pointer-dialog'; }
|
||||
const DETAIL_TYPES = {
|
||||
branches: 'branches',
|
||||
tags: 'tags',
|
||||
};
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
detailType: String,
|
||||
repoName: String,
|
||||
hasNewItemName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
itemDetail: String,
|
||||
_itemName: String,
|
||||
_itemRevision: String,
|
||||
_itemAnnotation: String,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreatePointerDialog extends mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateItemName(_itemName)',
|
||||
];
|
||||
}
|
||||
static get is() { return 'gr-create-pointer-dialog'; }
|
||||
|
||||
_updateItemName(name) {
|
||||
this.hasNewItemName = !!name;
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
detailType: String,
|
||||
repoName: String,
|
||||
hasNewItemName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
itemDetail: String,
|
||||
_itemName: String,
|
||||
_itemRevision: String,
|
||||
_itemAnnotation: String,
|
||||
};
|
||||
}
|
||||
|
||||
_computeItemUrl(project) {
|
||||
if (this.itemDetail === DETAIL_TYPES.branches) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(this.repoName, true) + ',branches';
|
||||
} else if (this.itemDetail === DETAIL_TYPES.tags) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(this.repoName, true) + ',tags';
|
||||
}
|
||||
}
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateItemName(_itemName)',
|
||||
];
|
||||
}
|
||||
|
||||
handleCreateItem() {
|
||||
const USE_HEAD = this._itemRevision ? this._itemRevision : 'HEAD';
|
||||
if (this.itemDetail === DETAIL_TYPES.branches) {
|
||||
return this.$.restAPI.createRepoBranch(this.repoName,
|
||||
this._itemName, {revision: USE_HEAD})
|
||||
.then(itemRegistered => {
|
||||
if (itemRegistered.status === 201) {
|
||||
page.show(this._computeItemUrl(this.itemDetail));
|
||||
}
|
||||
});
|
||||
} else if (this.itemDetail === DETAIL_TYPES.tags) {
|
||||
return this.$.restAPI.createRepoTag(this.repoName,
|
||||
this._itemName,
|
||||
{revision: USE_HEAD, message: this._itemAnnotation || null})
|
||||
.then(itemRegistered => {
|
||||
if (itemRegistered.status === 201) {
|
||||
page.show(this._computeItemUrl(this.itemDetail));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_updateItemName(name) {
|
||||
this.hasNewItemName = !!name;
|
||||
}
|
||||
|
||||
_computeHideItemClass(type) {
|
||||
return type === DETAIL_TYPES.branches ? 'hideItem' : '';
|
||||
_computeItemUrl(project) {
|
||||
if (this.itemDetail === DETAIL_TYPES.branches) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(this.repoName, true) + ',branches';
|
||||
} else if (this.itemDetail === DETAIL_TYPES.tags) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(this.repoName, true) + ',tags';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreatePointerDialog.is, GrCreatePointerDialog);
|
||||
})();
|
||||
handleCreateItem() {
|
||||
const USE_HEAD = this._itemRevision ? this._itemRevision : 'HEAD';
|
||||
if (this.itemDetail === DETAIL_TYPES.branches) {
|
||||
return this.$.restAPI.createRepoBranch(this.repoName,
|
||||
this._itemName, {revision: USE_HEAD})
|
||||
.then(itemRegistered => {
|
||||
if (itemRegistered.status === 201) {
|
||||
page.show(this._computeItemUrl(this.itemDetail));
|
||||
}
|
||||
});
|
||||
} else if (this.itemDetail === DETAIL_TYPES.tags) {
|
||||
return this.$.restAPI.createRepoTag(this.repoName,
|
||||
this._itemName,
|
||||
{revision: USE_HEAD, message: this._itemAnnotation || null})
|
||||
.then(itemRegistered => {
|
||||
if (itemRegistered.status === 201) {
|
||||
page.show(this._computeItemUrl(this.itemDetail));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_computeHideItemClass(type) {
|
||||
return type === DETAIL_TYPES.branches ? 'hideItem' : '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreatePointerDialog.is, GrCreatePointerDialog);
|
||||
|
@ -1,33 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.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">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
|
||||
<dom-module id="gr-create-pointer-dialog">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -49,41 +38,23 @@ limitations under the License.
|
||||
<div id="form">
|
||||
<section id="itemNameSection">
|
||||
<span class="title">[[detailType]] name</span>
|
||||
<iron-input
|
||||
placeholder="[[detailType]] Name"
|
||||
bind-value="{{_itemName}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
placeholder="[[detailType]] Name"
|
||||
bind-value="{{_itemName}}">
|
||||
<iron-input placeholder="[[detailType]] Name" bind-value="{{_itemName}}">
|
||||
<input is="iron-input" placeholder="[[detailType]] Name" bind-value="{{_itemName}}">
|
||||
</iron-input>
|
||||
</section>
|
||||
<section id="itemRevisionSection">
|
||||
<span class="title">Initial Revision</span>
|
||||
<iron-input
|
||||
placeholder="Revision (Branch or SHA-1)"
|
||||
bind-value="{{_itemRevision}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
placeholder="Revision (Branch or SHA-1)"
|
||||
bind-value="{{_itemRevision}}">
|
||||
<iron-input placeholder="Revision (Branch or SHA-1)" bind-value="{{_itemRevision}}">
|
||||
<input is="iron-input" placeholder="Revision (Branch or SHA-1)" bind-value="{{_itemRevision}}">
|
||||
</iron-input>
|
||||
</section>
|
||||
<section id="itemAnnotationSection"
|
||||
class$="[[_computeHideItemClass(itemDetail)]]">
|
||||
<section id="itemAnnotationSection" class\$="[[_computeHideItemClass(itemDetail)]]">
|
||||
<span class="title">Annotation</span>
|
||||
<iron-input
|
||||
placeholder="Annotation (Optional)"
|
||||
bind-value="{{_itemAnnotation}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
placeholder="Annotation (Optional)"
|
||||
bind-value="{{_itemAnnotation}}">
|
||||
<iron-input placeholder="Annotation (Optional)" bind-value="{{_itemAnnotation}}">
|
||||
<input is="iron-input" placeholder="Annotation (Optional)" bind-value="{{_itemAnnotation}}">
|
||||
</iron-input>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-create-pointer-dialog.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-create-pointer-dialog</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-create-pointer-dialog.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-create-pointer-dialog.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-pointer-dialog.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,103 +40,106 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-create-pointer-dialog tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-pointer-dialog.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
suite('gr-create-pointer-dialog tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
const ironInput = function(element) {
|
||||
return Polymer.dom(element).querySelector('iron-input');
|
||||
};
|
||||
const ironInput = function(element) {
|
||||
return dom(element).querySelector('iron-input');
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('branch created', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoBranch',
|
||||
() => Promise.resolve({}));
|
||||
test('branch created', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoBranch',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
|
||||
element._itemName = 'test-branch';
|
||||
element.itemDetail = 'branches';
|
||||
element._itemName = 'test-branch';
|
||||
element.itemDetail = 'branches';
|
||||
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-branch2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-branch2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('tag created', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoTag',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
|
||||
element._itemName = 'test-tag';
|
||||
element.itemDetail = 'tags';
|
||||
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-tag2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('tag created with annotations', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoTag',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
|
||||
element._itemName = 'test-tag';
|
||||
element._itemAnnotation = 'test-message';
|
||||
element.itemDetail = 'tags';
|
||||
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
|
||||
ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-tag2');
|
||||
assert.equal(element._itemAnnotation, 'test-message2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns hideItem if type is branches', () => {
|
||||
assert.equal(element._computeHideItemClass('branches'), 'hideItem');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns strings if not branches', () => {
|
||||
assert.equal(element._computeHideItemClass('tags'), '');
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-branch2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('tag created', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoTag',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
|
||||
element._itemName = 'test-tag';
|
||||
element.itemDetail = 'tags';
|
||||
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-tag2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('tag created with annotations', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'createRepoTag',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewItemName);
|
||||
|
||||
element._itemName = 'test-tag';
|
||||
element._itemAnnotation = 'test-message';
|
||||
element.itemDetail = 'tags';
|
||||
|
||||
ironInput(element.$.itemNameSection).bindValue = 'test-tag2';
|
||||
ironInput(element.$.itemAnnotationSection).bindValue = 'test-message2';
|
||||
ironInput(element.$.itemRevisionSection).bindValue = 'HEAD';
|
||||
|
||||
setTimeout(() => {
|
||||
assert.isTrue(element.hasNewItemName);
|
||||
assert.equal(element._itemName, 'test-tag2');
|
||||
assert.equal(element._itemAnnotation, 'test-message2');
|
||||
assert.equal(element._itemRevision, 'HEAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns hideItem if type is branches', () => {
|
||||
assert.equal(element._computeHideItemClass('branches'), 'hideItem');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns strings if not branches', () => {
|
||||
assert.equal(element._computeHideItemClass('tags'), '');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,130 +14,145 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreateRepoDialog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-create-repo-dialog'; }
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-autocomplete/gr-autocomplete.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-create-repo-dialog_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
hasNewRepoName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrCreateRepoDialog extends mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-create-repo-dialog'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
hasNewRepoName: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
},
|
||||
|
||||
/** @type {?} */
|
||||
_repoConfig: {
|
||||
type: Object,
|
||||
value: () => {
|
||||
// Set default values for dropdowns.
|
||||
return {
|
||||
create_empty_commit: true,
|
||||
permissions_only: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
_repoCreated: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_repoOwner: String,
|
||||
_repoOwnerId: {
|
||||
type: String,
|
||||
observer: '_repoOwnerIdUpdate',
|
||||
},
|
||||
|
||||
/** @type {?} */
|
||||
_repoConfig: {
|
||||
type: Object,
|
||||
value: () => {
|
||||
// Set default values for dropdowns.
|
||||
return {
|
||||
create_empty_commit: true,
|
||||
permissions_only: false,
|
||||
};
|
||||
},
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getRepoSuggestions.bind(this);
|
||||
},
|
||||
_repoCreated: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_repoOwner: String,
|
||||
_repoOwnerId: {
|
||||
type: String,
|
||||
observer: '_repoOwnerIdUpdate',
|
||||
},
|
||||
_queryGroups: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getRepoSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_queryGroups: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateRepoName(_repoConfig.name)',
|
||||
];
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_updateRepoName(_repoConfig.name)',
|
||||
];
|
||||
}
|
||||
_computeRepoUrl(repoName) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(repoName, true);
|
||||
}
|
||||
|
||||
_computeRepoUrl(repoName) {
|
||||
return this.getBaseUrl() + '/admin/repos/' +
|
||||
this.encodeURL(repoName, true);
|
||||
}
|
||||
_updateRepoName(name) {
|
||||
this.hasNewRepoName = !!name;
|
||||
}
|
||||
|
||||
_updateRepoName(name) {
|
||||
this.hasNewRepoName = !!name;
|
||||
}
|
||||
|
||||
_repoOwnerIdUpdate(id) {
|
||||
if (id) {
|
||||
this.set('_repoConfig.owners', [id]);
|
||||
} else {
|
||||
this.set('_repoConfig.owners', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
handleCreateRepo() {
|
||||
return this.$.restAPI.createRepo(this._repoConfig)
|
||||
.then(repoRegistered => {
|
||||
if (repoRegistered.status === 201) {
|
||||
this._repoCreated = true;
|
||||
page.show(this._computeRepoUrl(this._repoConfig.name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getRepoSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedProjects(input)
|
||||
.then(response => {
|
||||
const repos = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
repos.push({
|
||||
name: key,
|
||||
value: response[key],
|
||||
});
|
||||
}
|
||||
return repos;
|
||||
});
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
_repoOwnerIdUpdate(id) {
|
||||
if (id) {
|
||||
this.set('_repoConfig.owners', [id]);
|
||||
} else {
|
||||
this.set('_repoConfig.owners', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreateRepoDialog.is, GrCreateRepoDialog);
|
||||
})();
|
||||
handleCreateRepo() {
|
||||
return this.$.restAPI.createRepo(this._repoConfig)
|
||||
.then(repoRegistered => {
|
||||
if (repoRegistered.status === 201) {
|
||||
this._repoCreated = true;
|
||||
page.show(this._computeRepoUrl(this._repoConfig.name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getRepoSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedProjects(input)
|
||||
.then(response => {
|
||||
const repos = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
repos.push({
|
||||
name: key,
|
||||
value: response[key],
|
||||
});
|
||||
}
|
||||
return repos;
|
||||
});
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrCreateRepoDialog.is, GrCreateRepoDialog);
|
||||
|
@ -1,34 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
|
||||
<dom-module id="gr-create-repo-dialog">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -48,42 +36,28 @@ limitations under the License.
|
||||
<div id="form">
|
||||
<section>
|
||||
<span class="title">Repository name</span>
|
||||
<iron-input autocomplete="on"
|
||||
bind-value="{{_repoConfig.name}}">
|
||||
<input is="iron-input"
|
||||
id="repoNameInput"
|
||||
autocomplete="on"
|
||||
bind-value="{{_repoConfig.name}}">
|
||||
<iron-input autocomplete="on" bind-value="{{_repoConfig.name}}">
|
||||
<input is="iron-input" id="repoNameInput" autocomplete="on" bind-value="{{_repoConfig.name}}">
|
||||
</iron-input>
|
||||
</section>
|
||||
<section>
|
||||
<span class="title">Rights inherit from</span>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="rightsInheritFromInput"
|
||||
text="{{_repoConfig.parent}}"
|
||||
query="[[_query]]"
|
||||
placeholder="Optional, defaults to 'All-Projects'">
|
||||
<gr-autocomplete id="rightsInheritFromInput" text="{{_repoConfig.parent}}" query="[[_query]]" placeholder="Optional, defaults to 'All-Projects'">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
</section>
|
||||
<section>
|
||||
<span class="title">Owner</span>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="ownerInput"
|
||||
text="{{_repoOwner}}"
|
||||
value="{{_repoOwnerId}}"
|
||||
query="[[_queryGroups]]">
|
||||
<gr-autocomplete id="ownerInput" text="{{_repoOwner}}" value="{{_repoOwnerId}}" query="[[_queryGroups]]">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
</section>
|
||||
<section>
|
||||
<span class="title">Create initial empty commit</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="initialCommit"
|
||||
bind-value="{{_repoConfig.create_empty_commit}}">
|
||||
<gr-select id="initialCommit" bind-value="{{_repoConfig.create_empty_commit}}">
|
||||
<select>
|
||||
<option value="false">False</option>
|
||||
<option value="true">True</option>
|
||||
@ -94,9 +68,7 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Only serve as parent for other repositories</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="parentRepo"
|
||||
bind-value="{{_repoConfig.permissions_only}}">
|
||||
<gr-select id="parentRepo" bind-value="{{_repoConfig.permissions_only}}">
|
||||
<select>
|
||||
<option value="false">False</option>
|
||||
<option value="true">True</option>
|
||||
@ -107,6 +79,4 @@ limitations under the License.
|
||||
</div>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-create-repo-dialog.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-create-repo-dialog</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-create-repo-dialog.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-create-repo-dialog.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-repo-dialog.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,74 +40,76 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-create-repo-dialog tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-create-repo-dialog.js';
|
||||
suite('gr-create-repo-dialog tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('default values are populated', () => {
|
||||
assert.isTrue(element.$.initialCommit.bindValue);
|
||||
assert.isFalse(element.$.parentRepo.bindValue);
|
||||
});
|
||||
test('default values are populated', () => {
|
||||
assert.isTrue(element.$.initialCommit.bindValue);
|
||||
assert.isFalse(element.$.parentRepo.bindValue);
|
||||
});
|
||||
|
||||
test('repo created', done => {
|
||||
const configInputObj = {
|
||||
name: 'test-repo',
|
||||
create_empty_commit: true,
|
||||
parent: 'All-Project',
|
||||
permissions_only: false,
|
||||
owners: ['testId'],
|
||||
};
|
||||
test('repo created', done => {
|
||||
const configInputObj = {
|
||||
name: 'test-repo',
|
||||
create_empty_commit: true,
|
||||
parent: 'All-Project',
|
||||
permissions_only: false,
|
||||
owners: ['testId'],
|
||||
};
|
||||
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createRepo', () => Promise.resolve({}));
|
||||
const saveStub = sandbox.stub(element.$.restAPI,
|
||||
'createRepo', () => Promise.resolve({}));
|
||||
|
||||
assert.isFalse(element.hasNewRepoName);
|
||||
assert.isFalse(element.hasNewRepoName);
|
||||
|
||||
element._repoConfig = {
|
||||
name: 'test-repo',
|
||||
create_empty_commit: true,
|
||||
parent: 'All-Project',
|
||||
permissions_only: false,
|
||||
};
|
||||
element._repoConfig = {
|
||||
name: 'test-repo',
|
||||
create_empty_commit: true,
|
||||
parent: 'All-Project',
|
||||
permissions_only: false,
|
||||
};
|
||||
|
||||
element._repoOwner = 'test';
|
||||
element._repoOwnerId = 'testId';
|
||||
element._repoOwner = 'test';
|
||||
element._repoOwnerId = 'testId';
|
||||
|
||||
element.$.repoNameInput.bindValue = configInputObj.name;
|
||||
element.$.rightsInheritFromInput.bindValue = configInputObj.parent;
|
||||
element.$.ownerInput.text = configInputObj.owners[0];
|
||||
element.$.initialCommit.bindValue =
|
||||
configInputObj.create_empty_commit;
|
||||
element.$.parentRepo.bindValue =
|
||||
configInputObj.permissions_only;
|
||||
element.$.repoNameInput.bindValue = configInputObj.name;
|
||||
element.$.rightsInheritFromInput.bindValue = configInputObj.parent;
|
||||
element.$.ownerInput.text = configInputObj.owners[0];
|
||||
element.$.initialCommit.bindValue =
|
||||
configInputObj.create_empty_commit;
|
||||
element.$.parentRepo.bindValue =
|
||||
configInputObj.permissions_only;
|
||||
|
||||
assert.isTrue(element.hasNewRepoName);
|
||||
assert.isTrue(element.hasNewRepoName);
|
||||
|
||||
assert.deepEqual(element._repoConfig, configInputObj);
|
||||
assert.deepEqual(element._repoConfig, configInputObj);
|
||||
|
||||
element.handleCreateRepo().then(() => {
|
||||
assert.isTrue(saveStub.lastCall.calledWithExactly(configInputObj));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('testing observer of _repoOwner', () => {
|
||||
element._repoOwnerId = 'test-5';
|
||||
assert.deepEqual(element._repoConfig.owners, ['test-5']);
|
||||
element.handleCreateRepo().then(() => {
|
||||
assert.isTrue(saveStub.lastCall.calledWithExactly(configInputObj));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('testing observer of _repoOwner', () => {
|
||||
element._repoOwnerId = 'test-5';
|
||||
assert.deepEqual(element._repoConfig.owners, ['test-5']);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,113 +14,127 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.js';
|
||||
|
||||
const GROUP_EVENTS = ['ADD_GROUP', 'REMOVE_GROUP'];
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-date-formatter/gr-date-formatter.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-account-link/gr-account-link.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-group-audit-log_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrGroupAuditLog extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-group-audit-log'; }
|
||||
const GROUP_EVENTS = ['ADD_GROUP', 'REMOVE_GROUP'];
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: String,
|
||||
_auditLog: Array,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrGroupAuditLog extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.fire('title-change', {title: 'Audit Log'});
|
||||
}
|
||||
static get is() { return 'gr-group-audit-log'; }
|
||||
|
||||
/** @override */
|
||||
ready() {
|
||||
super.ready();
|
||||
this._getAuditLogs();
|
||||
}
|
||||
|
||||
_getAuditLogs() {
|
||||
if (!this.groupId) { return ''; }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getGroupAuditLog(this.groupId, errFn)
|
||||
.then(auditLog => {
|
||||
if (!auditLog) {
|
||||
this._auditLog = [];
|
||||
return;
|
||||
}
|
||||
this._auditLog = auditLog;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_status(item) {
|
||||
return item.disabled ? 'Disabled' : 'Enabled';
|
||||
}
|
||||
|
||||
itemType(type) {
|
||||
let item;
|
||||
switch (type) {
|
||||
case 'ADD_GROUP':
|
||||
case 'ADD_USER':
|
||||
item = 'Added';
|
||||
break;
|
||||
case 'REMOVE_GROUP':
|
||||
case 'REMOVE_USER':
|
||||
item = 'Removed';
|
||||
break;
|
||||
default:
|
||||
item = '';
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
_isGroupEvent(type) {
|
||||
return GROUP_EVENTS.indexOf(type) !== -1;
|
||||
}
|
||||
|
||||
_computeGroupUrl(group) {
|
||||
if (group && group.url && group.id) {
|
||||
return Gerrit.Nav.getUrlForGroup(group.id);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_getIdForUser(account) {
|
||||
return account._account_id ? ' (' + account._account_id + ')' : '';
|
||||
}
|
||||
|
||||
_getNameForGroup(group) {
|
||||
if (group && group.name) {
|
||||
return group.name;
|
||||
} else if (group && group.id) {
|
||||
// The URL encoded id of the member
|
||||
return decodeURIComponent(group.id);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: String,
|
||||
_auditLog: Array,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrGroupAuditLog.is, GrGroupAuditLog);
|
||||
})();
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.fire('title-change', {title: 'Audit Log'});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
ready() {
|
||||
super.ready();
|
||||
this._getAuditLogs();
|
||||
}
|
||||
|
||||
_getAuditLogs() {
|
||||
if (!this.groupId) { return ''; }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getGroupAuditLog(this.groupId, errFn)
|
||||
.then(auditLog => {
|
||||
if (!auditLog) {
|
||||
this._auditLog = [];
|
||||
return;
|
||||
}
|
||||
this._auditLog = auditLog;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_status(item) {
|
||||
return item.disabled ? 'Disabled' : 'Enabled';
|
||||
}
|
||||
|
||||
itemType(type) {
|
||||
let item;
|
||||
switch (type) {
|
||||
case 'ADD_GROUP':
|
||||
case 'ADD_USER':
|
||||
item = 'Added';
|
||||
break;
|
||||
case 'REMOVE_GROUP':
|
||||
case 'REMOVE_USER':
|
||||
item = 'Removed';
|
||||
break;
|
||||
default:
|
||||
item = '';
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
_isGroupEvent(type) {
|
||||
return GROUP_EVENTS.indexOf(type) !== -1;
|
||||
}
|
||||
|
||||
_computeGroupUrl(group) {
|
||||
if (group && group.url && group.id) {
|
||||
return Gerrit.Nav.getUrlForGroup(group.id);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_getIdForUser(account) {
|
||||
return account._account_id ? ' (' + account._account_id + ')' : '';
|
||||
}
|
||||
|
||||
_getNameForGroup(group) {
|
||||
if (group && group.name) {
|
||||
return group.name;
|
||||
} else if (group && group.id) {
|
||||
// The URL encoded id of the member
|
||||
return decodeURIComponent(group.id);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrGroupAuditLog.is, GrGroupAuditLog);
|
||||
|
@ -1,32 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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/gr-list-view-behavior/gr-list-view-behavior.html">
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
|
||||
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
|
||||
|
||||
<dom-module id="gr-group-audit-log">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -38,28 +28,26 @@ limitations under the License.
|
||||
}
|
||||
</style>
|
||||
<table id="list" class="genericList">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="date topHeader">Date</th>
|
||||
<th class="type topHeader">Type</th>
|
||||
<th class="member topHeader">Member</th>
|
||||
<th class="by-user topHeader">By User</th>
|
||||
</tr>
|
||||
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<tr id="loading" class\$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody class$="[[computeLoadingClass(_loading)]]">
|
||||
</tbody><tbody class\$="[[computeLoadingClass(_loading)]]">
|
||||
<template is="dom-repeat" items="[[_auditLog]]">
|
||||
<tr class="table">
|
||||
<td class="date">
|
||||
<gr-date-formatter
|
||||
has-tooltip
|
||||
date-str="[[item.date]]">
|
||||
<gr-date-formatter has-tooltip="" date-str="[[item.date]]">
|
||||
</gr-date-formatter>
|
||||
</td>
|
||||
<td class="type">[[itemType(item.type)]]</td>
|
||||
<td class="member">
|
||||
<template is="dom-if" if="[[_isGroupEvent(item.type)]]">
|
||||
<a href$="[[_computeGroupUrl(item.member)]]">
|
||||
<a href\$="[[_computeGroupUrl(item.member)]]">
|
||||
[[_getNameForGroup(item.member)]]
|
||||
</a>
|
||||
</template>
|
||||
@ -77,6 +65,4 @@ limitations under the License.
|
||||
</tbody>
|
||||
</table>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-group-audit-log.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-group-audit-log</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-group-audit-log.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-group-audit-log.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group-audit-log.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,85 +40,87 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-group-audit-log tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group-audit-log.js';
|
||||
suite('gr-group-audit-log tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('members', () => {
|
||||
test('test _getNameForGroup', () => {
|
||||
let group = {
|
||||
member: {
|
||||
name: 'test-name',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getNameForGroup(group.member), 'test-name');
|
||||
|
||||
group = {
|
||||
member: {
|
||||
id: 'test-id',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getNameForGroup(group.member), 'test-id');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
test('test _isGroupEvent', () => {
|
||||
assert.isTrue(element._isGroupEvent('ADD_GROUP'));
|
||||
assert.isTrue(element._isGroupEvent('REMOVE_GROUP'));
|
||||
|
||||
suite('members', () => {
|
||||
test('test _getNameForGroup', () => {
|
||||
let group = {
|
||||
member: {
|
||||
name: 'test-name',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getNameForGroup(group.member), 'test-name');
|
||||
|
||||
group = {
|
||||
member: {
|
||||
id: 'test-id',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getNameForGroup(group.member), 'test-id');
|
||||
});
|
||||
|
||||
test('test _isGroupEvent', () => {
|
||||
assert.isTrue(element._isGroupEvent('ADD_GROUP'));
|
||||
assert.isTrue(element._isGroupEvent('REMOVE_GROUP'));
|
||||
|
||||
assert.isFalse(element._isGroupEvent('ADD_USER'));
|
||||
assert.isFalse(element._isGroupEvent('REMOVE_USER'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('users', () => {
|
||||
test('test _getIdForUser', () => {
|
||||
const account = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
_account_id: 12,
|
||||
},
|
||||
};
|
||||
assert.equal(element._getIdForUser(account.user), ' (12)');
|
||||
});
|
||||
|
||||
test('test _account_id not present', () => {
|
||||
const account = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getIdForUser(account.user), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupAuditLog', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._getAuditLogs();
|
||||
});
|
||||
assert.isFalse(element._isGroupEvent('ADD_USER'));
|
||||
assert.isFalse(element._isGroupEvent('REMOVE_USER'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('users', () => {
|
||||
test('test _getIdForUser', () => {
|
||||
const account = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
_account_id: 12,
|
||||
},
|
||||
};
|
||||
assert.equal(element._getIdForUser(account.user), ' (12)');
|
||||
});
|
||||
|
||||
test('test _account_id not present', () => {
|
||||
const account = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
},
|
||||
};
|
||||
assert.equal(element._getIdForUser(account.user), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupAuditLog', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._getAuditLogs();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,280 +14,300 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
|
||||
const SUGGESTIONS_LIMIT = 15;
|
||||
const SAVING_ERROR_TEXT = 'Group may not exist, or you may not have '+
|
||||
'permission to add it';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-account-link/gr-account-link.js';
|
||||
import '../../shared/gr-autocomplete/gr-autocomplete.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-overlay/gr-overlay.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-group-members_html.js';
|
||||
|
||||
const URL_REGEX = '^(?:[a-z]+:)?//';
|
||||
const SUGGESTIONS_LIMIT = 15;
|
||||
const SAVING_ERROR_TEXT = 'Group may not exist, or you may not have '+
|
||||
'permission to add it';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrGroupMembers extends Polymer.mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-group-members'; }
|
||||
const URL_REGEX = '^(?:[a-z]+:)?//';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: Number,
|
||||
_groupMemberSearchId: String,
|
||||
_groupMemberSearchName: String,
|
||||
_includedGroupSearchId: String,
|
||||
_includedGroupSearchName: String,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
/**
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrGroupMembers extends mixinBehaviors( [
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-group-members'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: Number,
|
||||
_groupMemberSearchId: String,
|
||||
_groupMemberSearchName: String,
|
||||
_includedGroupSearchId: String,
|
||||
_includedGroupSearchName: String,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_groupName: String,
|
||||
_groupMembers: Object,
|
||||
_includedGroups: Object,
|
||||
_itemName: String,
|
||||
_itemType: String,
|
||||
_queryMembers: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getAccountSuggestions.bind(this);
|
||||
},
|
||||
_groupName: String,
|
||||
_groupMembers: Object,
|
||||
_includedGroups: Object,
|
||||
_itemName: String,
|
||||
_itemType: String,
|
||||
_queryMembers: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getAccountSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_queryIncludedGroup: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
_queryIncludedGroup: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadGroupDetails();
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadGroupDetails();
|
||||
|
||||
this.fire('title-change', {title: 'Members'});
|
||||
}
|
||||
this.fire('title-change', {title: 'Members'});
|
||||
}
|
||||
|
||||
_loadGroupDetails() {
|
||||
if (!this.groupId) { return; }
|
||||
_loadGroupDetails() {
|
||||
if (!this.groupId) { return; }
|
||||
|
||||
const promises = [];
|
||||
const promises = [];
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getGroupConfig(this.groupId, errFn)
|
||||
.then(config => {
|
||||
if (!config || !config.name) { return Promise.resolve(); }
|
||||
return this.$.restAPI.getGroupConfig(this.groupId, errFn)
|
||||
.then(config => {
|
||||
if (!config || !config.name) { return Promise.resolve(); }
|
||||
|
||||
this._groupName = config.name;
|
||||
this._groupName = config.name;
|
||||
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin ? true : false;
|
||||
}));
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin ? true : false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(config.name)
|
||||
.then(isOwner => {
|
||||
this._groupOwner = isOwner ? true : false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getGroupMembers(config.name).then(
|
||||
members => {
|
||||
this._groupMembers = members;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIncludedGroup(config.name)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this._loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_computeGroupUrl(url) {
|
||||
if (!url) { return; }
|
||||
|
||||
const r = new RegExp(URL_REGEX, 'i');
|
||||
if (r.test(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// For GWT compatibility
|
||||
if (url.startsWith('#')) {
|
||||
return this.getBaseUrl() + url.slice(1);
|
||||
}
|
||||
return this.getBaseUrl() + url;
|
||||
}
|
||||
|
||||
_handleSavingGroupMember() {
|
||||
return this.$.restAPI.saveGroupMembers(this._groupName,
|
||||
this._groupMemberSearchId).then(config => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
this.$.restAPI.getGroupMembers(this._groupName).then(members => {
|
||||
this._groupMembers = members;
|
||||
});
|
||||
this._groupMemberSearchName = '';
|
||||
this._groupMemberSearchId = '';
|
||||
});
|
||||
}
|
||||
|
||||
_handleDeleteConfirm() {
|
||||
this.$.overlay.close();
|
||||
if (this._itemType === 'member') {
|
||||
return this.$.restAPI.deleteGroupMembers(this._groupName,
|
||||
this._itemId)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this.$.restAPI.getGroupMembers(this._groupName)
|
||||
.then(members => {
|
||||
this._groupMembers = members;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this._itemType === 'includedGroup') {
|
||||
return this.$.restAPI.deleteIncludedGroup(this._groupName,
|
||||
this._itemId)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204 || itemDeleted.status === 205) {
|
||||
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleConfirmDialogCancel() {
|
||||
this.$.overlay.close();
|
||||
}
|
||||
|
||||
_handleDeleteMember(e) {
|
||||
const id = e.model.get('item._account_id');
|
||||
const name = e.model.get('item.name');
|
||||
const username = e.model.get('item.username');
|
||||
const email = e.model.get('item.email');
|
||||
const item = username || name || email || id;
|
||||
if (!item) {
|
||||
return '';
|
||||
}
|
||||
this._itemName = item;
|
||||
this._itemId = id;
|
||||
this._itemType = 'member';
|
||||
this.$.overlay.open();
|
||||
}
|
||||
|
||||
_handleSavingIncludedGroups() {
|
||||
return this.$.restAPI.saveIncludedGroup(this._groupName,
|
||||
this._includedGroupSearchId.replace(/\+/g, ' '), err => {
|
||||
if (err.status === 404) {
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {message: SAVING_ERROR_TEXT},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(config.name)
|
||||
.then(isOwner => {
|
||||
this._groupOwner = isOwner ? true : false;
|
||||
}));
|
||||
return err;
|
||||
}
|
||||
throw Error(err.statusText);
|
||||
})
|
||||
.then(config => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
});
|
||||
this._includedGroupSearchName = '';
|
||||
this._includedGroupSearchId = '';
|
||||
|
||||
promises.push(this.$.restAPI.getGroupMembers(config.name).then(
|
||||
members => {
|
||||
this._groupMembers = members;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIncludedGroup(config.name)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this._loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_computeGroupUrl(url) {
|
||||
if (!url) { return; }
|
||||
|
||||
const r = new RegExp(URL_REGEX, 'i');
|
||||
if (r.test(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
_handleDeleteIncludedGroup(e) {
|
||||
const id = decodeURIComponent(e.model.get('item.id')).replace(/\+/g, ' ');
|
||||
const name = e.model.get('item.name');
|
||||
const item = name || id;
|
||||
if (!item) { return ''; }
|
||||
this._itemName = item;
|
||||
this._itemId = id;
|
||||
this._itemType = 'includedGroup';
|
||||
this.$.overlay.open();
|
||||
// For GWT compatibility
|
||||
if (url.startsWith('#')) {
|
||||
return this.getBaseUrl() + url.slice(1);
|
||||
}
|
||||
return this.getBaseUrl() + url;
|
||||
}
|
||||
|
||||
_getAccountSuggestions(input) {
|
||||
if (input.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedAccounts(
|
||||
input, SUGGESTIONS_LIMIT).then(accounts => {
|
||||
const accountSuggestions = [];
|
||||
let nameAndEmail;
|
||||
if (!accounts) { return []; }
|
||||
for (const key in accounts) {
|
||||
if (!accounts.hasOwnProperty(key)) { continue; }
|
||||
if (accounts[key].email !== undefined) {
|
||||
nameAndEmail = accounts[key].name +
|
||||
' <' + accounts[key].email + '>';
|
||||
} else {
|
||||
nameAndEmail = accounts[key].name;
|
||||
}
|
||||
accountSuggestions.push({
|
||||
name: nameAndEmail,
|
||||
value: accounts[key]._account_id,
|
||||
});
|
||||
}
|
||||
return accountSuggestions;
|
||||
_handleSavingGroupMember() {
|
||||
return this.$.restAPI.saveGroupMembers(this._groupName,
|
||||
this._groupMemberSearchId).then(config => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
this.$.restAPI.getGroupMembers(this._groupName).then(members => {
|
||||
this._groupMembers = members;
|
||||
});
|
||||
}
|
||||
this._groupMemberSearchName = '';
|
||||
this._groupMemberSearchId = '';
|
||||
});
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
_handleDeleteConfirm() {
|
||||
this.$.overlay.close();
|
||||
if (this._itemType === 'member') {
|
||||
return this.$.restAPI.deleteGroupMembers(this._groupName,
|
||||
this._itemId)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this.$.restAPI.getGroupMembers(this._groupName)
|
||||
.then(members => {
|
||||
this._groupMembers = members;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this._itemType === 'includedGroup') {
|
||||
return this.$.restAPI.deleteIncludedGroup(this._groupName,
|
||||
this._itemId)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204 || itemDeleted.status === 205) {
|
||||
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
_computeHideItemClass(owner, admin) {
|
||||
return admin || owner ? '' : 'canModify';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrGroupMembers.is, GrGroupMembers);
|
||||
})();
|
||||
_handleConfirmDialogCancel() {
|
||||
this.$.overlay.close();
|
||||
}
|
||||
|
||||
_handleDeleteMember(e) {
|
||||
const id = e.model.get('item._account_id');
|
||||
const name = e.model.get('item.name');
|
||||
const username = e.model.get('item.username');
|
||||
const email = e.model.get('item.email');
|
||||
const item = username || name || email || id;
|
||||
if (!item) {
|
||||
return '';
|
||||
}
|
||||
this._itemName = item;
|
||||
this._itemId = id;
|
||||
this._itemType = 'member';
|
||||
this.$.overlay.open();
|
||||
}
|
||||
|
||||
_handleSavingIncludedGroups() {
|
||||
return this.$.restAPI.saveIncludedGroup(this._groupName,
|
||||
this._includedGroupSearchId.replace(/\+/g, ' '), err => {
|
||||
if (err.status === 404) {
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {message: SAVING_ERROR_TEXT},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
return err;
|
||||
}
|
||||
throw Error(err.statusText);
|
||||
})
|
||||
.then(config => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
this.$.restAPI.getIncludedGroup(this._groupName)
|
||||
.then(includedGroup => {
|
||||
this._includedGroups = includedGroup;
|
||||
});
|
||||
this._includedGroupSearchName = '';
|
||||
this._includedGroupSearchId = '';
|
||||
});
|
||||
}
|
||||
|
||||
_handleDeleteIncludedGroup(e) {
|
||||
const id = decodeURIComponent(e.model.get('item.id')).replace(/\+/g, ' ');
|
||||
const name = e.model.get('item.name');
|
||||
const item = name || id;
|
||||
if (!item) { return ''; }
|
||||
this._itemName = item;
|
||||
this._itemId = id;
|
||||
this._itemType = 'includedGroup';
|
||||
this.$.overlay.open();
|
||||
}
|
||||
|
||||
_getAccountSuggestions(input) {
|
||||
if (input.length === 0) { return Promise.resolve([]); }
|
||||
return this.$.restAPI.getSuggestedAccounts(
|
||||
input, SUGGESTIONS_LIMIT).then(accounts => {
|
||||
const accountSuggestions = [];
|
||||
let nameAndEmail;
|
||||
if (!accounts) { return []; }
|
||||
for (const key in accounts) {
|
||||
if (!accounts.hasOwnProperty(key)) { continue; }
|
||||
if (accounts[key].email !== undefined) {
|
||||
nameAndEmail = accounts[key].name +
|
||||
' <' + accounts[key].email + '>';
|
||||
} else {
|
||||
nameAndEmail = accounts[key].name;
|
||||
}
|
||||
accountSuggestions.push({
|
||||
name: nameAndEmail,
|
||||
value: accounts[key]._account_id,
|
||||
});
|
||||
}
|
||||
return accountSuggestions;
|
||||
});
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
_computeHideItemClass(owner, admin) {
|
||||
return admin || owner ? '' : 'canModify';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrGroupMembers.is, GrGroupMembers);
|
||||
|
@ -1,38 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html">
|
||||
|
||||
<dom-module id="gr-group-members">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="gr-form-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -72,37 +56,29 @@ limitations under the License.
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<main class$="gr-form-styles [[_computeHideItemClass(_groupOwner, _isAdmin)]]">
|
||||
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<main class\$="gr-form-styles [[_computeHideItemClass(_groupOwner, _isAdmin)]]">
|
||||
<div id="loading" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
Loading...
|
||||
</div>
|
||||
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="loadedContent" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
<h1 id="Title">[[_groupName]]</h1>
|
||||
<div id="form">
|
||||
<h3 id="members">Members</h3>
|
||||
<fieldset>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="groupMemberSearchInput"
|
||||
text="{{_groupMemberSearchName}}"
|
||||
value="{{_groupMemberSearchId}}"
|
||||
query="[[_queryMembers]]"
|
||||
placeholder="Name Or Email">
|
||||
<gr-autocomplete id="groupMemberSearchInput" text="{{_groupMemberSearchName}}" value="{{_groupMemberSearchId}}" query="[[_queryMembers]]" placeholder="Name Or Email">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
<gr-button
|
||||
id="saveGroupMember"
|
||||
on-click="_handleSavingGroupMember"
|
||||
disabled="[[!_groupMemberSearchId]]">
|
||||
<gr-button id="saveGroupMember" on-click="_handleSavingGroupMember" disabled="[[!_groupMemberSearchId]]">
|
||||
Add
|
||||
</gr-button>
|
||||
<table id="groupMembers">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="nameHeader">Name</th>
|
||||
<th class="emailAddressHeader">Email Address</th>
|
||||
<th class="deleteHeader">Delete Member</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
</tbody><tbody>
|
||||
<template is="dom-repeat" items="[[_groupMembers]]">
|
||||
<tr>
|
||||
<td class="nameColumn">
|
||||
@ -110,9 +86,7 @@ limitations under the License.
|
||||
</td>
|
||||
<td>[[item.email]]</td>
|
||||
<td class="deleteColumn">
|
||||
<gr-button
|
||||
class="deleteMembersButton"
|
||||
on-click="_handleDeleteMember">
|
||||
<gr-button class="deleteMembersButton" on-click="_handleDeleteMember">
|
||||
Delete
|
||||
</gr-button>
|
||||
</td>
|
||||
@ -124,35 +98,26 @@ limitations under the License.
|
||||
<h3 id="includedGroups">Included Groups</h3>
|
||||
<fieldset>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="includedGroupSearchInput"
|
||||
text="{{_includedGroupSearchName}}"
|
||||
value="{{_includedGroupSearchId}}"
|
||||
query="[[_queryIncludedGroup]]"
|
||||
placeholder="Group Name">
|
||||
<gr-autocomplete id="includedGroupSearchInput" text="{{_includedGroupSearchName}}" value="{{_includedGroupSearchId}}" query="[[_queryIncludedGroup]]" placeholder="Group Name">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
<gr-button
|
||||
id="saveIncludedGroups"
|
||||
on-click="_handleSavingIncludedGroups"
|
||||
disabled="[[!_includedGroupSearchId]]">
|
||||
<gr-button id="saveIncludedGroups" on-click="_handleSavingIncludedGroups" disabled="[[!_includedGroupSearchId]]">
|
||||
Add
|
||||
</gr-button>
|
||||
<table id="includedGroups">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="groupNameHeader">Group Name</th>
|
||||
<th class="descriptionHeader">Description</th>
|
||||
<th class="deleteIncludedHeader">
|
||||
Delete Group
|
||||
</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
</tbody><tbody>
|
||||
<template is="dom-repeat" items="[[_includedGroups]]">
|
||||
<tr>
|
||||
<td class="nameColumn">
|
||||
<template is="dom-if" if="[[item.url]]">
|
||||
<a href$="[[_computeGroupUrl(item.url)]]"
|
||||
rel="noopener">
|
||||
<a href\$="[[_computeGroupUrl(item.url)]]" rel="noopener">
|
||||
[[item.name]]
|
||||
</a>
|
||||
</template>
|
||||
@ -162,9 +127,7 @@ limitations under the License.
|
||||
</td>
|
||||
<td>[[item.description]]</td>
|
||||
<td class="deleteColumn">
|
||||
<gr-button
|
||||
class="deleteIncludedGroupButton"
|
||||
on-click="_handleDeleteIncludedGroup">
|
||||
<gr-button class="deleteIncludedGroupButton" on-click="_handleDeleteIncludedGroup">
|
||||
Delete
|
||||
</gr-button>
|
||||
</td>
|
||||
@ -176,15 +139,8 @@ limitations under the License.
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<gr-overlay id="overlay" with-backdrop>
|
||||
<gr-confirm-delete-item-dialog
|
||||
class="confirmDialog"
|
||||
on-confirm="_handleDeleteConfirm"
|
||||
on-cancel="_handleConfirmDialogCancel"
|
||||
item="[[_itemName]]"
|
||||
item-type="[[_itemType]]"></gr-confirm-delete-item-dialog>
|
||||
<gr-overlay id="overlay" with-backdrop="">
|
||||
<gr-confirm-delete-item-dialog class="confirmDialog" on-confirm="_handleDeleteConfirm" on-cancel="_handleConfirmDialogCancel" item="[[_itemName]]" item-type="[[_itemType]]"></gr-confirm-delete-item-dialog>
|
||||
</gr-overlay>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-group-members.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-group-members</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-group-members.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-group-members.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group-members.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,342 +40,345 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-group-members tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
let groups;
|
||||
let groupMembers;
|
||||
let includedGroups;
|
||||
let groupStub;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group-members.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
suite('gr-group-members tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
let groups;
|
||||
let groupMembers;
|
||||
let includedGroups;
|
||||
let groupStub;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
groups = {
|
||||
name: 'Administrators',
|
||||
owner: 'Administrators',
|
||||
group_id: 1,
|
||||
};
|
||||
groups = {
|
||||
name: 'Administrators',
|
||||
owner: 'Administrators',
|
||||
group_id: 1,
|
||||
};
|
||||
|
||||
groupMembers = [
|
||||
{
|
||||
_account_id: 1000097,
|
||||
name: 'Jane Roe',
|
||||
email: 'jane.roe@example.com',
|
||||
username: 'jane',
|
||||
},
|
||||
{
|
||||
_account_id: 1000096,
|
||||
name: 'Test User',
|
||||
email: 'john.doe@example.com',
|
||||
},
|
||||
{
|
||||
_account_id: 1000095,
|
||||
name: 'Gerrit',
|
||||
},
|
||||
{
|
||||
_account_id: 1000098,
|
||||
},
|
||||
];
|
||||
|
||||
includedGroups = [{
|
||||
url: 'https://group/url',
|
||||
options: {},
|
||||
id: 'testId',
|
||||
name: 'testName',
|
||||
groupMembers = [
|
||||
{
|
||||
_account_id: 1000097,
|
||||
name: 'Jane Roe',
|
||||
email: 'jane.roe@example.com',
|
||||
username: 'jane',
|
||||
},
|
||||
{
|
||||
url: '/group/url',
|
||||
options: {},
|
||||
id: 'testId2',
|
||||
name: 'testName2',
|
||||
_account_id: 1000096,
|
||||
name: 'Test User',
|
||||
email: 'john.doe@example.com',
|
||||
},
|
||||
{
|
||||
url: '#/group/url',
|
||||
options: {},
|
||||
id: 'testId3',
|
||||
name: 'testName3',
|
||||
_account_id: 1000095,
|
||||
name: 'Gerrit',
|
||||
},
|
||||
];
|
||||
{
|
||||
_account_id: 1000098,
|
||||
},
|
||||
];
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getSuggestedAccounts(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
_account_id: 1000096,
|
||||
name: 'test-account',
|
||||
email: 'test.account@example.com',
|
||||
username: 'test123',
|
||||
},
|
||||
{
|
||||
_account_id: 1001439,
|
||||
name: 'test-admin',
|
||||
email: 'test.admin@example.com',
|
||||
username: 'test_admin',
|
||||
},
|
||||
{
|
||||
_account_id: 1001439,
|
||||
name: 'test-git',
|
||||
username: 'test_git',
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
getSuggestedGroups(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve({
|
||||
'test-admin': {
|
||||
id: '1ce023d3fb4e4260776fb92cd08b52bbd21ce70a',
|
||||
},
|
||||
'test/Administrator (admin)': {
|
||||
id: 'test%3Aadmin',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
getConfig() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getGroupMembers() {
|
||||
return Promise.resolve(groupMembers);
|
||||
},
|
||||
getIsGroupOwner() {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
getIncludedGroup() {
|
||||
return Promise.resolve(includedGroups);
|
||||
},
|
||||
getAccountCapabilities() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element, 'getBaseUrl').returns('https://test/site');
|
||||
element.groupId = 1;
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(groups));
|
||||
return element._loadGroupDetails();
|
||||
includedGroups = [{
|
||||
url: 'https://group/url',
|
||||
options: {},
|
||||
id: 'testId',
|
||||
name: 'testName',
|
||||
},
|
||||
{
|
||||
url: '/group/url',
|
||||
options: {},
|
||||
id: 'testId2',
|
||||
name: 'testName2',
|
||||
},
|
||||
{
|
||||
url: '#/group/url',
|
||||
options: {},
|
||||
id: 'testId3',
|
||||
name: 'testName3',
|
||||
},
|
||||
];
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getSuggestedAccounts(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
_account_id: 1000096,
|
||||
name: 'test-account',
|
||||
email: 'test.account@example.com',
|
||||
username: 'test123',
|
||||
},
|
||||
{
|
||||
_account_id: 1001439,
|
||||
name: 'test-admin',
|
||||
email: 'test.admin@example.com',
|
||||
username: 'test_admin',
|
||||
},
|
||||
{
|
||||
_account_id: 1001439,
|
||||
name: 'test-git',
|
||||
username: 'test_git',
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
getSuggestedGroups(input) {
|
||||
if (input.startsWith('test')) {
|
||||
return Promise.resolve({
|
||||
'test-admin': {
|
||||
id: '1ce023d3fb4e4260776fb92cd08b52bbd21ce70a',
|
||||
},
|
||||
'test/Administrator (admin)': {
|
||||
id: 'test%3Aadmin',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
},
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
getConfig() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getGroupMembers() {
|
||||
return Promise.resolve(groupMembers);
|
||||
},
|
||||
getIsGroupOwner() {
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
getIncludedGroup() {
|
||||
return Promise.resolve(includedGroups);
|
||||
},
|
||||
getAccountCapabilities() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element, 'getBaseUrl').returns('https://test/site');
|
||||
element.groupId = 1;
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(groups));
|
||||
return element._loadGroupDetails();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('_includedGroups', () => {
|
||||
assert.equal(element._includedGroups.length, 3);
|
||||
assert.equal(Polymer.dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[0].href, includedGroups[0].url);
|
||||
assert.equal(Polymer.dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[1].href,
|
||||
'https://test/site/group/url');
|
||||
assert.equal(Polymer.dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[2].href,
|
||||
'https://test/site/group/url');
|
||||
});
|
||||
test('_includedGroups', () => {
|
||||
assert.equal(element._includedGroups.length, 3);
|
||||
assert.equal(dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[0].href, includedGroups[0].url);
|
||||
assert.equal(dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[1].href,
|
||||
'https://test/site/group/url');
|
||||
assert.equal(dom(element.root)
|
||||
.querySelectorAll('.nameColumn a')[2].href,
|
||||
'https://test/site/group/url');
|
||||
});
|
||||
|
||||
test('save members correctly', () => {
|
||||
element._groupOwner = true;
|
||||
test('save members correctly', () => {
|
||||
element._groupOwner = true;
|
||||
|
||||
const memberName = 'test-admin';
|
||||
const memberName = 'test-admin';
|
||||
|
||||
const saveStub = sandbox.stub(element.$.restAPI, 'saveGroupMembers',
|
||||
() => Promise.resolve({}));
|
||||
const saveStub = sandbox.stub(element.$.restAPI, 'saveGroupMembers',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
const button = element.$.saveGroupMember;
|
||||
const button = element.$.saveGroupMember;
|
||||
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
|
||||
element.$.groupMemberSearchInput.text = memberName;
|
||||
element.$.groupMemberSearchInput.value = 1234;
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
|
||||
return element._handleSavingGroupMember().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
|
||||
element.$.groupMemberSearchInput.text = memberName;
|
||||
element.$.groupMemberSearchInput.value = 1234;
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
|
||||
return element._handleSavingGroupMember().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.isTrue(saveStub.lastCall.calledWithExactly('Administrators',
|
||||
1234));
|
||||
});
|
||||
});
|
||||
|
||||
test('save included groups correctly', () => {
|
||||
element._groupOwner = true;
|
||||
|
||||
const includedGroupName = 'testName';
|
||||
|
||||
const saveIncludedGroupStub = sandbox.stub(
|
||||
element.$.restAPI, 'saveIncludedGroup', () => Promise.resolve({}));
|
||||
|
||||
const button = element.$.saveIncludedGroups;
|
||||
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
|
||||
element.$.includedGroupSearchInput.text = includedGroupName;
|
||||
element.$.includedGroupSearchInput.value = 'testId';
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
|
||||
return element._handleSavingIncludedGroups().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(saveIncludedGroupStub.lastCall.args[0], 'Administrators');
|
||||
assert.equal(saveIncludedGroupStub.lastCall.args[1], 'testId');
|
||||
});
|
||||
});
|
||||
|
||||
test('add included group 404 shows helpful error text', () => {
|
||||
element._groupOwner = true;
|
||||
|
||||
const memberName = 'bad-name';
|
||||
const alertStub = sandbox.stub();
|
||||
element.addEventListener('show-alert', alertStub);
|
||||
const error = new Error('error');
|
||||
error.status = 404;
|
||||
sandbox.stub(element.$.restAPI, 'saveGroupMembers',
|
||||
() => Promise.reject(error));
|
||||
|
||||
element.$.groupMemberSearchInput.text = memberName;
|
||||
element.$.groupMemberSearchInput.value = 1234;
|
||||
|
||||
return element._handleSavingIncludedGroups().then(() => {
|
||||
assert.isTrue(alertStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('_getAccountSuggestions empty', done => {
|
||||
element
|
||||
._getAccountSuggestions('nonexistent').then(accounts => {
|
||||
assert.equal(accounts.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getAccountSuggestions non-empty', done => {
|
||||
element
|
||||
._getAccountSuggestions('test-').then(accounts => {
|
||||
assert.equal(accounts.length, 3);
|
||||
assert.equal(accounts[0].name,
|
||||
'test-account <test.account@example.com>');
|
||||
assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
|
||||
assert.equal(accounts[2].name, 'test-git');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions empty', done => {
|
||||
element
|
||||
._getGroupSuggestions('nonexistent').then(groups => {
|
||||
assert.equal(groups.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions non-empty', done => {
|
||||
element
|
||||
._getGroupSuggestions('test').then(groups => {
|
||||
assert.equal(groups.length, 2);
|
||||
assert.equal(groups[0].name, 'test-admin');
|
||||
assert.equal(groups[1].name, 'test/Administrator (admin)');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns string for admin', () => {
|
||||
const admin = true;
|
||||
const owner = false;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), '');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns hideItem for admin and owner', () => {
|
||||
const admin = false;
|
||||
const owner = false;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), 'canModify');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns string for owner', () => {
|
||||
const admin = false;
|
||||
const owner = true;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), '');
|
||||
});
|
||||
|
||||
test('delete member', () => {
|
||||
const deletelBtns = Polymer.dom(element.root)
|
||||
.querySelectorAll('.deleteMembersButton');
|
||||
MockInteractions.tap(deletelBtns[0]);
|
||||
assert.equal(element._itemId, '1000097');
|
||||
assert.equal(element._itemName, 'jane');
|
||||
MockInteractions.tap(deletelBtns[1]);
|
||||
assert.equal(element._itemId, '1000096');
|
||||
assert.equal(element._itemName, 'Test User');
|
||||
MockInteractions.tap(deletelBtns[2]);
|
||||
assert.equal(element._itemId, '1000095');
|
||||
assert.equal(element._itemName, 'Gerrit');
|
||||
MockInteractions.tap(deletelBtns[3]);
|
||||
assert.equal(element._itemId, '1000098');
|
||||
assert.equal(element._itemName, '1000098');
|
||||
});
|
||||
|
||||
test('delete included groups', () => {
|
||||
const deletelBtns = Polymer.dom(element.root)
|
||||
.querySelectorAll('.deleteIncludedGroupButton');
|
||||
MockInteractions.tap(deletelBtns[0]);
|
||||
assert.equal(element._itemId, 'testId');
|
||||
assert.equal(element._itemName, 'testName');
|
||||
MockInteractions.tap(deletelBtns[1]);
|
||||
assert.equal(element._itemId, 'testId2');
|
||||
assert.equal(element._itemName, 'testName2');
|
||||
MockInteractions.tap(deletelBtns[2]);
|
||||
assert.equal(element._itemId, 'testId3');
|
||||
assert.equal(element._itemName, 'testName3');
|
||||
});
|
||||
|
||||
test('_computeLoadingClass', () => {
|
||||
assert.equal(element._computeLoadingClass(true), 'loading');
|
||||
|
||||
assert.equal(element._computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('_computeGroupUrl', () => {
|
||||
assert.isUndefined(element._computeGroupUrl(undefined));
|
||||
|
||||
assert.isUndefined(element._computeGroupUrl(false));
|
||||
|
||||
let url = '#/admin/groups/uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
|
||||
assert.equal(element._computeGroupUrl(url),
|
||||
'https://test/site/admin/groups/' +
|
||||
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498');
|
||||
|
||||
url = 'https://gerrit.local/admin/groups/' +
|
||||
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
|
||||
assert.equal(element._computeGroupUrl(url), url);
|
||||
});
|
||||
|
||||
test('fires page-error', done => {
|
||||
groupStub.restore();
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupConfig', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadGroupDetails();
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.isTrue(saveStub.lastCall.calledWithExactly('Administrators',
|
||||
1234));
|
||||
});
|
||||
});
|
||||
|
||||
test('save included groups correctly', () => {
|
||||
element._groupOwner = true;
|
||||
|
||||
const includedGroupName = 'testName';
|
||||
|
||||
const saveIncludedGroupStub = sandbox.stub(
|
||||
element.$.restAPI, 'saveIncludedGroup', () => Promise.resolve({}));
|
||||
|
||||
const button = element.$.saveIncludedGroups;
|
||||
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
|
||||
element.$.includedGroupSearchInput.text = includedGroupName;
|
||||
element.$.includedGroupSearchInput.value = 'testId';
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
|
||||
return element._handleSavingIncludedGroups().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(saveIncludedGroupStub.lastCall.args[0], 'Administrators');
|
||||
assert.equal(saveIncludedGroupStub.lastCall.args[1], 'testId');
|
||||
});
|
||||
});
|
||||
|
||||
test('add included group 404 shows helpful error text', () => {
|
||||
element._groupOwner = true;
|
||||
|
||||
const memberName = 'bad-name';
|
||||
const alertStub = sandbox.stub();
|
||||
element.addEventListener('show-alert', alertStub);
|
||||
const error = new Error('error');
|
||||
error.status = 404;
|
||||
sandbox.stub(element.$.restAPI, 'saveGroupMembers',
|
||||
() => Promise.reject(error));
|
||||
|
||||
element.$.groupMemberSearchInput.text = memberName;
|
||||
element.$.groupMemberSearchInput.value = 1234;
|
||||
|
||||
return element._handleSavingIncludedGroups().then(() => {
|
||||
assert.isTrue(alertStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('_getAccountSuggestions empty', done => {
|
||||
element
|
||||
._getAccountSuggestions('nonexistent').then(accounts => {
|
||||
assert.equal(accounts.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getAccountSuggestions non-empty', done => {
|
||||
element
|
||||
._getAccountSuggestions('test-').then(accounts => {
|
||||
assert.equal(accounts.length, 3);
|
||||
assert.equal(accounts[0].name,
|
||||
'test-account <test.account@example.com>');
|
||||
assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
|
||||
assert.equal(accounts[2].name, 'test-git');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions empty', done => {
|
||||
element
|
||||
._getGroupSuggestions('nonexistent').then(groups => {
|
||||
assert.equal(groups.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions non-empty', done => {
|
||||
element
|
||||
._getGroupSuggestions('test').then(groups => {
|
||||
assert.equal(groups.length, 2);
|
||||
assert.equal(groups[0].name, 'test-admin');
|
||||
assert.equal(groups[1].name, 'test/Administrator (admin)');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns string for admin', () => {
|
||||
const admin = true;
|
||||
const owner = false;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), '');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns hideItem for admin and owner', () => {
|
||||
const admin = false;
|
||||
const owner = false;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), 'canModify');
|
||||
});
|
||||
|
||||
test('_computeHideItemClass returns string for owner', () => {
|
||||
const admin = false;
|
||||
const owner = true;
|
||||
assert.equal(element._computeHideItemClass(owner, admin), '');
|
||||
});
|
||||
|
||||
test('delete member', () => {
|
||||
const deletelBtns = dom(element.root)
|
||||
.querySelectorAll('.deleteMembersButton');
|
||||
MockInteractions.tap(deletelBtns[0]);
|
||||
assert.equal(element._itemId, '1000097');
|
||||
assert.equal(element._itemName, 'jane');
|
||||
MockInteractions.tap(deletelBtns[1]);
|
||||
assert.equal(element._itemId, '1000096');
|
||||
assert.equal(element._itemName, 'Test User');
|
||||
MockInteractions.tap(deletelBtns[2]);
|
||||
assert.equal(element._itemId, '1000095');
|
||||
assert.equal(element._itemName, 'Gerrit');
|
||||
MockInteractions.tap(deletelBtns[3]);
|
||||
assert.equal(element._itemId, '1000098');
|
||||
assert.equal(element._itemName, '1000098');
|
||||
});
|
||||
|
||||
test('delete included groups', () => {
|
||||
const deletelBtns = dom(element.root)
|
||||
.querySelectorAll('.deleteIncludedGroupButton');
|
||||
MockInteractions.tap(deletelBtns[0]);
|
||||
assert.equal(element._itemId, 'testId');
|
||||
assert.equal(element._itemName, 'testName');
|
||||
MockInteractions.tap(deletelBtns[1]);
|
||||
assert.equal(element._itemId, 'testId2');
|
||||
assert.equal(element._itemName, 'testName2');
|
||||
MockInteractions.tap(deletelBtns[2]);
|
||||
assert.equal(element._itemId, 'testId3');
|
||||
assert.equal(element._itemName, 'testName3');
|
||||
});
|
||||
|
||||
test('_computeLoadingClass', () => {
|
||||
assert.equal(element._computeLoadingClass(true), 'loading');
|
||||
|
||||
assert.equal(element._computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('_computeGroupUrl', () => {
|
||||
assert.isUndefined(element._computeGroupUrl(undefined));
|
||||
|
||||
assert.isUndefined(element._computeGroupUrl(false));
|
||||
|
||||
let url = '#/admin/groups/uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
|
||||
assert.equal(element._computeGroupUrl(url),
|
||||
'https://test/site/admin/groups/' +
|
||||
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498');
|
||||
|
||||
url = 'https://gerrit.local/admin/groups/' +
|
||||
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
|
||||
assert.equal(element._computeGroupUrl(url), url);
|
||||
});
|
||||
|
||||
test('fires page-error', done => {
|
||||
groupStub.restore();
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupConfig', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadGroupDetails();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,238 +14,253 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
|
||||
const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-autocomplete/gr-autocomplete.js';
|
||||
import '../../shared/gr-copy-clipboard/gr-copy-clipboard.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-group_html.js';
|
||||
|
||||
const OPTIONS = {
|
||||
submitFalse: {
|
||||
value: false,
|
||||
label: 'False',
|
||||
},
|
||||
submitTrue: {
|
||||
value: true,
|
||||
label: 'True',
|
||||
},
|
||||
};
|
||||
const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
|
||||
|
||||
const OPTIONS = {
|
||||
submitFalse: {
|
||||
value: false,
|
||||
label: 'False',
|
||||
},
|
||||
submitTrue: {
|
||||
value: true,
|
||||
label: 'True',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrGroup extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-group'; }
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
* Fired when the group name changes.
|
||||
*
|
||||
* @event name-changed
|
||||
*/
|
||||
class GrGroup extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-group'; }
|
||||
/**
|
||||
* Fired when the group name changes.
|
||||
*
|
||||
* @event name-changed
|
||||
*/
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: Number,
|
||||
_rename: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
static get properties() {
|
||||
return {
|
||||
groupId: Number,
|
||||
_rename: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_groupIsInternal: Boolean,
|
||||
_description: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_owner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_options: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
/** @type {?} */
|
||||
_groupConfig: Object,
|
||||
_groupConfigOwner: String,
|
||||
_groupName: Object,
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_submitTypes: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(OPTIONS);
|
||||
},
|
||||
_groupIsInternal: Boolean,
|
||||
_description: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
_owner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_options: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
/** @type {?} */
|
||||
_groupConfig: Object,
|
||||
_groupConfigOwner: String,
|
||||
_groupName: Object,
|
||||
_groupOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_submitTypes: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(OPTIONS);
|
||||
},
|
||||
},
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleConfigName(_groupConfig.name)',
|
||||
'_handleConfigOwner(_groupConfig.owner, _groupConfigOwner)',
|
||||
'_handleConfigDescription(_groupConfig.description)',
|
||||
'_handleConfigOptions(_groupConfig.options.visible_to_all)',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadGroup();
|
||||
}
|
||||
|
||||
_loadGroup() {
|
||||
if (!this.groupId) { return; }
|
||||
|
||||
const promises = [];
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getGroupConfig(this.groupId, errFn)
|
||||
.then(config => {
|
||||
if (!config || !config.name) { return Promise.resolve(); }
|
||||
|
||||
this._groupName = config.name;
|
||||
this._groupIsInternal = !!config.id.match(INTERNAL_GROUP_REGEX);
|
||||
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin ? true : false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(config.name)
|
||||
.then(isOwner => {
|
||||
this._groupOwner = isOwner ? true : false;
|
||||
}));
|
||||
|
||||
// If visible to all is undefined, set to false. If it is defined
|
||||
// as false, setting to false is fine. If any optional values
|
||||
// are added with a default of true, then this would need to be an
|
||||
// undefined check and not a truthy/falsy check.
|
||||
if (!config.options.visible_to_all) {
|
||||
config.options.visible_to_all = false;
|
||||
}
|
||||
this._groupConfig = config;
|
||||
|
||||
this.fire('title-change', {title: config.name});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this._loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_handleSaveName() {
|
||||
return this.$.restAPI.saveGroupName(this.groupId, this._groupConfig.name)
|
||||
.then(config => {
|
||||
if (config.status === 200) {
|
||||
this._groupName = this._groupConfig.name;
|
||||
this.fire('name-changed', {name: this._groupConfig.name,
|
||||
external: this._groupIsExtenral});
|
||||
this._rename = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveOwner() {
|
||||
let owner = this._groupConfig.owner;
|
||||
if (this._groupConfigOwner) {
|
||||
owner = decodeURIComponent(this._groupConfigOwner);
|
||||
}
|
||||
return this.$.restAPI.saveGroupOwner(this.groupId,
|
||||
owner).then(config => {
|
||||
this._owner = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveDescription() {
|
||||
return this.$.restAPI.saveGroupDescription(this.groupId,
|
||||
this._groupConfig.description).then(config => {
|
||||
this._description = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveOptions() {
|
||||
const visible = this._groupConfig.options.visible_to_all;
|
||||
|
||||
const options = {visible_to_all: visible};
|
||||
|
||||
return this.$.restAPI.saveGroupOptions(this.groupId,
|
||||
options).then(config => {
|
||||
this._options = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigName() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._rename = true;
|
||||
}
|
||||
|
||||
_handleConfigOwner() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._owner = true;
|
||||
}
|
||||
|
||||
_handleConfigDescription() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._description = true;
|
||||
}
|
||||
|
||||
_handleConfigOptions() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._options = true;
|
||||
}
|
||||
|
||||
_computeHeaderClass(configChanged) {
|
||||
return configChanged ? 'edited' : '';
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
_computeGroupDisabled(owner, admin, groupIsInternal) {
|
||||
return groupIsInternal && (admin || owner) ? false : true;
|
||||
}
|
||||
},
|
||||
_isAdmin: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrGroup.is, GrGroup);
|
||||
})();
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleConfigName(_groupConfig.name)',
|
||||
'_handleConfigOwner(_groupConfig.owner, _groupConfigOwner)',
|
||||
'_handleConfigDescription(_groupConfig.description)',
|
||||
'_handleConfigOptions(_groupConfig.options.visible_to_all)',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadGroup();
|
||||
}
|
||||
|
||||
_loadGroup() {
|
||||
if (!this.groupId) { return; }
|
||||
|
||||
const promises = [];
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getGroupConfig(this.groupId, errFn)
|
||||
.then(config => {
|
||||
if (!config || !config.name) { return Promise.resolve(); }
|
||||
|
||||
this._groupName = config.name;
|
||||
this._groupIsInternal = !!config.id.match(INTERNAL_GROUP_REGEX);
|
||||
|
||||
promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
|
||||
this._isAdmin = isAdmin ? true : false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getIsGroupOwner(config.name)
|
||||
.then(isOwner => {
|
||||
this._groupOwner = isOwner ? true : false;
|
||||
}));
|
||||
|
||||
// If visible to all is undefined, set to false. If it is defined
|
||||
// as false, setting to false is fine. If any optional values
|
||||
// are added with a default of true, then this would need to be an
|
||||
// undefined check and not a truthy/falsy check.
|
||||
if (!config.options.visible_to_all) {
|
||||
config.options.visible_to_all = false;
|
||||
}
|
||||
this._groupConfig = config;
|
||||
|
||||
this.fire('title-change', {title: config.name});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this._loading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_handleSaveName() {
|
||||
return this.$.restAPI.saveGroupName(this.groupId, this._groupConfig.name)
|
||||
.then(config => {
|
||||
if (config.status === 200) {
|
||||
this._groupName = this._groupConfig.name;
|
||||
this.fire('name-changed', {name: this._groupConfig.name,
|
||||
external: this._groupIsExtenral});
|
||||
this._rename = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveOwner() {
|
||||
let owner = this._groupConfig.owner;
|
||||
if (this._groupConfigOwner) {
|
||||
owner = decodeURIComponent(this._groupConfigOwner);
|
||||
}
|
||||
return this.$.restAPI.saveGroupOwner(this.groupId,
|
||||
owner).then(config => {
|
||||
this._owner = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveDescription() {
|
||||
return this.$.restAPI.saveGroupDescription(this.groupId,
|
||||
this._groupConfig.description).then(config => {
|
||||
this._description = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveOptions() {
|
||||
const visible = this._groupConfig.options.visible_to_all;
|
||||
|
||||
const options = {visible_to_all: visible};
|
||||
|
||||
return this.$.restAPI.saveGroupOptions(this.groupId,
|
||||
options).then(config => {
|
||||
this._options = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigName() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._rename = true;
|
||||
}
|
||||
|
||||
_handleConfigOwner() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._owner = true;
|
||||
}
|
||||
|
||||
_handleConfigDescription() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._description = true;
|
||||
}
|
||||
|
||||
_handleConfigOptions() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._options = true;
|
||||
}
|
||||
|
||||
_computeHeaderClass(configChanged) {
|
||||
return configChanged ? 'edited' : '';
|
||||
}
|
||||
|
||||
_getGroupSuggestions(input) {
|
||||
return this.$.restAPI.getSuggestedGroups(input)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: decodeURIComponent(response[key].id),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
_computeGroupDisabled(owner, admin, groupIsInternal) {
|
||||
return groupIsInternal && (admin || owner) ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrGroup.is, GrGroup);
|
||||
|
@ -1,33 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
|
||||
<dom-module id="gr-group">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -44,77 +33,57 @@ limitations under the License.
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<main class="gr-form-styles read-only">
|
||||
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="loading" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
Loading...
|
||||
</div>
|
||||
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="loadedContent" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
<h1 id="Title">[[_groupName]]</h1>
|
||||
<h2 id="configurations">General</h2>
|
||||
<div id="form">
|
||||
<fieldset>
|
||||
<h3 id="groupUUID">Group UUID</h3>
|
||||
<fieldset>
|
||||
<gr-copy-clipboard
|
||||
text="[[groupId]]"></gr-copy-clipboard>
|
||||
<gr-copy-clipboard text="[[groupId]]"></gr-copy-clipboard>
|
||||
</fieldset>
|
||||
<h3 id="groupName" class$="[[_computeHeaderClass(_rename)]]">
|
||||
<h3 id="groupName" class\$="[[_computeHeaderClass(_rename)]]">
|
||||
Group Name
|
||||
</h3>
|
||||
<fieldset>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="groupNameInput"
|
||||
text="{{_groupConfig.name}}"
|
||||
disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]"></gr-autocomplete>
|
||||
<gr-autocomplete id="groupNameInput" text="{{_groupConfig.name}}" disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]"></gr-autocomplete>
|
||||
</span>
|
||||
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button
|
||||
id="inputUpdateNameBtn"
|
||||
on-click="_handleSaveName"
|
||||
disabled="[[!_rename]]">
|
||||
<span class="value" disabled\$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button id="inputUpdateNameBtn" on-click="_handleSaveName" disabled="[[!_rename]]">
|
||||
Rename Group</gr-button>
|
||||
</span>
|
||||
</fieldset>
|
||||
<h3 class$="[[_computeHeaderClass(_owner)]]">
|
||||
<h3 class\$="[[_computeHeaderClass(_owner)]]">
|
||||
Owners
|
||||
</h3>
|
||||
<fieldset>
|
||||
<span class="value">
|
||||
<gr-autocomplete
|
||||
id="groupOwnerInput"
|
||||
text="{{_groupConfig.owner}}"
|
||||
value="{{_groupConfigOwner}}"
|
||||
query="[[_query]]"
|
||||
disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-autocomplete id="groupOwnerInput" text="{{_groupConfig.owner}}" value="{{_groupConfigOwner}}" query="[[_query]]" disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
</gr-autocomplete>
|
||||
</span>
|
||||
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button
|
||||
on-click="_handleSaveOwner"
|
||||
disabled="[[!_owner]]">
|
||||
<span class="value" disabled\$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button on-click="_handleSaveOwner" disabled="[[!_owner]]">
|
||||
Change Owners</gr-button>
|
||||
</span>
|
||||
</fieldset>
|
||||
<h3 class$="[[_computeHeaderClass(_description)]]">
|
||||
<h3 class\$="[[_computeHeaderClass(_description)]]">
|
||||
Description
|
||||
</h3>
|
||||
<fieldset>
|
||||
<div>
|
||||
<iron-autogrow-textarea
|
||||
class="description"
|
||||
autocomplete="on"
|
||||
bind-value="{{_groupConfig.description}}"
|
||||
disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]"></iron-autogrow-textarea>
|
||||
<iron-autogrow-textarea class="description" autocomplete="on" bind-value="{{_groupConfig.description}}" disabled="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]"></iron-autogrow-textarea>
|
||||
</div>
|
||||
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button
|
||||
on-click="_handleSaveDescription"
|
||||
disabled="[[!_description]]">
|
||||
<span class="value" disabled\$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button on-click="_handleSaveDescription" disabled="[[!_description]]">
|
||||
Save Description
|
||||
</gr-button>
|
||||
</span>
|
||||
</fieldset>
|
||||
<h3 id="options" class$="[[_computeHeaderClass(_options)]]">
|
||||
<h3 id="options" class\$="[[_computeHeaderClass(_options)]]">
|
||||
Group Options
|
||||
</h3>
|
||||
<fieldset id="visableToAll">
|
||||
@ -123,10 +92,8 @@ limitations under the License.
|
||||
Make group visible to all registered users
|
||||
</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="visibleToAll"
|
||||
bind-value="{{_groupConfig.options.visible_to_all}}">
|
||||
<select disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-select id="visibleToAll" bind-value="{{_groupConfig.options.visible_to_all}}">
|
||||
<select disabled\$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<template is="dom-repeat" items="[[_submitTypes]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
@ -134,10 +101,8 @@ limitations under the License.
|
||||
</gr-select>
|
||||
</span>
|
||||
</section>
|
||||
<span class="value" disabled$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button
|
||||
on-click="_handleSaveOptions"
|
||||
disabled="[[!_options]]">
|
||||
<span class="value" disabled\$="[[_computeGroupDisabled(_groupOwner, _isAdmin, _groupIsInternal)]]">
|
||||
<gr-button on-click="_handleSaveOptions" disabled="[[!_options]]">
|
||||
Save Group Options
|
||||
</gr-button>
|
||||
</span>
|
||||
@ -147,6 +112,4 @@ limitations under the License.
|
||||
</div>
|
||||
</main>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-group.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-group</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-group.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-group.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,222 +40,224 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-group tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
let groupStub;
|
||||
const group = {
|
||||
id: '6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
url: '#/admin/groups/uuid-6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
options: {},
|
||||
description: 'Gerrit Site Administrators',
|
||||
group_id: 1,
|
||||
owner: 'Administrators',
|
||||
owner_id: '6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
name: 'Administrators',
|
||||
};
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-group.js';
|
||||
suite('gr-group tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
let groupStub;
|
||||
const group = {
|
||||
id: '6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
url: '#/admin/groups/uuid-6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
options: {},
|
||||
description: 'Gerrit Site Administrators',
|
||||
group_id: 1,
|
||||
owner: 'Administrators',
|
||||
owner_id: '6a1e70e1a88782771a91808c8af9bbb7a9871389',
|
||||
name: 'Administrators',
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(group)
|
||||
);
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(group)
|
||||
);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('loading displays before group config is loaded', () => {
|
||||
assert.isTrue(element.$.loading.classList.contains('loading'));
|
||||
assert.isFalse(getComputedStyle(element.$.loading).display === 'none');
|
||||
assert.isTrue(element.$.loadedContent.classList.contains('loading'));
|
||||
assert.isTrue(getComputedStyle(element.$.loadedContent)
|
||||
.display === 'none');
|
||||
});
|
||||
test('loading displays before group config is loaded', () => {
|
||||
assert.isTrue(element.$.loading.classList.contains('loading'));
|
||||
assert.isFalse(getComputedStyle(element.$.loading).display === 'none');
|
||||
assert.isTrue(element.$.loadedContent.classList.contains('loading'));
|
||||
assert.isTrue(getComputedStyle(element.$.loadedContent)
|
||||
.display === 'none');
|
||||
});
|
||||
|
||||
test('default values are populated with internal group', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
element.groupId = 1;
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(element._groupIsInternal);
|
||||
assert.isFalse(element.$.visibleToAll.bindValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('default values with external group', done => {
|
||||
const groupExternal = Object.assign({}, group);
|
||||
groupExternal.id = 'external-group-id';
|
||||
groupStub.restore();
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(groupExternal));
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
element.groupId = 1;
|
||||
element._loadGroup().then(() => {
|
||||
assert.isFalse(element._groupIsInternal);
|
||||
assert.isFalse(element.$.visibleToAll.bindValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('rename group', done => {
|
||||
const groupName = 'test-group';
|
||||
const groupName2 = 'test-group2';
|
||||
element.groupId = 1;
|
||||
element._groupConfig = {
|
||||
name: groupName,
|
||||
};
|
||||
element._groupConfigOwner = 'testId';
|
||||
element._groupName = groupName;
|
||||
element._groupOwner = true;
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'saveGroupName',
|
||||
() => Promise.resolve({status: 200}));
|
||||
|
||||
const button = element.$.inputUpdateNameBtn;
|
||||
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
|
||||
element.$.groupNameInput.text = groupName2;
|
||||
|
||||
element.$.groupOwnerInput.text = 'testId2';
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
assert.isTrue(element.$.groupName.classList.contains('edited'));
|
||||
|
||||
element._handleSaveName().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(element._groupName, groupName2);
|
||||
done();
|
||||
});
|
||||
|
||||
element._handleSaveOwner().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(element._groupConfigOwner, 'testId2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test for undefined group name', done => {
|
||||
groupStub.restore();
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isUndefined(element.groupId);
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
assert.isDefined(element.groupId);
|
||||
|
||||
// Test that loading shows instead of filling
|
||||
// in group details
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(element.$.loading.classList.contains('loading'));
|
||||
|
||||
assert.isTrue(element._loading);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test fire event', done => {
|
||||
element._groupConfig = {
|
||||
name: 'test-group',
|
||||
};
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'saveGroupName')
|
||||
.returns(Promise.resolve({status: 200}));
|
||||
|
||||
const showStub = sandbox.stub(element, 'fire');
|
||||
element._handleSaveName()
|
||||
.then(() => {
|
||||
assert.isTrue(showStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeGroupDisabled', () => {
|
||||
let admin = true;
|
||||
let owner = false;
|
||||
let groupIsInternal = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), false);
|
||||
|
||||
admin = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
owner = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), false);
|
||||
|
||||
owner = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
groupIsInternal = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
admin = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
});
|
||||
|
||||
test('_computeLoadingClass', () => {
|
||||
assert.equal(element._computeLoadingClass(true), 'loading');
|
||||
assert.equal(element._computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('fires page-error', done => {
|
||||
groupStub.restore();
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupConfig', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadGroup();
|
||||
test('default values are populated with internal group', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
element.groupId = 1;
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(element._groupIsInternal);
|
||||
assert.isFalse(element.$.visibleToAll.bindValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('default values with external group', done => {
|
||||
const groupExternal = Object.assign({}, group);
|
||||
groupExternal.id = 'external-group-id';
|
||||
groupStub.restore();
|
||||
groupStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve(groupExternal));
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
element.groupId = 1;
|
||||
element._loadGroup().then(() => {
|
||||
assert.isFalse(element._groupIsInternal);
|
||||
assert.isFalse(element.$.visibleToAll.bindValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('rename group', done => {
|
||||
const groupName = 'test-group';
|
||||
const groupName2 = 'test-group2';
|
||||
element.groupId = 1;
|
||||
element._groupConfig = {
|
||||
name: groupName,
|
||||
};
|
||||
element._groupConfigOwner = 'testId';
|
||||
element._groupName = groupName;
|
||||
element._groupOwner = true;
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getIsGroupOwner',
|
||||
() => Promise.resolve(true));
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'saveGroupName',
|
||||
() => Promise.resolve({status: 200}));
|
||||
|
||||
const button = element.$.inputUpdateNameBtn;
|
||||
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
|
||||
element.$.groupNameInput.text = groupName2;
|
||||
|
||||
element.$.groupOwnerInput.text = 'testId2';
|
||||
|
||||
assert.isFalse(button.hasAttribute('disabled'));
|
||||
assert.isTrue(element.$.groupName.classList.contains('edited'));
|
||||
|
||||
element._handleSaveName().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(element._groupName, groupName2);
|
||||
done();
|
||||
});
|
||||
|
||||
element._handleSaveOwner().then(() => {
|
||||
assert.isTrue(button.hasAttribute('disabled'));
|
||||
assert.isFalse(element.$.Title.classList.contains('edited'));
|
||||
assert.equal(element._groupConfigOwner, 'testId2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test for undefined group name', done => {
|
||||
groupStub.restore();
|
||||
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getGroupConfig',
|
||||
() => Promise.resolve({}));
|
||||
|
||||
assert.isUndefined(element.groupId);
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
assert.isDefined(element.groupId);
|
||||
|
||||
// Test that loading shows instead of filling
|
||||
// in group details
|
||||
element._loadGroup().then(() => {
|
||||
assert.isTrue(element.$.loading.classList.contains('loading'));
|
||||
|
||||
assert.isTrue(element._loading);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('test fire event', done => {
|
||||
element._groupConfig = {
|
||||
name: 'test-group',
|
||||
};
|
||||
|
||||
sandbox.stub(element.$.restAPI, 'saveGroupName')
|
||||
.returns(Promise.resolve({status: 200}));
|
||||
|
||||
const showStub = sandbox.stub(element, 'fire');
|
||||
element._handleSaveName()
|
||||
.then(() => {
|
||||
assert.isTrue(showStub.called);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_computeGroupDisabled', () => {
|
||||
let admin = true;
|
||||
let owner = false;
|
||||
let groupIsInternal = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), false);
|
||||
|
||||
admin = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
owner = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), false);
|
||||
|
||||
owner = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
groupIsInternal = false;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
|
||||
admin = true;
|
||||
assert.equal(element._computeGroupDisabled(owner, admin,
|
||||
groupIsInternal), true);
|
||||
});
|
||||
|
||||
test('_computeLoadingClass', () => {
|
||||
assert.equal(element._computeLoadingClass(true), 'loading');
|
||||
assert.equal(element._computeLoadingClass(false), '');
|
||||
});
|
||||
|
||||
test('fires page-error', done => {
|
||||
groupStub.restore();
|
||||
|
||||
element.groupId = 1;
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getGroupConfig', (group, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadGroup();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,301 +14,318 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 20;
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-access-behavior/gr-access-behavior.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-menu-page-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-autocomplete/gr-autocomplete.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-rule-editor/gr-rule-editor.js';
|
||||
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-permission_html.js';
|
||||
|
||||
const RANGE_NAMES = [
|
||||
'QUERY LIMIT',
|
||||
'BATCH CHANGES LIMIT',
|
||||
];
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 20;
|
||||
|
||||
const RANGE_NAMES = [
|
||||
'QUERY LIMIT',
|
||||
'BATCH CHANGES LIMIT',
|
||||
];
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
*/
|
||||
/**
|
||||
* Fired when the permission has been modified or removed.
|
||||
*
|
||||
* @event access-modified
|
||||
*/
|
||||
/**
|
||||
* Fired when a permission that was previously added was removed.
|
||||
*
|
||||
* @event added-permission-removed
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrPermission extends mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-access-section_test.
|
||||
*/
|
||||
/**
|
||||
* Fired when the permission has been modified or removed.
|
||||
*
|
||||
* @event access-modified
|
||||
*/
|
||||
/**
|
||||
* Fired when a permission that was previously added was removed.
|
||||
*
|
||||
* @event added-permission-removed
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrPermission extends Polymer.mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
/**
|
||||
* Unused in this element, but called by other elements in tests
|
||||
* e.g gr-access-section_test.
|
||||
*/
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-permission'; }
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
labels: Object,
|
||||
name: String,
|
||||
/** @type {?} */
|
||||
permission: {
|
||||
type: Object,
|
||||
observer: '_sortPermission',
|
||||
notify: true,
|
||||
static get is() { return 'gr-permission'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
labels: Object,
|
||||
name: String,
|
||||
/** @type {?} */
|
||||
permission: {
|
||||
type: Object,
|
||||
observer: '_sortPermission',
|
||||
notify: true,
|
||||
},
|
||||
groups: Object,
|
||||
section: String,
|
||||
editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
_label: {
|
||||
type: Object,
|
||||
computed: '_computeLabel(permission, labels)',
|
||||
},
|
||||
_groupFilter: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
groups: Object,
|
||||
section: String,
|
||||
editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
_label: {
|
||||
type: Object,
|
||||
computed: '_computeLabel(permission, labels)',
|
||||
},
|
||||
_groupFilter: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getGroupSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_rules: Array,
|
||||
_groupsWithRules: Object,
|
||||
_deleted: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_originalExclusiveValue: Boolean,
|
||||
};
|
||||
}
|
||||
},
|
||||
_rules: Array,
|
||||
_groupsWithRules: Object,
|
||||
_deleted: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_originalExclusiveValue: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleRulesChanged(_rules.splices)',
|
||||
];
|
||||
}
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleRulesChanged(_rules.splices)',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-saved',
|
||||
() => this._handleAccessSaved());
|
||||
}
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-saved',
|
||||
() => this._handleAccessSaved());
|
||||
}
|
||||
|
||||
/** @override */
|
||||
ready() {
|
||||
super.ready();
|
||||
this._setupValues();
|
||||
}
|
||||
/** @override */
|
||||
ready() {
|
||||
super.ready();
|
||||
this._setupValues();
|
||||
}
|
||||
|
||||
_setupValues() {
|
||||
if (!this.permission) { return; }
|
||||
this._originalExclusiveValue = !!this.permission.value.exclusive;
|
||||
Polymer.dom.flush();
|
||||
}
|
||||
_setupValues() {
|
||||
if (!this.permission) { return; }
|
||||
this._originalExclusiveValue = !!this.permission.value.exclusive;
|
||||
flush();
|
||||
}
|
||||
|
||||
_handleAccessSaved() {
|
||||
// Set a new 'original' value to keep track of after the value has been
|
||||
// saved.
|
||||
this._setupValues();
|
||||
}
|
||||
_handleAccessSaved() {
|
||||
// Set a new 'original' value to keep track of after the value has been
|
||||
// saved.
|
||||
this._setupValues();
|
||||
}
|
||||
|
||||
_permissionIsOwnerOrGlobal(permissionId, section) {
|
||||
return permissionId === 'owner' || section === 'GLOBAL_CAPABILITIES';
|
||||
}
|
||||
_permissionIsOwnerOrGlobal(permissionId, section) {
|
||||
return permissionId === 'owner' || section === 'GLOBAL_CAPABILITIES';
|
||||
}
|
||||
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld) { return; }
|
||||
// Restore original values if no longer editing.
|
||||
if (!editing) {
|
||||
this._deleted = false;
|
||||
delete this.permission.value.deleted;
|
||||
this._groupFilter = '';
|
||||
this._rules = this._rules.filter(rule => !rule.value.added);
|
||||
for (const key of Object.keys(this.permission.value.rules)) {
|
||||
if (this.permission.value.rules[key].added) {
|
||||
delete this.permission.value.rules[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Restore exclusive bit to original.
|
||||
this.set(['permission', 'value', 'exclusive'],
|
||||
this._originalExclusiveValue);
|
||||
}
|
||||
}
|
||||
|
||||
_handleAddedRuleRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._rules = this._rules.slice(0, index)
|
||||
.concat(this._rules.slice(index + 1, this._rules.length));
|
||||
}
|
||||
|
||||
_handleValueChange() {
|
||||
this.permission.value.modified = true;
|
||||
// Allows overall access page to know a change has been made.
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleRemovePermission() {
|
||||
if (this.permission.value.added) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'added-permission-removed', {bubbles: true, composed: true}));
|
||||
}
|
||||
this._deleted = true;
|
||||
this.permission.value.deleted = true;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleRulesChanged(changeRecord) {
|
||||
// Update the groups to exclude in the autocomplete.
|
||||
this._groupsWithRules = this._computeGroupsWithRules(this._rules);
|
||||
}
|
||||
|
||||
_sortPermission(permission) {
|
||||
this._rules = this.toSortedArray(permission.value.rules);
|
||||
}
|
||||
|
||||
_computeSectionClass(editing, deleted) {
|
||||
const classList = [];
|
||||
if (editing) {
|
||||
classList.push('editing');
|
||||
}
|
||||
if (deleted) {
|
||||
classList.push('deleted');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_handleUndoRemove() {
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld) { return; }
|
||||
// Restore original values if no longer editing.
|
||||
if (!editing) {
|
||||
this._deleted = false;
|
||||
delete this.permission.value.deleted;
|
||||
}
|
||||
|
||||
_computeLabel(permission, labels) {
|
||||
if (!labels || !permission ||
|
||||
!permission.value || !permission.value.label) { return; }
|
||||
|
||||
const labelName = permission.value.label;
|
||||
|
||||
// It is possible to have a label name that is not included in the
|
||||
// 'labels' object. In this case, treat it like anything else.
|
||||
if (!labels[labelName]) { return; }
|
||||
const label = {
|
||||
name: labelName,
|
||||
values: this._computeLabelValues(labels[labelName].values),
|
||||
};
|
||||
return label;
|
||||
}
|
||||
|
||||
_computeLabelValues(values) {
|
||||
const valuesArr = [];
|
||||
const keys = Object.keys(values)
|
||||
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
||||
|
||||
for (const key of keys) {
|
||||
let text = values[key];
|
||||
if (!text) { text = ''; }
|
||||
// The value from the server being used to choose which item is
|
||||
// selected is in integer form, so this must be converted.
|
||||
valuesArr.push({value: parseInt(key, 10), text});
|
||||
}
|
||||
return valuesArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array} rules
|
||||
* @return {!Object} Object with groups with rues as keys, and true as
|
||||
* value.
|
||||
*/
|
||||
_computeGroupsWithRules(rules) {
|
||||
const groups = {};
|
||||
for (const rule of rules) {
|
||||
groups[rule.id] = true;
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
_computeGroupName(groups, groupId) {
|
||||
return groups && groups[groupId] && groups[groupId].name ?
|
||||
groups[groupId].name : groupId;
|
||||
}
|
||||
|
||||
_getGroupSuggestions() {
|
||||
return this.$.restAPI.getSuggestedGroups(
|
||||
this._groupFilter,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: response[key],
|
||||
});
|
||||
}
|
||||
// Does not return groups in which we already have rules for.
|
||||
return groups
|
||||
.filter(group => !this._groupsWithRules[group.value.id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding a skeleton item to the dom-repeat.
|
||||
* gr-rule-editor handles setting the default values.
|
||||
*/
|
||||
_handleAddRuleItem(e) {
|
||||
// The group id is encoded, but have to decode in order for the access
|
||||
// API to work as expected.
|
||||
const groupId = decodeURIComponent(e.detail.value.id)
|
||||
.replace(/\+/g, ' ');
|
||||
// We cannot use "this.set(...)" here, because groupId may contain dots,
|
||||
// and dots in property path names are totally unsupported by Polymer.
|
||||
// Apparently Polymer picks up this change anyway, otherwise we should
|
||||
// have looked at using MutableData:
|
||||
// https://polymer-library.polymer-project.org/2.0/docs/devguide/data-system#mutable-data
|
||||
this.permission.value.rules[groupId] = {};
|
||||
|
||||
// Purposely don't recompute sorted array so that the newly added rule
|
||||
// is the last item of the array.
|
||||
this.push('_rules', {
|
||||
id: groupId,
|
||||
});
|
||||
|
||||
// Add the new group name to the groups object so the name renders
|
||||
// correctly.
|
||||
if (this.groups && !this.groups[groupId]) {
|
||||
this.groups[groupId] = {name: this.$.groupAutocomplete.text};
|
||||
this._groupFilter = '';
|
||||
this._rules = this._rules.filter(rule => !rule.value.added);
|
||||
for (const key of Object.keys(this.permission.value.rules)) {
|
||||
if (this.permission.value.rules[key].added) {
|
||||
delete this.permission.value.rules[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for new rule to get value populated via gr-rule-editor, and then
|
||||
// add to permission values as well, so that the change gets propogated
|
||||
// back to the section. Since the rule is inside a dom-repeat, a flush
|
||||
// is needed.
|
||||
Polymer.dom.flush();
|
||||
const value = this._rules[this._rules.length - 1].value;
|
||||
value.added = true;
|
||||
// See comment above for why we cannot use "this.set(...)" here.
|
||||
this.permission.value.rules[groupId] = value;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_computeHasRange(name) {
|
||||
if (!name) { return false; }
|
||||
|
||||
return RANGE_NAMES.includes(name.toUpperCase());
|
||||
// Restore exclusive bit to original.
|
||||
this.set(['permission', 'value', 'exclusive'],
|
||||
this._originalExclusiveValue);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrPermission.is, GrPermission);
|
||||
})();
|
||||
_handleAddedRuleRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._rules = this._rules.slice(0, index)
|
||||
.concat(this._rules.slice(index + 1, this._rules.length));
|
||||
}
|
||||
|
||||
_handleValueChange() {
|
||||
this.permission.value.modified = true;
|
||||
// Allows overall access page to know a change has been made.
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleRemovePermission() {
|
||||
if (this.permission.value.added) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'added-permission-removed', {bubbles: true, composed: true}));
|
||||
}
|
||||
this._deleted = true;
|
||||
this.permission.value.deleted = true;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_handleRulesChanged(changeRecord) {
|
||||
// Update the groups to exclude in the autocomplete.
|
||||
this._groupsWithRules = this._computeGroupsWithRules(this._rules);
|
||||
}
|
||||
|
||||
_sortPermission(permission) {
|
||||
this._rules = this.toSortedArray(permission.value.rules);
|
||||
}
|
||||
|
||||
_computeSectionClass(editing, deleted) {
|
||||
const classList = [];
|
||||
if (editing) {
|
||||
classList.push('editing');
|
||||
}
|
||||
if (deleted) {
|
||||
classList.push('deleted');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_handleUndoRemove() {
|
||||
this._deleted = false;
|
||||
delete this.permission.value.deleted;
|
||||
}
|
||||
|
||||
_computeLabel(permission, labels) {
|
||||
if (!labels || !permission ||
|
||||
!permission.value || !permission.value.label) { return; }
|
||||
|
||||
const labelName = permission.value.label;
|
||||
|
||||
// It is possible to have a label name that is not included in the
|
||||
// 'labels' object. In this case, treat it like anything else.
|
||||
if (!labels[labelName]) { return; }
|
||||
const label = {
|
||||
name: labelName,
|
||||
values: this._computeLabelValues(labels[labelName].values),
|
||||
};
|
||||
return label;
|
||||
}
|
||||
|
||||
_computeLabelValues(values) {
|
||||
const valuesArr = [];
|
||||
const keys = Object.keys(values)
|
||||
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
||||
|
||||
for (const key of keys) {
|
||||
let text = values[key];
|
||||
if (!text) { text = ''; }
|
||||
// The value from the server being used to choose which item is
|
||||
// selected is in integer form, so this must be converted.
|
||||
valuesArr.push({value: parseInt(key, 10), text});
|
||||
}
|
||||
return valuesArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array} rules
|
||||
* @return {!Object} Object with groups with rues as keys, and true as
|
||||
* value.
|
||||
*/
|
||||
_computeGroupsWithRules(rules) {
|
||||
const groups = {};
|
||||
for (const rule of rules) {
|
||||
groups[rule.id] = true;
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
_computeGroupName(groups, groupId) {
|
||||
return groups && groups[groupId] && groups[groupId].name ?
|
||||
groups[groupId].name : groupId;
|
||||
}
|
||||
|
||||
_getGroupSuggestions() {
|
||||
return this.$.restAPI.getSuggestedGroups(
|
||||
this._groupFilter,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(response => {
|
||||
const groups = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
groups.push({
|
||||
name: key,
|
||||
value: response[key],
|
||||
});
|
||||
}
|
||||
// Does not return groups in which we already have rules for.
|
||||
return groups
|
||||
.filter(group => !this._groupsWithRules[group.value.id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding a skeleton item to the dom-repeat.
|
||||
* gr-rule-editor handles setting the default values.
|
||||
*/
|
||||
_handleAddRuleItem(e) {
|
||||
// The group id is encoded, but have to decode in order for the access
|
||||
// API to work as expected.
|
||||
const groupId = decodeURIComponent(e.detail.value.id)
|
||||
.replace(/\+/g, ' ');
|
||||
// We cannot use "this.set(...)" here, because groupId may contain dots,
|
||||
// and dots in property path names are totally unsupported by Polymer.
|
||||
// Apparently Polymer picks up this change anyway, otherwise we should
|
||||
// have looked at using MutableData:
|
||||
// https://polymer-library.polymer-project.org/2.0/docs/devguide/data-system#mutable-data
|
||||
this.permission.value.rules[groupId] = {};
|
||||
|
||||
// Purposely don't recompute sorted array so that the newly added rule
|
||||
// is the last item of the array.
|
||||
this.push('_rules', {
|
||||
id: groupId,
|
||||
});
|
||||
|
||||
// Add the new group name to the groups object so the name renders
|
||||
// correctly.
|
||||
if (this.groups && !this.groups[groupId]) {
|
||||
this.groups[groupId] = {name: this.$.groupAutocomplete.text};
|
||||
}
|
||||
|
||||
// Wait for new rule to get value populated via gr-rule-editor, and then
|
||||
// add to permission values as well, so that the change gets propogated
|
||||
// back to the section. Since the rule is inside a dom-repeat, a flush
|
||||
// is needed.
|
||||
flush();
|
||||
const value = this._rules[this._rules.length - 1].value;
|
||||
value.added = true;
|
||||
// See comment above for why we cannot use "this.set(...)" here.
|
||||
this.permission.value.rules[groupId] = value;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('access-modified', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
_computeHasRange(name) {
|
||||
if (!name) { return false; }
|
||||
|
||||
return RANGE_NAMES.includes(name.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrPermission.is, GrPermission);
|
||||
|
@ -1,34 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.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/gr-menu-page-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-rule-editor/gr-rule-editor.html">
|
||||
|
||||
<dom-module id="gr-permission">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
@ -88,49 +76,23 @@ limitations under the License.
|
||||
<style include="gr-menu-page-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<section
|
||||
id="permission"
|
||||
class$="gr-form-styles [[_computeSectionClass(editing, _deleted)]]">
|
||||
<section id="permission" class\$="gr-form-styles [[_computeSectionClass(editing, _deleted)]]">
|
||||
<div id="mainContainer">
|
||||
<div class="header">
|
||||
<span class="title">[[name]]</span>
|
||||
<div class="right">
|
||||
<template is=dom-if if="[[!_permissionIsOwnerOrGlobal(permission.id, section)]]">
|
||||
<paper-toggle-button
|
||||
id="exclusiveToggle"
|
||||
checked="{{permission.value.exclusive}}"
|
||||
on-change="_handleValueChange"
|
||||
disabled$="[[!editing]]"></paper-toggle-button>Exclusive
|
||||
<template is="dom-if" if="[[!_permissionIsOwnerOrGlobal(permission.id, section)]]">
|
||||
<paper-toggle-button id="exclusiveToggle" checked="{{permission.value.exclusive}}" on-change="_handleValueChange" disabled\$="[[!editing]]"></paper-toggle-button>Exclusive
|
||||
</template>
|
||||
<gr-button
|
||||
link
|
||||
id="removeBtn"
|
||||
on-click="_handleRemovePermission">Remove</gr-button>
|
||||
<gr-button link="" id="removeBtn" on-click="_handleRemovePermission">Remove</gr-button>
|
||||
</div>
|
||||
</div><!-- end header -->
|
||||
<div class="rules">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="{{_rules}}"
|
||||
as="rule">
|
||||
<gr-rule-editor
|
||||
has-range="[[_computeHasRange(name)]]"
|
||||
label="[[_label]]"
|
||||
editing="[[editing]]"
|
||||
group-id="[[rule.id]]"
|
||||
group-name="[[_computeGroupName(groups, rule.id)]]"
|
||||
permission="[[permission.id]]"
|
||||
rule="{{rule}}"
|
||||
section="[[section]]"
|
||||
on-added-rule-removed="_handleAddedRuleRemoved"></gr-rule-editor>
|
||||
<template is="dom-repeat" items="{{_rules}}" as="rule">
|
||||
<gr-rule-editor has-range="[[_computeHasRange(name)]]" label="[[_label]]" editing="[[editing]]" group-id="[[rule.id]]" group-name="[[_computeGroupName(groups, rule.id)]]" permission="[[permission.id]]" rule="{{rule}}" section="[[section]]" on-added-rule-removed="_handleAddedRuleRemoved"></gr-rule-editor>
|
||||
</template>
|
||||
<div id="addRule">
|
||||
<gr-autocomplete
|
||||
id="groupAutocomplete"
|
||||
text="{{_groupFilter}}"
|
||||
query="[[_query]]"
|
||||
placeholder="Add group"
|
||||
on-commit="_handleAddRuleItem">
|
||||
<gr-autocomplete id="groupAutocomplete" text="{{_groupFilter}}" query="[[_query]]" placeholder="Add group" on-commit="_handleAddRuleItem">
|
||||
</gr-autocomplete>
|
||||
</div>
|
||||
<!-- end addRule -->
|
||||
@ -138,13 +100,8 @@ limitations under the License.
|
||||
</div><!-- end mainContainer -->
|
||||
<div id="deletedContainer">
|
||||
<span>[[name]] was deleted</span>
|
||||
<gr-button
|
||||
link
|
||||
id="undoRemoveBtn"
|
||||
on-click="_handleUndoRemove">Undo</gr-button>
|
||||
<gr-button link="" id="undoRemoveBtn" on-click="_handleUndoRemove">Undo</gr-button>
|
||||
</div><!-- end deletedContainer -->
|
||||
</section>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-permission.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-permission</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-permission.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-permission.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-permission.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,399 +41,401 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-permission tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-permission.js';
|
||||
suite('gr-permission tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups').returns(
|
||||
Promise.resolve({
|
||||
'Administrators': {
|
||||
id: '4c97682e6ce61b7247f3381b6f1789356666de7f',
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element.$.restAPI, 'getSuggestedGroups').returns(
|
||||
Promise.resolve({
|
||||
'Administrators': {
|
||||
id: '4c97682e6ce61b7247f3381b6f1789356666de7f',
|
||||
},
|
||||
'Anonymous Users': {
|
||||
id: 'global%3AAnonymous-Users',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('unit tests', () => {
|
||||
test('_sortPermission', () => {
|
||||
const permission = {
|
||||
id: 'submit',
|
||||
value: {
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
'Anonymous Users': {
|
||||
id: 'global%3AAnonymous-Users',
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedRules = [
|
||||
{
|
||||
id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
{
|
||||
id: 'global:Project-Owners',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
];
|
||||
|
||||
element._sortPermission(permission);
|
||||
assert.deepEqual(element._rules, expectedRules);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
test('_computeLabel and _computeLabelValues', () => {
|
||||
const labels = {
|
||||
'Code-Review': {
|
||||
default_value: 0,
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
},
|
||||
};
|
||||
let permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLabelValues = [
|
||||
{value: -2, text: 'This shall not be merged'},
|
||||
{value: -1, text: 'I would prefer this is not merged as is'},
|
||||
{value: 0, text: 'No score'},
|
||||
{value: 1, text: 'Looks good to me, but someone else must approve'},
|
||||
{value: 2, text: 'Looks good to me, approved'},
|
||||
];
|
||||
|
||||
const expectedLabel = {
|
||||
name: 'Code-Review',
|
||||
values: expectedLabelValues,
|
||||
};
|
||||
|
||||
assert.deepEqual(element._computeLabelValues(
|
||||
labels['Code-Review'].values), expectedLabelValues);
|
||||
|
||||
assert.deepEqual(element._computeLabel(permission, labels),
|
||||
expectedLabel);
|
||||
|
||||
permission = {
|
||||
id: 'label-reviewDB',
|
||||
value: {
|
||||
label: 'reviewDB',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert.isNotOk(element._computeLabel(permission, labels));
|
||||
});
|
||||
|
||||
suite('unit tests', () => {
|
||||
test('_sortPermission', () => {
|
||||
const permission = {
|
||||
id: 'submit',
|
||||
value: {
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
test('_computeSectionClass', () => {
|
||||
let deleted = true;
|
||||
let editing = false;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), 'deleted');
|
||||
|
||||
const expectedRules = [
|
||||
deleted = false;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), '');
|
||||
|
||||
editing = true;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), 'editing');
|
||||
|
||||
deleted = true;
|
||||
assert.equal(element._computeSectionClass(editing, deleted),
|
||||
'editing deleted');
|
||||
});
|
||||
|
||||
test('_computeGroupName', () => {
|
||||
const groups = {
|
||||
abc123: {name: 'test group'},
|
||||
bcd234: {},
|
||||
};
|
||||
assert.equal(element._computeGroupName(groups, 'abc123'), 'test group');
|
||||
assert.equal(element._computeGroupName(groups, 'bcd234'), 'bcd234');
|
||||
});
|
||||
|
||||
test('_computeGroupsWithRules', () => {
|
||||
const rules = [
|
||||
{
|
||||
id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
{
|
||||
id: 'global:Project-Owners',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
];
|
||||
const groupsWithRules = {
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': true,
|
||||
'global:Project-Owners': true,
|
||||
};
|
||||
assert.deepEqual(element._computeGroupsWithRules(rules),
|
||||
groupsWithRules);
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions without existing rules', done => {
|
||||
element._groupsWithRules = {};
|
||||
|
||||
element._getGroupSuggestions().then(groups => {
|
||||
assert.deepEqual(groups, [
|
||||
{
|
||||
id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
{
|
||||
id: 'global:Project-Owners',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
];
|
||||
|
||||
element._sortPermission(permission);
|
||||
assert.deepEqual(element._rules, expectedRules);
|
||||
});
|
||||
|
||||
test('_computeLabel and _computeLabelValues', () => {
|
||||
const labels = {
|
||||
'Code-Review': {
|
||||
default_value: 0,
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
},
|
||||
};
|
||||
let permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLabelValues = [
|
||||
{value: -2, text: 'This shall not be merged'},
|
||||
{value: -1, text: 'I would prefer this is not merged as is'},
|
||||
{value: 0, text: 'No score'},
|
||||
{value: 1, text: 'Looks good to me, but someone else must approve'},
|
||||
{value: 2, text: 'Looks good to me, approved'},
|
||||
];
|
||||
|
||||
const expectedLabel = {
|
||||
name: 'Code-Review',
|
||||
values: expectedLabelValues,
|
||||
};
|
||||
|
||||
assert.deepEqual(element._computeLabelValues(
|
||||
labels['Code-Review'].values), expectedLabelValues);
|
||||
|
||||
assert.deepEqual(element._computeLabel(permission, labels),
|
||||
expectedLabel);
|
||||
|
||||
permission = {
|
||||
id: 'label-reviewDB',
|
||||
value: {
|
||||
label: 'reviewDB',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert.isNotOk(element._computeLabel(permission, labels));
|
||||
});
|
||||
|
||||
test('_computeSectionClass', () => {
|
||||
let deleted = true;
|
||||
let editing = false;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), 'deleted');
|
||||
|
||||
deleted = false;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), '');
|
||||
|
||||
editing = true;
|
||||
assert.equal(element._computeSectionClass(editing, deleted), 'editing');
|
||||
|
||||
deleted = true;
|
||||
assert.equal(element._computeSectionClass(editing, deleted),
|
||||
'editing deleted');
|
||||
});
|
||||
|
||||
test('_computeGroupName', () => {
|
||||
const groups = {
|
||||
abc123: {name: 'test group'},
|
||||
bcd234: {},
|
||||
};
|
||||
assert.equal(element._computeGroupName(groups, 'abc123'), 'test group');
|
||||
assert.equal(element._computeGroupName(groups, 'bcd234'), 'bcd234');
|
||||
});
|
||||
|
||||
test('_computeGroupsWithRules', () => {
|
||||
const rules = [
|
||||
{
|
||||
id: '4c97682e6ce6b7247f3381b6f1789356666de7f',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
{
|
||||
id: 'global:Project-Owners',
|
||||
value: {action: 'ALLOW', force: false},
|
||||
},
|
||||
];
|
||||
const groupsWithRules = {
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': true,
|
||||
'global:Project-Owners': true,
|
||||
};
|
||||
assert.deepEqual(element._computeGroupsWithRules(rules),
|
||||
groupsWithRules);
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions without existing rules', done => {
|
||||
element._groupsWithRules = {};
|
||||
|
||||
element._getGroupSuggestions().then(groups => {
|
||||
assert.deepEqual(groups, [
|
||||
{
|
||||
name: 'Administrators',
|
||||
value: {id: '4c97682e6ce61b7247f3381b6f1789356666de7f'},
|
||||
}, {
|
||||
name: 'Anonymous Users',
|
||||
value: {id: 'global%3AAnonymous-Users'},
|
||||
},
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_getGroupSuggestions with existing rules filters them', done => {
|
||||
element._groupsWithRules = {
|
||||
'4c97682e6ce61b7247f3381b6f1789356666de7f': true,
|
||||
};
|
||||
|
||||
element._getGroupSuggestions().then(groups => {
|
||||
assert.deepEqual(groups, [{
|
||||
name: 'Administrators',
|
||||
value: {id: '4c97682e6ce61b7247f3381b6f1789356666de7f'},
|
||||
}, {
|
||||
name: 'Anonymous Users',
|
||||
value: {id: 'global%3AAnonymous-Users'},
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_handleRemovePermission', () => {
|
||||
element.editing = true;
|
||||
element.permission = {value: {rules: {}}};
|
||||
element._handleRemovePermission();
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.permission.value.deleted);
|
||||
|
||||
element.editing = false;
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.permission.value.deleted);
|
||||
});
|
||||
|
||||
test('_handleUndoRemove', () => {
|
||||
element.permission = {value: {deleted: true, rules: {}}};
|
||||
element._handleUndoRemove();
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.permission.value.deleted);
|
||||
});
|
||||
|
||||
test('_computeHasRange', () => {
|
||||
assert.isTrue(element._computeHasRange('Query Limit'));
|
||||
|
||||
assert.isTrue(element._computeHasRange('Batch Changes Limit'));
|
||||
|
||||
assert.isFalse(element._computeHasRange('test'));
|
||||
},
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('interactions', () => {
|
||||
setup(() => {
|
||||
sandbox.spy(element, '_computeLabel');
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
element.permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
element._setupValues();
|
||||
flushAsynchronousOperations();
|
||||
test('_getGroupSuggestions with existing rules filters them', done => {
|
||||
element._groupsWithRules = {
|
||||
'4c97682e6ce61b7247f3381b6f1789356666de7f': true,
|
||||
};
|
||||
|
||||
element._getGroupSuggestions().then(groups => {
|
||||
assert.deepEqual(groups, [{
|
||||
name: 'Anonymous Users',
|
||||
value: {id: 'global%3AAnonymous-Users'},
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('adding a rule', () => {
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.groups = {};
|
||||
element.$.groupAutocomplete.text = 'ldap/tests te.st';
|
||||
const e = {
|
||||
detail: {
|
||||
value: {
|
||||
id: 'ldap:CN=test+te.st',
|
||||
},
|
||||
},
|
||||
};
|
||||
element.editing = true;
|
||||
assert.equal(element._rules.length, 2);
|
||||
assert.equal(Object.keys(element._groupsWithRules).length, 2);
|
||||
element._handleAddRuleItem(e);
|
||||
flushAsynchronousOperations();
|
||||
assert.deepEqual(element.groups, {'ldap:CN=test te.st': {
|
||||
name: 'ldap/tests te.st'}});
|
||||
assert.equal(element._rules.length, 3);
|
||||
assert.equal(Object.keys(element._groupsWithRules).length, 3);
|
||||
assert.deepEqual(element.permission.value.rules['ldap:CN=test te.st'],
|
||||
{action: 'ALLOW', min: -2, max: 2, added: true});
|
||||
// New rule should be removed if cancel from editing.
|
||||
element.editing = false;
|
||||
assert.equal(element._rules.length, 2);
|
||||
assert.equal(Object.keys(element.permission.value.rules).length, 2);
|
||||
});
|
||||
test('_handleRemovePermission', () => {
|
||||
element.editing = true;
|
||||
element.permission = {value: {rules: {}}};
|
||||
element._handleRemovePermission();
|
||||
assert.isTrue(element._deleted);
|
||||
assert.isTrue(element.permission.value.deleted);
|
||||
|
||||
test('removing an added rule', () => {
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.groups = {};
|
||||
element.$.groupAutocomplete.text = 'new group name';
|
||||
assert.equal(element._rules.length, 2);
|
||||
element.shadowRoot
|
||||
.querySelector('gr-rule-editor').fire('added-rule-removed');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element._rules.length, 1);
|
||||
});
|
||||
element.editing = false;
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.permission.value.deleted);
|
||||
});
|
||||
|
||||
test('removing an added permission', () => {
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-permission-removed', removeStub);
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.permission.value.added = true;
|
||||
MockInteractions.tap(element.$.removeBtn);
|
||||
assert.isTrue(removeStub.called);
|
||||
});
|
||||
test('_handleUndoRemove', () => {
|
||||
element.permission = {value: {deleted: true, rules: {}}};
|
||||
element._handleUndoRemove();
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isNotOk(element.permission.value.deleted);
|
||||
});
|
||||
|
||||
test('removing the permission', () => {
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
test('_computeHasRange', () => {
|
||||
assert.isTrue(element._computeHasRange('Query Limit'));
|
||||
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-permission-removed', removeStub);
|
||||
assert.isTrue(element._computeHasRange('Batch Changes Limit'));
|
||||
|
||||
assert.isFalse(element.$.permission.classList.contains('deleted'));
|
||||
assert.isFalse(element._deleted);
|
||||
MockInteractions.tap(element.$.removeBtn);
|
||||
assert.isTrue(element.$.permission.classList.contains('deleted'));
|
||||
assert.isTrue(element._deleted);
|
||||
MockInteractions.tap(element.$.undoRemoveBtn);
|
||||
assert.isFalse(element.$.permission.classList.contains('deleted'));
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isFalse(removeStub.called);
|
||||
});
|
||||
|
||||
test('modify a permission', () => {
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
|
||||
assert.isFalse(element._originalExclusiveValue);
|
||||
assert.isNotOk(element.permission.value.modified);
|
||||
MockInteractions.tap(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle'));
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element.permission.value.exclusive);
|
||||
assert.isTrue(element.permission.value.modified);
|
||||
assert.isFalse(element._originalExclusiveValue);
|
||||
element.editing = false;
|
||||
assert.isFalse(element.permission.value.exclusive);
|
||||
});
|
||||
|
||||
test('_handleValueChange', () => {
|
||||
const modifiedHandler = sandbox.stub();
|
||||
element.permission = {value: {rules: {}}};
|
||||
element.addEventListener('access-modified', modifiedHandler);
|
||||
assert.isNotOk(element.permission.value.modified);
|
||||
element._handleValueChange();
|
||||
assert.isTrue(element.permission.value.modified);
|
||||
assert.isTrue(modifiedHandler.called);
|
||||
});
|
||||
|
||||
test('Exclusive hidden for owner permission', () => {
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'flex');
|
||||
element.set(['permission', 'id'], 'owner');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'none');
|
||||
});
|
||||
|
||||
test('Exclusive hidden for any global permissions', () => {
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'flex');
|
||||
element.section = 'GLOBAL_CAPABILITIES';
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'none');
|
||||
});
|
||||
assert.isFalse(element._computeHasRange('test'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('interactions', () => {
|
||||
setup(() => {
|
||||
sandbox.spy(element, '_computeLabel');
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.labels = {
|
||||
'Code-Review': {
|
||||
values: {
|
||||
' 0': 'No score',
|
||||
'-1': 'I would prefer this is not merged as is',
|
||||
'-2': 'This shall not be merged',
|
||||
'+1': 'Looks good to me, but someone else must approve',
|
||||
'+2': 'Looks good to me, approved',
|
||||
},
|
||||
default_value: 0,
|
||||
},
|
||||
};
|
||||
element.permission = {
|
||||
id: 'label-Code-Review',
|
||||
value: {
|
||||
label: 'Code-Review',
|
||||
rules: {
|
||||
'global:Project-Owners': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
'4c97682e6ce6b7247f3381b6f1789356666de7f': {
|
||||
action: 'ALLOW',
|
||||
force: false,
|
||||
min: -2,
|
||||
max: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
element._setupValues();
|
||||
flushAsynchronousOperations();
|
||||
});
|
||||
|
||||
test('adding a rule', () => {
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.groups = {};
|
||||
element.$.groupAutocomplete.text = 'ldap/tests te.st';
|
||||
const e = {
|
||||
detail: {
|
||||
value: {
|
||||
id: 'ldap:CN=test+te.st',
|
||||
},
|
||||
},
|
||||
};
|
||||
element.editing = true;
|
||||
assert.equal(element._rules.length, 2);
|
||||
assert.equal(Object.keys(element._groupsWithRules).length, 2);
|
||||
element._handleAddRuleItem(e);
|
||||
flushAsynchronousOperations();
|
||||
assert.deepEqual(element.groups, {'ldap:CN=test te.st': {
|
||||
name: 'ldap/tests te.st'}});
|
||||
assert.equal(element._rules.length, 3);
|
||||
assert.equal(Object.keys(element._groupsWithRules).length, 3);
|
||||
assert.deepEqual(element.permission.value.rules['ldap:CN=test te.st'],
|
||||
{action: 'ALLOW', min: -2, max: 2, added: true});
|
||||
// New rule should be removed if cancel from editing.
|
||||
element.editing = false;
|
||||
assert.equal(element._rules.length, 2);
|
||||
assert.equal(Object.keys(element.permission.value.rules).length, 2);
|
||||
});
|
||||
|
||||
test('removing an added rule', () => {
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.groups = {};
|
||||
element.$.groupAutocomplete.text = 'new group name';
|
||||
assert.equal(element._rules.length, 2);
|
||||
element.shadowRoot
|
||||
.querySelector('gr-rule-editor').fire('added-rule-removed');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element._rules.length, 1);
|
||||
});
|
||||
|
||||
test('removing an added permission', () => {
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-permission-removed', removeStub);
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
element.permission.value.added = true;
|
||||
MockInteractions.tap(element.$.removeBtn);
|
||||
assert.isTrue(removeStub.called);
|
||||
});
|
||||
|
||||
test('removing the permission', () => {
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
|
||||
const removeStub = sandbox.stub();
|
||||
element.addEventListener('added-permission-removed', removeStub);
|
||||
|
||||
assert.isFalse(element.$.permission.classList.contains('deleted'));
|
||||
assert.isFalse(element._deleted);
|
||||
MockInteractions.tap(element.$.removeBtn);
|
||||
assert.isTrue(element.$.permission.classList.contains('deleted'));
|
||||
assert.isTrue(element._deleted);
|
||||
MockInteractions.tap(element.$.undoRemoveBtn);
|
||||
assert.isFalse(element.$.permission.classList.contains('deleted'));
|
||||
assert.isFalse(element._deleted);
|
||||
assert.isFalse(removeStub.called);
|
||||
});
|
||||
|
||||
test('modify a permission', () => {
|
||||
element.editing = true;
|
||||
element.name = 'Priority';
|
||||
element.section = 'refs/*';
|
||||
|
||||
assert.isFalse(element._originalExclusiveValue);
|
||||
assert.isNotOk(element.permission.value.modified);
|
||||
MockInteractions.tap(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle'));
|
||||
flushAsynchronousOperations();
|
||||
assert.isTrue(element.permission.value.exclusive);
|
||||
assert.isTrue(element.permission.value.modified);
|
||||
assert.isFalse(element._originalExclusiveValue);
|
||||
element.editing = false;
|
||||
assert.isFalse(element.permission.value.exclusive);
|
||||
});
|
||||
|
||||
test('_handleValueChange', () => {
|
||||
const modifiedHandler = sandbox.stub();
|
||||
element.permission = {value: {rules: {}}};
|
||||
element.addEventListener('access-modified', modifiedHandler);
|
||||
assert.isNotOk(element.permission.value.modified);
|
||||
element._handleValueChange();
|
||||
assert.isTrue(element.permission.value.modified);
|
||||
assert.isTrue(modifiedHandler.called);
|
||||
});
|
||||
|
||||
test('Exclusive hidden for owner permission', () => {
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'flex');
|
||||
element.set(['permission', 'id'], 'owner');
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'none');
|
||||
});
|
||||
|
||||
test('Exclusive hidden for any global permissions', () => {
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'flex');
|
||||
element.section = 'GLOBAL_CAPABILITIES';
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(getComputedStyle(element.shadowRoot
|
||||
.querySelector('#exclusiveToggle')).display,
|
||||
'none');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,84 +14,95 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/** @extends Polymer.Element */
|
||||
class GrPluginConfigArrayEditor extends Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element)) {
|
||||
static get is() { return 'gr-plugin-config-array-editor'; }
|
||||
/**
|
||||
* Fired when the plugin config option changes.
|
||||
*
|
||||
* @event plugin-config-option-changed
|
||||
*/
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-plugin-config-array-editor_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @extends Polymer.Element */
|
||||
class GrPluginConfigArrayEditor extends GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement)) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-plugin-config-array-editor'; }
|
||||
/**
|
||||
* Fired when the plugin config option changes.
|
||||
*
|
||||
* @event plugin-config-option-changed
|
||||
*/
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {?} */
|
||||
pluginOption: Object,
|
||||
/** @type {boolean} */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
computed: '_computeDisabled(pluginOption.*)',
|
||||
},
|
||||
/** @type {?} */
|
||||
pluginOption: Object,
|
||||
/** @type {boolean} */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
computed: '_computeDisabled(pluginOption.*)',
|
||||
},
|
||||
/** @type {?} */
|
||||
_newValue: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
_newValue: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_computeDisabled(record) {
|
||||
return !(record && record.base && record.base.info &&
|
||||
record.base.info.editable);
|
||||
}
|
||||
_computeDisabled(record) {
|
||||
return !(record && record.base && record.base.info &&
|
||||
record.base.info.editable);
|
||||
}
|
||||
|
||||
_handleAddTap(e) {
|
||||
_handleAddTap(e) {
|
||||
e.preventDefault();
|
||||
this._handleAdd();
|
||||
}
|
||||
|
||||
_handleInputKeydown(e) {
|
||||
// Enter.
|
||||
if (e.keyCode === 13) {
|
||||
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.dataset.item;
|
||||
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' : '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrPluginConfigArrayEditor.is,
|
||||
GrPluginConfigArrayEditor);
|
||||
})();
|
||||
_handleAdd() {
|
||||
if (!this._newValue.length) { return; }
|
||||
this._dispatchChanged(
|
||||
this.pluginOption.info.values.concat([this._newValue]));
|
||||
this._newValue = '';
|
||||
}
|
||||
|
||||
_handleDelete(e) {
|
||||
const value = dom(e).localTarget.dataset.item;
|
||||
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' : '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrPluginConfigArrayEditor.is,
|
||||
GrPluginConfigArrayEditor);
|
||||
|
@ -1,30 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -72,11 +64,7 @@ limitations under the License.
|
||||
<template is="dom-repeat" items="[[pluginOption.info.values]]">
|
||||
<div class="row">
|
||||
<span>[[item]]</span>
|
||||
<gr-button
|
||||
link
|
||||
disabled$="[[disabled]]"
|
||||
data-item$="[[item]]"
|
||||
on-click="_handleDelete">Delete</gr-button>
|
||||
<gr-button link="" disabled\$="[[disabled]]" data-item\$="[[item]]" on-click="_handleDelete">Delete</gr-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@ -84,23 +72,11 @@ limitations under the License.
|
||||
<template is="dom-if" if="[[!pluginOption.info.values.length]]">
|
||||
<div class="row placeholder">None configured.</div>
|
||||
</template>
|
||||
<div class$="row [[_computeShowInputRow(disabled)]]">
|
||||
<iron-input
|
||||
on-keydown="_handleInputKeydown"
|
||||
bind-value="{{_newValue}}">
|
||||
<input
|
||||
is="iron-input"
|
||||
id="input"
|
||||
on-keydown="_handleInputKeydown"
|
||||
bind-value="{{_newValue}}">
|
||||
<div class\$="row [[_computeShowInputRow(disabled)]]">
|
||||
<iron-input on-keydown="_handleInputKeydown" bind-value="{{_newValue}}">
|
||||
<input is="iron-input" id="input" on-keydown="_handleInputKeydown" bind-value="{{_newValue}}">
|
||||
</iron-input>
|
||||
<gr-button
|
||||
id="addButton"
|
||||
disabled$="[[!_newValue.length]]"
|
||||
link
|
||||
on-click="_handleAddTap">Add</gr-button>
|
||||
<gr-button id="addButton" disabled\$="[[!_newValue.length]]" link="" on-click="_handleAddTap">Add</gr-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script src="gr-plugin-config-array-editor.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ 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/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-plugin-config-array-editor.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-plugin-config-array-editor.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-plugin-config-array-editor.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,112 +40,115 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-plugin-config-array-editor tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
let dispatchStub;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-plugin-config-array-editor.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
suite('gr-plugin-config-array-editor tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
let dispatchStub;
|
||||
|
||||
const getAll = str => Polymer.dom(element.root).querySelectorAll(str);
|
||||
const getAll = str => 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(() => {
|
||||
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);
|
||||
test('with enter', () => {
|
||||
element._newValue = '';
|
||||
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isFalse(dispatchStub.called);
|
||||
element.pluginOption.info.editable = true;
|
||||
element.notifyPath('pluginOption.info.editable');
|
||||
flushAsynchronousOperations();
|
||||
|
||||
MockInteractions.tap(button);
|
||||
element._newValue = 'test';
|
||||
MockInteractions.pressAndReleaseKeyOn(element.$.input, 13); // Enter
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(dispatchStub.called);
|
||||
assert.deepEqual(dispatchStub.lastCall.args[0], ['test2']);
|
||||
assert.equal(dispatchStub.lastCall.args[0], 'test');
|
||||
assert.equal(element._newValue, '');
|
||||
});
|
||||
|
||||
test('_dispatchChanged', () => {
|
||||
const eventStub = sandbox.stub(element, 'dispatchEvent');
|
||||
element._dispatchChanged(['new-test-value']);
|
||||
test('with add btn', () => {
|
||||
element._newValue = '';
|
||||
MockInteractions.tap(element.$.addButton);
|
||||
flushAsynchronousOperations();
|
||||
|
||||
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');
|
||||
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>
|
||||
|
@ -14,110 +14,122 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrPluginList extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-plugin-list'; }
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-list-view/gr-list-view.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-plugin-list_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrPluginList extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.ListViewBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-plugin-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/plugins',
|
||||
},
|
||||
_plugins: Array,
|
||||
/**
|
||||
* Because we request one more than the pluginsPerPage, _shownPlugins
|
||||
* maybe one less than _plugins.
|
||||
* */
|
||||
_shownPlugins: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_plugins)',
|
||||
},
|
||||
_pluginsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.fire('title-change', {title: 'Plugins'});
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getPlugins(this._filter, this._pluginsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_getPlugins(filter, pluginsPerPage, offset) {
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
return this.$.restAPI.getPlugins(filter, pluginsPerPage, offset, errFn)
|
||||
.then(plugins => {
|
||||
if (!plugins) {
|
||||
this._plugins = [];
|
||||
return;
|
||||
}
|
||||
this._plugins = Object.keys(plugins)
|
||||
.map(key => {
|
||||
const plugin = plugins[key];
|
||||
plugin.name = key;
|
||||
return plugin;
|
||||
});
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_status(item) {
|
||||
return item.disabled === true ? 'Disabled' : 'Enabled';
|
||||
}
|
||||
|
||||
_computePluginUrl(id) {
|
||||
return this.getUrl('/', id);
|
||||
}
|
||||
_offset: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/plugins',
|
||||
},
|
||||
_plugins: Array,
|
||||
/**
|
||||
* Because we request one more than the pluginsPerPage, _shownPlugins
|
||||
* maybe one less than _plugins.
|
||||
* */
|
||||
_shownPlugins: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_plugins)',
|
||||
},
|
||||
_pluginsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrPluginList.is, GrPluginList);
|
||||
})();
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this.fire('title-change', {title: 'Plugins'});
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getPlugins(this._filter, this._pluginsPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_getPlugins(filter, pluginsPerPage, offset) {
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
return this.$.restAPI.getPlugins(filter, pluginsPerPage, offset, errFn)
|
||||
.then(plugins => {
|
||||
if (!plugins) {
|
||||
this._plugins = [];
|
||||
return;
|
||||
}
|
||||
this._plugins = Object.keys(plugins)
|
||||
.map(key => {
|
||||
const plugin = plugins[key];
|
||||
plugin.name = key;
|
||||
return plugin;
|
||||
});
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_status(item) {
|
||||
return item.disabled === true ? 'Disabled' : 'Enabled';
|
||||
}
|
||||
|
||||
_computePluginUrl(id) {
|
||||
return this.getUrl('/', id);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrPluginList.is, GrPluginList);
|
||||
|
@ -1,58 +1,44 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<dom-module id="gr-plugin-list">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<style include="gr-table-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<gr-list-view
|
||||
filter="[[_filter]]"
|
||||
items-per-page="[[_pluginsPerPage]]"
|
||||
items="[[_plugins]]"
|
||||
loading="[[_loading]]"
|
||||
offset="[[_offset]]"
|
||||
path="[[_path]]">
|
||||
<gr-list-view filter="[[_filter]]" items-per-page="[[_pluginsPerPage]]" items="[[_plugins]]" loading="[[_loading]]" offset="[[_offset]]" path="[[_path]]">
|
||||
<table id="list" class="genericList">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="name topHeader">Plugin Name</th>
|
||||
<th class="version topHeader">Version</th>
|
||||
<th class="status topHeader">Status</th>
|
||||
</tr>
|
||||
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<tr id="loading" class\$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody class$="[[computeLoadingClass(_loading)]]">
|
||||
</tbody><tbody class\$="[[computeLoadingClass(_loading)]]">
|
||||
<template is="dom-repeat" items="[[_shownPlugins]]">
|
||||
<tr class="table">
|
||||
<td class="name">
|
||||
<template is="dom-if" if="[[item.index_url]]">
|
||||
<a href$="[[_computePluginUrl(item.index_url)]]">[[item.id]]</a>
|
||||
<a href\$="[[_computePluginUrl(item.index_url)]]">[[item.id]]</a>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!item.index_url]]">
|
||||
[[item.id]]
|
||||
@ -66,6 +52,4 @@ limitations under the License.
|
||||
</table>
|
||||
</gr-list-view>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-plugin-list.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ 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-list</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-plugin-list.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-plugin-list.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-plugin-list.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,151 +41,154 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
let counter;
|
||||
const pluginGenerator = () => {
|
||||
const plugin = {
|
||||
id: `test${++counter}`,
|
||||
version: '3.0-SNAPSHOT',
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
if (counter !== 2) {
|
||||
plugin.index_url = `plugins/test${counter}/`;
|
||||
}
|
||||
return plugin;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-plugin-list.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
let counter;
|
||||
const pluginGenerator = () => {
|
||||
const plugin = {
|
||||
id: `test${++counter}`,
|
||||
version: '3.0-SNAPSHOT',
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
suite('gr-plugin-list tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let plugins;
|
||||
let sandbox;
|
||||
let value;
|
||||
if (counter !== 2) {
|
||||
plugin.index_url = `plugins/test${counter}/`;
|
||||
}
|
||||
return plugin;
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
counter = 0;
|
||||
suite('gr-plugin-list tests', () => {
|
||||
let element;
|
||||
let plugins;
|
||||
let sandbox;
|
||||
let value;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
counter = 0;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with plugins', () => {
|
||||
setup(done => {
|
||||
plugins = _.times(26, pluginGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getPlugins(num, offset) {
|
||||
return Promise.resolve(plugins);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with plugins', () => {
|
||||
setup(done => {
|
||||
plugins = _.times(26, pluginGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getPlugins(num, offset) {
|
||||
return Promise.resolve(plugins);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('plugin in the list is formatted correctly', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._plugins[2].id, 'test3');
|
||||
assert.equal(element._plugins[2].index_url, 'plugins/test3/');
|
||||
assert.equal(element._plugins[2].version, '3.0-SNAPSHOT');
|
||||
assert.equal(element._plugins[2].disabled, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('with and without urls', done => {
|
||||
flush(() => {
|
||||
const names = Polymer.dom(element.root).querySelectorAll('.name');
|
||||
assert.isOk(names[1].querySelector('a'));
|
||||
assert.equal(names[1].querySelector('a').innerText, 'test1');
|
||||
assert.isNotOk(names[2].querySelector('a'));
|
||||
assert.equal(names[2].innerText, 'test2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_shownPlugins', () => {
|
||||
assert.equal(element._shownPlugins.length, 25);
|
||||
test('plugin in the list is formatted correctly', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._plugins[2].id, 'test3');
|
||||
assert.equal(element._plugins[2].index_url, 'plugins/test3/');
|
||||
assert.equal(element._plugins[2].version, '3.0-SNAPSHOT');
|
||||
assert.equal(element._plugins[2].disabled, false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('list with less then 26 plugins', () => {
|
||||
setup(done => {
|
||||
plugins = _.times(25, pluginGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getPlugins(num, offset) {
|
||||
return Promise.resolve(plugins);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('_shownPlugins', () => {
|
||||
assert.equal(element._shownPlugins.length, 25);
|
||||
test('with and without urls', done => {
|
||||
flush(() => {
|
||||
const names = dom(element.root).querySelectorAll('.name');
|
||||
assert.isOk(names[1].querySelector('a'));
|
||||
assert.equal(names[1].querySelector('a').innerText, 'test1');
|
||||
assert.isNotOk(names[2].querySelector('a'));
|
||||
assert.equal(names[2].innerText, 'test2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('filter', () => {
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getPlugins',
|
||||
() => Promise.resolve(plugins));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[0],
|
||||
'test');
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[1],
|
||||
25);
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[2],
|
||||
25);
|
||||
done();
|
||||
});
|
||||
test('_shownPlugins', () => {
|
||||
assert.equal(element._shownPlugins.length, 25);
|
||||
});
|
||||
});
|
||||
|
||||
suite('list with less then 26 plugins', () => {
|
||||
setup(done => {
|
||||
plugins = _.times(25, pluginGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getPlugins(num, offset) {
|
||||
return Promise.resolve(plugins);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._plugins = _.times(25, pluginGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
test('_shownPlugins', () => {
|
||||
assert.equal(element._shownPlugins.length, 25);
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
const response = {status: 404};
|
||||
sandbox.stub(element.$.restAPI, 'getPlugins',
|
||||
(filter, pluginsPerPage, opt_offset, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value);
|
||||
suite('filter', () => {
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getPlugins',
|
||||
() => Promise.resolve(plugins));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[0],
|
||||
'test');
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[1],
|
||||
25);
|
||||
assert.equal(element.$.restAPI.getPlugins.lastCall.args[2],
|
||||
25);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._plugins = _.times(25, pluginGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
const response = {status: 404};
|
||||
sandbox.stub(element.$.restAPI, 'getPlugins',
|
||||
(filter, pluginsPerPage, opt_offset, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,497 +14,515 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const Defs = {};
|
||||
import '../../../behaviors/base-url-behavior/base-url-behavior.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../behaviors/gr-access-behavior/gr-access-behavior.js';
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '../../../styles/gr-menu-page-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-access-section/gr-access-section.js';
|
||||
import '../../../scripts/util.js';
|
||||
import {flush, dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-access_html.js';
|
||||
|
||||
const NOTHING_TO_SAVE = 'No changes to save.';
|
||||
const Defs = {};
|
||||
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 50;
|
||||
const NOTHING_TO_SAVE = 'No changes to save.';
|
||||
|
||||
/**
|
||||
* Fired when save is a no-op
|
||||
*
|
||||
* @event show-alert
|
||||
*/
|
||||
const MAX_AUTOCOMPLETE_RESULTS = 50;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* value: !Object,
|
||||
* }}
|
||||
*/
|
||||
Defs.rule;
|
||||
/**
|
||||
* Fired when save is a no-op
|
||||
*
|
||||
* @event show-alert
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* rules: !Object<string, Defs.rule>
|
||||
* }}
|
||||
*/
|
||||
Defs.permission;
|
||||
/**
|
||||
* @typedef {{
|
||||
* value: !Object,
|
||||
* }}
|
||||
*/
|
||||
Defs.rule;
|
||||
|
||||
/**
|
||||
* Can be an empty object or consist of permissions.
|
||||
*
|
||||
* @typedef {{
|
||||
* permissions: !Object<string, Defs.permission>
|
||||
* }}
|
||||
*/
|
||||
Defs.permissions;
|
||||
/**
|
||||
* @typedef {{
|
||||
* rules: !Object<string, Defs.rule>
|
||||
* }}
|
||||
*/
|
||||
Defs.permission;
|
||||
|
||||
/**
|
||||
* Can be an empty object or consist of permissions.
|
||||
*
|
||||
* @typedef {!Object<string, Defs.permissions>}
|
||||
*/
|
||||
Defs.sections;
|
||||
/**
|
||||
* Can be an empty object or consist of permissions.
|
||||
*
|
||||
* @typedef {{
|
||||
* permissions: !Object<string, Defs.permission>
|
||||
* }}
|
||||
*/
|
||||
Defs.permissions;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* remove: !Defs.sections,
|
||||
* add: !Defs.sections,
|
||||
* }}
|
||||
*/
|
||||
Defs.projectAccessInput;
|
||||
/**
|
||||
* Can be an empty object or consist of permissions.
|
||||
*
|
||||
* @typedef {!Object<string, Defs.permissions>}
|
||||
*/
|
||||
Defs.sections;
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoAccess extends Polymer.mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-access'; }
|
||||
/**
|
||||
* @typedef {{
|
||||
* remove: !Defs.sections,
|
||||
* add: !Defs.sections,
|
||||
* }}
|
||||
*/
|
||||
Defs.projectAccessInput;
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
repo: {
|
||||
type: String,
|
||||
observer: '_repoChanged',
|
||||
/**
|
||||
* @appliesMixin Gerrit.AccessMixin
|
||||
* @appliesMixin Gerrit.BaseUrlMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoAccess extends mixinBehaviors( [
|
||||
Gerrit.AccessBehavior,
|
||||
Gerrit.BaseUrlBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-repo-access'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
repo: {
|
||||
type: String,
|
||||
observer: '_repoChanged',
|
||||
},
|
||||
// The current path
|
||||
path: String,
|
||||
|
||||
_canUpload: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_inheritFromFilter: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getInheritFromSuggestions.bind(this);
|
||||
},
|
||||
// The current path
|
||||
path: String,
|
||||
},
|
||||
_ownerOf: Array,
|
||||
_capabilities: Object,
|
||||
_groups: Object,
|
||||
/** @type {?} */
|
||||
_inheritsFrom: Object,
|
||||
_labels: Object,
|
||||
_local: Object,
|
||||
_editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
_modified: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_sections: Array,
|
||||
_weblinks: Array,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_canUpload: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_inheritFromFilter: String,
|
||||
_query: {
|
||||
type: Function,
|
||||
value() {
|
||||
return this._getInheritFromSuggestions.bind(this);
|
||||
},
|
||||
},
|
||||
_ownerOf: Array,
|
||||
_capabilities: Object,
|
||||
_groups: Object,
|
||||
/** @type {?} */
|
||||
_inheritsFrom: Object,
|
||||
_labels: Object,
|
||||
_local: Object,
|
||||
_editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_handleEditingChanged',
|
||||
},
|
||||
_modified: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_sections: Array,
|
||||
_weblinks: Array,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-modified',
|
||||
() =>
|
||||
this._handleAccessModified());
|
||||
}
|
||||
|
||||
/** @override */
|
||||
created() {
|
||||
super.created();
|
||||
this.addEventListener('access-modified',
|
||||
() =>
|
||||
this._handleAccessModified());
|
||||
}
|
||||
_handleAccessModified() {
|
||||
this._modified = true;
|
||||
}
|
||||
|
||||
_handleAccessModified() {
|
||||
this._modified = true;
|
||||
}
|
||||
/**
|
||||
* @param {string} repo
|
||||
* @return {!Promise}
|
||||
*/
|
||||
_repoChanged(repo) {
|
||||
this._loading = true;
|
||||
|
||||
/**
|
||||
* @param {string} repo
|
||||
* @return {!Promise}
|
||||
*/
|
||||
_repoChanged(repo) {
|
||||
this._loading = true;
|
||||
if (!repo) { return Promise.resolve(); }
|
||||
|
||||
if (!repo) { return Promise.resolve(); }
|
||||
return this._reload(repo);
|
||||
}
|
||||
|
||||
return this._reload(repo);
|
||||
}
|
||||
_reload(repo) {
|
||||
const promises = [];
|
||||
|
||||
_reload(repo) {
|
||||
const promises = [];
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
this._editing = false;
|
||||
|
||||
this._editing = false;
|
||||
// Always reset sections when a project changes.
|
||||
this._sections = [];
|
||||
promises.push(this.$.restAPI.getRepoAccessRights(repo, errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
// Always reset sections when a project changes.
|
||||
this._sections = [];
|
||||
promises.push(this.$.restAPI.getRepoAccessRights(repo, errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
// Keep a copy of the original inherit from values separate from
|
||||
// the ones data bound to gr-autocomplete, so the original value
|
||||
// can be restored if the user cancels.
|
||||
this._inheritsFrom = res.inherits_from ? Object.assign({},
|
||||
res.inherits_from) : null;
|
||||
this._originalInheritsFrom = res.inherits_from ? Object.assign({},
|
||||
res.inherits_from) : null;
|
||||
// Initialize the filter value so when the user clicks edit, the
|
||||
// current value appears. If there is no parent repo, it is
|
||||
// initialized as an empty string.
|
||||
this._inheritFromFilter = res.inherits_from ?
|
||||
this._inheritsFrom.name : '';
|
||||
this._local = res.local;
|
||||
this._groups = res.groups;
|
||||
this._weblinks = res.config_web_links || [];
|
||||
this._canUpload = res.can_upload;
|
||||
this._ownerOf = res.owner_of || [];
|
||||
return this.toSortedArray(this._local);
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getCapabilities(errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
return res;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getRepo(repo, errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
return res.labels;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(([sections, capabilities, labels]) => {
|
||||
this._capabilities = capabilities;
|
||||
this._labels = labels;
|
||||
this._sections = sections;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleUpdateInheritFrom(e) {
|
||||
if (!this._inheritsFrom) {
|
||||
this._inheritsFrom = {};
|
||||
}
|
||||
this._inheritsFrom.id = e.detail.value;
|
||||
this._inheritsFrom.name = this._inheritFromFilter;
|
||||
this._handleAccessModified();
|
||||
}
|
||||
|
||||
_getInheritFromSuggestions() {
|
||||
return this.$.restAPI.getRepos(
|
||||
this._inheritFromFilter,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(response => {
|
||||
const projects = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
projects.push({
|
||||
name: response[key].name,
|
||||
value: response[key].id,
|
||||
});
|
||||
}
|
||||
return projects;
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_handleEdit() {
|
||||
this._editing = !this._editing;
|
||||
}
|
||||
|
||||
_editOrCancel(editing) {
|
||||
return editing ? 'Cancel' : 'Edit';
|
||||
}
|
||||
|
||||
_computeWebLinkClass(weblinks) {
|
||||
return weblinks && weblinks.length ? 'show' : '';
|
||||
}
|
||||
|
||||
_computeShowInherit(inheritsFrom) {
|
||||
return inheritsFrom ? 'show' : '';
|
||||
}
|
||||
|
||||
_handleAddedSectionRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._sections = this._sections.slice(0, index)
|
||||
.concat(this._sections.slice(index + 1, this._sections.length));
|
||||
}
|
||||
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld || editing) { return; }
|
||||
// Remove any unsaved but added refs.
|
||||
if (this._sections) {
|
||||
this._sections = this._sections.filter(p => !p.value.added);
|
||||
}
|
||||
// Restore inheritFrom.
|
||||
if (this._inheritsFrom) {
|
||||
this._inheritsFrom = Object.assign({}, this._originalInheritsFrom);
|
||||
this._inheritFromFilter = this._inheritsFrom.name;
|
||||
}
|
||||
for (const key of Object.keys(this._local)) {
|
||||
if (this._local[key].added) {
|
||||
delete this._local[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Defs.projectAccessInput} addRemoveObj
|
||||
* @param {!Array} path
|
||||
* @param {string} type add or remove
|
||||
* @param {!Object=} opt_value value to add if the type is 'add'
|
||||
* @return {!Defs.projectAccessInput}
|
||||
*/
|
||||
_updateAddRemoveObj(addRemoveObj, path, type, opt_value) {
|
||||
let curPos = addRemoveObj[type];
|
||||
for (const item of path) {
|
||||
if (!curPos[item]) {
|
||||
if (item === path[path.length - 1] && type === 'remove') {
|
||||
if (path[path.length - 2] === 'permissions') {
|
||||
curPos[item] = {rules: {}};
|
||||
} else if (path.length === 1) {
|
||||
curPos[item] = {permissions: {}};
|
||||
} else {
|
||||
curPos[item] = {};
|
||||
}
|
||||
} else if (item === path[path.length - 1] && type === 'add') {
|
||||
curPos[item] = opt_value;
|
||||
} else {
|
||||
curPos[item] = {};
|
||||
}
|
||||
}
|
||||
curPos = curPos[item];
|
||||
}
|
||||
return addRemoveObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to recursively remove any objects with a 'deleted' bit.
|
||||
*/
|
||||
_recursivelyRemoveDeleted(obj) {
|
||||
for (const k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) { continue; }
|
||||
|
||||
if (typeof obj[k] == 'object') {
|
||||
if (obj[k].deleted) {
|
||||
delete obj[k];
|
||||
return;
|
||||
}
|
||||
this._recursivelyRemoveDeleted(obj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_recursivelyUpdateAddRemoveObj(obj, addRemoveObj, path = []) {
|
||||
for (const k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) { continue; }
|
||||
if (typeof obj[k] == 'object') {
|
||||
const updatedId = obj[k].updatedId;
|
||||
const ref = updatedId ? updatedId : k;
|
||||
if (obj[k].deleted) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(k), 'remove');
|
||||
continue;
|
||||
} else if (obj[k].modified) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(k), 'remove');
|
||||
this._updateAddRemoveObj(addRemoveObj, path.concat(ref), 'add',
|
||||
obj[k]);
|
||||
/* Special case for ref changes because they need to be added and
|
||||
removed in a different way. The new ref needs to include all
|
||||
changes but also the initial state. To do this, instead of
|
||||
continuing with the same recursion, just remove anything that is
|
||||
deleted in the current state. */
|
||||
if (updatedId && updatedId !== k) {
|
||||
this._recursivelyRemoveDeleted(addRemoveObj.add[updatedId]);
|
||||
}
|
||||
continue;
|
||||
} else if (obj[k].added) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(ref), 'add', obj[k]);
|
||||
/**
|
||||
* As add / delete both can happen in the new section,
|
||||
* so here to make sure it will remove the deleted ones.
|
||||
*
|
||||
* @see Issue 11339
|
||||
*/
|
||||
this._recursivelyRemoveDeleted(addRemoveObj.add[k]);
|
||||
continue;
|
||||
}
|
||||
this._recursivelyUpdateAddRemoveObj(obj[k], addRemoveObj,
|
||||
path.concat(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object formatted for saving or submitting access changes for
|
||||
* review
|
||||
*
|
||||
* @return {!Defs.projectAccessInput}
|
||||
*/
|
||||
_computeAddAndRemove() {
|
||||
const addRemoveObj = {
|
||||
add: {},
|
||||
remove: {},
|
||||
};
|
||||
|
||||
const originalInheritsFromId = this._originalInheritsFrom ?
|
||||
this.singleDecodeURL(this._originalInheritsFrom.id) :
|
||||
null;
|
||||
const inheritsFromId = this._inheritsFrom ?
|
||||
this.singleDecodeURL(this._inheritsFrom.id) :
|
||||
null;
|
||||
|
||||
const inheritFromChanged =
|
||||
// Inherit from changed
|
||||
(originalInheritsFromId &&
|
||||
originalInheritsFromId !== inheritsFromId) ||
|
||||
// Inherit from added (did not have one initially);
|
||||
(!originalInheritsFromId && inheritsFromId);
|
||||
|
||||
this._recursivelyUpdateAddRemoveObj(this._local, addRemoveObj);
|
||||
|
||||
if (inheritFromChanged) {
|
||||
addRemoveObj.parent = inheritsFromId;
|
||||
}
|
||||
return addRemoveObj;
|
||||
}
|
||||
|
||||
_handleCreateSection() {
|
||||
let newRef = 'refs/for/*';
|
||||
// Avoid using an already used key for the placeholder, since it
|
||||
// immediately gets added to an object.
|
||||
while (this._local[newRef]) {
|
||||
newRef = `${newRef}*`;
|
||||
}
|
||||
const section = {permissions: {}, added: true};
|
||||
this.push('_sections', {id: newRef, value: section});
|
||||
this.set(['_local', newRef], section);
|
||||
Polymer.dom.flush();
|
||||
Polymer.dom(this.root).querySelector('gr-access-section:last-of-type')
|
||||
.editReference();
|
||||
}
|
||||
|
||||
_getObjforSave() {
|
||||
const addRemoveObj = this._computeAddAndRemove();
|
||||
// If there are no changes, don't actually save.
|
||||
if (!Object.keys(addRemoveObj.add).length &&
|
||||
!Object.keys(addRemoveObj.remove).length &&
|
||||
!addRemoveObj.parent) {
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {message: NOTHING_TO_SAVE},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
// Keep a copy of the original inherit from values separate from
|
||||
// the ones data bound to gr-autocomplete, so the original value
|
||||
// can be restored if the user cancels.
|
||||
this._inheritsFrom = res.inherits_from ? Object.assign({},
|
||||
res.inherits_from) : null;
|
||||
this._originalInheritsFrom = res.inherits_from ? Object.assign({},
|
||||
res.inherits_from) : null;
|
||||
// Initialize the filter value so when the user clicks edit, the
|
||||
// current value appears. If there is no parent repo, it is
|
||||
// initialized as an empty string.
|
||||
this._inheritFromFilter = res.inherits_from ?
|
||||
this._inheritsFrom.name : '';
|
||||
this._local = res.local;
|
||||
this._groups = res.groups;
|
||||
this._weblinks = res.config_web_links || [];
|
||||
this._canUpload = res.can_upload;
|
||||
this._ownerOf = res.owner_of || [];
|
||||
return this.toSortedArray(this._local);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const obj = {
|
||||
add: addRemoveObj.add,
|
||||
remove: addRemoveObj.remove,
|
||||
};
|
||||
if (addRemoveObj.parent) {
|
||||
obj.parent = addRemoveObj.parent;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
_handleSave(e) {
|
||||
const obj = this._getObjforSave();
|
||||
if (!obj) { return; }
|
||||
const button = e && e.target;
|
||||
if (button) {
|
||||
button.loading = true;
|
||||
promises.push(this.$.restAPI.getCapabilities(errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
return res;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getRepo(repo, errFn)
|
||||
.then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
return res.labels;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(([sections, capabilities, labels]) => {
|
||||
this._capabilities = capabilities;
|
||||
this._labels = labels;
|
||||
this._sections = sections;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleUpdateInheritFrom(e) {
|
||||
if (!this._inheritsFrom) {
|
||||
this._inheritsFrom = {};
|
||||
}
|
||||
this._inheritsFrom.id = e.detail.value;
|
||||
this._inheritsFrom.name = this._inheritFromFilter;
|
||||
this._handleAccessModified();
|
||||
}
|
||||
|
||||
_getInheritFromSuggestions() {
|
||||
return this.$.restAPI.getRepos(
|
||||
this._inheritFromFilter,
|
||||
MAX_AUTOCOMPLETE_RESULTS)
|
||||
.then(response => {
|
||||
const projects = [];
|
||||
for (const key in response) {
|
||||
if (!response.hasOwnProperty(key)) { continue; }
|
||||
projects.push({
|
||||
name: response[key].name,
|
||||
value: response[key].id,
|
||||
});
|
||||
}
|
||||
return projects;
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_handleEdit() {
|
||||
this._editing = !this._editing;
|
||||
}
|
||||
|
||||
_editOrCancel(editing) {
|
||||
return editing ? 'Cancel' : 'Edit';
|
||||
}
|
||||
|
||||
_computeWebLinkClass(weblinks) {
|
||||
return weblinks && weblinks.length ? 'show' : '';
|
||||
}
|
||||
|
||||
_computeShowInherit(inheritsFrom) {
|
||||
return inheritsFrom ? 'show' : '';
|
||||
}
|
||||
|
||||
_handleAddedSectionRemoved(e) {
|
||||
const index = e.model.index;
|
||||
this._sections = this._sections.slice(0, index)
|
||||
.concat(this._sections.slice(index + 1, this._sections.length));
|
||||
}
|
||||
|
||||
_handleEditingChanged(editing, editingOld) {
|
||||
// Ignore when editing gets set initially.
|
||||
if (!editingOld || editing) { return; }
|
||||
// Remove any unsaved but added refs.
|
||||
if (this._sections) {
|
||||
this._sections = this._sections.filter(p => !p.value.added);
|
||||
}
|
||||
// Restore inheritFrom.
|
||||
if (this._inheritsFrom) {
|
||||
this._inheritsFrom = Object.assign({}, this._originalInheritsFrom);
|
||||
this._inheritFromFilter = this._inheritsFrom.name;
|
||||
}
|
||||
for (const key of Object.keys(this._local)) {
|
||||
if (this._local[key].added) {
|
||||
delete this._local[key];
|
||||
}
|
||||
return this.$.restAPI.setRepoAccessRights(this.repo, obj)
|
||||
.then(() => {
|
||||
this._reload(this.repo);
|
||||
})
|
||||
.finally(() => {
|
||||
this._modified = false;
|
||||
if (button) {
|
||||
button.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveForReview(e) {
|
||||
const obj = this._getObjforSave();
|
||||
if (!obj) { return; }
|
||||
const button = e && e.target;
|
||||
if (button) {
|
||||
button.loading = true;
|
||||
}
|
||||
return this.$.restAPI
|
||||
.setRepoAccessRightsForReview(this.repo, obj)
|
||||
.then(change => {
|
||||
Gerrit.Nav.navigateToChange(change);
|
||||
})
|
||||
.finally(() => {
|
||||
this._modified = false;
|
||||
if (button) {
|
||||
button.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_computeSaveReviewBtnClass(canUpload) {
|
||||
return !canUpload ? 'invisible' : '';
|
||||
}
|
||||
|
||||
_computeSaveBtnClass(ownerOf) {
|
||||
return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
|
||||
}
|
||||
|
||||
_computeMainClass(ownerOf, canUpload, editing) {
|
||||
const classList = [];
|
||||
if (ownerOf && ownerOf.length > 0 || canUpload) {
|
||||
classList.push('admin');
|
||||
}
|
||||
if (editing) {
|
||||
classList.push('editing');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_computeParentHref(repoName) {
|
||||
return this.getBaseUrl() +
|
||||
`/admin/repos/${this.encodeURL(repoName, true)},access`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoAccess.is, GrRepoAccess);
|
||||
})();
|
||||
/**
|
||||
* @param {!Defs.projectAccessInput} addRemoveObj
|
||||
* @param {!Array} path
|
||||
* @param {string} type add or remove
|
||||
* @param {!Object=} opt_value value to add if the type is 'add'
|
||||
* @return {!Defs.projectAccessInput}
|
||||
*/
|
||||
_updateAddRemoveObj(addRemoveObj, path, type, opt_value) {
|
||||
let curPos = addRemoveObj[type];
|
||||
for (const item of path) {
|
||||
if (!curPos[item]) {
|
||||
if (item === path[path.length - 1] && type === 'remove') {
|
||||
if (path[path.length - 2] === 'permissions') {
|
||||
curPos[item] = {rules: {}};
|
||||
} else if (path.length === 1) {
|
||||
curPos[item] = {permissions: {}};
|
||||
} else {
|
||||
curPos[item] = {};
|
||||
}
|
||||
} else if (item === path[path.length - 1] && type === 'add') {
|
||||
curPos[item] = opt_value;
|
||||
} else {
|
||||
curPos[item] = {};
|
||||
}
|
||||
}
|
||||
curPos = curPos[item];
|
||||
}
|
||||
return addRemoveObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to recursively remove any objects with a 'deleted' bit.
|
||||
*/
|
||||
_recursivelyRemoveDeleted(obj) {
|
||||
for (const k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) { continue; }
|
||||
|
||||
if (typeof obj[k] == 'object') {
|
||||
if (obj[k].deleted) {
|
||||
delete obj[k];
|
||||
return;
|
||||
}
|
||||
this._recursivelyRemoveDeleted(obj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_recursivelyUpdateAddRemoveObj(obj, addRemoveObj, path = []) {
|
||||
for (const k in obj) {
|
||||
if (!obj.hasOwnProperty(k)) { continue; }
|
||||
if (typeof obj[k] == 'object') {
|
||||
const updatedId = obj[k].updatedId;
|
||||
const ref = updatedId ? updatedId : k;
|
||||
if (obj[k].deleted) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(k), 'remove');
|
||||
continue;
|
||||
} else if (obj[k].modified) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(k), 'remove');
|
||||
this._updateAddRemoveObj(addRemoveObj, path.concat(ref), 'add',
|
||||
obj[k]);
|
||||
/* Special case for ref changes because they need to be added and
|
||||
removed in a different way. The new ref needs to include all
|
||||
changes but also the initial state. To do this, instead of
|
||||
continuing with the same recursion, just remove anything that is
|
||||
deleted in the current state. */
|
||||
if (updatedId && updatedId !== k) {
|
||||
this._recursivelyRemoveDeleted(addRemoveObj.add[updatedId]);
|
||||
}
|
||||
continue;
|
||||
} else if (obj[k].added) {
|
||||
this._updateAddRemoveObj(addRemoveObj,
|
||||
path.concat(ref), 'add', obj[k]);
|
||||
/**
|
||||
* As add / delete both can happen in the new section,
|
||||
* so here to make sure it will remove the deleted ones.
|
||||
*
|
||||
* @see Issue 11339
|
||||
*/
|
||||
this._recursivelyRemoveDeleted(addRemoveObj.add[k]);
|
||||
continue;
|
||||
}
|
||||
this._recursivelyUpdateAddRemoveObj(obj[k], addRemoveObj,
|
||||
path.concat(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object formatted for saving or submitting access changes for
|
||||
* review
|
||||
*
|
||||
* @return {!Defs.projectAccessInput}
|
||||
*/
|
||||
_computeAddAndRemove() {
|
||||
const addRemoveObj = {
|
||||
add: {},
|
||||
remove: {},
|
||||
};
|
||||
|
||||
const originalInheritsFromId = this._originalInheritsFrom ?
|
||||
this.singleDecodeURL(this._originalInheritsFrom.id) :
|
||||
null;
|
||||
const inheritsFromId = this._inheritsFrom ?
|
||||
this.singleDecodeURL(this._inheritsFrom.id) :
|
||||
null;
|
||||
|
||||
const inheritFromChanged =
|
||||
// Inherit from changed
|
||||
(originalInheritsFromId &&
|
||||
originalInheritsFromId !== inheritsFromId) ||
|
||||
// Inherit from added (did not have one initially);
|
||||
(!originalInheritsFromId && inheritsFromId);
|
||||
|
||||
this._recursivelyUpdateAddRemoveObj(this._local, addRemoveObj);
|
||||
|
||||
if (inheritFromChanged) {
|
||||
addRemoveObj.parent = inheritsFromId;
|
||||
}
|
||||
return addRemoveObj;
|
||||
}
|
||||
|
||||
_handleCreateSection() {
|
||||
let newRef = 'refs/for/*';
|
||||
// Avoid using an already used key for the placeholder, since it
|
||||
// immediately gets added to an object.
|
||||
while (this._local[newRef]) {
|
||||
newRef = `${newRef}*`;
|
||||
}
|
||||
const section = {permissions: {}, added: true};
|
||||
this.push('_sections', {id: newRef, value: section});
|
||||
this.set(['_local', newRef], section);
|
||||
flush();
|
||||
dom(this.root).querySelector('gr-access-section:last-of-type')
|
||||
.editReference();
|
||||
}
|
||||
|
||||
_getObjforSave() {
|
||||
const addRemoveObj = this._computeAddAndRemove();
|
||||
// If there are no changes, don't actually save.
|
||||
if (!Object.keys(addRemoveObj.add).length &&
|
||||
!Object.keys(addRemoveObj.remove).length &&
|
||||
!addRemoveObj.parent) {
|
||||
this.dispatchEvent(new CustomEvent('show-alert', {
|
||||
detail: {message: NOTHING_TO_SAVE},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const obj = {
|
||||
add: addRemoveObj.add,
|
||||
remove: addRemoveObj.remove,
|
||||
};
|
||||
if (addRemoveObj.parent) {
|
||||
obj.parent = addRemoveObj.parent;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
_handleSave(e) {
|
||||
const obj = this._getObjforSave();
|
||||
if (!obj) { return; }
|
||||
const button = e && e.target;
|
||||
if (button) {
|
||||
button.loading = true;
|
||||
}
|
||||
return this.$.restAPI.setRepoAccessRights(this.repo, obj)
|
||||
.then(() => {
|
||||
this._reload(this.repo);
|
||||
})
|
||||
.finally(() => {
|
||||
this._modified = false;
|
||||
if (button) {
|
||||
button.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_handleSaveForReview(e) {
|
||||
const obj = this._getObjforSave();
|
||||
if (!obj) { return; }
|
||||
const button = e && e.target;
|
||||
if (button) {
|
||||
button.loading = true;
|
||||
}
|
||||
return this.$.restAPI
|
||||
.setRepoAccessRightsForReview(this.repo, obj)
|
||||
.then(change => {
|
||||
Gerrit.Nav.navigateToChange(change);
|
||||
})
|
||||
.finally(() => {
|
||||
this._modified = false;
|
||||
if (button) {
|
||||
button.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_computeSaveReviewBtnClass(canUpload) {
|
||||
return !canUpload ? 'invisible' : '';
|
||||
}
|
||||
|
||||
_computeSaveBtnClass(ownerOf) {
|
||||
return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
|
||||
}
|
||||
|
||||
_computeMainClass(ownerOf, canUpload, editing) {
|
||||
const classList = [];
|
||||
if (ownerOf && ownerOf.length > 0 || canUpload) {
|
||||
classList.push('admin');
|
||||
}
|
||||
if (editing) {
|
||||
classList.push('editing');
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
_computeParentHref(repoName) {
|
||||
return this.getBaseUrl() +
|
||||
`/admin/repos/${this.encodeURL(repoName, true)},access`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoAccess.is, GrRepoAccess);
|
||||
|
@ -1,38 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
|
||||
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.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-access-section/gr-access-section.html">
|
||||
|
||||
<script src="../../../scripts/util.js"></script>
|
||||
|
||||
<dom-module id="gr-repo-access">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -73,25 +57,18 @@ limitations under the License.
|
||||
<style include="gr-menu-page-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<main class$="[[_computeMainClass(_ownerOf, _canUpload, _editing)]]">
|
||||
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<main class\$="[[_computeMainClass(_ownerOf, _canUpload, _editing)]]">
|
||||
<div id="loading" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
Loading...
|
||||
</div>
|
||||
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<h3 id="inheritsFrom" class$="[[_computeShowInherit(_inheritsFrom)]]">
|
||||
<div id="loadedContent" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
<h3 id="inheritsFrom" class\$="[[_computeShowInherit(_inheritsFrom)]]">
|
||||
<span class="rightsText">Rights Inherit From</span>
|
||||
<a
|
||||
href$="[[_computeParentHref(_inheritsFrom.name)]]"
|
||||
rel="noopener"
|
||||
id="inheritFromName">
|
||||
<a href\$="[[_computeParentHref(_inheritsFrom.name)]]" rel="noopener" id="inheritFromName">
|
||||
[[_inheritsFrom.name]]</a>
|
||||
<gr-autocomplete
|
||||
id="editInheritFromInput"
|
||||
text="{{_inheritFromFilter}}"
|
||||
query="[[_query]]"
|
||||
on-commit="_handleUpdateInheritFrom"></gr-autocomplete>
|
||||
<gr-autocomplete id="editInheritFromInput" text="{{_inheritFromFilter}}" query="[[_query]]" on-commit="_handleUpdateInheritFrom"></gr-autocomplete>
|
||||
</h3>
|
||||
<div class$="weblinks [[_computeWebLinkClass(_weblinks)]]">
|
||||
<div class\$="weblinks [[_computeWebLinkClass(_weblinks)]]">
|
||||
History:
|
||||
<template is="dom-repeat" items="[[_weblinks]]" as="link">
|
||||
<a href="[[link.url]]" class="weblink" rel="noopener" target="[[link.target]]">
|
||||
@ -99,41 +76,16 @@ limitations under the License.
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
<gr-button id="editBtn"
|
||||
on-click="_handleEdit">[[_editOrCancel(_editing)]]</gr-button>
|
||||
<gr-button id="saveBtn"
|
||||
primary
|
||||
class$="[[_computeSaveBtnClass(_ownerOf)]]"
|
||||
on-click="_handleSave"
|
||||
disabled="[[!_modified]]">Save</gr-button>
|
||||
<gr-button id="saveReviewBtn"
|
||||
primary
|
||||
class$="[[_computeSaveReviewBtnClass(_canUpload)]]"
|
||||
on-click="_handleSaveForReview"
|
||||
disabled="[[!_modified]]">Save for review</gr-button>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="{{_sections}}"
|
||||
initial-count="5"
|
||||
target-framerate="60"
|
||||
as="section">
|
||||
<gr-access-section
|
||||
capabilities="[[_capabilities]]"
|
||||
section="{{section}}"
|
||||
labels="[[_labels]]"
|
||||
can-upload="[[_canUpload]]"
|
||||
editing="[[_editing]]"
|
||||
owner-of="[[_ownerOf]]"
|
||||
groups="[[_groups]]"
|
||||
on-added-section-removed="_handleAddedSectionRemoved"></gr-access-section>
|
||||
<gr-button id="editBtn" on-click="_handleEdit">[[_editOrCancel(_editing)]]</gr-button>
|
||||
<gr-button id="saveBtn" primary="" class\$="[[_computeSaveBtnClass(_ownerOf)]]" on-click="_handleSave" disabled="[[!_modified]]">Save</gr-button>
|
||||
<gr-button id="saveReviewBtn" primary="" class\$="[[_computeSaveReviewBtnClass(_canUpload)]]" on-click="_handleSaveForReview" disabled="[[!_modified]]">Save for review</gr-button>
|
||||
<template is="dom-repeat" items="{{_sections}}" initial-count="5" target-framerate="60" as="section">
|
||||
<gr-access-section capabilities="[[_capabilities]]" section="{{section}}" labels="[[_labels]]" can-upload="[[_canUpload]]" editing="[[_editing]]" owner-of="[[_ownerOf]]" groups="[[_groups]]" on-added-section-removed="_handleAddedSectionRemoved"></gr-access-section>
|
||||
</template>
|
||||
<div class="referenceContainer">
|
||||
<gr-button id="addReferenceBtn"
|
||||
on-click="_handleCreateSection">Add Reference</gr-button>
|
||||
<gr-button id="addReferenceBtn" on-click="_handleCreateSection">Add Reference</gr-button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo-access.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,34 +14,41 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/** @extends Polymer.Element */
|
||||
class GrRepoCommand extends Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element)) {
|
||||
static get is() { return 'gr-repo-command'; }
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-command_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
title: String,
|
||||
disabled: Boolean,
|
||||
tooltip: String,
|
||||
};
|
||||
}
|
||||
/** @extends Polymer.Element */
|
||||
class GrRepoCommand extends GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement)) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
/**
|
||||
* Fired when command button is tapped.
|
||||
*
|
||||
* @event command-tap
|
||||
*/
|
||||
static get is() { return 'gr-repo-command'; }
|
||||
|
||||
_onCommandTap() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('command-tap', {bubbles: true, composed: true}));
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
title: String,
|
||||
disabled: Boolean,
|
||||
tooltip: String,
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrRepoCommand.is, GrRepoCommand);
|
||||
})();
|
||||
/**
|
||||
* Fired when command button is tapped.
|
||||
*
|
||||
* @event command-tap
|
||||
*/
|
||||
|
||||
_onCommandTap() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('command-tap', {bubbles: true, composed: true}));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoCommand.is, GrRepoCommand);
|
||||
|
@ -1,25 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
|
||||
<dom-module id="gr-repo-command">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
@ -27,13 +24,7 @@ limitations under the License.
|
||||
}
|
||||
</style>
|
||||
<h3>[[title]]</h3>
|
||||
<gr-button
|
||||
title$="[[tooltip]]"
|
||||
disabled$="[[disabled]]"
|
||||
on-click
|
||||
="_onCommandTap">
|
||||
<gr-button title\$="[[tooltip]]" disabled\$="[[disabled]]" on-click="_onCommandTap">
|
||||
[[title]]
|
||||
</gr-button>
|
||||
</template>
|
||||
<script src="gr-repo-command.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-repo-command</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-repo-command.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-repo-command.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-command.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,21 +40,24 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-repo-command tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-command.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
suite('gr-repo-command tests', () => {
|
||||
let element;
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('dispatched command-tap on button tap', done => {
|
||||
element.addEventListener('command-tap', () => {
|
||||
done();
|
||||
});
|
||||
MockInteractions.tap(
|
||||
Polymer.dom(element.root).querySelector('gr-button'));
|
||||
});
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
test('dispatched command-tap on button tap', done => {
|
||||
element.addEventListener('command-tap', () => {
|
||||
done();
|
||||
});
|
||||
MockInteractions.tap(
|
||||
dom(element.root).querySelector('gr-button'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,113 +14,131 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const GC_MESSAGE = 'Garbage collection completed successfully.';
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
|
||||
import '../../plugins/gr-endpoint-param/gr-endpoint-param.js';
|
||||
import '../../shared/gr-dialog/gr-dialog.js';
|
||||
import '../../shared/gr-overlay/gr-overlay.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-create-change-dialog/gr-create-change-dialog.js';
|
||||
import '../gr-repo-command/gr-repo-command.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-commands_html.js';
|
||||
|
||||
const CONFIG_BRANCH = 'refs/meta/config';
|
||||
const CONFIG_PATH = 'project.config';
|
||||
const EDIT_CONFIG_SUBJECT = 'Edit Repo Config';
|
||||
const INITIAL_PATCHSET = 1;
|
||||
const CREATE_CHANGE_FAILED_MESSAGE = 'Failed to create change.';
|
||||
const CREATE_CHANGE_SUCCEEDED_MESSAGE = 'Navigating to change';
|
||||
const GC_MESSAGE = 'Garbage collection completed successfully.';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoCommands extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-commands'; }
|
||||
const CONFIG_BRANCH = 'refs/meta/config';
|
||||
const CONFIG_PATH = 'project.config';
|
||||
const EDIT_CONFIG_SUBJECT = 'Edit Repo Config';
|
||||
const INITIAL_PATCHSET = 1;
|
||||
const CREATE_CHANGE_FAILED_MESSAGE = 'Failed to create change.';
|
||||
const CREATE_CHANGE_SUCCEEDED_MESSAGE = 'Navigating to change';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
repo: String,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
_canCreate: Boolean,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoCommands extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadRepo();
|
||||
static get is() { return 'gr-repo-commands'; }
|
||||
|
||||
this.fire('title-change', {title: 'Repo Commands'});
|
||||
}
|
||||
|
||||
_loadRepo() {
|
||||
if (!this.repo) { return Promise.resolve(); }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getProjectConfig(this.repo, errFn)
|
||||
.then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
this._repoConfig = config;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_handleRunningGC() {
|
||||
return this.$.restAPI.runRepoGC(this.repo).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'show-alert',
|
||||
{detail: {message: GC_MESSAGE}, bubbles: true, composed: true}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_createNewChange() {
|
||||
this.$.createChangeOverlay.open();
|
||||
}
|
||||
|
||||
_handleCreateChange() {
|
||||
this.$.createNewChangeModal.handleCreateChange();
|
||||
this._handleCloseCreateChange();
|
||||
}
|
||||
|
||||
_handleCloseCreateChange() {
|
||||
this.$.createChangeOverlay.close();
|
||||
}
|
||||
|
||||
_handleEditRepoConfig() {
|
||||
return this.$.restAPI.createChange(this.repo, CONFIG_BRANCH,
|
||||
EDIT_CONFIG_SUBJECT, undefined, false, true).then(change => {
|
||||
const message = change ?
|
||||
CREATE_CHANGE_SUCCEEDED_MESSAGE :
|
||||
CREATE_CHANGE_FAILED_MESSAGE;
|
||||
this.dispatchEvent(new CustomEvent('show-alert',
|
||||
{detail: {message}, bubbles: true, composed: true}));
|
||||
if (!change) { return; }
|
||||
|
||||
Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
|
||||
change, CONFIG_PATH, INITIAL_PATCHSET));
|
||||
});
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
repo: String,
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
_canCreate: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrRepoCommands.is, GrRepoCommands);
|
||||
})();
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadRepo();
|
||||
|
||||
this.fire('title-change', {title: 'Repo Commands'});
|
||||
}
|
||||
|
||||
_loadRepo() {
|
||||
if (!this.repo) { return Promise.resolve(); }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
return this.$.restAPI.getProjectConfig(this.repo, errFn)
|
||||
.then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
this._repoConfig = config;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_handleRunningGC() {
|
||||
return this.$.restAPI.runRepoGC(this.repo).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'show-alert',
|
||||
{detail: {message: GC_MESSAGE}, bubbles: true, composed: true}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_createNewChange() {
|
||||
this.$.createChangeOverlay.open();
|
||||
}
|
||||
|
||||
_handleCreateChange() {
|
||||
this.$.createNewChangeModal.handleCreateChange();
|
||||
this._handleCloseCreateChange();
|
||||
}
|
||||
|
||||
_handleCloseCreateChange() {
|
||||
this.$.createChangeOverlay.close();
|
||||
}
|
||||
|
||||
_handleEditRepoConfig() {
|
||||
return this.$.restAPI.createChange(this.repo, CONFIG_BRANCH,
|
||||
EDIT_CONFIG_SUBJECT, undefined, false, true).then(change => {
|
||||
const message = change ?
|
||||
CREATE_CHANGE_SUCCEEDED_MESSAGE :
|
||||
CREATE_CHANGE_FAILED_MESSAGE;
|
||||
this.dispatchEvent(new CustomEvent('show-alert',
|
||||
{detail: {message}, bubbles: true, composed: true}));
|
||||
if (!change) { return; }
|
||||
|
||||
Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
|
||||
change, CONFIG_PATH, INITIAL_PATCHSET));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoCommands.is, GrRepoCommands);
|
||||
|
@ -1,36 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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-autogrow-textarea/iron-autogrow-textarea.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
|
||||
<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
|
||||
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-create-change-dialog/gr-create-change-dialog.html">
|
||||
<link rel="import" href="../gr-repo-command/gr-repo-command.html">
|
||||
|
||||
<dom-module id="gr-repo-commands">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -42,24 +28,15 @@ limitations under the License.
|
||||
</style>
|
||||
<main class="gr-form-styles read-only">
|
||||
<h1 id="Title">Repository Commands</h1>
|
||||
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">Loading...</div>
|
||||
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="loading" class\$="[[_computeLoadingClass(_loading)]]">Loading...</div>
|
||||
<div id="loadedContent" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
<h2 id="options">Command</h2>
|
||||
<div id="form">
|
||||
<gr-repo-command
|
||||
title="Create change"
|
||||
on-command-tap="_createNewChange">
|
||||
<gr-repo-command title="Create change" on-command-tap="_createNewChange">
|
||||
</gr-repo-command>
|
||||
<gr-repo-command
|
||||
id="editRepoConfig"
|
||||
title="Edit repo config"
|
||||
on-command-tap="_handleEditRepoConfig">
|
||||
<gr-repo-command id="editRepoConfig" title="Edit repo config" on-command-tap="_handleEditRepoConfig">
|
||||
</gr-repo-command>
|
||||
<gr-repo-command
|
||||
title="[[_repoConfig.actions.gc.label]]"
|
||||
tooltip="[[_repoConfig.actions.gc.title]]"
|
||||
hidden$="[[!_repoConfig.actions.gc.enabled]]"
|
||||
on-command-tap="_handleRunningGC">
|
||||
<gr-repo-command title="[[_repoConfig.actions.gc.label]]" tooltip="[[_repoConfig.actions.gc.title]]" hidden\$="[[!_repoConfig.actions.gc.enabled]]" on-command-tap="_handleRunningGC">
|
||||
</gr-repo-command>
|
||||
<gr-endpoint-decorator name="repo-command">
|
||||
<gr-endpoint-param name="config" value="[[_repoConfig]]">
|
||||
@ -70,25 +47,15 @@ limitations under the License.
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<gr-overlay id="createChangeOverlay" with-backdrop>
|
||||
<gr-dialog
|
||||
id="createChangeDialog"
|
||||
confirm-label="Create"
|
||||
disabled="[[!_canCreate]]"
|
||||
on-confirm="_handleCreateChange"
|
||||
on-cancel="_handleCloseCreateChange">
|
||||
<gr-overlay id="createChangeOverlay" with-backdrop="">
|
||||
<gr-dialog id="createChangeDialog" confirm-label="Create" disabled="[[!_canCreate]]" on-confirm="_handleCreateChange" on-cancel="_handleCloseCreateChange">
|
||||
<div class="header" slot="header">
|
||||
Create Change
|
||||
</div>
|
||||
<div class="main" slot="main">
|
||||
<gr-create-change-dialog
|
||||
id="createNewChangeModal"
|
||||
can-create="{{_canCreate}}"
|
||||
repo-name="[[repo]]"></gr-create-change-dialog>
|
||||
<gr-create-change-dialog id="createNewChangeModal" can-create="{{_canCreate}}" repo-name="[[repo]]"></gr-create-change-dialog>
|
||||
</div>
|
||||
</gr-dialog>
|
||||
</gr-overlay>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo-commands.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-repo-commands</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-repo-commands.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-repo-commands.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-commands.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,111 +41,113 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-repo-commands tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
let repoStub;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-commands.js';
|
||||
suite('gr-repo-commands tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
let repoStub;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
repoStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getProjectConfig',
|
||||
() => Promise.resolve({}));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('create new change dialog', () => {
|
||||
test('_createNewChange opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createChangeOverlay, 'open');
|
||||
element._createNewChange();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateChange called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateChange');
|
||||
element.$.createChangeDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateChange.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreateChange called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreateChange');
|
||||
element.$.createChangeDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreateChange.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('edit repo config', () => {
|
||||
let createChangeStub;
|
||||
let urlStub;
|
||||
let handleSpy;
|
||||
let alertStub;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
repoStub = sandbox.stub(
|
||||
element.$.restAPI,
|
||||
'getProjectConfig',
|
||||
() => Promise.resolve({}));
|
||||
createChangeStub = sandbox.stub(element.$.restAPI, 'createChange');
|
||||
urlStub = sandbox.stub(Gerrit.Nav, 'getEditUrlForDiff');
|
||||
sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
|
||||
handleSpy = sandbox.spy(element, '_handleEditRepoConfig');
|
||||
alertStub = sandbox.stub();
|
||||
element.addEventListener('show-alert', alertStub);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
test('successful creation of change', () => {
|
||||
const change = {_number: '1'};
|
||||
createChangeStub.returns(Promise.resolve(change));
|
||||
MockInteractions.tap(element.$.editRepoConfig.shadowRoot
|
||||
.querySelector('gr-button'));
|
||||
return handleSpy.lastCall.returnValue.then(() => {
|
||||
flushAsynchronousOperations();
|
||||
|
||||
suite('create new change dialog', () => {
|
||||
test('_createNewChange opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createChangeOverlay, 'open');
|
||||
element._createNewChange();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateChange called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateChange');
|
||||
element.$.createChangeDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateChange.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreateChange called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreateChange');
|
||||
element.$.createChangeDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreateChange.called);
|
||||
assert.isTrue(alertStub.called);
|
||||
assert.equal(alertStub.lastCall.args[0].detail.message,
|
||||
'Navigating to change');
|
||||
assert.isTrue(urlStub.called);
|
||||
assert.deepEqual(urlStub.lastCall.args,
|
||||
[change, 'project.config', 1]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('edit repo config', () => {
|
||||
let createChangeStub;
|
||||
let urlStub;
|
||||
let handleSpy;
|
||||
let alertStub;
|
||||
test('unsuccessful creation of change', () => {
|
||||
createChangeStub.returns(Promise.resolve(null));
|
||||
MockInteractions.tap(element.$.editRepoConfig.shadowRoot
|
||||
.querySelector('gr-button'));
|
||||
return handleSpy.lastCall.returnValue.then(() => {
|
||||
flushAsynchronousOperations();
|
||||
|
||||
setup(() => {
|
||||
createChangeStub = sandbox.stub(element.$.restAPI, 'createChange');
|
||||
urlStub = sandbox.stub(Gerrit.Nav, 'getEditUrlForDiff');
|
||||
sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
|
||||
handleSpy = sandbox.spy(element, '_handleEditRepoConfig');
|
||||
alertStub = sandbox.stub();
|
||||
element.addEventListener('show-alert', alertStub);
|
||||
});
|
||||
|
||||
test('successful creation of change', () => {
|
||||
const change = {_number: '1'};
|
||||
createChangeStub.returns(Promise.resolve(change));
|
||||
MockInteractions.tap(element.$.editRepoConfig.shadowRoot
|
||||
.querySelector('gr-button'));
|
||||
return handleSpy.lastCall.returnValue.then(() => {
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(alertStub.called);
|
||||
assert.equal(alertStub.lastCall.args[0].detail.message,
|
||||
'Navigating to change');
|
||||
assert.isTrue(urlStub.called);
|
||||
assert.deepEqual(urlStub.lastCall.args,
|
||||
[change, 'project.config', 1]);
|
||||
});
|
||||
});
|
||||
|
||||
test('unsuccessful creation of change', () => {
|
||||
createChangeStub.returns(Promise.resolve(null));
|
||||
MockInteractions.tap(element.$.editRepoConfig.shadowRoot
|
||||
.querySelector('gr-button'));
|
||||
return handleSpy.lastCall.returnValue.then(() => {
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(alertStub.called);
|
||||
assert.equal(alertStub.lastCall.args[0].detail.message,
|
||||
'Failed to create change.');
|
||||
assert.isFalse(urlStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
repoStub.restore();
|
||||
|
||||
element.repo = 'test';
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getProjectConfig', (repo, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadRepo();
|
||||
assert.isTrue(alertStub.called);
|
||||
assert.equal(alertStub.lastCall.args[0].detail.message,
|
||||
'Failed to create change.');
|
||||
assert.isFalse(urlStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
repoStub.restore();
|
||||
|
||||
element.repo = 'test';
|
||||
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getProjectConfig', (repo, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element._loadRepo();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
* 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.
|
||||
@ -14,89 +14,100 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoDashboards extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-dashboards'; }
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../core/gr-navigation/gr-navigation.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-dashboards_html.js';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
repo: {
|
||||
type: String,
|
||||
observer: '_repoChanged',
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_dashboards: Array,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoDashboards extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
_repoChanged(repo) {
|
||||
this._loading = true;
|
||||
if (!repo) { return Promise.resolve(); }
|
||||
static get is() { return 'gr-repo-dashboards'; }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
this.$.restAPI.getRepoDashboards(this.repo, errFn).then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
// Group by ref and sort by id.
|
||||
const dashboards = res.concat.apply([], res).sort((a, b) =>
|
||||
(a.id < b.id ? -1 : 1));
|
||||
const dashboardsByRef = {};
|
||||
dashboards.forEach(d => {
|
||||
if (!dashboardsByRef[d.ref]) {
|
||||
dashboardsByRef[d.ref] = [];
|
||||
}
|
||||
dashboardsByRef[d.ref].push(d);
|
||||
});
|
||||
|
||||
const dashboardBuilder = [];
|
||||
Object.keys(dashboardsByRef).sort()
|
||||
.forEach(ref => {
|
||||
dashboardBuilder.push({
|
||||
section: ref,
|
||||
dashboards: dashboardsByRef[ref],
|
||||
});
|
||||
});
|
||||
|
||||
this._dashboards = dashboardBuilder;
|
||||
this._loading = false;
|
||||
Polymer.dom.flush();
|
||||
});
|
||||
}
|
||||
|
||||
_getUrl(project, id) {
|
||||
if (!project || !id) { return ''; }
|
||||
|
||||
return Gerrit.Nav.getUrlForRepoDashboard(project, id);
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_computeInheritedFrom(project, definingProject) {
|
||||
return project === definingProject ? '' : definingProject;
|
||||
}
|
||||
|
||||
_computeIsDefault(isDefault) {
|
||||
return isDefault ? '✓' : '';
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
repo: {
|
||||
type: String,
|
||||
observer: '_repoChanged',
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_dashboards: Array,
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrRepoDashboards.is, GrRepoDashboards);
|
||||
})();
|
||||
_repoChanged(repo) {
|
||||
this._loading = true;
|
||||
if (!repo) { return Promise.resolve(); }
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
this.$.restAPI.getRepoDashboards(this.repo, errFn).then(res => {
|
||||
if (!res) { return Promise.resolve(); }
|
||||
|
||||
// Group by ref and sort by id.
|
||||
const dashboards = res.concat.apply([], res).sort((a, b) =>
|
||||
(a.id < b.id ? -1 : 1));
|
||||
const dashboardsByRef = {};
|
||||
dashboards.forEach(d => {
|
||||
if (!dashboardsByRef[d.ref]) {
|
||||
dashboardsByRef[d.ref] = [];
|
||||
}
|
||||
dashboardsByRef[d.ref].push(d);
|
||||
});
|
||||
|
||||
const dashboardBuilder = [];
|
||||
Object.keys(dashboardsByRef).sort()
|
||||
.forEach(ref => {
|
||||
dashboardBuilder.push({
|
||||
section: ref,
|
||||
dashboards: dashboardsByRef[ref],
|
||||
});
|
||||
});
|
||||
|
||||
this._dashboards = dashboardBuilder;
|
||||
this._loading = false;
|
||||
flush();
|
||||
});
|
||||
}
|
||||
|
||||
_getUrl(project, id) {
|
||||
if (!project || !id) { return ''; }
|
||||
|
||||
return Gerrit.Nav.getUrlForRepoDashboard(project, id);
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_computeInheritedFrom(project, definingProject) {
|
||||
return project === definingProject ? '' : definingProject;
|
||||
}
|
||||
|
||||
_computeIsDefault(isDefault) {
|
||||
return isDefault ? '✓' : '';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoDashboards.is, GrRepoDashboards);
|
||||
|
@ -1,27 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.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">
|
||||
|
||||
<dom-module id="gr-repo-dashboards">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
@ -38,8 +33,8 @@ limitations under the License.
|
||||
<style include="gr-table-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<table id="list" class$="genericList [[_computeLoadingClass(_loading)]]">
|
||||
<tr class="headerRow">
|
||||
<table id="list" class\$="genericList [[_computeLoadingClass(_loading)]]">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="topHeader">Dashboard name</th>
|
||||
<th class="topHeader">Dashboard title</th>
|
||||
<th class="topHeader">Dashboard description</th>
|
||||
@ -49,14 +44,14 @@ limitations under the License.
|
||||
<tr id="loadingContainer">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody id="dashboards">
|
||||
</tbody><tbody id="dashboards">
|
||||
<template is="dom-repeat" items="[[_dashboards]]">
|
||||
<tr class="groupHeader">
|
||||
<td colspan="5">[[item.section]]</td>
|
||||
</tr>
|
||||
<template is="dom-repeat" items="[[item.dashboards]]">
|
||||
<tr class="table">
|
||||
<td class="name"><a href$="[[_getUrl(item.project, item.id)]]">[[item.path]]</a></td>
|
||||
<td class="name"><a href\$="[[_getUrl(item.project, item.id)]]">[[item.path]]</a></td>
|
||||
<td class="title">[[item.title]]</td>
|
||||
<td class="desc">[[item.description]]</td>
|
||||
<td class="inherited">[[_computeInheritedFrom(item.project, item.defining_project)]]</td>
|
||||
@ -67,6 +62,4 @@ limitations under the License.
|
||||
</tbody>
|
||||
</table>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo-dashboards.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-repo-dashboards</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-repo-dashboards.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-repo-dashboards.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-dashboards.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,128 +40,130 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-repo-dashboards tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-dashboards.js';
|
||||
suite('gr-repo-dashboards tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('dashboard table', () => {
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
sandbox.stub(element.$.restAPI, 'getRepoDashboards').returns(
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 'default:contributor',
|
||||
project: 'gerrit',
|
||||
defining_project: 'gerrit',
|
||||
ref: 'default',
|
||||
path: 'contributor',
|
||||
description: 'Own contributions.',
|
||||
foreach: 'owner:self',
|
||||
url: '/dashboard/?params',
|
||||
title: 'Contributor Dashboard',
|
||||
sections: [
|
||||
{
|
||||
name: 'Mine To Rebase',
|
||||
query: 'is:open -is:mergeable',
|
||||
},
|
||||
{
|
||||
name: 'My Recently Merged',
|
||||
query: 'is:merged limit:10',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom:custom2',
|
||||
project: 'gerrit',
|
||||
defining_project: 'Public-Projects',
|
||||
ref: 'custom',
|
||||
path: 'open',
|
||||
description: 'Recent open changes.',
|
||||
url: '/dashboard/?params',
|
||||
title: 'Open Changes',
|
||||
sections: [
|
||||
{
|
||||
name: 'Open Changes',
|
||||
query: 'status:open project:${project} -age:7w',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'default:abc',
|
||||
project: 'gerrit',
|
||||
ref: 'default',
|
||||
},
|
||||
{
|
||||
id: 'custom:custom1',
|
||||
project: 'gerrit',
|
||||
ref: 'custom',
|
||||
},
|
||||
]));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('dashboard table', () => {
|
||||
setup(() => {
|
||||
sandbox.stub(element.$.restAPI, 'getRepoDashboards').returns(
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 'default:contributor',
|
||||
project: 'gerrit',
|
||||
defining_project: 'gerrit',
|
||||
ref: 'default',
|
||||
path: 'contributor',
|
||||
description: 'Own contributions.',
|
||||
foreach: 'owner:self',
|
||||
url: '/dashboard/?params',
|
||||
title: 'Contributor Dashboard',
|
||||
sections: [
|
||||
{
|
||||
name: 'Mine To Rebase',
|
||||
query: 'is:open -is:mergeable',
|
||||
},
|
||||
{
|
||||
name: 'My Recently Merged',
|
||||
query: 'is:merged limit:10',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'custom:custom2',
|
||||
project: 'gerrit',
|
||||
defining_project: 'Public-Projects',
|
||||
ref: 'custom',
|
||||
path: 'open',
|
||||
description: 'Recent open changes.',
|
||||
url: '/dashboard/?params',
|
||||
title: 'Open Changes',
|
||||
sections: [
|
||||
{
|
||||
name: 'Open Changes',
|
||||
query: 'status:open project:${project} -age:7w',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'default:abc',
|
||||
project: 'gerrit',
|
||||
ref: 'default',
|
||||
},
|
||||
{
|
||||
id: 'custom:custom1',
|
||||
project: 'gerrit',
|
||||
ref: 'custom',
|
||||
},
|
||||
]));
|
||||
});
|
||||
|
||||
test('loading, sections, and ordering', done => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.notEqual(getComputedStyle(element.$.loadingContainer).display,
|
||||
test('loading, sections, and ordering', done => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.notEqual(getComputedStyle(element.$.loadingContainer).display,
|
||||
'none');
|
||||
assert.equal(getComputedStyle(element.$.dashboards).display,
|
||||
'none');
|
||||
element.repo = 'test';
|
||||
flush(() => {
|
||||
assert.equal(getComputedStyle(element.$.loadingContainer).display,
|
||||
'none');
|
||||
assert.equal(getComputedStyle(element.$.dashboards).display,
|
||||
assert.notEqual(getComputedStyle(element.$.dashboards).display,
|
||||
'none');
|
||||
element.repo = 'test';
|
||||
flush(() => {
|
||||
assert.equal(getComputedStyle(element.$.loadingContainer).display,
|
||||
'none');
|
||||
assert.notEqual(getComputedStyle(element.$.dashboards).display,
|
||||
'none');
|
||||
|
||||
assert.equal(element._dashboards.length, 2);
|
||||
assert.equal(element._dashboards[0].section, 'custom');
|
||||
assert.equal(element._dashboards[1].section, 'default');
|
||||
assert.equal(element._dashboards.length, 2);
|
||||
assert.equal(element._dashboards[0].section, 'custom');
|
||||
assert.equal(element._dashboards[1].section, 'default');
|
||||
|
||||
const dashboards = element._dashboards[0].dashboards;
|
||||
assert.equal(dashboards.length, 2);
|
||||
assert.equal(dashboards[0].id, 'custom:custom1');
|
||||
assert.equal(dashboards[1].id, 'custom:custom2');
|
||||
const dashboards = element._dashboards[0].dashboards;
|
||||
assert.equal(dashboards.length, 2);
|
||||
assert.equal(dashboards[0].id, 'custom:custom1');
|
||||
assert.equal(dashboards[1].id, 'custom:custom2');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('test url', () => {
|
||||
test('_getUrl', () => {
|
||||
sandbox.stub(Gerrit.Nav, 'getUrlForRepoDashboard',
|
||||
() => '/r/dashboard/test');
|
||||
|
||||
assert.equal(element._getUrl('/dashboard/test', {}), '/r/dashboard/test');
|
||||
|
||||
assert.equal(element._getUrl(undefined, undefined), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getRepoDashboards', (repo, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element.repo = 'test';
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('test url', () => {
|
||||
test('_getUrl', () => {
|
||||
sandbox.stub(Gerrit.Nav, 'getUrlForRepoDashboard',
|
||||
() => '/r/dashboard/test');
|
||||
|
||||
assert.equal(element._getUrl('/dashboard/test', {}), '/r/dashboard/test');
|
||||
|
||||
assert.equal(element._getUrl(undefined, undefined), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('404', () => {
|
||||
test('fires page-error', done => {
|
||||
const response = {status: 404};
|
||||
sandbox.stub(
|
||||
element.$.restAPI, 'getRepoDashboards', (repo, errFn) => {
|
||||
errFn(response);
|
||||
});
|
||||
|
||||
element.addEventListener('page-error', e => {
|
||||
assert.deepEqual(e.detail.response, response);
|
||||
done();
|
||||
});
|
||||
|
||||
element.repo = 'test';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,279 +14,302 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.js';
|
||||
|
||||
const DETAIL_TYPES = {
|
||||
BRANCHES: 'branches',
|
||||
TAGS: 'tags',
|
||||
};
|
||||
import '../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-account-link/gr-account-link.js';
|
||||
import '../../shared/gr-button/gr-button.js';
|
||||
import '../../shared/gr-date-formatter/gr-date-formatter.js';
|
||||
import '../../shared/gr-dialog/gr-dialog.js';
|
||||
import '../../shared/gr-list-view/gr-list-view.js';
|
||||
import '../../shared/gr-overlay/gr-overlay.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-create-pointer-dialog/gr-create-pointer-dialog.js';
|
||||
import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.js';
|
||||
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-detail-list_html.js';
|
||||
|
||||
const PGP_START = '-----BEGIN PGP SIGNATURE-----';
|
||||
const DETAIL_TYPES = {
|
||||
BRANCHES: 'branches',
|
||||
TAGS: 'tags',
|
||||
};
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoDetailList extends Polymer.mixinBehaviors( [
|
||||
Gerrit.ListViewBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-detail-list'; }
|
||||
const PGP_START = '-----BEGIN PGP SIGNATURE-----';
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @appliesMixin Gerrit.URLEncodingMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoDetailList extends mixinBehaviors( [
|
||||
Gerrit.ListViewBehavior,
|
||||
Gerrit.FireBehavior,
|
||||
Gerrit.URLEncodingBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-repo-detail-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
* The kind of detail we are displaying, possibilities are determined by
|
||||
* the const DETAIL_TYPES.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
/**
|
||||
* The kind of detail we are displaying, possibilities are determined by
|
||||
* the const DETAIL_TYPES.
|
||||
*/
|
||||
detailType: String,
|
||||
detailType: String,
|
||||
|
||||
_editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loggedIn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_repo: Object,
|
||||
_items: Array,
|
||||
/**
|
||||
* Because we request one more than the projectsPerPage, _shownProjects
|
||||
* maybe one less than _projects.
|
||||
*/
|
||||
_shownItems: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_items)',
|
||||
},
|
||||
_itemsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: String,
|
||||
_refName: String,
|
||||
_hasNewItemName: Boolean,
|
||||
_isEditing: Boolean,
|
||||
_revisedRef: String,
|
||||
};
|
||||
}
|
||||
_editing: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isOwner: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loggedIn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_repo: Object,
|
||||
_items: Array,
|
||||
/**
|
||||
* Because we request one more than the projectsPerPage, _shownProjects
|
||||
* maybe one less than _projects.
|
||||
*/
|
||||
_shownItems: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_items)',
|
||||
},
|
||||
_itemsPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: String,
|
||||
_refName: String,
|
||||
_hasNewItemName: Boolean,
|
||||
_isEditing: Boolean,
|
||||
_revisedRef: String,
|
||||
};
|
||||
}
|
||||
|
||||
_determineIfOwner(repo) {
|
||||
return this.$.restAPI.getRepoAccess(repo)
|
||||
.then(access =>
|
||||
this._isOwner = access && !!access[repo].is_owner);
|
||||
}
|
||||
_determineIfOwner(repo) {
|
||||
return this.$.restAPI.getRepoAccess(repo)
|
||||
.then(access =>
|
||||
this._isOwner = access && !!access[repo].is_owner);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
if (!params || !params.repo) { return; }
|
||||
_paramsChanged(params) {
|
||||
if (!params || !params.repo) { return; }
|
||||
|
||||
this._repo = params.repo;
|
||||
this._repo = params.repo;
|
||||
|
||||
this._getLoggedIn().then(loggedIn => {
|
||||
this._loggedIn = loggedIn;
|
||||
if (loggedIn) {
|
||||
this._determineIfOwner(this._repo);
|
||||
}
|
||||
this._getLoggedIn().then(loggedIn => {
|
||||
this._loggedIn = loggedIn;
|
||||
if (loggedIn) {
|
||||
this._determineIfOwner(this._repo);
|
||||
}
|
||||
});
|
||||
|
||||
this.detailType = params.detail;
|
||||
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getItems(this._filter, this._repo,
|
||||
this._itemsPerPage, this._offset, this.detailType);
|
||||
}
|
||||
|
||||
_getItems(filter, repo, itemsPerPage, offset, detailType) {
|
||||
this._loading = true;
|
||||
this._items = [];
|
||||
flush();
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return this.$.restAPI.getRepoBranches(
|
||||
filter, repo, itemsPerPage, offset, errFn).then(items => {
|
||||
if (!items) { return; }
|
||||
this._items = items;
|
||||
this._loading = false;
|
||||
});
|
||||
|
||||
this.detailType = params.detail;
|
||||
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getItems(this._filter, this._repo,
|
||||
this._itemsPerPage, this._offset, this.detailType);
|
||||
}
|
||||
|
||||
_getItems(filter, repo, itemsPerPage, offset, detailType) {
|
||||
this._loading = true;
|
||||
this._items = [];
|
||||
Polymer.dom.flush();
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return this.$.restAPI.getRepoBranches(
|
||||
filter, repo, itemsPerPage, offset, errFn).then(items => {
|
||||
if (!items) { return; }
|
||||
this._items = items;
|
||||
this._loading = false;
|
||||
});
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return this.$.restAPI.getRepoTags(
|
||||
filter, repo, itemsPerPage, offset, errFn).then(items => {
|
||||
if (!items) { return; }
|
||||
this._items = items;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getPath(repo) {
|
||||
return `/admin/repos/${this.encodeURL(repo, false)},` +
|
||||
`${this.detailType}`;
|
||||
}
|
||||
|
||||
_computeWeblink(repo) {
|
||||
if (!repo.web_links) { return ''; }
|
||||
const webLinks = repo.web_links;
|
||||
return webLinks.length ? webLinks : null;
|
||||
}
|
||||
|
||||
_computeMessage(message) {
|
||||
if (!message) { return; }
|
||||
// Strip PGP info.
|
||||
return message.split(PGP_START)[0];
|
||||
}
|
||||
|
||||
_stripRefs(item, detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return item.replace('refs/heads/', '');
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return item.replace('refs/tags/', '');
|
||||
}
|
||||
}
|
||||
|
||||
_getLoggedIn() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
}
|
||||
|
||||
_computeEditingClass(isEditing) {
|
||||
return isEditing ? 'editing' : '';
|
||||
}
|
||||
|
||||
_computeCanEditClass(ref, detailType, isOwner) {
|
||||
return isOwner && this._stripRefs(ref, detailType) === 'HEAD' ?
|
||||
'canEdit' : '';
|
||||
}
|
||||
|
||||
_handleEditRevision(e) {
|
||||
this._revisedRef = e.model.get('item.revision');
|
||||
this._isEditing = true;
|
||||
}
|
||||
|
||||
_handleCancelRevision() {
|
||||
this._isEditing = false;
|
||||
}
|
||||
|
||||
_handleSaveRevision(e) {
|
||||
this._setRepoHead(this._repo, this._revisedRef, e);
|
||||
}
|
||||
|
||||
_setRepoHead(repo, ref, e) {
|
||||
return this.$.restAPI.setRepoHead(repo, ref).then(res => {
|
||||
if (res.status < 400) {
|
||||
this._isEditing = false;
|
||||
e.model.set('item.revision', ref);
|
||||
// This is needed to refresh _items property with fresh data,
|
||||
// specifically can_delete from the json response.
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return this.$.restAPI.getRepoTags(
|
||||
filter, repo, itemsPerPage, offset, errFn).then(items => {
|
||||
if (!items) { return; }
|
||||
this._items = items;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_computeItemName(detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return 'Branch';
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return 'Tag';
|
||||
}
|
||||
}
|
||||
|
||||
_handleDeleteItemConfirm() {
|
||||
this.$.overlay.close();
|
||||
if (this.detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return this.$.restAPI.deleteRepoBranches(this._repo, this._refName)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
});
|
||||
} else if (this.detailType === DETAIL_TYPES.TAGS) {
|
||||
return this.$.restAPI.deleteRepoTags(this._repo, this._refName)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleConfirmDialogCancel() {
|
||||
this.$.overlay.close();
|
||||
}
|
||||
|
||||
_handleDeleteItem(e) {
|
||||
const name = this._stripRefs(e.model.get('item.ref'), this.detailType);
|
||||
if (!name) { return; }
|
||||
this._refName = name;
|
||||
this.$.overlay.open();
|
||||
}
|
||||
|
||||
_computeHideDeleteClass(owner, canDelete) {
|
||||
if (canDelete || owner) {
|
||||
return 'show';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_handleCreateItem() {
|
||||
this.$.createNewModal.handleCreateItem();
|
||||
this._handleCloseCreate();
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_hideIfBranch(type) {
|
||||
if (type === DETAIL_TYPES.BRANCHES) {
|
||||
return 'hideItem';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_computeHideTagger(tagger) {
|
||||
return tagger ? '' : 'hide';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoDetailList.is, GrRepoDetailList);
|
||||
})();
|
||||
_getPath(repo) {
|
||||
return `/admin/repos/${this.encodeURL(repo, false)},` +
|
||||
`${this.detailType}`;
|
||||
}
|
||||
|
||||
_computeWeblink(repo) {
|
||||
if (!repo.web_links) { return ''; }
|
||||
const webLinks = repo.web_links;
|
||||
return webLinks.length ? webLinks : null;
|
||||
}
|
||||
|
||||
_computeMessage(message) {
|
||||
if (!message) { return; }
|
||||
// Strip PGP info.
|
||||
return message.split(PGP_START)[0];
|
||||
}
|
||||
|
||||
_stripRefs(item, detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return item.replace('refs/heads/', '');
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return item.replace('refs/tags/', '');
|
||||
}
|
||||
}
|
||||
|
||||
_getLoggedIn() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
}
|
||||
|
||||
_computeEditingClass(isEditing) {
|
||||
return isEditing ? 'editing' : '';
|
||||
}
|
||||
|
||||
_computeCanEditClass(ref, detailType, isOwner) {
|
||||
return isOwner && this._stripRefs(ref, detailType) === 'HEAD' ?
|
||||
'canEdit' : '';
|
||||
}
|
||||
|
||||
_handleEditRevision(e) {
|
||||
this._revisedRef = e.model.get('item.revision');
|
||||
this._isEditing = true;
|
||||
}
|
||||
|
||||
_handleCancelRevision() {
|
||||
this._isEditing = false;
|
||||
}
|
||||
|
||||
_handleSaveRevision(e) {
|
||||
this._setRepoHead(this._repo, this._revisedRef, e);
|
||||
}
|
||||
|
||||
_setRepoHead(repo, ref, e) {
|
||||
return this.$.restAPI.setRepoHead(repo, ref).then(res => {
|
||||
if (res.status < 400) {
|
||||
this._isEditing = false;
|
||||
e.model.set('item.revision', ref);
|
||||
// This is needed to refresh _items property with fresh data,
|
||||
// specifically can_delete from the json response.
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_computeItemName(detailType) {
|
||||
if (detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return 'Branch';
|
||||
} else if (detailType === DETAIL_TYPES.TAGS) {
|
||||
return 'Tag';
|
||||
}
|
||||
}
|
||||
|
||||
_handleDeleteItemConfirm() {
|
||||
this.$.overlay.close();
|
||||
if (this.detailType === DETAIL_TYPES.BRANCHES) {
|
||||
return this.$.restAPI.deleteRepoBranches(this._repo, this._refName)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
});
|
||||
} else if (this.detailType === DETAIL_TYPES.TAGS) {
|
||||
return this.$.restAPI.deleteRepoTags(this._repo, this._refName)
|
||||
.then(itemDeleted => {
|
||||
if (itemDeleted.status === 204) {
|
||||
this._getItems(
|
||||
this._filter, this._repo, this._itemsPerPage,
|
||||
this._offset, this.detailType);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleConfirmDialogCancel() {
|
||||
this.$.overlay.close();
|
||||
}
|
||||
|
||||
_handleDeleteItem(e) {
|
||||
const name = this._stripRefs(e.model.get('item.ref'), this.detailType);
|
||||
if (!name) { return; }
|
||||
this._refName = name;
|
||||
this.$.overlay.open();
|
||||
}
|
||||
|
||||
_computeHideDeleteClass(owner, canDelete) {
|
||||
if (canDelete || owner) {
|
||||
return 'show';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_handleCreateItem() {
|
||||
this.$.createNewModal.handleCreateItem();
|
||||
this._handleCloseCreate();
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_hideIfBranch(type) {
|
||||
if (type === DETAIL_TYPES.BRANCHES) {
|
||||
return 'hideItem';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
_computeHideTagger(tagger) {
|
||||
return tagger ? '' : 'hide';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoDetailList.is, GrRepoDetailList);
|
||||
|
@ -1,40 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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/gr-list-view-behavior/gr-list-view-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="/bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
|
||||
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
|
||||
<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-create-pointer-dialog/gr-create-pointer-dialog.html">
|
||||
<link rel="import" href="../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html">
|
||||
|
||||
<dom-module id="gr-repo-detail-list">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="gr-form-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -88,100 +70,68 @@ limitations under the License.
|
||||
<style include="gr-table-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<gr-list-view
|
||||
create-new="[[_loggedIn]]"
|
||||
filter="[[_filter]]"
|
||||
items-per-page="[[_itemsPerPage]]"
|
||||
items="[[_items]]"
|
||||
loading="[[_loading]]"
|
||||
offset="[[_offset]]"
|
||||
on-create-clicked="_handleCreateClicked"
|
||||
path="[[_getPath(_repo, detailType)]]">
|
||||
<gr-list-view create-new="[[_loggedIn]]" filter="[[_filter]]" items-per-page="[[_itemsPerPage]]" items="[[_items]]" loading="[[_loading]]" offset="[[_offset]]" on-create-clicked="_handleCreateClicked" path="[[_getPath(_repo, detailType)]]">
|
||||
<table id="list" class="genericList gr-form-styles">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="name topHeader">Name</th>
|
||||
<th class="revision topHeader">Revision</th>
|
||||
<th class$="message topHeader [[_hideIfBranch(detailType)]]">
|
||||
<th class\$="message topHeader [[_hideIfBranch(detailType)]]">
|
||||
Message</th>
|
||||
<th class$="tagger topHeader [[_hideIfBranch(detailType)]]">
|
||||
<th class\$="tagger topHeader [[_hideIfBranch(detailType)]]">
|
||||
Tagger</th>
|
||||
<th class="repositoryBrowser topHeader">
|
||||
Repository Browser</th>
|
||||
<th class="delete topHeader"></th>
|
||||
</tr>
|
||||
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<tr id="loading" class\$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody class$="[[computeLoadingClass(_loading)]]">
|
||||
</tbody><tbody class\$="[[computeLoadingClass(_loading)]]">
|
||||
<template is="dom-repeat" items="[[_shownItems]]">
|
||||
<tr class="table">
|
||||
<td class$="[[detailType]] name">[[_stripRefs(item.ref, detailType)]]</td>
|
||||
<td class$="[[detailType]] revision [[_computeCanEditClass(item.ref, detailType, _isOwner)]]">
|
||||
<td class\$="[[detailType]] name">[[_stripRefs(item.ref, detailType)]]</td>
|
||||
<td class\$="[[detailType]] revision [[_computeCanEditClass(item.ref, detailType, _isOwner)]]">
|
||||
<span class="revisionNoEditing">
|
||||
[[item.revision]]
|
||||
</span>
|
||||
<span class$="revisionEdit [[_computeEditingClass(_isEditing)]]">
|
||||
<span class\$="revisionEdit [[_computeEditingClass(_isEditing)]]">
|
||||
<span class="revisionWithEditing">
|
||||
[[item.revision]]
|
||||
</span>
|
||||
<gr-button
|
||||
link
|
||||
on-click="_handleEditRevision"
|
||||
class="editBtn">
|
||||
<gr-button link="" on-click="_handleEditRevision" class="editBtn">
|
||||
edit
|
||||
</gr-button>
|
||||
<iron-input
|
||||
bind-value="{{_revisedRef}}"
|
||||
class="editItem">
|
||||
<input
|
||||
is="iron-input"
|
||||
bind-value="{{_revisedRef}}">
|
||||
<iron-input bind-value="{{_revisedRef}}" class="editItem">
|
||||
<input is="iron-input" bind-value="{{_revisedRef}}">
|
||||
</iron-input>
|
||||
<gr-button
|
||||
link
|
||||
on-click="_handleCancelRevision"
|
||||
class="cancelBtn editItem">
|
||||
<gr-button link="" on-click="_handleCancelRevision" class="cancelBtn editItem">
|
||||
Cancel
|
||||
</gr-button>
|
||||
<gr-button
|
||||
link
|
||||
on-click="_handleSaveRevision"
|
||||
class="saveBtn editItem"
|
||||
disabled="[[!_revisedRef]]">
|
||||
<gr-button link="" on-click="_handleSaveRevision" class="saveBtn editItem" disabled="[[!_revisedRef]]">
|
||||
Save
|
||||
</gr-button>
|
||||
</span>
|
||||
</td>
|
||||
<td class$="message [[_hideIfBranch(detailType)]]">
|
||||
<td class\$="message [[_hideIfBranch(detailType)]]">
|
||||
[[_computeMessage(item.message)]]
|
||||
</td>
|
||||
<td class$="tagger [[_hideIfBranch(detailType)]]">
|
||||
<div class$="tagger [[_computeHideTagger(item.tagger)]]">
|
||||
<gr-account-link
|
||||
account="[[item.tagger]]">
|
||||
<td class\$="tagger [[_hideIfBranch(detailType)]]">
|
||||
<div class\$="tagger [[_computeHideTagger(item.tagger)]]">
|
||||
<gr-account-link account="[[item.tagger]]">
|
||||
</gr-account-link>
|
||||
(<gr-date-formatter
|
||||
has-tooltip
|
||||
date-str="[[item.tagger.date]]">
|
||||
(<gr-date-formatter has-tooltip="" date-str="[[item.tagger.date]]">
|
||||
</gr-date-formatter>)
|
||||
</div>
|
||||
</td>
|
||||
<td class="repositoryBrowser">
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeWeblink(item)]]" as="link">
|
||||
<a href$="[[link.url]]"
|
||||
class="webLink"
|
||||
rel="noopener"
|
||||
target="_blank">
|
||||
<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="delete">
|
||||
<gr-button
|
||||
link
|
||||
class$="deleteButton [[_computeHideDeleteClass(_isOwner, item.can_delete)]]"
|
||||
on-click="_handleDeleteItem">
|
||||
<gr-button link="" class\$="deleteButton [[_computeHideDeleteClass(_isOwner, item.can_delete)]]" on-click="_handleDeleteItem">
|
||||
Delete
|
||||
</gr-button>
|
||||
</td>
|
||||
@ -189,36 +139,19 @@ limitations under the License.
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<gr-overlay id="overlay" with-backdrop>
|
||||
<gr-confirm-delete-item-dialog
|
||||
class="confirmDialog"
|
||||
on-confirm="_handleDeleteItemConfirm"
|
||||
on-cancel="_handleConfirmDialogCancel"
|
||||
item="[[_refName]]"
|
||||
item-type="[[detailType]]"></gr-confirm-delete-item-dialog>
|
||||
<gr-overlay id="overlay" with-backdrop="">
|
||||
<gr-confirm-delete-item-dialog class="confirmDialog" on-confirm="_handleDeleteItemConfirm" on-cancel="_handleConfirmDialogCancel" item="[[_refName]]" item-type="[[detailType]]"></gr-confirm-delete-item-dialog>
|
||||
</gr-overlay>
|
||||
</gr-list-view>
|
||||
<gr-overlay id="createOverlay" with-backdrop>
|
||||
<gr-dialog
|
||||
id="createDialog"
|
||||
disabled="[[!_hasNewItemName]]"
|
||||
confirm-label="Create"
|
||||
on-confirm="_handleCreateItem"
|
||||
on-cancel="_handleCloseCreate">
|
||||
<gr-overlay id="createOverlay" with-backdrop="">
|
||||
<gr-dialog id="createDialog" disabled="[[!_hasNewItemName]]" confirm-label="Create" on-confirm="_handleCreateItem" on-cancel="_handleCloseCreate">
|
||||
<div class="header" slot="header">
|
||||
Create [[_computeItemName(detailType)]]
|
||||
</div>
|
||||
<div class="main" slot="main">
|
||||
<gr-create-pointer-dialog
|
||||
id="createNewModal"
|
||||
detail-type="[[_computeItemName(detailType)]]"
|
||||
has-new-item-name="{{_hasNewItemName}}"
|
||||
item-detail="[[detailType]]"
|
||||
repo-name="[[_repo]]"></gr-create-pointer-dialog>
|
||||
<gr-create-pointer-dialog id="createNewModal" detail-type="[[_computeItemName(detailType)]]" has-new-item-name="{{_hasNewItemName}}" item-detail="[[detailType]]" repo-name="[[_repo]]"></gr-create-pointer-dialog>
|
||||
</div>
|
||||
</gr-dialog>
|
||||
</gr-overlay>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo-detail-list.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,160 +14,174 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
import '../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.js';
|
||||
import '../../../styles/gr-table-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-dialog/gr-dialog.js';
|
||||
import '../../shared/gr-list-view/gr-list-view.js';
|
||||
import '../../shared/gr-overlay/gr-overlay.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../gr-create-repo-dialog/gr-create-repo-dialog.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-list_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoList extends mixinBehaviors( [
|
||||
Gerrit.ListViewBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-repo-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/repos',
|
||||
},
|
||||
_hasNewRepoName: Boolean,
|
||||
_createNewCapability: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_repos: Array,
|
||||
|
||||
/**
|
||||
* Because we request one more than the projectsPerPage, _shownProjects
|
||||
* maybe one less than _projects.
|
||||
* */
|
||||
_shownRepos: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_repos)',
|
||||
},
|
||||
|
||||
_reposPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._getCreateRepoCapability();
|
||||
this.fire('title-change', {title: 'Repos'});
|
||||
this._maybeOpenCreateOverlay(this.params);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getRepos(this._filter, this._reposPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.ListViewMixin
|
||||
* @extends Polymer.Element
|
||||
* Opens the create overlay if the route has a hash 'create'
|
||||
*
|
||||
* @param {!Object} params
|
||||
*/
|
||||
class GrRepoList extends Polymer.mixinBehaviors( [
|
||||
Gerrit.ListViewBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-list'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* URL params passed from the router.
|
||||
*/
|
||||
params: {
|
||||
type: Object,
|
||||
observer: '_paramsChanged',
|
||||
},
|
||||
|
||||
/**
|
||||
* Offset of currently visible query results.
|
||||
*/
|
||||
_offset: Number,
|
||||
_path: {
|
||||
type: String,
|
||||
readOnly: true,
|
||||
value: '/admin/repos',
|
||||
},
|
||||
_hasNewRepoName: Boolean,
|
||||
_createNewCapability: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_repos: Array,
|
||||
|
||||
/**
|
||||
* Because we request one more than the projectsPerPage, _shownProjects
|
||||
* maybe one less than _projects.
|
||||
* */
|
||||
_shownRepos: {
|
||||
type: Array,
|
||||
computed: 'computeShownItems(_repos)',
|
||||
},
|
||||
|
||||
_reposPerPage: {
|
||||
type: Number,
|
||||
value: 25,
|
||||
},
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_filter: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._getCreateRepoCapability();
|
||||
this.fire('title-change', {title: 'Repos'});
|
||||
this._maybeOpenCreateOverlay(this.params);
|
||||
}
|
||||
|
||||
_paramsChanged(params) {
|
||||
this._loading = true;
|
||||
this._filter = this.getFilterValue(params);
|
||||
this._offset = this.getOffsetValue(params);
|
||||
|
||||
return this._getRepos(this._filter, this._reposPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the create overlay if the route has a hash 'create'
|
||||
*
|
||||
* @param {!Object} params
|
||||
*/
|
||||
_maybeOpenCreateOverlay(params) {
|
||||
if (params && params.openCreateModal) {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
}
|
||||
|
||||
_computeRepoUrl(name) {
|
||||
return this.getUrl(this._path + '/', name);
|
||||
}
|
||||
|
||||
_computeChangesLink(name) {
|
||||
return Gerrit.Nav.getUrlForProjectChanges(name);
|
||||
}
|
||||
|
||||
_getCreateRepoCapability() {
|
||||
return this.$.restAPI.getAccount().then(account => {
|
||||
if (!account) { return; }
|
||||
return this.$.restAPI.getAccountCapabilities(['createProject'])
|
||||
.then(capabilities => {
|
||||
if (capabilities.createProject) {
|
||||
this._createNewCapability = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getRepos(filter, reposPerPage, offset) {
|
||||
this._repos = [];
|
||||
return this.$.restAPI.getRepos(filter, reposPerPage, offset)
|
||||
.then(repos => {
|
||||
// Late response.
|
||||
if (filter !== this._filter || !repos) { return; }
|
||||
this._repos = repos;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_refreshReposList() {
|
||||
this.$.restAPI.invalidateReposCache();
|
||||
return this._getRepos(this._filter, this._reposPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_handleCreateRepo() {
|
||||
this.$.createNewModal.handleCreateRepo().then(() => {
|
||||
this._refreshReposList();
|
||||
});
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
_maybeOpenCreateOverlay(params) {
|
||||
if (params && params.openCreateModal) {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_readOnly(item) {
|
||||
return item.state === 'READ_ONLY' ? 'Y' : '';
|
||||
}
|
||||
|
||||
_computeWeblink(repo) {
|
||||
if (!repo.web_links) { return ''; }
|
||||
const webLinks = repo.web_links;
|
||||
return webLinks.length ? webLinks : null;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoList.is, GrRepoList);
|
||||
})();
|
||||
_computeRepoUrl(name) {
|
||||
return this.getUrl(this._path + '/', name);
|
||||
}
|
||||
|
||||
_computeChangesLink(name) {
|
||||
return Gerrit.Nav.getUrlForProjectChanges(name);
|
||||
}
|
||||
|
||||
_getCreateRepoCapability() {
|
||||
return this.$.restAPI.getAccount().then(account => {
|
||||
if (!account) { return; }
|
||||
return this.$.restAPI.getAccountCapabilities(['createProject'])
|
||||
.then(capabilities => {
|
||||
if (capabilities.createProject) {
|
||||
this._createNewCapability = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getRepos(filter, reposPerPage, offset) {
|
||||
this._repos = [];
|
||||
return this.$.restAPI.getRepos(filter, reposPerPage, offset)
|
||||
.then(repos => {
|
||||
// Late response.
|
||||
if (filter !== this._filter || !repos) { return; }
|
||||
this._repos = repos;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
_refreshReposList() {
|
||||
this.$.restAPI.invalidateReposCache();
|
||||
return this._getRepos(this._filter, this._reposPerPage,
|
||||
this._offset);
|
||||
}
|
||||
|
||||
_handleCreateRepo() {
|
||||
this.$.createNewModal.handleCreateRepo().then(() => {
|
||||
this._refreshReposList();
|
||||
});
|
||||
}
|
||||
|
||||
_handleCloseCreate() {
|
||||
this.$.createOverlay.close();
|
||||
}
|
||||
|
||||
_handleCreateClicked() {
|
||||
this.$.createOverlay.open();
|
||||
}
|
||||
|
||||
_readOnly(item) {
|
||||
return item.state === 'READ_ONLY' ? 'Y' : '';
|
||||
}
|
||||
|
||||
_computeWeblink(repo) {
|
||||
if (!repo.web_links) { return ''; }
|
||||
const webLinks = repo.web_links;
|
||||
return webLinks.length ? webLinks : null;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoList.is, GrRepoList);
|
||||
|
@ -1,32 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/gr-list-view-behavior/gr-list-view-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-table-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
|
||||
<link rel="import" href="../../shared/gr-list-view/gr-list-view.html">
|
||||
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../gr-create-repo-dialog/gr-create-repo-dialog.html">
|
||||
|
||||
<dom-module id="gr-repo-list">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -47,44 +37,32 @@ limitations under the License.
|
||||
white-space:nowrap;
|
||||
}
|
||||
</style>
|
||||
<gr-list-view
|
||||
create-new=[[_createNewCapability]]
|
||||
filter="[[_filter]]"
|
||||
items-per-page="[[_reposPerPage]]"
|
||||
items="[[_repos]]"
|
||||
loading="[[_loading]]"
|
||||
offset="[[_offset]]"
|
||||
on-create-clicked="_handleCreateClicked"
|
||||
path="[[_path]]">
|
||||
<gr-list-view create-new="[[_createNewCapability]]" filter="[[_filter]]" items-per-page="[[_reposPerPage]]" items="[[_repos]]" loading="[[_loading]]" offset="[[_offset]]" on-create-clicked="_handleCreateClicked" path="[[_path]]">
|
||||
<table id="list" class="genericList">
|
||||
<tr class="headerRow">
|
||||
<tbody><tr class="headerRow">
|
||||
<th class="name topHeader">Repository Name</th>
|
||||
<th class="repositoryBrowser topHeader">Repository Browser</th>
|
||||
<th class="changesLink topHeader">Changes</th>
|
||||
<th class="topHeader readOnly">Read only</th>
|
||||
<th class="description topHeader">Repository Description</th>
|
||||
</tr>
|
||||
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<tr id="loading" class\$="loadingMsg [[computeLoadingClass(_loading)]]">
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
<tbody class$="[[computeLoadingClass(_loading)]]">
|
||||
</tbody><tbody class\$="[[computeLoadingClass(_loading)]]">
|
||||
<template is="dom-repeat" items="[[_shownRepos]]">
|
||||
<tr class="table">
|
||||
<td class="name">
|
||||
<a href$="[[_computeRepoUrl(item.name)]]">[[item.name]]</a>
|
||||
<a href\$="[[_computeRepoUrl(item.name)]]">[[item.name]]</a>
|
||||
</td>
|
||||
<td class="repositoryBrowser">
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeWeblink(item)]]" as="link">
|
||||
<a href$="[[link.url]]"
|
||||
class="webLink"
|
||||
rel="noopener"
|
||||
target="_blank">
|
||||
<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="changesLink"><a href$="[[_computeChangesLink(item.name)]]">view all</a></td>
|
||||
<td class="changesLink"><a href\$="[[_computeChangesLink(item.name)]]">view all</a></td>
|
||||
<td class="readOnly">[[_readOnly(item)]]</td>
|
||||
<td class="description">[[item.description]]</td>
|
||||
</tr>
|
||||
@ -92,26 +70,15 @@ limitations under the License.
|
||||
</tbody>
|
||||
</table>
|
||||
</gr-list-view>
|
||||
<gr-overlay id="createOverlay" with-backdrop>
|
||||
<gr-dialog
|
||||
id="createDialog"
|
||||
class="confirmDialog"
|
||||
disabled="[[!_hasNewRepoName]]"
|
||||
confirm-label="Create"
|
||||
on-confirm="_handleCreateRepo"
|
||||
on-cancel="_handleCloseCreate">
|
||||
<gr-overlay id="createOverlay" with-backdrop="">
|
||||
<gr-dialog id="createDialog" class="confirmDialog" disabled="[[!_hasNewRepoName]]" confirm-label="Create" on-confirm="_handleCreateRepo" on-cancel="_handleCloseCreate">
|
||||
<div class="header" slot="header">
|
||||
Create Repository
|
||||
</div>
|
||||
<div class="main" slot="main">
|
||||
<gr-create-repo-dialog
|
||||
has-new-repo-name="{{_hasNewRepoName}}"
|
||||
params="[[params]]"
|
||||
id="createNewModal"></gr-create-repo-dialog>
|
||||
<gr-create-repo-dialog has-new-repo-name="{{_hasNewRepoName}}" params="[[params]]" id="createNewModal"></gr-create-repo-dialog>
|
||||
</div>
|
||||
</gr-dialog>
|
||||
</gr-overlay>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo-list.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,16 +19,21 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-repo-list</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/page/page.js"></script>
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-repo-list.html">
|
||||
<script src="/node_modules/page/page.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-repo-list.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-list.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -36,166 +41,168 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
let counter;
|
||||
const repoGenerator = () => {
|
||||
return {
|
||||
id: `test${++counter}`,
|
||||
state: 'ACTIVE',
|
||||
web_links: [
|
||||
{
|
||||
name: 'diffusion',
|
||||
url: `https://phabricator.example.org/r/project/test${counter}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-list.js';
|
||||
let counter;
|
||||
const repoGenerator = () => {
|
||||
return {
|
||||
id: `test${++counter}`,
|
||||
state: 'ACTIVE',
|
||||
web_links: [
|
||||
{
|
||||
name: 'diffusion',
|
||||
url: `https://phabricator.example.org/r/project/test${counter}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
suite('gr-repo-list tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let repos;
|
||||
let sandbox;
|
||||
let value;
|
||||
suite('gr-repo-list tests', () => {
|
||||
let element;
|
||||
let repos;
|
||||
let sandbox;
|
||||
let value;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.stub(page, 'show');
|
||||
element = fixture('basic');
|
||||
counter = 0;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with repos', () => {
|
||||
setup(done => {
|
||||
repos = _.times(26, repoGenerator);
|
||||
stub('gr-rest-api-interface', {
|
||||
getRepos(num, offset) {
|
||||
return Promise.resolve(repos);
|
||||
},
|
||||
});
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('test for test repo in the list', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._repos[1].id, 'test2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_shownRepos', () => {
|
||||
assert.equal(element._shownRepos.length, 25);
|
||||
});
|
||||
|
||||
test('_maybeOpenCreateOverlay', () => {
|
||||
const overlayOpen = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._maybeOpenCreateOverlay();
|
||||
assert.isFalse(overlayOpen.called);
|
||||
const params = {};
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isFalse(overlayOpen.called);
|
||||
params.openCreateModal = true;
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isTrue(overlayOpen.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('list with less then 25 repos', () => {
|
||||
setup(done => {
|
||||
repos = _.times(25, repoGenerator);
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getRepos(num, offset) {
|
||||
return Promise.resolve(repos);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('_shownRepos', () => {
|
||||
assert.equal(element._shownRepos.length, 25);
|
||||
});
|
||||
});
|
||||
|
||||
suite('filter', () => {
|
||||
let reposFiltered;
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.stub(page, 'show');
|
||||
element = fixture('basic');
|
||||
counter = 0;
|
||||
repos = _.times(25, repoGenerator);
|
||||
reposFiltered = _.times(1, repoGenerator);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
suite('list with repos', () => {
|
||||
setup(done => {
|
||||
repos = _.times(26, repoGenerator);
|
||||
stub('gr-rest-api-interface', {
|
||||
getRepos(num, offset) {
|
||||
return Promise.resolve(repos);
|
||||
},
|
||||
});
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('test for test repo in the list', done => {
|
||||
flush(() => {
|
||||
assert.equal(element._repos[1].id, 'test2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('_shownRepos', () => {
|
||||
assert.equal(element._shownRepos.length, 25);
|
||||
});
|
||||
|
||||
test('_maybeOpenCreateOverlay', () => {
|
||||
const overlayOpen = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._maybeOpenCreateOverlay();
|
||||
assert.isFalse(overlayOpen.called);
|
||||
const params = {};
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isFalse(overlayOpen.called);
|
||||
params.openCreateModal = true;
|
||||
element._maybeOpenCreateOverlay(params);
|
||||
assert.isTrue(overlayOpen.called);
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getRepos', () => Promise.resolve(repos));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.isTrue(element.$.restAPI.getRepos.lastCall
|
||||
.calledWithExactly('test', 25, 25));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('list with less then 25 repos', () => {
|
||||
setup(done => {
|
||||
repos = _.times(25, repoGenerator);
|
||||
test('latest repos requested are always set', done => {
|
||||
const repoStub = sandbox.stub(element.$.restAPI, 'getRepos');
|
||||
repoStub.withArgs('test').returns(Promise.resolve(repos));
|
||||
repoStub.withArgs('filter').returns(Promise.resolve(reposFiltered));
|
||||
element._filter = 'test';
|
||||
|
||||
stub('gr-rest-api-interface', {
|
||||
getRepos(num, offset) {
|
||||
return Promise.resolve(repos);
|
||||
},
|
||||
});
|
||||
|
||||
element._paramsChanged(value).then(() => { flush(done); });
|
||||
});
|
||||
|
||||
test('_shownRepos', () => {
|
||||
assert.equal(element._shownRepos.length, 25);
|
||||
});
|
||||
});
|
||||
|
||||
suite('filter', () => {
|
||||
let reposFiltered;
|
||||
setup(() => {
|
||||
repos = _.times(25, repoGenerator);
|
||||
reposFiltered = _.times(1, repoGenerator);
|
||||
});
|
||||
|
||||
test('_paramsChanged', done => {
|
||||
sandbox.stub(element.$.restAPI, 'getRepos', () => Promise.resolve(repos));
|
||||
const value = {
|
||||
filter: 'test',
|
||||
offset: 25,
|
||||
};
|
||||
element._paramsChanged(value).then(() => {
|
||||
assert.isTrue(element.$.restAPI.getRepos.lastCall
|
||||
.calledWithExactly('test', 25, 25));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('latest repos requested are always set', done => {
|
||||
const repoStub = sandbox.stub(element.$.restAPI, 'getRepos');
|
||||
repoStub.withArgs('test').returns(Promise.resolve(repos));
|
||||
repoStub.withArgs('filter').returns(Promise.resolve(reposFiltered));
|
||||
element._filter = 'test';
|
||||
|
||||
// Repos are not set because the element._filter differs.
|
||||
element._getRepos('filter', 25, 0).then(() => {
|
||||
assert.deepEqual(element._repos, []);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._repos = _.times(25, repoGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
suite('create new', () => {
|
||||
test('_handleCreateClicked called when create-click fired', () => {
|
||||
sandbox.stub(element, '_handleCreateClicked');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-list-view').fire('create-clicked');
|
||||
assert.isTrue(element._handleCreateClicked.called);
|
||||
});
|
||||
|
||||
test('_handleCreateClicked opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._handleCreateClicked();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateRepo called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateRepo');
|
||||
element.$.createDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateRepo.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreate called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreate');
|
||||
element.$.createDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreate.called);
|
||||
// Repos are not set because the element._filter differs.
|
||||
element._getRepos('filter', 25, 0).then(() => {
|
||||
assert.deepEqual(element._repos, []);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('loading', () => {
|
||||
test('correct contents are displayed', () => {
|
||||
assert.isTrue(element._loading);
|
||||
assert.equal(element.computeLoadingClass(element._loading), 'loading');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'block');
|
||||
|
||||
element._loading = false;
|
||||
element._repos = _.times(25, repoGenerator);
|
||||
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(element.computeLoadingClass(element._loading), '');
|
||||
assert.equal(getComputedStyle(element.$.loading).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
suite('create new', () => {
|
||||
test('_handleCreateClicked called when create-click fired', () => {
|
||||
sandbox.stub(element, '_handleCreateClicked');
|
||||
element.shadowRoot
|
||||
.querySelector('gr-list-view').fire('create-clicked');
|
||||
assert.isTrue(element._handleCreateClicked.called);
|
||||
});
|
||||
|
||||
test('_handleCreateClicked opens modal', () => {
|
||||
const openStub = sandbox.stub(element.$.createOverlay, 'open');
|
||||
element._handleCreateClicked();
|
||||
assert.isTrue(openStub.called);
|
||||
});
|
||||
|
||||
test('_handleCreateRepo called when confirm fired', () => {
|
||||
sandbox.stub(element, '_handleCreateRepo');
|
||||
element.$.createDialog.fire('confirm');
|
||||
assert.isTrue(element._handleCreateRepo.called);
|
||||
});
|
||||
|
||||
test('_handleCloseCreate called when cancel fired', () => {
|
||||
sandbox.stub(element, '_handleCloseCreate');
|
||||
element.$.createDialog.fire('cancel');
|
||||
assert.isTrue(element._handleCloseCreate.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,128 +14,145 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import '../../../behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../../shared/gr-icons/gr-icons.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import '../../shared/gr-tooltip-content/gr-tooltip-content.js';
|
||||
import '../gr-plugin-config-array-editor/gr-plugin-config-array-editor.js';
|
||||
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo-plugin-config_html.js';
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.RepoPluginConfigMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepoPluginConfig extends mixinBehaviors( [
|
||||
Gerrit.RepoPluginConfig,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
static get is() { return 'gr-repo-plugin-config'; }
|
||||
/**
|
||||
* @appliesMixin Gerrit.RepoPluginConfigMixin
|
||||
* @extends Polymer.Element
|
||||
* Fired when the plugin config changes.
|
||||
*
|
||||
* @event plugin-config-changed
|
||||
*/
|
||||
class GrRepoPluginConfig extends Polymer.mixinBehaviors( [
|
||||
Gerrit.RepoPluginConfig,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo-plugin-config'; }
|
||||
/**
|
||||
* Fired when the plugin config changes.
|
||||
*
|
||||
* @event plugin-config-changed
|
||||
*/
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {?} */
|
||||
pluginData: Object,
|
||||
/** @type {Array} */
|
||||
_pluginConfigOptions: {
|
||||
type: Array,
|
||||
computed: '_computePluginConfigOptions(pluginData.*)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_computePluginConfigOptions(dataRecord) {
|
||||
if (!dataRecord || !dataRecord.base || !dataRecord.base.config) {
|
||||
return [];
|
||||
}
|
||||
const {config} = dataRecord.base;
|
||||
return Object.keys(config)
|
||||
.map(_key => { return {_key, info: config[_key]}; });
|
||||
}
|
||||
|
||||
_isArray(type) {
|
||||
return type === this.ENTRY_TYPES.ARRAY;
|
||||
}
|
||||
|
||||
_isBoolean(type) {
|
||||
return type === this.ENTRY_TYPES.BOOLEAN;
|
||||
}
|
||||
|
||||
_isList(type) {
|
||||
return type === this.ENTRY_TYPES.LIST;
|
||||
}
|
||||
|
||||
_isString(type) {
|
||||
// Treat numbers like strings for simplicity.
|
||||
return type === this.ENTRY_TYPES.STRING ||
|
||||
type === this.ENTRY_TYPES.INT ||
|
||||
type === this.ENTRY_TYPES.LONG;
|
||||
}
|
||||
|
||||
_computeDisabled(editable) {
|
||||
return editable === 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value - fallback to 'false' if undefined
|
||||
*/
|
||||
_computeChecked(value = 'false') {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
_handleStringChange(e) {
|
||||
const el = Polymer.dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(el.value, _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_handleListChange(e) {
|
||||
const el = Polymer.dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(el.value, _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_handleBooleanChange(e) {
|
||||
const el = Polymer.dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(JSON.stringify(el.checked), _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_buildConfigChangeInfo(value, _key) {
|
||||
const info = this.pluginData.config[_key];
|
||||
info.value = value;
|
||||
return {
|
||||
_key,
|
||||
info,
|
||||
notifyPath: `${_key}.value`,
|
||||
};
|
||||
}
|
||||
|
||||
_handleArrayChange({detail}) {
|
||||
this._handleChange(detail);
|
||||
}
|
||||
|
||||
_handleChange({_key, info, notifyPath}) {
|
||||
const {name, config} = this.pluginData;
|
||||
|
||||
/** @type {Object} */
|
||||
const detail = {
|
||||
name,
|
||||
config: Object.assign(config, {[_key]: info}, {}),
|
||||
notifyPath: `${name}.${notifyPath}`,
|
||||
};
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
this.PLUGIN_CONFIG_CHANGED, {detail, bubbles: true, composed: true}));
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {?} */
|
||||
pluginData: Object,
|
||||
/** @type {Array} */
|
||||
_pluginConfigOptions: {
|
||||
type: Array,
|
||||
computed: '_computePluginConfigOptions(pluginData.*)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define(GrRepoPluginConfig.is, GrRepoPluginConfig);
|
||||
})();
|
||||
_computePluginConfigOptions(dataRecord) {
|
||||
if (!dataRecord || !dataRecord.base || !dataRecord.base.config) {
|
||||
return [];
|
||||
}
|
||||
const {config} = dataRecord.base;
|
||||
return Object.keys(config)
|
||||
.map(_key => { return {_key, info: config[_key]}; });
|
||||
}
|
||||
|
||||
_isArray(type) {
|
||||
return type === this.ENTRY_TYPES.ARRAY;
|
||||
}
|
||||
|
||||
_isBoolean(type) {
|
||||
return type === this.ENTRY_TYPES.BOOLEAN;
|
||||
}
|
||||
|
||||
_isList(type) {
|
||||
return type === this.ENTRY_TYPES.LIST;
|
||||
}
|
||||
|
||||
_isString(type) {
|
||||
// Treat numbers like strings for simplicity.
|
||||
return type === this.ENTRY_TYPES.STRING ||
|
||||
type === this.ENTRY_TYPES.INT ||
|
||||
type === this.ENTRY_TYPES.LONG;
|
||||
}
|
||||
|
||||
_computeDisabled(editable) {
|
||||
return editable === 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value - fallback to 'false' if undefined
|
||||
*/
|
||||
_computeChecked(value = 'false') {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
_handleStringChange(e) {
|
||||
const el = dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(el.value, _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_handleListChange(e) {
|
||||
const el = dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(el.value, _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_handleBooleanChange(e) {
|
||||
const el = dom(e).localTarget;
|
||||
const _key = el.getAttribute('data-option-key');
|
||||
const configChangeInfo =
|
||||
this._buildConfigChangeInfo(JSON.stringify(el.checked), _key);
|
||||
this._handleChange(configChangeInfo);
|
||||
}
|
||||
|
||||
_buildConfigChangeInfo(value, _key) {
|
||||
const info = this.pluginData.config[_key];
|
||||
info.value = value;
|
||||
return {
|
||||
_key,
|
||||
info,
|
||||
notifyPath: `${_key}.value`,
|
||||
};
|
||||
}
|
||||
|
||||
_handleArrayChange({detail}) {
|
||||
this._handleChange(detail);
|
||||
}
|
||||
|
||||
_handleChange({_key, info, notifyPath}) {
|
||||
const {name, config} = this.pluginData;
|
||||
|
||||
/** @type {Object} */
|
||||
const detail = {
|
||||
name,
|
||||
config: Object.assign(config, {[_key]: info}, {}),
|
||||
notifyPath: `${name}.${notifyPath}`,
|
||||
};
|
||||
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
this.PLUGIN_CONFIG_CHANGED, {detail, bubbles: true, composed: true}));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepoPluginConfig.is, GrRepoPluginConfig);
|
||||
|
@ -1,35 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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="../../../behaviors/gr-repo-plugin-config-behavior/gr-repo-plugin-config-behavior.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../../shared/gr-icons/gr-icons.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
|
||||
<link rel="import" href="../gr-plugin-config-array-editor/gr-plugin-config-array-editor.html">
|
||||
|
||||
<dom-module id="gr-repo-plugin-config">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -53,55 +40,31 @@ limitations under the License.
|
||||
<fieldset>
|
||||
<h4>[[pluginData.name]]</h4>
|
||||
<template is="dom-repeat" items="[[_pluginConfigOptions]]" as="option">
|
||||
<section class$="section [[option.info.type]]">
|
||||
<section class\$="section [[option.info.type]]">
|
||||
<span class="title">
|
||||
<gr-tooltip-content
|
||||
has-tooltip="[[option.info.description]]"
|
||||
show-icon="[[option.info.description]]"
|
||||
title="[[option.info.description]]">
|
||||
<gr-tooltip-content has-tooltip="[[option.info.description]]" show-icon="[[option.info.description]]" title="[[option.info.description]]">
|
||||
<span>[[option.info.display_name]]</span>
|
||||
</gr-tooltip-content>
|
||||
</span>
|
||||
<span class="value">
|
||||
<template is="dom-if" if="[[_isArray(option.info.type)]]">
|
||||
<gr-plugin-config-array-editor
|
||||
on-plugin-config-option-changed="_handleArrayChange"
|
||||
plugin-option="[[option]]"></gr-plugin-config-array-editor>
|
||||
<gr-plugin-config-array-editor on-plugin-config-option-changed="_handleArrayChange" plugin-option="[[option]]"></gr-plugin-config-array-editor>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_isBoolean(option.info.type)]]">
|
||||
<paper-toggle-button
|
||||
checked="[[_computeChecked(option.info.value)]]"
|
||||
on-change="_handleBooleanChange"
|
||||
data-option-key$="[[option._key]]"
|
||||
disabled$="[[_computeDisabled(option.info.editable)]]"></paper-toggle-button>
|
||||
<paper-toggle-button checked="[[_computeChecked(option.info.value)]]" on-change="_handleBooleanChange" data-option-key\$="[[option._key]]" disabled\$="[[_computeDisabled(option.info.editable)]]"></paper-toggle-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_isList(option.info.type)]]">
|
||||
<gr-select
|
||||
bind-value$="[[option.info.value]]"
|
||||
on-change="_handleListChange">
|
||||
<select
|
||||
data-option-key$="[[option._key]]"
|
||||
disabled$="[[_computeDisabled(option.info.editable)]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[option.info.permitted_values]]"
|
||||
as="value">
|
||||
<option value$="[[value]]">[[value]]</option>
|
||||
<gr-select bind-value\$="[[option.info.value]]" on-change="_handleListChange">
|
||||
<select data-option-key\$="[[option._key]]" disabled\$="[[_computeDisabled(option.info.editable)]]">
|
||||
<template is="dom-repeat" items="[[option.info.permitted_values]]" as="value">
|
||||
<option value\$="[[value]]">[[value]]</option>
|
||||
</template>
|
||||
</select>
|
||||
</gr-select>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_isString(option.info.type)]]">
|
||||
<iron-input
|
||||
bind-value="[[option.info.value]]"
|
||||
on-input="_handleStringChange"
|
||||
data-option-key$="[[option._key]]"
|
||||
disabled$="[[_computeDisabled(option.info.editable)]]">
|
||||
<input
|
||||
is="iron-input"
|
||||
value="[[option.info.value]]"
|
||||
on-input="_handleStringChange"
|
||||
data-option-key$="[[option._key]]"
|
||||
disabled$="[[_computeDisabled(option.info.editable)]]">
|
||||
<iron-input bind-value="[[option.info.value]]" on-input="_handleStringChange" data-option-key\$="[[option._key]]" disabled\$="[[_computeDisabled(option.info.editable)]]">
|
||||
<input is="iron-input" value="[[option.info.value]]" on-input="_handleStringChange" data-option-key\$="[[option._key]]" disabled\$="[[_computeDisabled(option.info.editable)]]">
|
||||
</iron-input>
|
||||
</template>
|
||||
<template is="dom-if" if="[[option.info.inherited_value]]">
|
||||
@ -114,6 +77,4 @@ limitations under the License.
|
||||
</template>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
<script src="gr-repo-plugin-config.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
@ -19,15 +19,20 @@ limitations under the License.
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-repo-plugin-config</title>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
|
||||
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/bower_components/web-component-tester/browser.js"></script>
|
||||
<script src="../../../test/test-pre-setup.js"></script>
|
||||
<link rel="import" href="../../../test/common-test-setup.html"/>
|
||||
<link rel="import" href="gr-repo-plugin-config.html">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
<script type="module" src="../../../test/test-pre-setup.js"></script>
|
||||
<script type="module" src="../../../test/common-test-setup.js"></script>
|
||||
<script type="module" src="./gr-repo-plugin-config.js"></script>
|
||||
|
||||
<script>void(0);</script>
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-plugin-config.js';
|
||||
void(0);
|
||||
</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
@ -35,150 +40,152 @@ limitations under the License.
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-repo-plugin-config tests', async () => {
|
||||
await readyToTest();
|
||||
let element;
|
||||
let sandbox;
|
||||
<script type="module">
|
||||
import '../../../test/test-pre-setup.js';
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-repo-plugin-config.js';
|
||||
suite('gr-repo-plugin-config tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(() => sandbox.restore());
|
||||
|
||||
test('_computePluginConfigOptions', () => {
|
||||
assert.deepEqual(element._computePluginConfigOptions(), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions({}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions({base: {}}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions(
|
||||
{base: {config: {}}}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions(
|
||||
{base: {config: {testKey: 'testInfo'}}}),
|
||||
[{_key: 'testKey', info: 'testInfo'}]);
|
||||
});
|
||||
|
||||
test('_computeDisabled', () => {
|
||||
assert.isFalse(element._computeDisabled('true'));
|
||||
assert.isTrue(element._computeDisabled('false'));
|
||||
});
|
||||
|
||||
test('_handleChange', () => {
|
||||
const eventStub = sandbox.stub(element, 'dispatchEvent');
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test'}},
|
||||
};
|
||||
element._handleChange({
|
||||
_key: 'plugin',
|
||||
info: {value: 'newTest'},
|
||||
notifyPath: 'plugin.value',
|
||||
});
|
||||
|
||||
assert.isTrue(eventStub.called);
|
||||
|
||||
const {detail} = eventStub.lastCall.args[0];
|
||||
assert.equal(detail.name, 'testName');
|
||||
assert.deepEqual(detail.config, {plugin: {value: 'newTest'}});
|
||||
assert.equal(detail.notifyPath, 'testName.plugin.value');
|
||||
});
|
||||
|
||||
suite('option types', () => {
|
||||
let changeStub;
|
||||
let buildStub;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
changeStub = sandbox.stub(element, '_handleChange');
|
||||
buildStub = sandbox.stub(element, '_buildConfigChangeInfo');
|
||||
});
|
||||
|
||||
teardown(() => sandbox.restore());
|
||||
|
||||
test('_computePluginConfigOptions', () => {
|
||||
assert.deepEqual(element._computePluginConfigOptions(), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions({}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions({base: {}}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions(
|
||||
{base: {config: {}}}), []);
|
||||
assert.deepEqual(element._computePluginConfigOptions(
|
||||
{base: {config: {testKey: 'testInfo'}}}),
|
||||
[{_key: 'testKey', info: 'testInfo'}]);
|
||||
});
|
||||
|
||||
test('_computeDisabled', () => {
|
||||
assert.isFalse(element._computeDisabled('true'));
|
||||
assert.isTrue(element._computeDisabled('false'));
|
||||
});
|
||||
|
||||
test('_handleChange', () => {
|
||||
const eventStub = sandbox.stub(element, 'dispatchEvent');
|
||||
test('ARRAY type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test'}},
|
||||
config: {plugin: {value: 'test', type: 'ARRAY'}},
|
||||
};
|
||||
element._handleChange({
|
||||
_key: 'plugin',
|
||||
info: {value: 'newTest'},
|
||||
notifyPath: 'plugin.value',
|
||||
});
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(eventStub.called);
|
||||
|
||||
const {detail} = eventStub.lastCall.args[0];
|
||||
assert.equal(detail.name, 'testName');
|
||||
assert.deepEqual(detail.config, {plugin: {value: 'newTest'}});
|
||||
assert.equal(detail.notifyPath, 'testName.plugin.value');
|
||||
const editor = element.shadowRoot
|
||||
.querySelector('gr-plugin-config-array-editor');
|
||||
assert.ok(editor);
|
||||
element._handleArrayChange({detail: 'test'});
|
||||
assert.isTrue(changeStub.called);
|
||||
assert.equal(changeStub.lastCall.args[0], 'test');
|
||||
});
|
||||
|
||||
suite('option types', () => {
|
||||
let changeStub;
|
||||
let buildStub;
|
||||
|
||||
setup(() => {
|
||||
changeStub = sandbox.stub(element, '_handleChange');
|
||||
buildStub = sandbox.stub(element, '_buildConfigChangeInfo');
|
||||
});
|
||||
|
||||
test('ARRAY type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test', type: 'ARRAY'}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const editor = element.shadowRoot
|
||||
.querySelector('gr-plugin-config-array-editor');
|
||||
assert.ok(editor);
|
||||
element._handleArrayChange({detail: 'test'});
|
||||
assert.isTrue(changeStub.called);
|
||||
assert.equal(changeStub.lastCall.args[0], 'test');
|
||||
});
|
||||
|
||||
test('BOOLEAN type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'true', type: 'BOOLEAN'}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const toggle = element.shadowRoot
|
||||
.querySelector('paper-toggle-button');
|
||||
assert.ok(toggle);
|
||||
toggle.click();
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['false', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
|
||||
test('INT/LONG/STRING type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test', type: 'STRING'}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const input = element.shadowRoot
|
||||
.querySelector('input');
|
||||
assert.ok(input);
|
||||
input.value = 'newTest';
|
||||
input.dispatchEvent(new Event('input'));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['newTest', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
|
||||
test('LIST type option', () => {
|
||||
const permitted_values = ['test', 'newTest'];
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test', type: 'LIST', permitted_values}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const select = element.shadowRoot
|
||||
.querySelector('select');
|
||||
assert.ok(select);
|
||||
select.value = 'newTest';
|
||||
select.dispatchEvent(new Event(
|
||||
'change', {bubbles: true, composed: true}));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['newTest', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('_buildConfigChangeInfo', () => {
|
||||
test('BOOLEAN type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test'}},
|
||||
config: {plugin: {value: 'true', type: 'BOOLEAN'}},
|
||||
};
|
||||
const detail = element._buildConfigChangeInfo('newTest', 'plugin');
|
||||
assert.equal(detail._key, 'plugin');
|
||||
assert.deepEqual(detail.info, {value: 'newTest'});
|
||||
assert.equal(detail.notifyPath, 'plugin.value');
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const toggle = element.shadowRoot
|
||||
.querySelector('paper-toggle-button');
|
||||
assert.ok(toggle);
|
||||
toggle.click();
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['false', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
|
||||
test('INT/LONG/STRING type option', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test', type: 'STRING'}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const input = element.shadowRoot
|
||||
.querySelector('input');
|
||||
assert.ok(input);
|
||||
input.value = 'newTest';
|
||||
input.dispatchEvent(new Event('input'));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['newTest', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
|
||||
test('LIST type option', () => {
|
||||
const permitted_values = ['test', 'newTest'];
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test', type: 'LIST', permitted_values}},
|
||||
};
|
||||
flushAsynchronousOperations();
|
||||
|
||||
const select = element.shadowRoot
|
||||
.querySelector('select');
|
||||
assert.ok(select);
|
||||
select.value = 'newTest';
|
||||
select.dispatchEvent(new Event(
|
||||
'change', {bubbles: true, composed: true}));
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(buildStub.called);
|
||||
assert.deepEqual(buildStub.lastCall.args, ['newTest', 'plugin']);
|
||||
|
||||
assert.isTrue(changeStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('_buildConfigChangeInfo', () => {
|
||||
element.pluginData = {
|
||||
name: 'testName',
|
||||
config: {plugin: {value: 'test'}},
|
||||
};
|
||||
const detail = element._buildConfigChangeInfo('newTest', 'plugin');
|
||||
assert.equal(detail._key, 'plugin');
|
||||
assert.deepEqual(detail.info, {value: 'newTest'});
|
||||
assert.equal(detail.notifyPath, 'plugin.value');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -14,348 +14,366 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
import '../../../scripts/bundled-polymer.js';
|
||||
|
||||
const STATES = {
|
||||
active: {value: 'ACTIVE', label: 'Active'},
|
||||
readOnly: {value: 'READ_ONLY', label: 'Read Only'},
|
||||
hidden: {value: 'HIDDEN', label: 'Hidden'},
|
||||
};
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '@polymer/iron-input/iron-input.js';
|
||||
import '../../../behaviors/fire-behavior/fire-behavior.js';
|
||||
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js';
|
||||
import '../../plugins/gr-endpoint-param/gr-endpoint-param.js';
|
||||
import '../../shared/gr-download-commands/gr-download-commands.js';
|
||||
import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
|
||||
import '../../shared/gr-select/gr-select.js';
|
||||
import '../../../styles/gr-form-styles.js';
|
||||
import '../../../styles/gr-subpage-styles.js';
|
||||
import '../../../styles/shared-styles.js';
|
||||
import '../gr-repo-plugin-config/gr-repo-plugin-config.js';
|
||||
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
|
||||
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-repo_html.js';
|
||||
|
||||
const SUBMIT_TYPES = {
|
||||
// Exclude INHERIT, which is handled specially.
|
||||
mergeIfNecessary: {
|
||||
value: 'MERGE_IF_NECESSARY',
|
||||
label: 'Merge if necessary',
|
||||
},
|
||||
fastForwardOnly: {
|
||||
value: 'FAST_FORWARD_ONLY',
|
||||
label: 'Fast forward only',
|
||||
},
|
||||
rebaseAlways: {
|
||||
value: 'REBASE_ALWAYS',
|
||||
label: 'Rebase Always',
|
||||
},
|
||||
rebaseIfNecessary: {
|
||||
value: 'REBASE_IF_NECESSARY',
|
||||
label: 'Rebase if necessary',
|
||||
},
|
||||
mergeAlways: {
|
||||
value: 'MERGE_ALWAYS',
|
||||
label: 'Merge always',
|
||||
},
|
||||
cherryPick: {
|
||||
value: 'CHERRY_PICK',
|
||||
label: 'Cherry pick',
|
||||
},
|
||||
};
|
||||
const STATES = {
|
||||
active: {value: 'ACTIVE', label: 'Active'},
|
||||
readOnly: {value: 'READ_ONLY', label: 'Read Only'},
|
||||
hidden: {value: 'HIDDEN', label: 'Hidden'},
|
||||
};
|
||||
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepo extends Polymer.mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], Polymer.GestureEventListeners(
|
||||
Polymer.LegacyElementMixin(
|
||||
Polymer.Element))) {
|
||||
static get is() { return 'gr-repo'; }
|
||||
const SUBMIT_TYPES = {
|
||||
// Exclude INHERIT, which is handled specially.
|
||||
mergeIfNecessary: {
|
||||
value: 'MERGE_IF_NECESSARY',
|
||||
label: 'Merge if necessary',
|
||||
},
|
||||
fastForwardOnly: {
|
||||
value: 'FAST_FORWARD_ONLY',
|
||||
label: 'Fast forward only',
|
||||
},
|
||||
rebaseAlways: {
|
||||
value: 'REBASE_ALWAYS',
|
||||
label: 'Rebase Always',
|
||||
},
|
||||
rebaseIfNecessary: {
|
||||
value: 'REBASE_IF_NECESSARY',
|
||||
label: 'Rebase if necessary',
|
||||
},
|
||||
mergeAlways: {
|
||||
value: 'MERGE_ALWAYS',
|
||||
label: 'Merge always',
|
||||
},
|
||||
cherryPick: {
|
||||
value: 'CHERRY_PICK',
|
||||
label: 'Cherry pick',
|
||||
},
|
||||
};
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
repo: String,
|
||||
/**
|
||||
* @appliesMixin Gerrit.FireMixin
|
||||
* @extends Polymer.Element
|
||||
*/
|
||||
class GrRepo extends mixinBehaviors( [
|
||||
Gerrit.FireBehavior,
|
||||
], GestureEventListeners(
|
||||
LegacyElementMixin(
|
||||
PolymerElement))) {
|
||||
static get template() { return htmlTemplate; }
|
||||
|
||||
_configChanged: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
static get is() { return 'gr-repo'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
params: Object,
|
||||
repo: String,
|
||||
|
||||
_configChanged: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_loggedIn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_loggedInChanged',
|
||||
},
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
/** @type {?} */
|
||||
_pluginData: {
|
||||
type: Array,
|
||||
computed: '_computePluginData(_repoConfig.plugin_config.*)',
|
||||
},
|
||||
_readOnly: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_states: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(STATES);
|
||||
},
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_submitTypes: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(SUBMIT_TYPES);
|
||||
},
|
||||
_loggedIn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: '_loggedInChanged',
|
||||
},
|
||||
/** @type {?} */
|
||||
_repoConfig: Object,
|
||||
/** @type {?} */
|
||||
_pluginData: {
|
||||
type: Array,
|
||||
computed: '_computePluginData(_repoConfig.plugin_config.*)',
|
||||
},
|
||||
_readOnly: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
_states: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(STATES);
|
||||
},
|
||||
},
|
||||
_submitTypes: {
|
||||
type: Array,
|
||||
value() {
|
||||
return Object.values(SUBMIT_TYPES);
|
||||
},
|
||||
},
|
||||
_schemes: {
|
||||
type: Array,
|
||||
value() { return []; },
|
||||
computed: '_computeSchemes(_schemesObj)',
|
||||
observer: '_schemesChanged',
|
||||
},
|
||||
_selectedCommand: {
|
||||
type: String,
|
||||
value: 'Clone',
|
||||
},
|
||||
_selectedScheme: String,
|
||||
_schemesObj: Object,
|
||||
};
|
||||
}
|
||||
},
|
||||
_schemes: {
|
||||
type: Array,
|
||||
value() { return []; },
|
||||
computed: '_computeSchemes(_schemesObj)',
|
||||
observer: '_schemesChanged',
|
||||
},
|
||||
_selectedCommand: {
|
||||
type: String,
|
||||
value: 'Clone',
|
||||
},
|
||||
_selectedScheme: String,
|
||||
_schemesObj: Object,
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleConfigChanged(_repoConfig.*)',
|
||||
];
|
||||
}
|
||||
static get observers() {
|
||||
return [
|
||||
'_handleConfigChanged(_repoConfig.*)',
|
||||
];
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadRepo();
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
this._loadRepo();
|
||||
|
||||
this.fire('title-change', {title: this.repo});
|
||||
}
|
||||
this.fire('title-change', {title: this.repo});
|
||||
}
|
||||
|
||||
_computePluginData(configRecord) {
|
||||
if (!configRecord ||
|
||||
!configRecord.base) { return []; }
|
||||
_computePluginData(configRecord) {
|
||||
if (!configRecord ||
|
||||
!configRecord.base) { return []; }
|
||||
|
||||
const pluginConfig = configRecord.base;
|
||||
return Object.keys(pluginConfig)
|
||||
.map(name => { return {name, config: pluginConfig[name]}; });
|
||||
}
|
||||
const pluginConfig = configRecord.base;
|
||||
return Object.keys(pluginConfig)
|
||||
.map(name => { return {name, config: pluginConfig[name]}; });
|
||||
}
|
||||
|
||||
_loadRepo() {
|
||||
if (!this.repo) { return Promise.resolve(); }
|
||||
_loadRepo() {
|
||||
if (!this.repo) { return Promise.resolve(); }
|
||||
|
||||
const promises = [];
|
||||
const promises = [];
|
||||
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
const errFn = response => {
|
||||
this.fire('page-error', {response});
|
||||
};
|
||||
|
||||
promises.push(this._getLoggedIn().then(loggedIn => {
|
||||
this._loggedIn = loggedIn;
|
||||
if (loggedIn) {
|
||||
this.$.restAPI.getRepoAccess(this.repo).then(access => {
|
||||
if (!access) { return Promise.resolve(); }
|
||||
promises.push(this._getLoggedIn().then(loggedIn => {
|
||||
this._loggedIn = loggedIn;
|
||||
if (loggedIn) {
|
||||
this.$.restAPI.getRepoAccess(this.repo).then(access => {
|
||||
if (!access) { return Promise.resolve(); }
|
||||
|
||||
// If the user is not an owner, is_owner is not a property.
|
||||
this._readOnly = !access[this.repo].is_owner;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getProjectConfig(this.repo, errFn)
|
||||
.then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
if (config.default_submit_type) {
|
||||
// The gr-select is bound to submit_type, which needs to be the
|
||||
// *configured* submit type. When default_submit_type is
|
||||
// present, the server reports the *effective* submit type in
|
||||
// submit_type, so we need to overwrite it before storing the
|
||||
// config in this.
|
||||
config.submit_type =
|
||||
config.default_submit_type.configured_value;
|
||||
}
|
||||
if (!config.state) {
|
||||
config.state = STATES.active.value;
|
||||
}
|
||||
this._repoConfig = config;
|
||||
this._loading = false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getConfig().then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
this._schemesObj = config.download.schemes;
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_computeHideClass(arr) {
|
||||
return !arr || !arr.length ? 'hide' : '';
|
||||
}
|
||||
|
||||
_loggedInChanged(_loggedIn) {
|
||||
if (!_loggedIn) { return; }
|
||||
this.$.restAPI.getPreferences().then(prefs => {
|
||||
if (prefs.download_scheme) {
|
||||
// Note (issue 5180): normalize the download scheme with lower-case.
|
||||
this._selectedScheme = prefs.download_scheme.toLowerCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_formatBooleanSelect(item) {
|
||||
if (!item) { return; }
|
||||
let inheritLabel = 'Inherit';
|
||||
if (!(item.inherited_value === undefined)) {
|
||||
inheritLabel = `Inherit (${item.inherited_value})`;
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: inheritLabel,
|
||||
value: 'INHERIT',
|
||||
},
|
||||
{
|
||||
label: 'True',
|
||||
value: 'TRUE',
|
||||
}, {
|
||||
label: 'False',
|
||||
value: 'FALSE',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
_formatSubmitTypeSelect(projectConfig) {
|
||||
if (!projectConfig) { return; }
|
||||
const allValues = Object.values(SUBMIT_TYPES);
|
||||
const type = projectConfig.default_submit_type;
|
||||
if (!type) {
|
||||
// Server is too old to report default_submit_type, so assume INHERIT
|
||||
// is not a valid value.
|
||||
return allValues;
|
||||
}
|
||||
|
||||
let inheritLabel = 'Inherit';
|
||||
if (type.inherited_value) {
|
||||
let inherited = type.inherited_value;
|
||||
for (const val of allValues) {
|
||||
if (val.value === type.inherited_value) {
|
||||
inherited = val.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
inheritLabel = `Inherit (${inherited})`;
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: inheritLabel,
|
||||
value: 'INHERIT',
|
||||
},
|
||||
...allValues,
|
||||
];
|
||||
}
|
||||
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_getLoggedIn() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
}
|
||||
|
||||
_formatRepoConfigForSave(repoConfig) {
|
||||
const configInputObj = {};
|
||||
for (const key in repoConfig) {
|
||||
if (repoConfig.hasOwnProperty(key)) {
|
||||
if (key === 'default_submit_type') {
|
||||
// default_submit_type is not in the input type, and the
|
||||
// configured value was already copied to submit_type by
|
||||
// _loadProject. Omit this property when saving.
|
||||
continue;
|
||||
}
|
||||
if (key === 'plugin_config') {
|
||||
configInputObj.plugin_config_values = repoConfig[key];
|
||||
} else if (typeof repoConfig[key] === 'object') {
|
||||
configInputObj[key] = repoConfig[key].configured_value;
|
||||
} else {
|
||||
configInputObj[key] = repoConfig[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return configInputObj;
|
||||
}
|
||||
|
||||
_handleSaveRepoConfig() {
|
||||
return this.$.restAPI.saveRepoConfig(this.repo,
|
||||
this._formatRepoConfigForSave(this._repoConfig)).then(() => {
|
||||
this._configChanged = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigChanged() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._configChanged = true;
|
||||
}
|
||||
|
||||
_computeButtonDisabled(readOnly, configChanged) {
|
||||
return readOnly || !configChanged;
|
||||
}
|
||||
|
||||
_computeHeaderClass(configChanged) {
|
||||
return configChanged ? 'edited' : '';
|
||||
}
|
||||
|
||||
_computeSchemes(schemesObj) {
|
||||
return Object.keys(schemesObj);
|
||||
}
|
||||
|
||||
_schemesChanged(schemes) {
|
||||
if (schemes.length === 0) { return; }
|
||||
if (!schemes.includes(this._selectedScheme)) {
|
||||
this._selectedScheme = schemes.sort()[0];
|
||||
}
|
||||
}
|
||||
|
||||
_computeCommands(repo, schemesObj, _selectedScheme) {
|
||||
if (!schemesObj || !repo || !_selectedScheme) {
|
||||
return [];
|
||||
}
|
||||
const commands = [];
|
||||
let commandObj;
|
||||
if (schemesObj.hasOwnProperty(_selectedScheme)) {
|
||||
commandObj = schemesObj[_selectedScheme].clone_commands;
|
||||
}
|
||||
for (const title in commandObj) {
|
||||
if (!commandObj.hasOwnProperty(title)) { continue; }
|
||||
commands.push({
|
||||
title,
|
||||
command: commandObj[title]
|
||||
.replace(/\$\{project\}/gi, encodeURI(repo))
|
||||
.replace(/\$\{project-base-name\}/gi,
|
||||
encodeURI(repo.substring(repo.lastIndexOf('/') + 1))),
|
||||
// If the user is not an owner, is_owner is not a property.
|
||||
this._readOnly = !access[this.repo].is_owner;
|
||||
});
|
||||
}
|
||||
return commands;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getProjectConfig(this.repo, errFn)
|
||||
.then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
if (config.default_submit_type) {
|
||||
// The gr-select is bound to submit_type, which needs to be the
|
||||
// *configured* submit type. When default_submit_type is
|
||||
// present, the server reports the *effective* submit type in
|
||||
// submit_type, so we need to overwrite it before storing the
|
||||
// config in this.
|
||||
config.submit_type =
|
||||
config.default_submit_type.configured_value;
|
||||
}
|
||||
if (!config.state) {
|
||||
config.state = STATES.active.value;
|
||||
}
|
||||
this._repoConfig = config;
|
||||
this._loading = false;
|
||||
}));
|
||||
|
||||
promises.push(this.$.restAPI.getConfig().then(config => {
|
||||
if (!config) { return Promise.resolve(); }
|
||||
|
||||
this._schemesObj = config.download.schemes;
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
_computeLoadingClass(loading) {
|
||||
return loading ? 'loading' : '';
|
||||
}
|
||||
|
||||
_computeHideClass(arr) {
|
||||
return !arr || !arr.length ? 'hide' : '';
|
||||
}
|
||||
|
||||
_loggedInChanged(_loggedIn) {
|
||||
if (!_loggedIn) { return; }
|
||||
this.$.restAPI.getPreferences().then(prefs => {
|
||||
if (prefs.download_scheme) {
|
||||
// Note (issue 5180): normalize the download scheme with lower-case.
|
||||
this._selectedScheme = prefs.download_scheme.toLowerCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_formatBooleanSelect(item) {
|
||||
if (!item) { return; }
|
||||
let inheritLabel = 'Inherit';
|
||||
if (!(item.inherited_value === undefined)) {
|
||||
inheritLabel = `Inherit (${item.inherited_value})`;
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: inheritLabel,
|
||||
value: 'INHERIT',
|
||||
},
|
||||
{
|
||||
label: 'True',
|
||||
value: 'TRUE',
|
||||
}, {
|
||||
label: 'False',
|
||||
value: 'FALSE',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
_formatSubmitTypeSelect(projectConfig) {
|
||||
if (!projectConfig) { return; }
|
||||
const allValues = Object.values(SUBMIT_TYPES);
|
||||
const type = projectConfig.default_submit_type;
|
||||
if (!type) {
|
||||
// Server is too old to report default_submit_type, so assume INHERIT
|
||||
// is not a valid value.
|
||||
return allValues;
|
||||
}
|
||||
|
||||
_computeRepositoriesClass(config) {
|
||||
return config ? 'showConfig': '';
|
||||
let inheritLabel = 'Inherit';
|
||||
if (type.inherited_value) {
|
||||
let inherited = type.inherited_value;
|
||||
for (const val of allValues) {
|
||||
if (val.value === type.inherited_value) {
|
||||
inherited = val.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
inheritLabel = `Inherit (${inherited})`;
|
||||
}
|
||||
return [
|
||||
{
|
||||
label: inheritLabel,
|
||||
value: 'INHERIT',
|
||||
},
|
||||
...allValues,
|
||||
];
|
||||
}
|
||||
|
||||
_computeChangesUrl(name) {
|
||||
return Gerrit.Nav.getUrlForProjectChanges(name);
|
||||
_isLoading() {
|
||||
return this._loading || this._loading === undefined;
|
||||
}
|
||||
|
||||
_getLoggedIn() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
}
|
||||
|
||||
_formatRepoConfigForSave(repoConfig) {
|
||||
const configInputObj = {};
|
||||
for (const key in repoConfig) {
|
||||
if (repoConfig.hasOwnProperty(key)) {
|
||||
if (key === 'default_submit_type') {
|
||||
// default_submit_type is not in the input type, and the
|
||||
// configured value was already copied to submit_type by
|
||||
// _loadProject. Omit this property when saving.
|
||||
continue;
|
||||
}
|
||||
if (key === 'plugin_config') {
|
||||
configInputObj.plugin_config_values = repoConfig[key];
|
||||
} else if (typeof repoConfig[key] === 'object') {
|
||||
configInputObj[key] = repoConfig[key].configured_value;
|
||||
} else {
|
||||
configInputObj[key] = repoConfig[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return configInputObj;
|
||||
}
|
||||
|
||||
_handlePluginConfigChanged({detail: {name, config, notifyPath}}) {
|
||||
this._repoConfig.plugin_config[name] = config;
|
||||
this.notifyPath('_repoConfig.plugin_config.' + notifyPath);
|
||||
_handleSaveRepoConfig() {
|
||||
return this.$.restAPI.saveRepoConfig(this.repo,
|
||||
this._formatRepoConfigForSave(this._repoConfig)).then(() => {
|
||||
this._configChanged = false;
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigChanged() {
|
||||
if (this._isLoading()) { return; }
|
||||
this._configChanged = true;
|
||||
}
|
||||
|
||||
_computeButtonDisabled(readOnly, configChanged) {
|
||||
return readOnly || !configChanged;
|
||||
}
|
||||
|
||||
_computeHeaderClass(configChanged) {
|
||||
return configChanged ? 'edited' : '';
|
||||
}
|
||||
|
||||
_computeSchemes(schemesObj) {
|
||||
return Object.keys(schemesObj);
|
||||
}
|
||||
|
||||
_schemesChanged(schemes) {
|
||||
if (schemes.length === 0) { return; }
|
||||
if (!schemes.includes(this._selectedScheme)) {
|
||||
this._selectedScheme = schemes.sort()[0];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepo.is, GrRepo);
|
||||
})();
|
||||
_computeCommands(repo, schemesObj, _selectedScheme) {
|
||||
if (!schemesObj || !repo || !_selectedScheme) {
|
||||
return [];
|
||||
}
|
||||
const commands = [];
|
||||
let commandObj;
|
||||
if (schemesObj.hasOwnProperty(_selectedScheme)) {
|
||||
commandObj = schemesObj[_selectedScheme].clone_commands;
|
||||
}
|
||||
for (const title in commandObj) {
|
||||
if (!commandObj.hasOwnProperty(title)) { continue; }
|
||||
commands.push({
|
||||
title,
|
||||
command: commandObj[title]
|
||||
.replace(/\$\{project\}/gi, encodeURI(repo))
|
||||
.replace(/\$\{project-base-name\}/gi,
|
||||
encodeURI(repo.substring(repo.lastIndexOf('/') + 1))),
|
||||
});
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
_computeRepositoriesClass(config) {
|
||||
return config ? 'showConfig': '';
|
||||
}
|
||||
|
||||
_computeChangesUrl(name) {
|
||||
return Gerrit.Nav.getUrlForProjectChanges(name);
|
||||
}
|
||||
|
||||
_handlePluginConfigChanged({detail: {name, config, notifyPath}}) {
|
||||
this._repoConfig.plugin_config[name] = config;
|
||||
this.notifyPath('_repoConfig.plugin_config.' + notifyPath);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(GrRepo.is, GrRepo);
|
||||
|
@ -1,37 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2017 The Android Open Source Project
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
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-autogrow-textarea/iron-autogrow-textarea.html">
|
||||
<link rel="import" href="/bower_components/iron-input/iron-input.html">
|
||||
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
|
||||
|
||||
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
|
||||
<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
|
||||
<link rel="import" href="../../shared/gr-download-commands/gr-download-commands.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-select/gr-select.html">
|
||||
<link rel="import" href="../../../styles/gr-form-styles.html">
|
||||
<link rel="import" href="../../../styles/gr-subpage-styles.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
<link rel="import" href="../gr-repo-plugin-config/gr-repo-plugin-config.html">
|
||||
|
||||
<dom-module id="gr-repo">
|
||||
<template>
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
@ -65,50 +50,37 @@ limitations under the License.
|
||||
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
|
||||
</style>
|
||||
<div class="info">
|
||||
<h1 id="Title" class$="name">
|
||||
<h1 id="Title" class\$="name">
|
||||
[[repo]]
|
||||
<hr/>
|
||||
<hr>
|
||||
</h1>
|
||||
<div>
|
||||
<a href$="[[_computeChangesUrl(repo)]]">(view changes)</a>
|
||||
<a href\$="[[_computeChangesUrl(repo)]]">(view changes)</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">Loading...</div>
|
||||
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="downloadContent" class$="[[_computeHideClass(_schemes)]]">
|
||||
<div id="loading" class\$="[[_computeLoadingClass(_loading)]]">Loading...</div>
|
||||
<div id="loadedContent" class\$="[[_computeLoadingClass(_loading)]]">
|
||||
<div id="downloadContent" class\$="[[_computeHideClass(_schemes)]]">
|
||||
<h2 id="download">Download</h2>
|
||||
<fieldset>
|
||||
<gr-download-commands
|
||||
id="downloadCommands"
|
||||
commands="[[_computeCommands(repo, _schemesObj, _selectedScheme)]]"
|
||||
schemes="[[_schemes]]"
|
||||
selected-scheme="{{_selectedScheme}}"></gr-download-commands>
|
||||
<gr-download-commands id="downloadCommands" commands="[[_computeCommands(repo, _schemesObj, _selectedScheme)]]" schemes="[[_schemes]]" selected-scheme="{{_selectedScheme}}"></gr-download-commands>
|
||||
</fieldset>
|
||||
</div>
|
||||
<h2 id="configurations"
|
||||
class$="[[_computeHeaderClass(_configChanged)]]">Configurations</h2>
|
||||
<h2 id="configurations" class\$="[[_computeHeaderClass(_configChanged)]]">Configurations</h2>
|
||||
<div id="form">
|
||||
<fieldset>
|
||||
<h3 id="Description">Description</h3>
|
||||
<fieldset>
|
||||
<iron-autogrow-textarea
|
||||
id="descriptionInput"
|
||||
class="description"
|
||||
autocomplete="on"
|
||||
placeholder="<Insert repo description here>"
|
||||
bind-value="{{_repoConfig.description}}"
|
||||
disabled$="[[_readOnly]]"></iron-autogrow-textarea>
|
||||
<iron-autogrow-textarea id="descriptionInput" class="description" autocomplete="on" placeholder="<Insert repo description here>" bind-value="{{_repoConfig.description}}" disabled\$="[[_readOnly]]"></iron-autogrow-textarea>
|
||||
</fieldset>
|
||||
<h3 id="Options">Repository Options</h3>
|
||||
<fieldset id="options">
|
||||
<section>
|
||||
<span class="title">State</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="stateSelect"
|
||||
bind-value="{{_repoConfig.state}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items=[[_states]]>
|
||||
<gr-select id="stateSelect" bind-value="{{_repoConfig.state}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_states]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -118,12 +90,9 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Submit type</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="submitTypeSelect"
|
||||
bind-value="{{_repoConfig.submit_type}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatSubmitTypeSelect(_repoConfig)]]">
|
||||
<gr-select id="submitTypeSelect" bind-value="{{_repoConfig.submit_type}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatSubmitTypeSelect(_repoConfig)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -133,12 +102,9 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Allow content merges</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="contentMergeSelect"
|
||||
bind-value="{{_repoConfig.use_content_merge.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.use_content_merge)]]">
|
||||
<gr-select id="contentMergeSelect" bind-value="{{_repoConfig.use_content_merge.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.use_content_merge)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -150,12 +116,9 @@ limitations under the License.
|
||||
Create a new change for every commit not in the target branch
|
||||
</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="newChangeSelect"
|
||||
bind-value="{{_repoConfig.create_new_change_for_all_not_in_target.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.create_new_change_for_all_not_in_target)]]">
|
||||
<gr-select id="newChangeSelect" bind-value="{{_repoConfig.create_new_change_for_all_not_in_target.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.create_new_change_for_all_not_in_target)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -165,46 +128,33 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Require Change-Id in commit message</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="requireChangeIdSelect"
|
||||
bind-value="{{_repoConfig.require_change_id.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.require_change_id)]]">
|
||||
<gr-select id="requireChangeIdSelect" bind-value="{{_repoConfig.require_change_id.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.require_change_id)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
</gr-select>
|
||||
</span>
|
||||
</section>
|
||||
<section
|
||||
id="enableSignedPushSettings"
|
||||
class$="repositorySettings [[_computeRepositoriesClass(_repoConfig.enable_signed_push)]]">
|
||||
<section id="enableSignedPushSettings" class\$="repositorySettings [[_computeRepositoriesClass(_repoConfig.enable_signed_push)]]">
|
||||
<span class="title">Enable signed push</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="enableSignedPush"
|
||||
bind-value="{{_repoConfig.enable_signed_push.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.enable_signed_push)]]">
|
||||
<gr-select id="enableSignedPush" bind-value="{{_repoConfig.enable_signed_push.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.enable_signed_push)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
</gr-select>
|
||||
</span>
|
||||
</section>
|
||||
<section
|
||||
id="requireSignedPushSettings"
|
||||
class$="repositorySettings [[_computeRepositoriesClass(_repoConfig.require_signed_push)]]">
|
||||
<section id="requireSignedPushSettings" class\$="repositorySettings [[_computeRepositoriesClass(_repoConfig.require_signed_push)]]">
|
||||
<span class="title">Require signed push</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="requireSignedPush"
|
||||
bind-value="{{_repoConfig.require_signed_push.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.require_signed_push)]]">
|
||||
<gr-select id="requireSignedPush" bind-value="{{_repoConfig.require_signed_push.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.require_signed_push)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -215,12 +165,9 @@ limitations under the License.
|
||||
<span class="title">
|
||||
Reject implicit merges when changes are pushed for review</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="rejectImplicitMergesSelect"
|
||||
bind-value="{{_repoConfig.reject_implicit_merges.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.reject_implicit_merges)]]">
|
||||
<gr-select id="rejectImplicitMergesSelect" bind-value="{{_repoConfig.reject_implicit_merges.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.reject_implicit_merges)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -231,12 +178,9 @@ limitations under the License.
|
||||
<span class="title">
|
||||
Enable adding unregistered users as reviewers and CCs on changes</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="unRegisteredCcSelect"
|
||||
bind-value="{{_repoConfig.enable_reviewer_by_email.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.enable_reviewer_by_email)]]">
|
||||
<gr-select id="unRegisteredCcSelect" bind-value="{{_repoConfig.enable_reviewer_by_email.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.enable_reviewer_by_email)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -247,12 +191,9 @@ limitations under the License.
|
||||
<span class="title">
|
||||
Set all new changes private by default</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="setAllnewChangesPrivateByDefaultSelect"
|
||||
bind-value="{{_repoConfig.private_by_default.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.private_by_default)]]">
|
||||
<gr-select id="setAllnewChangesPrivateByDefaultSelect" bind-value="{{_repoConfig.private_by_default.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.private_by_default)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -263,12 +204,9 @@ limitations under the License.
|
||||
<span class="title">
|
||||
Set new changes to "work in progress" by default</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="setAllNewChangesWorkInProgressByDefaultSelect"
|
||||
bind-value="{{_repoConfig.work_in_progress_by_default.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.work_in_progress_by_default)]]">
|
||||
<gr-select id="setAllNewChangesWorkInProgressByDefaultSelect" bind-value="{{_repoConfig.work_in_progress_by_default.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.work_in_progress_by_default)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -278,17 +216,8 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Maximum Git object size limit</span>
|
||||
<span class="value">
|
||||
<iron-input
|
||||
id="maxGitObjSizeIronInput"
|
||||
bind-value="{{_repoConfig.max_object_size_limit.configured_value}}"
|
||||
type="text"
|
||||
disabled$="[[_readOnly]]">
|
||||
<input
|
||||
id="maxGitObjSizeInput"
|
||||
bind-value="{{_repoConfig.max_object_size_limit.configured_value}}"
|
||||
is="iron-input"
|
||||
type="text"
|
||||
disabled$="[[_readOnly]]">
|
||||
<iron-input id="maxGitObjSizeIronInput" bind-value="{{_repoConfig.max_object_size_limit.configured_value}}" type="text" disabled\$="[[_readOnly]]">
|
||||
<input id="maxGitObjSizeInput" bind-value="{{_repoConfig.max_object_size_limit.configured_value}}" is="iron-input" type="text" disabled\$="[[_readOnly]]">
|
||||
</iron-input>
|
||||
<template is="dom-if" if="[[_repoConfig.max_object_size_limit.value]]">
|
||||
effective: [[_repoConfig.max_object_size_limit.value]] bytes
|
||||
@ -298,12 +227,9 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Match authored date with committer date upon submit</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="matchAuthoredDateWithCommitterDateSelect"
|
||||
bind-value="{{_repoConfig.match_author_to_committer_date.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.match_author_to_committer_date)]]">
|
||||
<gr-select id="matchAuthoredDateWithCommitterDateSelect" bind-value="{{_repoConfig.match_author_to_committer_date.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.match_author_to_committer_date)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -313,12 +239,9 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Reject empty commit upon submit</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="rejectEmptyCommitSelect"
|
||||
bind-value="{{_repoConfig.reject_empty_commit.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.reject_empty_commit)]]">
|
||||
<gr-select id="rejectEmptyCommitSelect" bind-value="{{_repoConfig.reject_empty_commit.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.reject_empty_commit)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -332,12 +255,9 @@ limitations under the License.
|
||||
<span class="title">
|
||||
Require a valid contributor agreement to upload</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="contributorAgreementSelect"
|
||||
bind-value="{{_repoConfig.use_contributor_agreements.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.use_contributor_agreements)]]">
|
||||
<gr-select id="contributorAgreementSelect" bind-value="{{_repoConfig.use_contributor_agreements.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.use_contributor_agreements)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -347,12 +267,9 @@ limitations under the License.
|
||||
<section>
|
||||
<span class="title">Require Signed-off-by in commit message</span>
|
||||
<span class="value">
|
||||
<gr-select
|
||||
id="useSignedOffBySelect"
|
||||
bind-value="{{_repoConfig.use_signed_off_by.configured_value}}">
|
||||
<select disabled$="[[_readOnly]]">
|
||||
<template is="dom-repeat"
|
||||
items="[[_formatBooleanSelect(_repoConfig.use_signed_off_by)]]">
|
||||
<gr-select id="useSignedOffBySelect" bind-value="{{_repoConfig.use_signed_off_by.configured_value}}">
|
||||
<select disabled\$="[[_readOnly]]">
|
||||
<template is="dom-repeat" items="[[_formatBooleanSelect(_repoConfig.use_signed_off_by)]]">
|
||||
<option value="[[item.value]]">[[item.label]]</option>
|
||||
</template>
|
||||
</select>
|
||||
@ -360,18 +277,13 @@ limitations under the License.
|
||||
</span>
|
||||
</section>
|
||||
</fieldset>
|
||||
<div
|
||||
class$="pluginConfig [[_computeHideClass(_pluginData)]]"
|
||||
on-plugin-config-changed="_handlePluginConfigChanged">
|
||||
<div class\$="pluginConfig [[_computeHideClass(_pluginData)]]" on-plugin-config-changed="_handlePluginConfigChanged">
|
||||
<h3>Plugins</h3>
|
||||
<template is="dom-repeat" items="[[_pluginData]]" as="data">
|
||||
<gr-repo-plugin-config
|
||||
plugin-data="[[data]]"></gr-repo-plugin-config>
|
||||
<gr-repo-plugin-config plugin-data="[[data]]"></gr-repo-plugin-config>
|
||||
</template>
|
||||
</div>
|
||||
<gr-button
|
||||
on-click="_handleSaveRepoConfig"
|
||||
disabled$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">Save changes</gr-button>
|
||||
<gr-button on-click="_handleSaveRepoConfig" disabled\$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">Save changes</gr-button>
|
||||
</fieldset>
|
||||
<gr-endpoint-decorator name="repo-config">
|
||||
<gr-endpoint-param name="repoName" value="[[repo]]"></gr-endpoint-param>
|
||||
@ -381,6 +293,4 @@ limitations under the License.
|
||||
</div>
|
||||
</main>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
</template>
|
||||
<script src="gr-repo.js"></script>
|
||||
</dom-module>
|
||||
`;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user