blob: b3f1e96f08cbdede899cf9cfb0446dcabd7e1dcc [file] [log] [blame]
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '@polymer/iron-input/iron-input';
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
import {
RepoName,
BranchName,
ChangeId,
InheritedBooleanInfo,
} from '../../../types/common';
import {InheritedBooleanInfoConfiguredValue} from '../../../constants/constants';
import {getAppContext} from '../../../services/app-context';
import {formStyles} from '../../../styles/gr-form-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {fire} from '../../../utils/event-util';
import {subscribe} from '../../lit/subscription-controller';
import {configModelToken} from '../../../models/config/config-model';
import {resolve} from '../../../models/dependency';
import {createChangeUrl} from '../../../models/views/change';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
const SUGGESTIONS_LIMIT = 15;
const REF_PREFIX = 'refs/heads/';
declare global {
interface HTMLElementTagNameMap {
'gr-create-change-dialog': GrCreateChangeDialog;
}
interface HTMLElementEventMap {
'can-create-change': CustomEvent<{}>;
}
}
@customElement('gr-create-change-dialog')
export class GrCreateChangeDialog extends LitElement {
// private but used in test
@query('#privateChangeCheckBox') privateChangeCheckBox!: HTMLInputElement;
@property({type: String})
repoName?: RepoName;
// private but used in test
@state() branch = '' as BranchName;
// private but used in test
@state() subject = '';
// private but used in test
@state() topic?: string;
@property({type: String})
baseChange?: ChangeId;
@state() private baseCommit?: string;
@property({type: Object})
privateByDefault?: InheritedBooleanInfo;
@state() private privateChangesEnabled = false;
private readonly query: (input: string) => Promise<{name: BranchName}[]>;
private readonly restApiService = getAppContext().restApiService;
private readonly configModel = resolve(this, configModelToken);
private readonly getNavigation = resolve(this, navigationToken);
constructor() {
super();
this.query = (input: string) => this.getRepoBranchesSuggestions(input);
subscribe(
this,
() => this.configModel().serverConfig$,
config => {
this.privateChangesEnabled =
config?.change?.disable_private_changes ?? false;
}
);
}
static override get styles() {
return [
formStyles,
sharedStyles,
css`
input:not([type='checkbox']),
gr-autocomplete,
iron-autogrow-textarea {
width: 100%;
}
.value {
width: 32em;
}
.hide {
display: none;
}
@media only screen and (max-width: 40em) {
.value {
width: 29em;
}
}
`,
];
}
override render() {
return html`
<div class="gr-form-styles">
<section class=${this.baseChange ? 'hide' : ''}>
<span class="title">Select branch for new change</span>
<span class="value">
<gr-autocomplete
id="branchInput"
.text=${this.branch}
.query=${this.query}
placeholder="Destination branch"
@text-changed=${(e: ValueChangedEvent<BranchName>) => {
this.branch = e.detail.value;
}}
>
</gr-autocomplete>
</span>
</section>
<section class=${this.baseChange ? 'hide' : ''}>
<span class="title">Provide base commit sha1 for change</span>
<span class="value">
<iron-input
.bindValue=${this.baseCommit}
@bind-value-changed=${(e: BindValueChangeEvent) => {
this.baseCommit = e.detail.value;
}}
>
<input
id="baseCommitInput"
maxlength="40"
placeholder="(optional)"
/>
</iron-input>
</span>
</section>
<section>
<span class="title">Enter topic for new change</span>
<span class="value">
<iron-input
.bindValue=${this.topic}
@bind-value-changed=${(e: BindValueChangeEvent) => {
this.topic = e.detail.value;
}}
>
<input
id="tagNameInput"
maxlength="1024"
placeholder="(optional)"
/>
</iron-input>
</span>
</section>
<section id="description">
<span class="title">Description</span>
<span class="value">
<iron-autogrow-textarea
id="messageInput"
class="message"
autocomplete="on"
rows="4"
maxRows="15"
.bindValue=${this.subject}
placeholder="Insert the description of the change."
@bind-value-changed=${(e: BindValueChangeEvent) => {
this.subject = e.detail.value ?? '';
}}
>
</iron-autogrow-textarea>
</span>
</section>
<section class=${this.privateChangesEnabled ? 'hide' : ''}>
<label class="title" for="privateChangeCheckBox"
>Private change</label
>
<span class="value">
<input
type="checkbox"
id="privateChangeCheckBox"
?checked=${this.formatPrivateByDefaultBoolean()}
/>
</span>
</section>
</div>
`;
}
override willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('branch') || changedProperties.has('subject')) {
this.allowCreate();
}
}
private allowCreate() {
fire(this, 'can-create-change', {});
}
handleCreateChange(): Promise<void> {
if (!this.repoName || !this.branch || !this.subject) {
return Promise.resolve();
}
const isPrivate = this.privateChangeCheckBox.checked;
const isWip = true;
return this.restApiService
.createChange(
this.repoName,
this.branch,
this.subject,
this.topic,
isPrivate,
isWip,
this.baseChange,
this.baseCommit || undefined
)
.then(change => {
if (!change) return;
this.getNavigation().setUrl(createChangeUrl({change}));
});
}
// private but used in test
getRepoBranchesSuggestions(input: string) {
if (!this.repoName) {
return Promise.reject(new Error('missing repo name'));
}
if (input.startsWith(REF_PREFIX)) {
input = input.substring(REF_PREFIX.length);
}
return this.restApiService
.getRepoBranches(
input,
this.repoName,
SUGGESTIONS_LIMIT,
/* offset=*/ undefined,
throwingErrorCallback
)
.then(response => {
if (!response) return [];
const branches: Array<{name: BranchName}> = [];
for (const branchInfo of response) {
let name: string = branchInfo.ref;
if (name.startsWith('refs/heads/')) {
name = name.substring('refs/heads/'.length);
}
branches.push({name: name as BranchName});
}
return branches;
});
}
// private but used in test
formatPrivateByDefaultBoolean() {
const config = this.privateByDefault;
if (config === undefined) return false;
switch (config.configured_value) {
case InheritedBooleanInfoConfiguredValue.TRUE:
return true;
case InheritedBooleanInfoConfiguredValue.FALSE:
return false;
case InheritedBooleanInfoConfiguredValue.INHERIT:
return !!config.inherited_value;
default:
return false;
}
}
}