Convert gr-repo to lit
Change-Id: I07fe7564acffbff3e7651ce590f30457305cebe2
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 47c820a..a7a615fc 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -101,7 +101,6 @@
"elements/admin/gr-repo-access/gr-repo-access_html.ts",
"elements/admin/gr-repo-commands/gr-repo-commands_html.ts",
"elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_html.ts",
- "elements/admin/gr-repo/gr-repo_html.ts",
"elements/admin/gr-rule-editor/gr-rule-editor_html.ts",
"elements/change-list/gr-change-list-view/gr-change-list-view_html.ts",
"elements/change-list/gr-change-list/gr-change-list_html.ts",
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index 7092c9b..37c88f2 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -43,7 +43,6 @@
export interface ConfigChangeInfo {
_key: string; // parameterName of PluginParameterToConfigParameterInfoMap
info: ConfigParameterInfo;
- notifyPath: string;
}
export interface PluginData {
@@ -54,7 +53,6 @@
export interface PluginConfigChangeDetail {
name: string; // parameterName of PluginParameterToConfigParameterInfoMap
config: PluginParameterToConfigParameterInfoMap;
- notifyPath: string;
}
@customElement('gr-repo-plugin-config')
@@ -248,7 +246,6 @@
return {
_key,
info,
- notifyPath: `${_key}.value`,
};
}
@@ -256,7 +253,7 @@
this._handleChange(e.detail);
}
- _handleChange({_key, info, notifyPath}: ConfigChangeInfo) {
+ _handleChange({_key, info}: ConfigChangeInfo) {
// If pluginData is not set, editors are not created and this method
// can't be called
const {name, config} = this.pluginData!;
@@ -265,7 +262,6 @@
const detail: PluginConfigChangeDetail = {
name,
config: {...config, [_key]: info},
- notifyPath: `${name}.${notifyPath}`,
};
this.dispatchEvent(
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
index 8c2e6b3..4076b747f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
@@ -44,7 +44,6 @@
element._handleChange({
_key: 'plugin',
info: {value: 'newTest'},
- notifyPath: 'plugin.value',
});
assert.isTrue(eventStub.called);
@@ -52,7 +51,6 @@
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', () => {
@@ -151,7 +149,6 @@
const detail = element._buildConfigChangeInfo('newTest', 'plugin');
assert.equal(detail._key, 'plugin');
assert.deepEqual(detail.info, {value: 'newTest'});
- assert.equal(detail.notifyPath, 'plugin.value');
});
});
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index 72fc0bfb..47a4362 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -21,32 +21,35 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-download-commands/gr-download-commands';
import '../../shared/gr-select/gr-select';
-import '../../../styles/gr-font-styles';
-import '../../../styles/gr-form-styles';
-import '../../../styles/gr-subpage-styles';
-import '../../../styles/shared-styles';
import '../gr-repo-plugin-config/gr-repo-plugin-config';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-repo_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
-import {customElement, property, observe} from '@polymer/decorators';
import {
ConfigInfo,
RepoName,
InheritedBooleanInfo,
SchemesInfoMap,
ConfigInput,
+ MaxObjectSizeLimitInfo,
PluginParameterToConfigParameterInfoMap,
- PluginNameToPluginParametersMap,
} from '../../../types/common';
-import {PluginData} from '../gr-repo-plugin-config/gr-repo-plugin-config';
-import {ProjectState} from '../../../constants/constants';
-import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
+import {
+ InheritedBooleanInfoConfiguredValue,
+ ProjectState,
+ SubmitType,
+} from '../../../constants/constants';
import {hasOwnProperty} from '../../../utils/common-util';
import {firePageError, fireTitleChange} from '../../../utils/event-util';
import {appContext} from '../../../services/app-context';
import {WebLinkInfo} from '../../../types/diff';
import {ErrorCallback} from '../../../api/rest';
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {formStyles} from '../../../styles/gr-form-styles';
+import {subpageStyles} from '../../../styles/gr-subpage-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {BindValueChangeEvent} from '../../../types/events';
+import {deepClone} from '../../../utils/object-util';
+import {LitElement, PropertyValues, css, html} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
const STATES = {
active: {value: ProjectState.ACTIVE, label: 'Active'},
@@ -82,92 +85,667 @@
},
};
-@customElement('gr-repo')
-export class GrRepo extends PolymerElement {
- static get template() {
- return htmlTemplate;
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-repo': GrRepo;
}
+}
+
+@customElement('gr-repo')
+export class GrRepo extends LitElement {
+ private schemes: string[] = [];
@property({type: String})
repo?: RepoName;
- @property({type: Boolean})
- _configChanged = false;
+ /* private but used in test */
+ @state() loading = true;
- @property({type: Boolean})
- _loading = true;
+ /* private but used in test */
+ @state() repoConfig?: ConfigInfo;
- @property({type: Boolean, observer: '_loggedInChanged'})
- _loggedIn = false;
+ /* private but used in test */
+ @state() readOnly = true;
- @property({type: Object})
- _repoConfig?: ConfigInfo;
+ @state() private states = Object.values(STATES);
- @property({
- type: Array,
- computed: '_computePluginData(_repoConfig.plugin_config.*)',
- })
- _pluginData?: PluginData[];
+ @state() private originalConfig?: ConfigInfo;
- @property({type: Boolean})
- _readOnly = true;
+ @state() private selectedScheme?: string;
- @property({type: Array})
- _states = Object.values(STATES);
+ /* private but used in test */
+ @state() schemesObj?: SchemesInfoMap;
- @property({
- type: Array,
- computed: '_computeSchemes(_schemesDefault, _schemesObj)',
- observer: '_schemesChanged',
- })
- _schemes: string[] = [];
+ @state() private weblinks: WebLinkInfo[] = [];
- // This is workaround to have _schemes with default value [],
- // because assignment doesn't work when property has a computed attribute.
- @property({type: Array})
- _schemesDefault: string[] = [];
-
- @property({type: String})
- _selectedCommand = 'Clone';
-
- @property({type: String})
- _selectedScheme?: string;
-
- @property({type: Object})
- _schemesObj?: SchemesInfoMap;
-
- @property({type: Array})
- weblinks: WebLinkInfo[] = [];
+ @state() private pluginConfigChanged = false;
private readonly restApiService = appContext.restApiService;
override connectedCallback() {
super.connectedCallback();
- this._loadRepo();
+ this.loadRepo();
fireTitleChange(this, `${this.repo}`);
}
- _computePluginData(
- configRecord?: PolymerDeepPropertyChange<
- PluginNameToPluginParametersMap,
- PluginNameToPluginParametersMap
- >
- ) {
- if (!configRecord || !configRecord.base) {
- return [];
- }
+ static override get styles() {
+ return [
+ fontStyles,
+ formStyles,
+ subpageStyles,
+ sharedStyles,
+ css`
+ .info {
+ margin-bottom: var(--spacing-xl);
+ }
+ h2.edited:after {
+ color: var(--deemphasized-text-color);
+ content: ' *';
+ }
+ .loading,
+ .hide {
+ display: none;
+ }
+ #loading.loading {
+ display: block;
+ }
+ #loading:not(.loading) {
+ display: none;
+ }
+ #options .repositorySettings {
+ display: none;
+ }
+ #options .repositorySettings.showConfig {
+ display: block;
+ }
+ `,
+ ];
+ }
- const pluginConfig = configRecord.base;
+ override render() {
+ const configChanged = this.hasConfigChanged();
+ return html`
+ <div class="main gr-form-styles read-only">
+ <div class="info">
+ <h1 id="Title" class="heading-1">${this.repo}</h1>
+ <hr />
+ <div>
+ <a href=${this.weblinks?.[0]?.url}
+ ><gr-button link ?disabled=${!this.weblinks?.[0]?.url}
+ >Browse</gr-button
+ ></a
+ ><a href=${this.computeChangesUrl(this.repo)}
+ ><gr-button link>View Changes</gr-button></a
+ >
+ </div>
+ </div>
+ <div id="loading" class=${this.loading ? 'loading' : ''}>
+ Loading...
+ </div>
+ <div id="loadedContent" class=${this.loading ? 'loading' : ''}>
+ ${this.renderDownloadCommands()}
+ <h2
+ id="configurations"
+ class="heading-2 ${configChanged ? 'edited' : ''}"
+ >
+ Configurations
+ </h2>
+ <div id="form">
+ <fieldset>
+ ${this.renderDescription()} ${this.renderRepoOptions()}
+ ${this.renderPluginConfig()}
+ <gr-button
+ ?disabled=${this.readOnly || !configChanged}
+ @click=${this.handleSaveRepoConfig}
+ >Save changes</gr-button
+ >
+ </fieldset>
+ <gr-endpoint-decorator name="repo-config">
+ <gr-endpoint-param
+ name="repoName"
+ .value=${this.repo}
+ ></gr-endpoint-param>
+ <gr-endpoint-param
+ name="readOnly"
+ .value=${this.readOnly}
+ ></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ </div>
+ </div>
+ </div>
+ `;
+ }
+
+ private renderDownloadCommands() {
+ return html`
+ <div
+ id="downloadContent"
+ class=${!this.schemes || !this.schemes.length ? 'hide' : ''}
+ >
+ <h2 id="download" class="heading-2">Download</h2>
+ <fieldset>
+ <gr-download-commands
+ id="downloadCommands"
+ .commands=${this.computeCommands(
+ this.repo,
+ this.schemesObj,
+ this.selectedScheme
+ )}
+ .schemes=${this.schemes}
+ .selectedScheme=${this.selectedScheme}
+ @selected-scheme-changed=${this.handleSelectedSchemeValueChanged}
+ ></gr-download-commands>
+ </fieldset>
+ </div>
+ `;
+ }
+
+ private renderDescription() {
+ return html`
+ <h3 id="Description" class="heading-3">Description</h3>
+ <fieldset>
+ <iron-autogrow-textarea
+ id="descriptionInput"
+ class="description"
+ autocomplete="on"
+ placeholder="<Insert repo description here>"
+ .bindValue=${this.repoConfig?.description}
+ ?disabled=${this.readOnly}
+ @bind-value-changed=${this.handleDescriptionBindValueChanged}
+ ></iron-autogrow-textarea>
+ </fieldset>
+ `;
+ }
+
+ private renderRepoOptions() {
+ return html`
+ <h3 id="Options" class="heading-3">Repository Options</h3>
+ <fieldset id="options">
+ ${this.renderState()} ${this.renderSubmitType()}
+ ${this.renderContentMerges()} ${this.renderNewChange()}
+ ${this.renderChangeId()} ${this.renderEnableSignedPush()}
+ ${this.renderRequireSignedPush()} ${this.renderRejectImplicitMerges()}
+ ${this.renderUnRegisteredCc()} ${this.renderPrivateByDefault()}
+ ${this.renderWorkInProgressByDefault()} ${this.renderMaxGitObjectSize()}
+ ${this.renderMatchAuthoredDateWithCommitterDate()}
+ ${this.renderRejectEmptyCommit()}
+ </fieldset>
+ <h3 id="Options" class="heading-3">Contributor Agreements</h3>
+ <fieldset id="agreements">
+ ${this.renderContributorAgreement()} ${this.renderUseSignedOffBy()}
+ </fieldset>
+ `;
+ }
+
+ private renderState() {
+ return html`
+ <section>
+ <span class="title">State</span>
+ <span class="value">
+ <gr-select
+ id="stateSelect"
+ .bindValue=${this.repoConfig?.state}
+ @bind-value-changed=${this.handleStateSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.states.map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderSubmitType() {
+ return html`
+ <section>
+ <span class="title">Submit type</span>
+ <span class="value">
+ <gr-select
+ id="submitTypeSelect"
+ .bindValue=${this.repoConfig?.submit_type}
+ @bind-value-changed=${this.handleSubmitTypeSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatSubmitTypeSelect(this.repoConfig).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderContentMerges() {
+ return html`
+ <section>
+ <span class="title">Allow content merges</span>
+ <span class="value">
+ <gr-select
+ id="contentMergeSelect"
+ .bindValue=${this.repoConfig?.use_content_merge?.configured_value}
+ @bind-value-changed=${this.handleContentMergeSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.use_content_merge
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderNewChange() {
+ return html`
+ <section>
+ <span class="title">
+ Create a new change for every commit not in the target branch
+ </span>
+ <span class="value">
+ <gr-select
+ id="newChangeSelect"
+ .bindValue=${this.repoConfig
+ ?.create_new_change_for_all_not_in_target?.configured_value}
+ @bind-value-changed=${this.handleNewChangeSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.create_new_change_for_all_not_in_target
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderChangeId() {
+ return html`
+ <section>
+ <span class="title">Require Change-Id in commit message</span>
+ <span class="value">
+ <gr-select
+ id="requireChangeIdSelect"
+ .bindValue=${this.repoConfig?.require_change_id?.configured_value}
+ @bind-value-changed=${this
+ .handleRequireChangeIdSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.require_change_id
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderEnableSignedPush() {
+ return html`
+ <section
+ id="enableSignedPushSettings"
+ class="repositorySettings ${this.repoConfig?.enable_signed_push
+ ? 'showConfig'
+ : ''}"
+ >
+ <span class="title">Enable signed push</span>
+ <span class="value">
+ <gr-select
+ id="enableSignedPush"
+ .bindValue=${this.repoConfig?.enable_signed_push?.configured_value}
+ @bind-value-changed=${this.handleEnableSignedPushBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.enable_signed_push
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderRequireSignedPush() {
+ return html`
+ <section
+ id="requireSignedPushSettings"
+ class="repositorySettings ${this.repoConfig?.require_signed_push
+ ? 'showConfig'
+ : ''}"
+ >
+ <span class="title">Require signed push</span>
+ <span class="value">
+ <gr-select
+ id="requireSignedPush"
+ .bindValue=${this.repoConfig?.require_signed_push?.configured_value}
+ @bind-value-changed=${this.handleRequireSignedPushBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.require_signed_push
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderRejectImplicitMerges() {
+ return html`
+ <section>
+ <span class="title">
+ Reject implicit merges when changes are pushed for review</span
+ >
+ <span class="value">
+ <gr-select
+ id="rejectImplicitMergesSelect"
+ .bindValue=${this.repoConfig?.reject_implicit_merges
+ ?.configured_value}
+ @bind-value-changed=${this
+ .handleRejectImplicitMergeSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.reject_implicit_merges
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderUnRegisteredCc() {
+ return html`
+ <section>
+ <span class="title">
+ Enable adding unregistered users as reviewers and CCs on changes</span
+ >
+ <span class="value">
+ <gr-select
+ id="unRegisteredCcSelect"
+ .bindValue=${this.repoConfig?.enable_reviewer_by_email
+ ?.configured_value}
+ @bind-value-changed=${this
+ .handleUnRegisteredCcSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.enable_reviewer_by_email
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderPrivateByDefault() {
+ return html`
+ <section>
+ <span class="title"> Set all new changes private by default</span>
+ <span class="value">
+ <gr-select
+ id="setAllnewChangesPrivateByDefaultSelect"
+ .bindValue=${this.repoConfig?.private_by_default?.configured_value}
+ @bind-value-changed=${this
+ .handleSetAllNewChangesPrivateByDefaultSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.private_by_default
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderWorkInProgressByDefault() {
+ return html`
+ <section>
+ <span class="title">
+ Set new changes to "work in progress" by default</span
+ >
+ <span class="value">
+ <gr-select
+ id="setAllNewChangesWorkInProgressByDefaultSelect"
+ .bindValue=${this.repoConfig?.work_in_progress_by_default
+ ?.configured_value}
+ @bind-value-changed=${this
+ .handleSetAllNewChangesWorkInProgressByDefaultSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.work_in_progress_by_default
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderMaxGitObjectSize() {
+ return html`
+ <section>
+ <span class="title">Maximum Git object size limit</span>
+ <span class="value">
+ <iron-input
+ id="maxGitObjSizeIronInput"
+ .bindValue=${this.repoConfig?.max_object_size_limit
+ ?.configured_value}
+ type="text"
+ ?disabled=${this.readOnly}
+ @bind-value-changed=${this.handleMaxGitObjSizeBindValueChanged}
+ >
+ <input
+ id="maxGitObjSizeInput"
+ type="text"
+ ?disabled=${this.readOnly}
+ />
+ </iron-input>
+ ${this.repoConfig?.max_object_size_limit?.value
+ ? `effective: ${this.repoConfig.max_object_size_limit.value} bytes`
+ : ''}
+ </span>
+ </section>
+ `;
+ }
+
+ private renderMatchAuthoredDateWithCommitterDate() {
+ return html`
+ <section>
+ <span class="title"
+ >Match authored date with committer date upon submit</span
+ >
+ <span class="value">
+ <gr-select
+ id="matchAuthoredDateWithCommitterDateSelect"
+ .bindValue=${this.repoConfig?.match_author_to_committer_date
+ ?.configured_value}
+ @bind-value-changed=${this
+ .handleMatchAuthoredDateWithCommitterDateSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.match_author_to_committer_date
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderRejectEmptyCommit() {
+ return html`
+ <section>
+ <span class="title">Reject empty commit upon submit</span>
+ <span class="value">
+ <gr-select
+ id="rejectEmptyCommitSelect"
+ .bindValue=${this.repoConfig?.reject_empty_commit?.configured_value}
+ @bind-value-changed=${this
+ .handleRejectEmptyCommitSelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.reject_empty_commit
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderContributorAgreement() {
+ return html`
+ <section>
+ <span class="title">
+ Require a valid contributor agreement to upload</span
+ >
+ <span class="value">
+ <gr-select
+ id="contributorAgreementSelect"
+ .bindValue=${this.repoConfig?.use_contributor_agreements
+ ?.configured_value}
+ @bind-value-changed=${this
+ .handleUseContributorAgreementsBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.use_contributor_agreements
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderUseSignedOffBy() {
+ return html`
+ <section>
+ <span class="title">Require Signed-off-by in commit message</span>
+ <span class="value">
+ <gr-select
+ id="useSignedOffBySelect"
+ .bindValue=${this.repoConfig?.use_signed_off_by?.configured_value}
+ @bind-value-changed=${this
+ .handleUseSignedOffBySelectBindValueChanged}
+ >
+ <select ?disabled=${this.readOnly}>
+ ${this.formatBooleanSelect(
+ this.repoConfig?.use_signed_off_by
+ ).map(
+ item => html`
+ <option value=${item.value}>${item.label}</option>
+ `
+ )}
+ </select>
+ </gr-select>
+ </span>
+ </section>
+ `;
+ }
+
+ private renderPluginConfig() {
+ const pluginData = this.computePluginData();
+ return html` <div
+ class="pluginConfig ${!pluginData || !pluginData.length ? 'hide' : ''}"
+ @plugin-config-changed=${this.handlePluginConfigChanged}
+ >
+ <h3 class="heading-3">Plugins</h3>
+ ${pluginData.map(
+ item => html`
+ <gr-repo-plugin-config .pluginData=${item}></gr-repo-plugin-config>
+ `
+ )}
+ </div>`;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('schemesObj')) {
+ this.computeSchemesAndDefault();
+ }
+ }
+
+ /* private but used in test */
+ computePluginData() {
+ if (!this.repoConfig || !this.repoConfig.plugin_config) return [];
+ const pluginConfig = this.repoConfig.plugin_config;
return Object.keys(pluginConfig).map(name => {
return {name, config: pluginConfig[name]};
});
}
- _loadRepo() {
- if (!this.repo) {
- return Promise.resolve();
- }
+ /* private but used in test */
+ async loadRepo() {
+ if (!this.repo) return Promise.resolve();
const promises = [];
@@ -176,11 +754,16 @@
};
promises.push(
- this._getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
+ this.restApiService.getLoggedIn().then(loggedIn => {
if (loggedIn) {
const repo = this.repo;
if (!repo) throw new Error('undefined repo');
+ this.restApiService.getPreferences().then(prefs => {
+ if (prefs?.download_scheme) {
+ // Note (issue 5180): normalize the download scheme with lower-case.
+ this.selectedScheme = prefs.download_scheme.toLowerCase();
+ }
+ });
this.restApiService.getRepo(repo).then(repo => {
if (!repo?.web_links) return;
this.weblinks = repo.web_links;
@@ -191,71 +774,60 @@
}
// If the user is not an owner, is_owner is not a property.
- this._readOnly = !access[repo]?.is_owner;
+ this.readOnly = !access[repo]?.is_owner;
});
}
})
);
- promises.push(
- this.restApiService.getProjectConfig(this.repo, errFn).then(config => {
- if (!config) {
- return;
- }
+ const repoConfigHelper = async () => {
+ const config = await this.restApiService.getProjectConfig(
+ this.repo as RepoName,
+ errFn
+ );
+ if (!config) return;
- 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 as ProjectState;
- }
- this._repoConfig = config;
- this._loading = false;
- })
- );
-
- promises.push(
- this.restApiService.getConfig().then(config => {
- if (!config) {
- return;
- }
-
- this._schemesObj = config.download.schemes;
- })
- );
-
- return Promise.all(promises);
- }
-
- _computeLoadingClass(loading: boolean) {
- return loading ? 'loading' : '';
- }
-
- _computeHideClass(arr?: PluginData[] | string[]) {
- return !arr || !arr.length ? 'hide' : '';
- }
-
- _loggedInChanged(_loggedIn?: boolean) {
- if (!_loggedIn) {
- return;
- }
- this.restApiService.getPreferences().then(prefs => {
- if (prefs?.download_scheme) {
- // Note (issue 5180): normalize the download scheme with lower-case.
- this._selectedScheme = prefs.download_scheme.toLowerCase();
+ 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 as ProjectState;
+ }
+ // To properly check if the config has changed we need it to be a string
+ // as it's converted to a string in the input.
+ if (config.description === undefined) {
+ config.description = '';
+ }
+ // To properly check if the config has changed we need it to be a string
+ // as it's converted to a string in the input.
+ if (config.max_object_size_limit.configured_value === undefined) {
+ config.max_object_size_limit.configured_value = '';
+ }
+ this.repoConfig = config;
+ this.originalConfig = deepClone(config);
+ this.loading = false;
+ };
+ promises.push(repoConfigHelper());
+
+ const configHelper = async () => {
+ const config = await this.restApiService.getConfig();
+ if (!config) return;
+
+ this.schemesObj = config.download.schemes;
+ };
+ promises.push(configHelper());
+
+ await Promise.all(promises);
}
- _formatBooleanSelect(item: InheritedBooleanInfo) {
- if (!item) {
- return;
- }
+ /* private but used in test */
+ formatBooleanSelect(item?: InheritedBooleanInfo) {
+ if (!item) return [];
let inheritLabel = 'Inherit';
if (!(item.inherited_value === undefined)) {
inheritLabel = `Inherit (${item.inherited_value})`;
@@ -276,12 +848,10 @@
];
}
- _formatSubmitTypeSelect(projectConfig: ConfigInfo) {
- if (!projectConfig) {
- return;
- }
+ private formatSubmitTypeSelect(repoConfig?: ConfigInfo) {
+ if (!repoConfig) return [];
const allValues = Object.values(SUBMIT_TYPES);
- const type = projectConfig.default_submit_type;
+ const type = repoConfig.default_submit_type;
if (!type) {
// Server is too old to report default_submit_type, so assume INHERIT
// is not a valid value.
@@ -307,17 +877,9 @@
];
}
- _isLoading() {
- return this._loading || this._loading === undefined;
- }
-
- _getLoggedIn() {
- return this.restApiService.getLoggedIn();
- }
-
- _formatRepoConfigForSave(repoConfig?: ConfigInfo): ConfigInput {
+ /* private but used in test */
+ formatRepoConfigForSave(repoConfig?: ConfigInfo): ConfigInput {
if (!repoConfig) return {};
-
const configInputObj: ConfigInput = {};
for (const configKey of Object.keys(repoConfig)) {
const key = configKey as keyof ConfigInfo;
@@ -332,7 +894,7 @@
} else if (typeof repoConfig[key] === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const repoConfigObj: any = repoConfig[key];
- if (repoConfigObj.configured_value) {
+ if (repoConfigObj.configured_value !== undefined) {
configInputObj[key as keyof ConfigInput] =
repoConfigObj.configured_value;
}
@@ -344,56 +906,173 @@
return configInputObj;
}
- _handleSaveRepoConfig() {
- if (!this._repoConfig || !this.repo)
+ /* private but used in test */
+ async handleSaveRepoConfig() {
+ if (!this.repoConfig || !this.repo)
return Promise.reject(new Error('undefined repoConfig or repo'));
- return this.restApiService
- .saveRepoConfig(
- this.repo,
- this._formatRepoConfigForSave(this._repoConfig)
+ await this.restApiService.saveRepoConfig(
+ this.repo,
+ this.formatRepoConfigForSave(this.repoConfig)
+ );
+ this.originalConfig = deepClone(this.repoConfig);
+ this.pluginConfigChanged = false;
+ return;
+ }
+
+ private isEdited(
+ original?: InheritedBooleanInfo | MaxObjectSizeLimitInfo,
+ repo?: InheritedBooleanInfo | MaxObjectSizeLimitInfo
+ ) {
+ return original?.configured_value !== repo?.configured_value;
+ }
+
+ private hasConfigChanged() {
+ const {repoConfig, originalConfig} = this;
+
+ if (!repoConfig || !originalConfig) return false;
+
+ if (originalConfig.description !== repoConfig.description) {
+ return true;
+ }
+ if (originalConfig.state !== repoConfig.state) {
+ return true;
+ }
+ if (originalConfig.submit_type !== repoConfig.submit_type) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.use_content_merge,
+ repoConfig.use_content_merge
)
- .then(() => {
- this._configChanged = false;
- });
- }
-
- @observe('_repoConfig.*')
- _handleConfigChanged() {
- if (this._isLoading()) {
- return;
+ ) {
+ return true;
}
- this._configChanged = true;
- }
-
- _computeButtonDisabled(readOnly: boolean, configChanged: boolean) {
- return readOnly || !configChanged;
- }
-
- _computeHeaderClass(configChanged: boolean) {
- return configChanged ? 'edited' : '';
- }
-
- _computeSchemes(schemesDefault: string[], schemesObj?: SchemesInfoMap) {
- return !schemesObj ? schemesDefault : Object.keys(schemesObj);
- }
-
- _schemesChanged(schemes: string[]) {
- if (schemes.length === 0) {
- return;
+ if (
+ this.isEdited(
+ originalConfig.create_new_change_for_all_not_in_target,
+ repoConfig.create_new_change_for_all_not_in_target
+ )
+ ) {
+ return true;
}
- if (!this._selectedScheme || !schemes.includes(this._selectedScheme)) {
- this._selectedScheme = schemes.sort()[0];
+ if (
+ this.isEdited(
+ originalConfig.require_change_id,
+ repoConfig.require_change_id
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.enable_signed_push,
+ repoConfig.enable_signed_push
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.require_signed_push,
+ repoConfig.require_signed_push
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.reject_implicit_merges,
+ repoConfig.reject_implicit_merges
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.enable_reviewer_by_email,
+ repoConfig.enable_reviewer_by_email
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.private_by_default,
+ repoConfig.private_by_default
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.work_in_progress_by_default,
+ repoConfig.work_in_progress_by_default
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.max_object_size_limit,
+ repoConfig.max_object_size_limit
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.match_author_to_committer_date,
+ repoConfig.match_author_to_committer_date
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.reject_empty_commit,
+ repoConfig.reject_empty_commit
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.use_contributor_agreements,
+ repoConfig.use_contributor_agreements
+ )
+ ) {
+ return true;
+ }
+ if (
+ this.isEdited(
+ originalConfig.use_signed_off_by,
+ repoConfig.use_signed_off_by
+ )
+ ) {
+ return true;
+ }
+
+ return this.pluginConfigChanged;
+ }
+
+ private computeSchemesAndDefault() {
+ this.schemes = !this.schemesObj ? [] : Object.keys(this.schemesObj);
+ if (this.schemes.length > 0) {
+ if (!this.selectedScheme || !this.schemes.includes(this.selectedScheme)) {
+ this.selectedScheme = this.schemes.sort()[0];
+ }
}
}
- _computeCommands(
+ private computeCommands(
repo?: RepoName,
schemesObj?: SchemesInfoMap,
- _selectedScheme?: string
+ selectedScheme?: string
) {
- if (!schemesObj || !repo || !_selectedScheme) return [];
- if (!hasOwnProperty(schemesObj, _selectedScheme)) return [];
- const commandObj = schemesObj[_selectedScheme].clone_commands;
+ if (!schemesObj || !repo || !selectedScheme) return [];
+ if (!hasOwnProperty(schemesObj, selectedScheme)) return [];
+ const commandObj = schemesObj[selectedScheme].clone_commands;
const commands = [];
for (const [title, command] of Object.entries(commandObj)) {
commands.push({
@@ -409,36 +1088,171 @@
return commands;
}
- _computeRepositoriesClass(config: InheritedBooleanInfo) {
- return config ? 'showConfig' : '';
+ private computeChangesUrl(name?: RepoName) {
+ if (!name) return '';
+ return GerritNav.getUrlForProjectChanges(name as RepoName);
}
- _computeChangesUrl(name: RepoName) {
- return GerritNav.getUrlForProjectChanges(name);
- }
-
- _computeBrowseUrl(weblinks: WebLinkInfo[]) {
- return weblinks?.[0]?.url;
- }
-
- _handlePluginConfigChanged({
- detail: {name, config, notifyPath},
+ /* private but used in test */
+ handlePluginConfigChanged({
+ detail: {name, config},
}: {
detail: {
name: string;
config: PluginParameterToConfigParameterInfoMap;
- notifyPath: string;
};
}) {
- if (this._repoConfig?.plugin_config) {
- this._repoConfig.plugin_config[name] = config;
- this.notifyPath('_repoConfig.plugin_config.' + notifyPath);
+ if (this.repoConfig?.plugin_config) {
+ this.repoConfig.plugin_config[name] = config;
+ this.pluginConfigChanged = true;
+ this.requestUpdate();
}
}
-}
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-repo': GrRepo;
+ private handleSelectedSchemeValueChanged(e: CustomEvent) {
+ if (this.loading) return;
+ this.selectedScheme = e.detail.value;
+ }
+
+ private handleDescriptionBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig || this.loading) return;
+ this.repoConfig = {
+ ...this.repoConfig,
+ description: e.detail.value,
+ };
+ this.requestUpdate();
+ }
+
+ private handleStateSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig || this.loading) return;
+ this.repoConfig = {
+ ...this.repoConfig,
+ state: e.detail.value as ProjectState,
+ };
+ this.requestUpdate();
+ }
+
+ private handleSubmitTypeSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig || this.loading) return;
+ this.repoConfig = {
+ ...this.repoConfig,
+ submit_type: e.detail.value as SubmitType,
+ };
+ this.requestUpdate();
+ }
+
+ private handleContentMergeSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.use_content_merge || this.loading) return;
+ this.repoConfig.use_content_merge.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleNewChangeSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (
+ !this.repoConfig?.create_new_change_for_all_not_in_target ||
+ this.loading
+ )
+ return;
+ this.repoConfig.create_new_change_for_all_not_in_target.configured_value = e
+ .detail.value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleRequireChangeIdSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.require_change_id || this.loading) return;
+ this.repoConfig.require_change_id.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleEnableSignedPushBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.enable_signed_push || this.loading) return;
+ this.repoConfig.enable_signed_push.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleRequireSignedPushBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.require_signed_push || this.loading) return;
+ this.repoConfig.require_signed_push.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleRejectImplicitMergeSelectBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.reject_implicit_merges || this.loading) return;
+ this.repoConfig.reject_implicit_merges.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleUnRegisteredCcSelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.enable_reviewer_by_email || this.loading) return;
+ this.repoConfig.enable_reviewer_by_email.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleSetAllNewChangesPrivateByDefaultSelectBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.private_by_default || this.loading) return;
+ this.repoConfig.private_by_default.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleSetAllNewChangesWorkInProgressByDefaultSelectBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.work_in_progress_by_default || this.loading) return;
+ this.repoConfig.work_in_progress_by_default.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleMaxGitObjSizeBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.max_object_size_limit || this.loading) return;
+ this.repoConfig.max_object_size_limit.value = e.detail.value;
+ this.repoConfig.max_object_size_limit.configured_value = e.detail.value;
+ this.requestUpdate();
+ }
+
+ private handleMatchAuthoredDateWithCommitterDateSelectBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.match_author_to_committer_date || this.loading)
+ return;
+ this.repoConfig.match_author_to_committer_date.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleRejectEmptyCommitSelectBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.reject_empty_commit || this.loading) return;
+ this.repoConfig.reject_empty_commit.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleUseContributorAgreementsBindValueChanged(
+ e: BindValueChangeEvent
+ ) {
+ if (!this.repoConfig?.use_contributor_agreements || this.loading) return;
+ this.repoConfig.use_contributor_agreements.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
+ }
+
+ private handleUseSignedOffBySelectBindValueChanged(e: BindValueChangeEvent) {
+ if (!this.repoConfig?.use_signed_off_by || this.loading) return;
+ this.repoConfig.use_signed_off_by.configured_value = e.detail
+ .value as InheritedBooleanInfoConfiguredValue;
+ this.requestUpdate();
}
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts
deleted file mode 100644
index 71abec0..0000000
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_html.ts
+++ /dev/null
@@ -1,449 +0,0 @@
-/**
- * @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';
-
-export const htmlTemplate = html`
- <style include="gr-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="gr-subpage-styles">
- .info {
- margin-bottom: var(--spacing-xl);
- }
- h2.edited:after {
- color: var(--deemphasized-text-color);
- content: ' *';
- }
- .loading,
- .hide {
- display: none;
- }
- #loading.loading {
- display: block;
- }
- #loading:not(.loading) {
- display: none;
- }
- #options .repositorySettings {
- display: none;
- }
- #options .repositorySettings.showConfig {
- display: block;
- }
- </style>
- <style include="gr-form-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <div class="main gr-form-styles read-only">
- <style include="shared-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <div class="info">
- <h1 id="Title" class="heading-1">[[repo]]</h1>
- <hr />
- <div>
- <a href$="[[_computeBrowseUrl(weblinks)]]"
- ><gr-button link disabled="[[!_computeBrowseUrl(weblinks)]]"
- >Browse</gr-button
- ></a
- ><a href$="[[_computeChangesUrl(repo)]]"
- ><gr-button link>View Changes</gr-button></a
- >
- </div>
- </div>
- <div id="loading" class$="[[_computeLoadingClass(_loading)]]">
- Loading...
- </div>
- <div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
- <div id="downloadContent" class$="[[_computeHideClass(_schemes)]]">
- <h2 id="download" class="heading-2">Download</h2>
- <fieldset>
- <gr-download-commands
- id="downloadCommands"
- commands="[[_computeCommands(repo, _schemesObj, _selectedScheme)]]"
- schemes="[[_schemes]]"
- selected-scheme="{{_selectedScheme}}"
- ></gr-download-commands>
- </fieldset>
- </div>
- <h2
- id="configurations"
- class$="heading-2 [[_computeHeaderClass(_configChanged)]]"
- >
- Configurations
- </h2>
- <div id="form">
- <fieldset>
- <h3 id="Description" class="heading-3">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>
- </fieldset>
- <h3 id="Options" class="heading-3">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]]">
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <section>
- <span class="title">
- 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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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>
- <template
- is="dom-if"
- if="[[_repoConfig.max_object_size_limit.value]]"
- >
- effective: [[_repoConfig.max_object_size_limit.value]] bytes
- </template>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- </fieldset>
- <h3 id="Options" class="heading-3">Contributor Agreements</h3>
- <fieldset id="agreements">
- <section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- <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)]]"
- >
- <option value="[[item.value]]">[[item.label]]</option>
- </template>
- </select>
- </gr-select>
- </span>
- </section>
- </fieldset>
- <div
- class$="pluginConfig [[_computeHideClass(_pluginData)]]"
- on-plugin-config-changed="_handlePluginConfigChanged"
- >
- <h3 class="heading-3">Plugins</h3>
- <template is="dom-repeat" items="[[_pluginData]]" as="data">
- <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
- >
- </fieldset>
- <gr-endpoint-decorator name="repo-config">
- <gr-endpoint-param
- name="repoName"
- value="[[repo]]"
- ></gr-endpoint-param>
- <gr-endpoint-param
- name="readOnly"
- value="[[_readOnly]]"
- ></gr-endpoint-param>
- </gr-endpoint-decorator>
- </div>
- </div>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
index 642b224..86dccff 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
@@ -36,7 +36,6 @@
GroupName,
InheritedBooleanInfo,
MaxObjectSizeLimitInfo,
- PluginNameToPluginParametersMap,
PluginParameterToConfigParameterInfoMap,
ProjectAccessGroups,
ProjectAccessInfoMap,
@@ -48,7 +47,6 @@
ProjectState,
SubmitType,
} from '../../../constants/constants';
-import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {
createConfig,
createDownloadSchemes,
@@ -163,74 +161,63 @@
return inputs.concat(textareas).concat(selects);
}
- setup(() => {
+ setup(async () => {
loggedInStub = stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getConfig').returns(Promise.resolve(createServerInfo()));
repoStub = stubRestApi('getProjectConfig').returns(
Promise.resolve(repoConf)
);
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- test('_computePluginData', () => {
- assert.deepEqual(element._computePluginData(), []);
- assert.deepEqual(
- element._computePluginData(
- {} as unknown as PolymerDeepPropertyChange<
- PluginNameToPluginParametersMap,
- PluginNameToPluginParametersMap
- >
- ),
- []
- );
- assert.deepEqual(
- element._computePluginData({base: {}} as PolymerDeepPropertyChange<
- PluginNameToPluginParametersMap,
- PluginNameToPluginParametersMap
- >),
- []
- );
- assert.deepEqual(
- element._computePluginData({
- base: {
- 'test-plugin': {test: {display_name: 'test plugin', type: 'STRING'}},
- } as PluginNameToPluginParametersMap,
- } as PolymerDeepPropertyChange<PluginNameToPluginParametersMap, PluginNameToPluginParametersMap>),
- [
- {
- name: 'test-plugin',
- config: {
- test: {
- display_name: 'test plugin',
- type: 'STRING' as ConfigParameterInfoType,
- },
- },
- },
- ]
- );
- });
-
- test('_handlePluginConfigChanged', async () => {
- const notifyStub = sinon.stub(element, 'notifyPath');
- element._repoConfig = {
+ test('_computePluginData', async () => {
+ element.repoConfig = {
...createConfig(),
plugin_config: {},
};
- element._handlePluginConfigChanged({
+ await element.updateComplete;
+ assert.deepEqual(element.computePluginData(), []);
+
+ element.repoConfig.plugin_config = {
+ 'test-plugin': {
+ test: {display_name: 'test plugin', type: 'STRING'},
+ } as PluginParameterToConfigParameterInfoMap,
+ };
+ await element.updateComplete;
+ assert.deepEqual(element.computePluginData(), [
+ {
+ name: 'test-plugin',
+ config: {
+ test: {
+ display_name: 'test plugin',
+ type: 'STRING' as ConfigParameterInfoType,
+ },
+ },
+ },
+ ]);
+ });
+
+ test('handlePluginConfigChanged', async () => {
+ const requestUpdateStub = sinon.stub(element, 'requestUpdate');
+ element.repoConfig = {
+ ...createConfig(),
+ plugin_config: {},
+ };
+ element.handlePluginConfigChanged({
detail: {
name: 'test',
config: {
test: {display_name: 'test plugin', type: 'STRING'},
} as PluginParameterToConfigParameterInfoMap,
- notifyPath: 'path',
},
});
- await flush();
+ await element.updateComplete;
- assert.deepEqual(element._repoConfig!.plugin_config!.test, {
+ assert.deepEqual(element.repoConfig!.plugin_config!.test, {
test: {display_name: 'test plugin', type: 'STRING'},
} as PluginParameterToConfigParameterInfoMap);
- assert.equal(notifyStub.lastCall.args[0], '_repoConfig.plugin_config.path');
+ assert.isTrue(requestUpdateStub.called);
});
test('loading displays before repo config is loaded', () => {
@@ -257,8 +244,8 @@
});
test('download commands visibility', async () => {
- element._loading = false;
- await flush();
+ element.loading = false;
+ await element.updateComplete;
assert.isTrue(
queryAndAssert<HTMLDivElement>(
element,
@@ -270,8 +257,8 @@
queryAndAssert<HTMLDivElement>(element, '#downloadContent')
).display === 'none'
);
- element._schemesObj = SCHEMES;
- await flush();
+ element.schemesObj = SCHEMES;
+ await element.updateComplete;
assert.isFalse(
queryAndAssert<HTMLDivElement>(
element,
@@ -286,13 +273,13 @@
});
test('form defaults to read only', () => {
- assert.isTrue(element._readOnly);
+ assert.isTrue(element.readOnly);
});
test('form defaults to read only when not logged in', async () => {
element.repo = REPO as RepoName;
- await element._loadRepo();
- assert.isTrue(element._readOnly);
+ await element.loadRepo();
+ assert.isTrue(element.readOnly);
});
test('form defaults to read only when logged in and not admin', async () => {
@@ -321,26 +308,26 @@
},
} as ProjectAccessInfoMap)
);
- await element._loadRepo();
- assert.isTrue(element._readOnly);
+ await element.loadRepo();
+ assert.isTrue(element.readOnly);
});
test('all form elements are disabled when not admin', async () => {
element.repo = REPO as RepoName;
- await element._loadRepo();
- flush();
+ await element.loadRepo();
+ await element.updateComplete;
const formFields = getFormFields();
for (const field of formFields) {
assert.isTrue(field.hasAttribute('disabled'));
}
});
- test('_formatBooleanSelect', () => {
+ test('formatBooleanSelect', () => {
let item: InheritedBooleanInfo = {
...createInheritedBoolean(true),
inherited_value: true,
};
- assert.deepEqual(element._formatBooleanSelect(item), [
+ assert.deepEqual(element.formatBooleanSelect(item), [
{
label: 'Inherit (true)',
value: 'INHERIT',
@@ -356,7 +343,7 @@
]);
item = {...createInheritedBoolean(false), inherited_value: false};
- assert.deepEqual(element._formatBooleanSelect(item), [
+ assert.deepEqual(element.formatBooleanSelect(item), [
{
label: 'Inherit (false)',
value: 'INHERIT',
@@ -373,7 +360,7 @@
// For items without inherited values
item = createInheritedBoolean(false);
- assert.deepEqual(element._formatBooleanSelect(item), [
+ assert.deepEqual(element.formatBooleanSelect(item), [
{
label: 'Inherit',
value: 'INHERIT',
@@ -407,7 +394,7 @@
pageErrorFired.resolve();
});
- element._loadRepo();
+ element.loadRepo();
await pageErrorFired;
});
@@ -442,18 +429,18 @@
});
test('all form elements are enabled', async () => {
- await element._loadRepo();
- await flush();
+ await element.loadRepo();
+ await element.updateComplete;
const formFields = getFormFields();
for (const field of formFields) {
assert.isFalse(field.hasAttribute('disabled'));
}
- assert.isFalse(element._loading);
+ assert.isFalse(element.loading);
});
test('state gets set correctly', async () => {
- await element._loadRepo();
- assert.equal(element._repoConfig!.state, ProjectState.ACTIVE);
+ await element.loadRepo();
+ assert.equal(element.repoConfig!.state, ProjectState.ACTIVE);
assert.equal(
queryAndAssert<GrSelect>(element, '#stateSelect').bindValue,
ProjectState.ACTIVE
@@ -461,7 +448,7 @@
});
test('inherited submit type value is calculated correctly', async () => {
- await element._loadRepo();
+ await element.loadRepo();
const sel = queryAndAssert<GrSelect>(element, '#submitTypeSelect');
assert.equal(sel.bindValue, 'INHERIT');
assert.equal(
@@ -499,7 +486,7 @@
const button = queryAll<GrButton>(element, 'gr-button')[2];
- await element._loadRepo();
+ await element.loadRepo();
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(
queryAndAssert<HTMLHeadingElement>(
@@ -556,6 +543,8 @@
queryAndAssert<GrSelect>(element, '#unRegisteredCcSelect').bindValue =
configInputObj.enable_reviewer_by_email;
+ await element.updateComplete;
+
assert.isFalse(button.hasAttribute('disabled'));
assert.isTrue(
queryAndAssert<HTMLHeadingElement>(
@@ -564,12 +553,10 @@
).classList.contains('edited')
);
- const formattedObj = element._formatRepoConfigForSave(
- element._repoConfig
- );
+ const formattedObj = element.formatRepoConfigForSave(element.repoConfig);
assert.deepEqual(formattedObj, configInputObj);
- await element._handleSaveRepoConfig();
+ await element.handleSaveRepoConfig();
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(
queryAndAssert<HTMLHeadingElement>(
diff --git a/polygerrit-ui/app/utils/object-util.ts b/polygerrit-ui/app/utils/object-util.ts
new file mode 100644
index 0000000..95676c5
--- /dev/null
+++ b/polygerrit-ui/app/utils/object-util.ts
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * @param obj Object
+ */
+export function deepClone(obj?: object) {
+ if (!obj) return undefined;
+ return JSON.parse(JSON.stringify(obj));
+}