diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
commit | edaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch) | |
tree | 11f143effbfeba52329fb7afbd05e6e2a3790241 /app/assets/javascripts/create_merge_request_dropdown.js | |
parent | d8a5691316400a0f7ec4f83832698f1988eb27c1 (diff) |
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/create_merge_request_dropdown.js')
-rw-r--r-- | app/assets/javascripts/create_merge_request_dropdown.js | 566 |
1 files changed, 0 insertions, 566 deletions
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js deleted file mode 100644 index ae6e6bf02e4..00000000000 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ /dev/null @@ -1,566 +0,0 @@ -import { debounce } from 'lodash'; -import { - init as initConfidentialMergeRequest, - isConfidentialIssue, - canCreateConfidentialMergeRequest, -} from './confidential_merge_request'; -import confidentialMergeRequestState from './confidential_merge_request/state'; -import DropLab from './filtered_search/droplab/drop_lab_deprecated'; -import ISetter from './filtered_search/droplab/plugins/input_setter'; -import createFlash from './flash'; -import axios from './lib/utils/axios_utils'; -import { __, sprintf } from './locale'; - -// Todo: Remove this when fixing issue in input_setter plugin -const InputSetter = { ...ISetter }; - -const CREATE_MERGE_REQUEST = 'create-mr'; -const CREATE_BRANCH = 'create-branch'; - -function createEndpoint(projectPath, endpoint) { - if (canCreateConfidentialMergeRequest()) { - return endpoint.replace( - projectPath, - confidentialMergeRequestState.selectedProject.pathWithNamespace, - ); - } - - return endpoint; -} - -export default class CreateMergeRequestDropdown { - constructor(wrapperEl) { - this.wrapperEl = wrapperEl; - this.availableButton = this.wrapperEl.querySelector('.available'); - this.branchInput = this.wrapperEl.querySelector('.js-branch-name'); - this.branchMessage = this.wrapperEl.querySelector('.js-branch-message'); - this.createMergeRequestButton = this.wrapperEl.querySelector('.js-create-merge-request'); - this.createMergeRequestLoading = this.createMergeRequestButton.querySelector('.js-spinner'); - this.createTargetButton = this.wrapperEl.querySelector('.js-create-target'); - this.dropdownList = this.wrapperEl.querySelector('.dropdown-menu'); - this.dropdownToggle = this.wrapperEl.querySelector('.js-dropdown-toggle'); - this.refInput = this.wrapperEl.querySelector('.js-ref'); - this.refMessage = this.wrapperEl.querySelector('.js-ref-message'); - this.unavailableButton = this.wrapperEl.querySelector('.unavailable'); - this.unavailableButtonSpinner = this.unavailableButton.querySelector('.gl-spinner'); - this.unavailableButtonText = this.unavailableButton.querySelector('.text'); - - this.branchCreated = false; - this.branchIsValid = true; - this.canCreatePath = this.wrapperEl.dataset.canCreatePath; - this.createBranchPath = this.wrapperEl.dataset.createBranchPath; - this.createMrPath = this.wrapperEl.dataset.createMrPath; - this.droplabInitialized = false; - this.isCreatingBranch = false; - this.isCreatingMergeRequest = false; - this.isGettingRef = false; - this.refCancelToken = null; - this.mergeRequestCreated = false; - this.refDebounce = debounce((value, target) => this.getRef(value, target), 500); - this.refIsValid = true; - this.refsPath = this.wrapperEl.dataset.refsPath; - this.suggestedRef = this.refInput.value; - this.projectPath = this.wrapperEl.dataset.projectPath; - this.projectId = this.wrapperEl.dataset.projectId; - - // These regexps are used to replace - // a backend generated new branch name and its source (ref) - // with user's inputs. - this.regexps = { - branch: { - createBranchPath: new RegExp('(branch_name=)(.+?)(?=&issue)'), - createMrPath: new RegExp('(branch_name=)(.+?)(?=&ref)'), - }, - ref: { - createBranchPath: new RegExp('(ref=)(.+?)$'), - createMrPath: new RegExp('(ref=)(.+?)$'), - }, - }; - - this.init(); - - if (isConfidentialIssue()) { - this.createMergeRequestButton.setAttribute( - 'data-dropdown-trigger', - '#create-merge-request-dropdown', - ); - initConfidentialMergeRequest(); - } - } - - available() { - this.availableButton.classList.remove('hidden'); - this.unavailableButton.classList.add('hidden'); - } - - bindEvents() { - this.createMergeRequestButton.addEventListener( - 'click', - this.onClickCreateMergeRequestButton.bind(this), - ); - this.createTargetButton.addEventListener( - 'click', - this.onClickCreateMergeRequestButton.bind(this), - ); - this.branchInput.addEventListener('input', this.onChangeInput.bind(this)); - this.branchInput.addEventListener('keyup', this.onChangeInput.bind(this)); - this.dropdownToggle.addEventListener('click', this.onClickSetFocusOnBranchNameInput.bind(this)); - // Detect for example when user pastes ref using the mouse - this.refInput.addEventListener('input', this.onChangeInput.bind(this)); - // Detect for example when user presses right arrow to apply the suggested ref - this.refInput.addEventListener('keyup', this.onChangeInput.bind(this)); - // Detect when user clicks inside the input to apply the suggested ref - this.refInput.addEventListener('click', this.onChangeInput.bind(this)); - // Detect when user clicks outside the input to apply the suggested ref - this.refInput.addEventListener('blur', this.onChangeInput.bind(this)); - // Detect when user presses tab to apply the suggested ref - this.refInput.addEventListener('keydown', CreateMergeRequestDropdown.processTab.bind(this)); - } - - checkAbilityToCreateBranch() { - this.setUnavailableButtonState(); - - axios - .get(this.canCreatePath) - .then(({ data }) => { - this.setUnavailableButtonState(false); - - if (data.can_create_branch) { - this.available(); - this.enable(); - this.updateBranchName(data.suggested_branch_name); - - if (!this.droplabInitialized) { - this.droplabInitialized = true; - this.initDroplab(); - this.bindEvents(); - } - } else { - this.hide(); - } - }) - .catch(() => { - this.unavailable(); - this.disable(); - createFlash({ - message: __('Failed to check related branches.'), - }); - }); - } - - createBranch() { - this.isCreatingBranch = true; - - return axios - .post(createEndpoint(this.projectPath, this.createBranchPath), { - confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null, - }) - .then(({ data }) => { - this.branchCreated = true; - window.location.href = data.url; - }) - .catch(() => - createFlash({ - message: __('Failed to create a branch for this issue. Please try again.'), - }), - ); - } - - createMergeRequest() { - this.isCreatingMergeRequest = true; - - return axios - .post(this.createMrPath, { - target_project_id: canCreateConfidentialMergeRequest() - ? confidentialMergeRequestState.selectedProject.id - : null, - }) - .then(({ data }) => { - this.mergeRequestCreated = true; - window.location.href = data.url; - }) - .catch(() => - createFlash({ - message: __('Failed to create merge request. Please try again.'), - }), - ); - } - - disable() { - this.disableCreateAction(); - } - - setLoading(loading) { - this.createMergeRequestLoading.classList.toggle('gl-display-none', !loading); - } - - disableCreateAction() { - this.createMergeRequestButton.classList.add('disabled'); - this.createMergeRequestButton.setAttribute('disabled', 'disabled'); - - this.createTargetButton.classList.add('disabled'); - this.createTargetButton.setAttribute('disabled', 'disabled'); - } - - enable() { - if (isConfidentialIssue() && !canCreateConfidentialMergeRequest()) return; - - this.createMergeRequestButton.classList.remove('disabled'); - this.createMergeRequestButton.removeAttribute('disabled'); - - this.createTargetButton.classList.remove('disabled'); - this.createTargetButton.removeAttribute('disabled'); - } - - static findByValue(objects, ref, returnFirstMatch = false) { - if (!objects || !objects.length) return false; - if (objects.indexOf(ref) > -1) return ref; - if (returnFirstMatch) return objects.find((item) => new RegExp(`^${ref}`).test(item)); - - return false; - } - - getDroplabConfig() { - return { - addActiveClassToDropdownButton: true, - InputSetter: [ - { - input: this.createMergeRequestButton, - valueAttribute: 'data-value', - inputAttribute: 'data-action', - }, - { - input: this.createMergeRequestButton, - valueAttribute: 'data-text', - }, - { - input: this.createTargetButton, - valueAttribute: 'data-value', - inputAttribute: 'data-action', - }, - { - input: this.createTargetButton, - valueAttribute: 'data-text', - }, - ], - hideOnClick: false, - }; - } - - static getInputSelectedText(input) { - const start = input.selectionStart; - const end = input.selectionEnd; - - return input.value.substr(start, end - start); - } - - getRef(ref, target = 'all') { - if (!ref) return false; - - this.refCancelToken = axios.CancelToken.source(); - - return axios - .get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`, { - cancelToken: this.refCancelToken.token, - }) - .then(({ data }) => { - const branches = data[Object.keys(data)[0]]; - const tags = data[Object.keys(data)[1]]; - let result; - - if (target === 'branch') { - result = CreateMergeRequestDropdown.findByValue(branches, ref); - } else { - result = - CreateMergeRequestDropdown.findByValue(branches, ref, true) || - CreateMergeRequestDropdown.findByValue(tags, ref, true); - this.suggestedRef = result; - } - - this.isGettingRef = false; - - return this.updateInputState(target, ref, result); - }) - .catch((thrown) => { - if (axios.isCancel(thrown)) { - return false; - } - this.unavailable(); - this.disable(); - createFlash({ - message: __('Failed to get ref.'), - }); - - this.isGettingRef = false; - - return false; - }); - } - - getTargetData(target) { - return { - input: this[`${target}Input`], - message: this[`${target}Message`], - }; - } - - hide() { - this.wrapperEl.classList.add('hidden'); - } - - init() { - this.checkAbilityToCreateBranch(); - } - - initDroplab() { - this.droplab = new DropLab(); - - this.droplab.init( - this.dropdownToggle, - this.dropdownList, - [InputSetter], - this.getDroplabConfig(), - ); - } - - inputsAreValid() { - return this.branchIsValid && this.refIsValid; - } - - isBusy() { - return ( - this.isCreatingMergeRequest || - this.mergeRequestCreated || - this.isCreatingBranch || - this.branchCreated || - this.isGettingRef - ); - } - - onChangeInput(event) { - this.disable(); - let target; - let value; - - // User changed input, cancel to prevent previous request from interfering - if (this.refCancelToken !== null) { - this.refCancelToken.cancel(); - } - - if (event.target === this.branchInput) { - target = 'branch'; - ({ value } = this.branchInput); - } else if (event.target === this.refInput) { - target = 'ref'; - if (event.target === document.activeElement) { - value = - event.target.value.slice(0, event.target.selectionStart) + - event.target.value.slice(event.target.selectionEnd); - } else { - value = event.target.value; - } - } else { - return false; - } - - if (this.isGettingRef) return false; - - // `ENTER` key submits the data. - if (event.keyCode === 13 && this.inputsAreValid()) { - event.preventDefault(); - return this.createMergeRequestButton.click(); - } - - // If the input is empty, use the original value generated by the backend. - if (!value) { - this.createBranchPath = this.wrapperEl.dataset.createBranchPath; - this.createMrPath = this.wrapperEl.dataset.createMrPath; - - if (target === 'branch') { - this.branchIsValid = true; - } else { - this.refIsValid = true; - } - - this.enable(); - this.showAvailableMessage(target); - this.refDebounce(value, target); - return true; - } - - this.showCheckingMessage(target); - this.refDebounce(value, target); - - return true; - } - - onClickCreateMergeRequestButton(event) { - let xhr = null; - event.preventDefault(); - - if (isConfidentialIssue() && !event.target.classList.contains('js-create-target')) { - this.droplab.hooks.forEach((hook) => hook.list.toggle()); - - return; - } - - if (this.isBusy()) { - return; - } - - if (event.target.dataset.action === CREATE_MERGE_REQUEST) { - xhr = this.createMergeRequest(); - } else if (event.target.dataset.action === CREATE_BRANCH) { - xhr = this.createBranch(); - } - - xhr.catch(() => { - this.isCreatingMergeRequest = false; - this.isCreatingBranch = false; - - this.enable(); - this.setLoading(false); - }); - - this.setLoading(true); - this.disable(); - } - - onClickSetFocusOnBranchNameInput() { - this.branchInput.focus(); - } - - // `TAB` autocompletes the source. - static processTab(event) { - if (event.keyCode !== 9 || this.isGettingRef) return; - - const selectedText = CreateMergeRequestDropdown.getInputSelectedText(this.refInput); - - // if nothing selected, we don't need to autocomplete anything. Do the default TAB action. - // If a user manually selected text, don't autocomplete anything. Do the default TAB action. - if (!selectedText || this.refInput.dataset.value === this.suggestedRef) return; - - event.preventDefault(); - const caretPositionEnd = this.refInput.value.length; - this.refInput.setSelectionRange(caretPositionEnd, caretPositionEnd); - } - - removeMessage(target) { - const { input, message } = this.getTargetData(target); - const inputClasses = ['gl-field-error-outline', 'gl-field-success-outline']; - const messageClasses = ['text-muted', 'text-danger', 'text-success']; - - inputClasses.forEach((cssClass) => input.classList.remove(cssClass)); - messageClasses.forEach((cssClass) => message.classList.remove(cssClass)); - message.style.display = 'none'; - } - - setUnavailableButtonState(isLoading = true) { - if (isLoading) { - this.unavailableButtonSpinner.classList.remove('hide'); - this.unavailableButtonText.textContent = __('Checking branch availability...'); - } else { - this.unavailableButtonSpinner.classList.add('hide'); - this.unavailableButtonText.textContent = __('New branch unavailable'); - } - } - - showAvailableMessage(target) { - const { input, message } = this.getTargetData(target); - const text = target === 'branch' ? __('Branch name') : __('Source'); - - this.removeMessage(target); - input.classList.add('gl-field-success-outline'); - message.classList.add('text-success'); - message.textContent = sprintf(__('%{text} is available'), { text }); - message.style.display = 'inline-block'; - } - - showCheckingMessage(target) { - const { message } = this.getTargetData(target); - const text = target === 'branch' ? __('branch name') : __('source'); - - this.removeMessage(target); - message.classList.add('text-muted'); - message.textContent = sprintf(__('Checking %{text} availability…'), { text }); - message.style.display = 'inline-block'; - } - - showNotAvailableMessage(target) { - const { input, message } = this.getTargetData(target); - const text = - target === 'branch' ? __('Branch is already taken') : __('Source is not available'); - - this.removeMessage(target); - input.classList.add('gl-field-error-outline'); - message.classList.add('text-danger'); - message.textContent = text; - message.style.display = 'inline-block'; - } - - unavailable() { - this.availableButton.classList.add('hidden'); - this.unavailableButton.classList.remove('hidden'); - } - - updateBranchName(suggestedBranchName) { - this.branchInput.value = suggestedBranchName; - this.updateCreatePaths('branch', suggestedBranchName); - } - - updateInputState(target, ref, result) { - // target - 'branch' or 'ref' - which the input field we are searching a ref for. - // ref - string - what a user typed. - // result - string - what has been found on backend. - - // If a found branch equals exact the same text a user typed, - // that means a new branch cannot be created as it already exists. - if (ref === result) { - if (target === 'branch') { - this.branchIsValid = false; - this.showNotAvailableMessage('branch'); - } else { - this.refIsValid = true; - this.refInput.dataset.value = ref; - this.showAvailableMessage('ref'); - this.updateCreatePaths(target, ref); - } - } else if (target === 'branch') { - this.branchIsValid = true; - this.showAvailableMessage('branch'); - this.updateCreatePaths(target, ref); - } else { - this.refIsValid = false; - this.refInput.dataset.value = ref; - this.disableCreateAction(); - this.showNotAvailableMessage('ref'); - - // Show ref hint. - if (result) { - this.refInput.value = result; - this.refInput.setSelectionRange(ref.length, result.length); - } - } - - if (this.inputsAreValid()) { - this.enable(); - } else { - this.disableCreateAction(); - } - } - - // target - 'branch' or 'ref' - // ref - string - the new value to use as branch or ref - updateCreatePaths(target, ref) { - const pathReplacement = `$1${encodeURIComponent(ref)}`; - - this.createBranchPath = this.createBranchPath.replace( - this.regexps[target].createBranchPath, - pathReplacement, - ); - this.createMrPath = this.createMrPath.replace( - this.regexps[target].createMrPath, - pathReplacement, - ); - } -} |