Skip to content
Snippets Groups Projects

Resolve "Customize branch name when using create branch in an issue"

Compare and
8 files
+ 336
40
Compare changes
  • Side-by-side
  • Inline
Files
8
@@ -12,22 +12,32 @@ const CREATE_BRANCH = 'create-branch';
export default class CreateMergeRequestDropdown {
constructor(wrapperEl) {
this.wrapperEl = wrapperEl;
this.availableButton = this.wrapperEl.querySelector('.available');
this.createMergeRequestButton = this.wrapperEl.querySelector('.js-create-merge-request');
this.dropdownToggle = this.wrapperEl.querySelector('.js-dropdown-toggle');
this.dropdownList = this.wrapperEl.querySelector('.dropdown-menu');
this.availableButton = this.wrapperEl.querySelector('.available');
this.dropdownToggle = this.wrapperEl.querySelector('.js-dropdown-toggle');
this.newBranchMessage = this.wrapperEl.querySelector('#new-branch-message');
this.branchInput = this.wrapperEl.querySelector('#new-branch-name');
this.refInput = this.wrapperEl.querySelector('#ref');
this.refMessage = this.wrapperEl.querySelector('#ref-message');
this.unavailableButton = this.wrapperEl.querySelector('.unavailable');
this.unavailableButtonArrow = this.unavailableButton.querySelector('.fa');
this.unavailableButtonText = this.unavailableButton.querySelector('.text');
this.createBranchPath = this.wrapperEl.dataset.createBranchPath;
this.branchCreated = false;
this.branchTaken = true;
this.canCreatePath = this.wrapperEl.dataset.canCreatePath;
this.createBranchPath = this.wrapperEl.dataset.createBranchPath;
this.createMrPath = this.wrapperEl.dataset.createMrPath;
this.droplabInitialized = false;
this.getRefsPath = this.wrapperEl.dataset.getRefsPath;
this.isCreatingBranch = false;
this.isCreatingMergeRequest = false;
this.isGettingRefs = false;
this.inputsAreValid = true;
this.mergeRequestCreated = false;
this.isCreatingBranch = false;
this.branchCreated = false;
this.delay = null;
this.refs = {};
this.init();
}
@@ -62,6 +72,11 @@ export default class CreateMergeRequestDropdown {
this.dropdownToggle.setAttribute('disabled', 'disabled');
}
disableCreateAction() {
this.createMergeRequestButton.classList.add('disabled');
this.createMergeRequestButton.setAttribute('disabled', 'disabled');
}
hide() {
this.wrapperEl.classList.add('hide');
}
@@ -107,6 +122,40 @@ export default class CreateMergeRequestDropdown {
});
}
getRefs() {
return $.ajax({
method: 'GET',
dataType: 'json',
url: this.getRefsPath,
beforeSend: () => {
this.isGettingRefs = true;
},
})
.always(() => {
this.isGettingRefs = false;
})
.done((data) => {
this.refs = data;
})
.fail((xhr) => {
this.unavailable();
this.disable();
new Flash('Failed to get refs.');
});
}
getRef(ref, type = 'all') {
if (!this.refs) return false;
const target = new RegExp('^' + $.trim(ref).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));
if (type === 'branch') {
return this.refs.Branches.find(value => target.test(value));
} else {
return this.refs.Branches.find(value => target.test(value)) || this.refs.Tags.find(value => target.test(value));
}
}
initDroplab() {
this.droplab = new DropLab();
@@ -128,15 +177,188 @@ export default class CreateMergeRequestDropdown {
}
bindEvents() {
this.createMergeRequestButton
.addEventListener('click', this.onClickCreateMergeRequestButton.bind(this));
this.createMergeRequestButton.addEventListener('click', this.onClickCreateMergeRequestButton.bind(this));
this.dropdownToggle.addEventListener('click', this.onClickSetFocusOnBranchNameInput.bind(this));
this.dropdownToggle.addEventListener('click', this.onClickGetRefs.bind(this));
this.branchInput.addEventListener('keyup', this.onChangeBranchInput.bind(this));
this.refInput.addEventListener('keyup', this.onChangeRefInput.bind(this));
this.refInput.addEventListener('keydown', this.processTab.bind(this));
}
isBusy() {
return this.isCreatingMergeRequest ||
this.mergeRequestCreated ||
this.isCreatingBranch ||
this.branchCreated;
this.branchCreated ||
this.isGettingRefs;
}
showCheckBranchExistsMessage() {
this.removeGlFieldErrorClasses('branch');
this.newBranchMessage.classList.add('gl-field-hint');
this.newBranchMessage.textContent = 'Checking branch name availability...';
this.newBranchMessage.classList.remove('hide');
}
showNewBranchTakenMessage() {
this.removeGlFieldErrorClasses('branch');
this.branchInput.classList.add('gl-field-error-outline');
this.newBranchMessage.classList.add('gl-field-error-message');
this.newBranchMessage.textContent = 'Branch name is already taken';
this.newBranchMessage.classList.remove('hide');
}
showNewBranchAvailableMessage() {
this.removeGlFieldErrorClasses('branch');
this.branchInput.classList.add('gl-field-success-outline');
this.newBranchMessage.classList.add('gl-field-success-message');
this.newBranchMessage.textContent = 'Branch name is available';
this.newBranchMessage.classList.remove('hide');
}
showCheckRefExistsMessage() {
this.removeGlFieldErrorClasses('ref');
this.refMessage.classList.add('gl-field-hint');
this.refMessage.textContent = 'Checking source availability...';
this.refMessage.classList.remove('hide');
}
showRefAvailableMessage() {
this.removeGlFieldErrorClasses('ref');
this.refInput.classList.add('gl-field-success-outline');
this.refMessage.classList.add('gl-field-success-message');
this.refMessage.textContent = 'Source is available';
this.refMessage.classList.remove('hide');
}
showRefNotAvailableMessage() {
this.removeGlFieldErrorClasses('ref');
this.refInput.classList.add('gl-field-error-outline');
this.refMessage.classList.add('gl-field-error-message');
this.refMessage.textContent = 'Source is not available';
this.refMessage.classList.remove('hide');
}
removeGlFieldErrorClasses(target) {
let input = null;
let message = null;
if (target === 'branch') {
input = this.branchInput;
message = this.newBranchMessage;
} else {
input = this.refInput;
message = this.refMessage;
}
input.classList.remove('gl-field-error-outline');
input.classList.remove('gl-field-success-outline');
message.classList.remove('gl-field-hint');
message.classList.remove('gl-field-error-message');
message.classList.remove('gl-field-success-message');
}
onClickSetFocusOnBranchNameInput() {
this.branchInput.focus();
}
onClickGetRefs() {
this.getRefs();
}
onChangeBranchInput(event) {
const branch = this.branchInput.value;
if (this.isGettingRefs) return;
// `ENTER` key submits the data.
if (event.keyCode === 13 && this.inputsAreValid) {
this.createMergeRequestButton.click();
return;
}
// `ESC` key closes the dropdown.
if (event.keyCode === 27) {
this.dropdownToggle.click();
return;
}
// If the input is empty, use the original branch name generated by the backend.
if (!branch) {
this.createBranchPath = this.wrapperEl.dataset.createBranchPath;
this.createMrPath = this.wrapperEl.dataset.createMrPath;
this.inputsAreValid = true;
this.enable();
this.showNewBranchAvailableMessage();
return;
}
if (this.getRef(branch, 'branch') === branch) {
this.inputsAreValid = false;
this.disableCreateAction();
this.showNewBranchTakenMessage();
} else {
this.inputsAreValid = true;
this.enable();
this.showNewBranchAvailableMessage();
this.createBranchPath = this.createBranchPath.replace(/(branch_name=)(.+?)(?=&issue)/, `$1${branch}`);
this.createMrPath = this.createMrPath.replace(/(branch_name=)(.+?)(?=&ref)/, `$1${branch}`);
}
}
onChangeRefInput(event) {
console.log(this.createMrPath)
// Remove selected text (autocomplete text).
const ref = this.refInput.value.slice(0, this.refInput.selectionStart) + this.refInput.value.slice(this.refInput.selectionEnd);
if (this.isGettingRefs) return;
// `ENTER` key submits the data.
if (event.keyCode === 13 && this.inputsAreValid) {
this.createMergeRequestButton.click();
return;
}
// `ESC` key closes the dropdown.
if (event.keyCode === 27) {
this.dropdownToggle.click();
return;
}
// If the input is empty, use the original ref name generated by the backend.
if (!ref) {
this.createBranchPath = this.wrapperEl.dataset.createBranchPath;
this.createMrPath = this.wrapperEl.dataset.createMrPath;
this.inputsAreValid = true;
this.enable();
this.showRefAvailableMessage();
return;
}
this.createBranchPath = this.createBranchPath.replace(/(ref=)(.+?)$/, `$1${ref}`);
this.createMrPath = this.createMrPath.replace(/(ref=)(.+?)$/, `$1${ref}`);
const foundRef = this.getRef(ref, 'all')
if (ref === foundRef) {
this.inputsAreValid = true;
this.enable();
this.showRefAvailableMessage();
} else {
this.inputsAreValid = false;
this.disableCreateAction();
this.showRefNotAvailableMessage();
// Show the first found ref as a hint.
if (foundRef) {
clearTimeout(this.delay);
this.delay = setTimeout(() => {
this.refInput.value = foundRef;
this.refInput.setSelectionRange(ref.length, foundRef.length)
}, 500);
}
}
}
onClickCreateMergeRequestButton(e) {
@@ -163,6 +385,18 @@ export default class CreateMergeRequestDropdown {
this.disable();
}
// `TAB` autocompletes the source.
processTab(event) {
if (event.keyCode !== 9) return;
const value = this.refInput.value;
event.preventDefault();
this.refInput.value = '';
this.refInput.value = value;
}
createMergeRequest() {
return $.ajax({
method: 'POST',
Loading