diff options
Diffstat (limited to 'app/assets/javascripts/projects/project_new.js')
-rw-r--r-- | app/assets/javascripts/projects/project_new.js | 93 |
1 files changed, 70 insertions, 23 deletions
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 8d71a3dab68..62e2cec874a 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,6 +1,8 @@ import $ from 'jquery'; import { debounce } from 'lodash'; import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants'; import axios from '../lib/utils/axios_utils'; import { convertToTitleCase, @@ -13,20 +15,26 @@ let hasUserDefinedProjectPath = false; let hasUserDefinedProjectName = false; const invalidInputClass = 'gl-field-error-outline'; +const cancelSource = axios.CancelToken.source(); +const endpoint = `${gon.relative_url_root}/import/url/validate`; +let importCredentialsValidationPromise = null; const validateImportCredentials = (url, user, password) => { - const endpoint = `${gon.relative_url_root}/import/url/validate`; - return axios - .post(endpoint, { - url, - user, - password, - }) + cancelSource.cancel(); + importCredentialsValidationPromise = axios + .post(endpoint, { url, user, password }, { cancelToken: cancelSource.cancel() }) .then(({ data }) => data) - .catch(() => ({ - // intentionally reporting success in case of validation error - // we do not want to block users from trying import in case of validation exception - success: true, - })); + .catch((thrown) => + axios.isCancel(thrown) + ? { + cancelled: true, + } + : { + // intentionally reporting success in case of validation error + // we do not want to block users from trying import in case of validation exception + success: true, + }, + ); + return importCredentialsValidationPromise; }; const onProjectNameChange = ($projectNameInput, $projectPathInput) => { @@ -72,7 +80,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => { .parents('.toggle-import-form') .find('#project_path'); - if (hasUserDefinedProjectPath) { + if (hasUserDefinedProjectPath || $currentProjectPath.length === 0) { return; } @@ -98,6 +106,21 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => { }; const bindHowToImport = () => { + const importLinks = document.querySelectorAll('.js-how-to-import-link'); + + importLinks.forEach((link) => { + const { modalTitle: title, modalMessage: modalHtmlMessage } = link.dataset; + + link.addEventListener('click', (e) => { + e.preventDefault(); + confirmAction('', { + modalHtmlMessage, + title, + hideCancel: true, + }); + }); + }); + $('.how_to_import_link').on('click', (e) => { e.preventDefault(); $(e.currentTarget).next('.modal').show(); @@ -114,7 +137,7 @@ const bindEvents = () => { const $projectImportUrlUser = $('#project_import_url_user'); const $projectImportUrlPassword = $('#project_import_url_password'); const $projectImportUrlError = $('.js-import-url-error'); - const $projectImportForm = $('.project-import form'); + const $projectImportForm = $('form.js-project-import'); const $projectPath = $('.tab-pane.active #project_path'); const $useTemplateBtn = $('.template-button > input'); const $projectFieldsForm = $('.project-fields-form'); @@ -124,7 +147,7 @@ const bindEvents = () => { const $projectTemplateButtons = $('.project-templates-buttons'); const $projectName = $('.tab-pane.active #project_name'); - if ($newProjectForm.length !== 1) { + if ($newProjectForm.length !== 1 && $projectImportForm.length !== 1) { return; } @@ -168,20 +191,28 @@ const bindEvents = () => { $projectPath.val($projectPath.val().trim()); }); - const updateUrlPathWarningVisibility = debounce(async () => { - const { success: isUrlValid } = await validateImportCredentials( + const updateUrlPathWarningVisibility = async () => { + const { success: isUrlValid, cancelled } = await validateImportCredentials( $projectImportUrl.val(), $projectImportUrlUser.val(), $projectImportUrlPassword.val(), ); + if (cancelled) { + return; + } + $projectImportUrl.toggleClass(invalidInputClass, !isUrlValid); $projectImportUrlError.toggleClass('hide', isUrlValid); - }, 500); + }; + const debouncedUpdateUrlPathWarningVisibility = debounce( + updateUrlPathWarningVisibility, + DEFAULT_DEBOUNCE_AND_THROTTLE_MS, + ); let isProjectImportUrlDirty = false; $projectImportUrl.on('blur', () => { isProjectImportUrlDirty = true; - updateUrlPathWarningVisibility(); + debouncedUpdateUrlPathWarningVisibility(); }); $projectImportUrl.on('keyup', () => { deriveProjectPathFromUrl($projectImportUrl); @@ -190,17 +221,33 @@ const bindEvents = () => { [$projectImportUrl, $projectImportUrlUser, $projectImportUrlPassword].forEach(($f) => { $f.on('input', () => { if (isProjectImportUrlDirty) { - updateUrlPathWarningVisibility(); + debouncedUpdateUrlPathWarningVisibility(); } }); }); - $projectImportForm.on('submit', (e) => { + $projectImportForm.on('submit', async (e) => { + e.preventDefault(); + + if (importCredentialsValidationPromise === null) { + // we didn't validate credentials yet + debouncedUpdateUrlPathWarningVisibility.cancel(); + updateUrlPathWarningVisibility(); + } + + const submitBtn = $projectImportForm.find('input[type="submit"]'); + + submitBtn.disable(); + await importCredentialsValidationPromise; + submitBtn.enable(); + const $invalidFields = $projectImportForm.find(`.${invalidInputClass}`); if ($invalidFields.length > 0) { $invalidFields[0].focus(); - e.preventDefault(); - e.stopPropagation(); + } else { + // calling .submit() on HTMLFormElement does not trigger 'submit' event + // We are using this behavior to bypass this handler and avoid infinite loop + $projectImportForm[0].submit(); } }); |