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)); +}