2018-03-09 15:18:59 -05:00
|
|
|
import $ from 'jquery';
|
2021-09-30 11:12:24 -04:00
|
|
|
import { debounce } from 'lodash';
|
2020-08-17 17:09:56 -04:00
|
|
|
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
|
2022-02-16 01:12:24 -05:00
|
|
|
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
2022-01-26 16:16:58 -05:00
|
|
|
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
|
2021-09-30 11:12:24 -04:00
|
|
|
import axios from '../lib/utils/axios_utils';
|
2020-08-25 05:52:44 -04:00
|
|
|
import {
|
|
|
|
convertToTitleCase,
|
|
|
|
humanize,
|
|
|
|
slugify,
|
|
|
|
convertUnicodeToAscii,
|
|
|
|
} from '../lib/utils/text_utility';
|
2018-02-23 04:00:19 -05:00
|
|
|
|
2018-01-16 11:20:59 -05:00
|
|
|
let hasUserDefinedProjectPath = false;
|
2020-01-17 16:08:29 -05:00
|
|
|
let hasUserDefinedProjectName = false;
|
2021-09-30 11:12:24 -04:00
|
|
|
const invalidInputClass = 'gl-field-error-outline';
|
|
|
|
|
2022-01-26 16:16:58 -05:00
|
|
|
const cancelSource = axios.CancelToken.source();
|
|
|
|
const endpoint = `${gon.relative_url_root}/import/url/validate`;
|
|
|
|
let importCredentialsValidationPromise = null;
|
2021-09-30 11:12:24 -04:00
|
|
|
const validateImportCredentials = (url, user, password) => {
|
2022-01-26 16:16:58 -05:00
|
|
|
cancelSource.cancel();
|
|
|
|
importCredentialsValidationPromise = axios
|
|
|
|
.post(endpoint, { url, user, password }, { cancelToken: cancelSource.cancel() })
|
2021-09-30 11:12:24 -04:00
|
|
|
.then(({ data }) => data)
|
2022-01-26 16:16:58 -05:00
|
|
|
.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;
|
2021-09-30 11:12:24 -04:00
|
|
|
};
|
2020-01-17 16:08:29 -05:00
|
|
|
|
|
|
|
const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
|
2020-08-25 05:52:44 -04:00
|
|
|
const slug = slugify(convertUnicodeToAscii($projectNameInput.val()));
|
2020-01-17 16:08:29 -05:00
|
|
|
$projectPathInput.val(slug);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingProjectName) => {
|
|
|
|
const slug = $projectPathInput.val();
|
|
|
|
|
|
|
|
if (!hasExistingProjectName) {
|
|
|
|
$projectNameInput.val(convertToTitleCase(humanize(slug, '[-_]')));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
|
2021-11-11 10:10:57 -05:00
|
|
|
const specialRepo = document.querySelector('.js-user-readme-repo');
|
|
|
|
|
2020-12-08 16:10:06 -05:00
|
|
|
// eslint-disable-next-line @gitlab/no-global-event-off
|
2020-01-17 16:08:29 -05:00
|
|
|
$projectNameInput.off('keyup change').on('keyup change', () => {
|
|
|
|
onProjectNameChange($projectNameInput, $projectPathInput);
|
|
|
|
hasUserDefinedProjectName = $projectNameInput.val().trim().length > 0;
|
|
|
|
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
|
|
|
|
});
|
|
|
|
|
2020-12-08 16:10:06 -05:00
|
|
|
// eslint-disable-next-line @gitlab/no-global-event-off
|
2020-01-17 16:08:29 -05:00
|
|
|
$projectPathInput.off('keyup change').on('keyup change', () => {
|
|
|
|
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
|
|
|
|
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
|
2021-11-11 10:10:57 -05:00
|
|
|
|
|
|
|
specialRepo.classList.toggle(
|
|
|
|
'gl-display-none',
|
|
|
|
$projectPathInput.val() !== $projectPathInput.data('username'),
|
|
|
|
);
|
2020-01-17 16:08:29 -05:00
|
|
|
});
|
|
|
|
};
|
2018-01-16 11:20:59 -05:00
|
|
|
|
2020-12-23 19:10:25 -05:00
|
|
|
const deriveProjectPathFromUrl = ($projectImportUrl) => {
|
2020-01-17 16:08:29 -05:00
|
|
|
const $currentProjectName = $projectImportUrl
|
|
|
|
.parents('.toggle-import-form')
|
|
|
|
.find('#project_name');
|
2018-10-30 16:28:31 -04:00
|
|
|
const $currentProjectPath = $projectImportUrl
|
|
|
|
.parents('.toggle-import-form')
|
|
|
|
.find('#project_path');
|
2020-01-17 16:08:29 -05:00
|
|
|
|
2022-01-26 16:16:58 -05:00
|
|
|
if (hasUserDefinedProjectPath || $currentProjectPath.length === 0) {
|
2018-01-16 11:20:59 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let importUrl = $projectImportUrl.val().trim();
|
|
|
|
if (importUrl.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
\/?: remove trailing slash
|
|
|
|
(\.git\/?)?: remove trailing .git (with optional trailing slash)
|
|
|
|
(\?.*)?: remove query string
|
|
|
|
(#.*)?: remove fragment identifier
|
|
|
|
*/
|
|
|
|
importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
|
|
|
|
|
|
|
|
// extract everything after the last slash
|
|
|
|
const pathMatch = /\/([^/]+)$/.exec(importUrl);
|
|
|
|
if (pathMatch) {
|
|
|
|
$currentProjectPath.val(pathMatch[1]);
|
2020-01-17 16:08:29 -05:00
|
|
|
onProjectPathChange($currentProjectName, $currentProjectPath, false);
|
2018-01-16 11:20:59 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-06 08:11:14 -04:00
|
|
|
const bindHowToImport = () => {
|
2022-02-16 01:12:24 -05:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2021-09-06 08:11:14 -04:00
|
|
|
};
|
|
|
|
|
2018-01-16 11:20:59 -05:00
|
|
|
const bindEvents = () => {
|
|
|
|
const $newProjectForm = $('#new_project');
|
|
|
|
const $projectImportUrl = $('#project_import_url');
|
2021-09-30 11:12:24 -04:00
|
|
|
const $projectImportUrlUser = $('#project_import_url_user');
|
|
|
|
const $projectImportUrlPassword = $('#project_import_url_password');
|
|
|
|
const $projectImportUrlError = $('.js-import-url-error');
|
2022-01-26 16:16:58 -05:00
|
|
|
const $projectImportForm = $('form.js-project-import');
|
2018-09-08 02:03:00 -04:00
|
|
|
const $projectPath = $('.tab-pane.active #project_path');
|
2018-01-16 11:20:59 -05:00
|
|
|
const $useTemplateBtn = $('.template-button > input');
|
|
|
|
const $projectFieldsForm = $('.project-fields-form');
|
|
|
|
const $selectedTemplateText = $('.selected-template');
|
|
|
|
const $changeTemplateBtn = $('.change-template');
|
2018-08-03 11:36:53 -04:00
|
|
|
const $selectedIcon = $('.selected-icon');
|
|
|
|
const $projectTemplateButtons = $('.project-templates-buttons');
|
2018-09-08 02:03:00 -04:00
|
|
|
const $projectName = $('.tab-pane.active #project_name');
|
2018-01-16 11:20:59 -05:00
|
|
|
|
2022-01-26 16:16:58 -05:00
|
|
|
if ($newProjectForm.length !== 1 && $projectImportForm.length !== 1) {
|
2018-01-16 11:20:59 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-06 08:11:14 -04:00
|
|
|
bindHowToImport();
|
2018-01-16 11:20:59 -05:00
|
|
|
|
2022-02-28 07:15:45 -05:00
|
|
|
$('.btn_import_gitlab_project').on('click contextmenu', () => {
|
|
|
|
const importHref = $('a.btn_import_gitlab_project').attr('data-href');
|
2018-10-30 16:28:31 -04:00
|
|
|
$('.btn_import_gitlab_project').attr(
|
|
|
|
'href',
|
|
|
|
`${importHref}?namespace_id=${$(
|
|
|
|
'#project_namespace_id',
|
|
|
|
).val()}&name=${$projectName.val()}&path=${$projectPath.val()}`,
|
|
|
|
);
|
2018-01-16 11:20:59 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
function chooseTemplate() {
|
2018-08-03 11:36:53 -04:00
|
|
|
$projectTemplateButtons.addClass('hidden');
|
2018-01-16 11:20:59 -05:00
|
|
|
$projectFieldsForm.addClass('selected');
|
2018-08-03 11:36:53 -04:00
|
|
|
$selectedIcon.empty();
|
2018-01-16 11:20:59 -05:00
|
|
|
const value = $(this).val();
|
2020-04-07 08:09:34 -04:00
|
|
|
|
2020-12-11 13:09:57 -05:00
|
|
|
const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value];
|
2018-01-16 11:20:59 -05:00
|
|
|
$selectedTemplateText.text(selectedTemplate.text);
|
2020-12-23 07:10:26 -05:00
|
|
|
$(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon);
|
2018-09-08 02:03:00 -04:00
|
|
|
|
|
|
|
const $activeTabProjectName = $('.tab-pane.active #project_name');
|
|
|
|
const $activeTabProjectPath = $('.tab-pane.active #project_path');
|
|
|
|
$activeTabProjectName.focus();
|
2020-01-17 16:08:29 -05:00
|
|
|
setProjectNamePathHandlers($activeTabProjectName, $activeTabProjectPath);
|
2018-01-16 11:20:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
$useTemplateBtn.on('change', chooseTemplate);
|
|
|
|
|
|
|
|
$changeTemplateBtn.on('click', () => {
|
2018-08-03 11:36:53 -04:00
|
|
|
$projectTemplateButtons.removeClass('hidden');
|
2018-01-16 11:20:59 -05:00
|
|
|
$projectFieldsForm.removeClass('selected');
|
|
|
|
$useTemplateBtn.prop('checked', false);
|
|
|
|
});
|
|
|
|
|
|
|
|
$newProjectForm.on('submit', () => {
|
|
|
|
$projectPath.val($projectPath.val().trim());
|
|
|
|
});
|
|
|
|
|
2022-01-26 16:16:58 -05:00
|
|
|
const updateUrlPathWarningVisibility = async () => {
|
|
|
|
const { success: isUrlValid, cancelled } = await validateImportCredentials(
|
2021-09-30 11:12:24 -04:00
|
|
|
$projectImportUrl.val(),
|
|
|
|
$projectImportUrlUser.val(),
|
|
|
|
$projectImportUrlPassword.val(),
|
|
|
|
);
|
2022-01-26 16:16:58 -05:00
|
|
|
if (cancelled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-30 11:12:24 -04:00
|
|
|
$projectImportUrl.toggleClass(invalidInputClass, !isUrlValid);
|
|
|
|
$projectImportUrlError.toggleClass('hide', isUrlValid);
|
2022-01-26 16:16:58 -05:00
|
|
|
};
|
|
|
|
const debouncedUpdateUrlPathWarningVisibility = debounce(
|
|
|
|
updateUrlPathWarningVisibility,
|
|
|
|
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
|
|
|
|
);
|
2021-07-13 11:08:38 -04:00
|
|
|
|
|
|
|
let isProjectImportUrlDirty = false;
|
|
|
|
$projectImportUrl.on('blur', () => {
|
|
|
|
isProjectImportUrlDirty = true;
|
2022-01-26 16:16:58 -05:00
|
|
|
debouncedUpdateUrlPathWarningVisibility();
|
2021-07-13 11:08:38 -04:00
|
|
|
});
|
|
|
|
$projectImportUrl.on('keyup', () => {
|
|
|
|
deriveProjectPathFromUrl($projectImportUrl);
|
2021-09-30 11:12:24 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
[$projectImportUrl, $projectImportUrlUser, $projectImportUrlPassword].forEach(($f) => {
|
|
|
|
$f.on('input', () => {
|
|
|
|
if (isProjectImportUrlDirty) {
|
2022-01-26 16:16:58 -05:00
|
|
|
debouncedUpdateUrlPathWarningVisibility();
|
2021-09-30 11:12:24 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-01-26 16:16:58 -05:00
|
|
|
$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();
|
|
|
|
|
2021-09-30 11:12:24 -04:00
|
|
|
const $invalidFields = $projectImportForm.find(`.${invalidInputClass}`);
|
|
|
|
if ($invalidFields.length > 0) {
|
|
|
|
$invalidFields[0].focus();
|
2022-01-26 16:16:58 -05:00
|
|
|
} 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();
|
2021-07-13 11:08:38 -04:00
|
|
|
}
|
|
|
|
});
|
2018-09-08 02:03:00 -04:00
|
|
|
|
2019-04-17 05:00:50 -04:00
|
|
|
$('.js-import-git-toggle-button').on('click', () => {
|
|
|
|
const $projectMirror = $('#project_mirror');
|
|
|
|
|
|
|
|
$projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
|
2020-01-17 16:08:29 -05:00
|
|
|
setProjectNamePathHandlers(
|
|
|
|
$('.tab-pane.active #project_name'),
|
|
|
|
$('.tab-pane.active #project_path'),
|
|
|
|
);
|
2019-04-17 05:00:50 -04:00
|
|
|
});
|
|
|
|
|
2020-01-17 16:08:29 -05:00
|
|
|
setProjectNamePathHandlers($projectName, $projectPath);
|
2018-01-16 11:20:59 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
export default {
|
|
|
|
bindEvents,
|
|
|
|
deriveProjectPathFromUrl,
|
2018-09-08 02:03:00 -04:00
|
|
|
onProjectNameChange,
|
2020-01-17 16:08:29 -05:00
|
|
|
onProjectPathChange,
|
2018-01-16 11:20:59 -05:00
|
|
|
};
|
2021-09-06 08:11:14 -04:00
|
|
|
|
|
|
|
export { bindHowToImport };
|