Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-18 12:45:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-18 12:45:46 +0300
commita7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch)
tree7452bd5c3545c2fa67a28aa013835fb4fa071baf /app/assets/javascripts/projects
parentee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff)
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/components/project_delete_button.vue71
-rw-r--r--app/assets/javascripts/projects/components/shared/delete_button.vue84
-rw-r--r--app/assets/javascripts/projects/new/components/deployment_target_select.vue61
-rw-r--r--app/assets/javascripts/projects/new/constants.js20
-rw-r--r--app/assets/javascripts/projects/new/index.js14
-rw-r--r--app/assets/javascripts/projects/project_new.js93
-rw-r--r--app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue23
-rw-r--r--app/assets/javascripts/projects/settings/components/transfer_project_form.vue12
-rw-r--r--app/assets/javascripts/projects/settings/constants.js7
-rw-r--r--app/assets/javascripts/projects/settings/init_transfer_project_form.js10
10 files changed, 289 insertions, 106 deletions
diff --git a/app/assets/javascripts/projects/components/project_delete_button.vue b/app/assets/javascripts/projects/components/project_delete_button.vue
index eaf93e2da4f..924b6f55db4 100644
--- a/app/assets/javascripts/projects/components/project_delete_button.vue
+++ b/app/assets/javascripts/projects/components/project_delete_button.vue
@@ -1,12 +1,8 @@
<script>
-import { GlAlert, GlSprintf } from '@gitlab/ui';
-import { __ } from '~/locale';
import SharedDeleteButton from './shared/delete_button.vue';
export default {
components: {
- GlSprintf,
- GlAlert,
SharedDeleteButton,
},
props: {
@@ -39,66 +35,17 @@ export default {
required: true,
},
},
- strings: {
- alertTitle: __('You are about to permanently delete this project'),
- alertBody: __(
- 'After a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc.',
- ),
- isNotForkMessage: __(
- 'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
- ),
- isForkMessage: __('This forked project has the following:'),
- },
};
</script>
<template>
- <shared-delete-button v-bind="{ confirmPhrase, formPath }">
- <template #modal-body>
- <gl-alert
- class="gl-mb-5"
- variant="danger"
- :title="$options.strings.alertTitle"
- :dismissible="false"
- >
- <p>
- <gl-sprintf v-if="isFork" :message="$options.strings.isForkMessage" />
- <gl-sprintf v-else :message="$options.strings.isNotForkMessage">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <ul>
- <li>
- <gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
- <template #issuesCount>{{ issuesCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf
- :message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
- >
- <template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
- <template #forksCount>{{ forksCount }}</template>
- </gl-sprintf>
- </li>
- <li>
- <gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
- <template #starsCount>{{ starsCount }}</template>
- </gl-sprintf>
- </li>
- </ul>
- <gl-sprintf :message="$options.strings.alertBody">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </gl-alert>
- </template>
- </shared-delete-button>
+ <shared-delete-button
+ :confirm-phrase="confirmPhrase"
+ :form-path="formPath"
+ :is-fork="isFork"
+ :issues-count="issuesCount"
+ :merge-requests-count="mergeRequestsCount"
+ :forks-count="forksCount"
+ :stars-count="starsCount"
+ />
</template>
diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue
index 2e46f437ace..fd71a246a26 100644
--- a/app/assets/javascripts/projects/components/shared/delete_button.vue
+++ b/app/assets/javascripts/projects/components/shared/delete_button.vue
@@ -1,14 +1,16 @@
<script>
-import { GlModal, GlModalDirective, GlFormInput, GlButton } from '@gitlab/ui';
+import { GlModal, GlModalDirective, GlFormInput, GlButton, GlAlert, GlSprintf } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import csrf from '~/lib/utils/csrf';
import { __ } from '~/locale';
export default {
components: {
+ GlAlert,
GlModal,
GlFormInput,
GlButton,
+ GlSprintf,
},
directives: {
GlModal: GlModalDirective,
@@ -22,6 +24,26 @@ export default {
type: String,
required: true,
},
+ isFork: {
+ type: Boolean,
+ required: true,
+ },
+ issuesCount: {
+ type: Number,
+ required: true,
+ },
+ mergeRequestsCount: {
+ type: Number,
+ required: true,
+ },
+ forksCount: {
+ type: Number,
+ required: true,
+ },
+ starsCount: {
+ type: Number,
+ required: true,
+ },
},
data() {
return {
@@ -55,8 +77,17 @@ export default {
},
strings: {
deleteProject: __('Delete project'),
- title: __('Delete project. Are you ABSOLUTELY SURE?'),
- confirmText: __('Please type the following to confirm:'),
+ title: __('Are you absolutely sure?'),
+ confirmText: __('Enter the following to confirm:'),
+ isForkAlertTitle: __('You are about to delete this forked project containing:'),
+ isNotForkAlertTitle: __('You are about to delete this project containing:'),
+ isForkAlertBody: __('This process deletes the project repository and all related resources.'),
+ isNotForkAlertBody: __(
+ 'This project is %{strongStart}NOT%{strongEnd} a fork. This process deletes the project repository and all related resources.',
+ ),
+ isNotForkMessage: __(
+ 'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
+ ),
},
};
</script>
@@ -83,7 +114,52 @@ export default {
>
<template #modal-title>{{ $options.strings.title }}</template>
<div>
- <slot name="modal-body"></slot>
+ <gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
+ <h4 v-if="isFork" data-testid="delete-alert-title" class="gl-alert-title">
+ {{ $options.strings.isForkAlertTitle }}
+ </h4>
+ <h4 v-else data-testid="delete-alert-title" class="gl-alert-title">
+ {{ $options.strings.isNotForkAlertTitle }}
+ </h4>
+ <ul>
+ <li>
+ <gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
+ <template #issuesCount>{{ issuesCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li>
+ <gl-sprintf
+ :message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
+ >
+ <template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li>
+ <gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
+ <template #forksCount>{{ forksCount }}</template>
+ </gl-sprintf>
+ </li>
+ <li>
+ <gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
+ <template #starsCount>{{ starsCount }}</template>
+ </gl-sprintf>
+ </li>
+ </ul>
+ <gl-sprintf
+ v-if="isFork"
+ data-testid="delete-alert-body"
+ :message="$options.strings.isForkAlertBody"
+ />
+ <gl-sprintf
+ v-else
+ data-testid="delete-alert-body"
+ :message="$options.strings.isNotForkAlertBody"
+ >
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
<p class="gl-mb-1">{{ $options.strings.confirmText }}</p>
<p>
<code class="gl-white-space-pre-wrap">{{ confirmPhrase }}</code>
diff --git a/app/assets/javascripts/projects/new/components/deployment_target_select.vue b/app/assets/javascripts/projects/new/components/deployment_target_select.vue
new file mode 100644
index 00000000000..f3b7e39f148
--- /dev/null
+++ b/app/assets/javascripts/projects/new/components/deployment_target_select.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import Tracking from '~/tracking';
+import {
+ DEPLOYMENT_TARGET_SELECTIONS,
+ DEPLOYMENT_TARGET_LABEL,
+ DEPLOYMENT_TARGET_EVENT,
+ NEW_PROJECT_FORM,
+} from '../constants';
+
+const trackingMixin = Tracking.mixin({ label: DEPLOYMENT_TARGET_LABEL });
+
+export default {
+ i18n: {
+ deploymentTargetLabel: s__('Deployment Target|Project deployment target (optional)'),
+ defaultOption: s__('Deployment Target|Select the deployment target'),
+ },
+ deploymentTargets: DEPLOYMENT_TARGET_SELECTIONS,
+ selectId: 'deployment-target-select',
+ components: {
+ GlFormGroup,
+ GlFormSelect,
+ },
+ mixins: [trackingMixin],
+ data() {
+ return {
+ selectedTarget: null,
+ formSubmitted: false,
+ };
+ },
+ mounted() {
+ const form = document.getElementById(NEW_PROJECT_FORM);
+ form.addEventListener('submit', () => {
+ this.formSubmitted = true;
+ this.trackSelection();
+ });
+ },
+ methods: {
+ trackSelection() {
+ if (this.formSubmitted && this.selectedTarget) {
+ this.track(DEPLOYMENT_TARGET_EVENT, { property: this.selectedTarget });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-group :label="$options.i18n.deploymentTargetLabel" :label-for="$options.selectId">
+ <gl-form-select
+ :id="$options.selectId"
+ v-model="selectedTarget"
+ :options="$options.deploymentTargets"
+ >
+ <template #first>
+ <option :value="null" disabled>{{ $options.i18n.defaultOption }}</option>
+ </template>
+ </gl-form-select>
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/projects/new/constants.js b/app/assets/javascripts/projects/new/constants.js
new file mode 100644
index 00000000000..c5e6722981b
--- /dev/null
+++ b/app/assets/javascripts/projects/new/constants.js
@@ -0,0 +1,20 @@
+import { s__ } from '~/locale';
+
+export const DEPLOYMENT_TARGET_SELECTIONS = [
+ s__('DeploymentTarget|Kubernetes (GKE, EKS, OpenShift, and so on)'),
+ s__('DeploymentTarget|Managed container runtime (Fargate, Cloud Run, DigitalOcean App)'),
+ s__('DeploymentTarget|Self-managed container runtime (Podman, Docker Swarm, Docker Compose)'),
+ s__('DeploymentTarget|Heroku'),
+ s__('DeploymentTarget|Virtual machine (for example, EC2)'),
+ s__('DeploymentTarget|Mobile app store'),
+ s__('DeploymentTarget|Registry (package or container)'),
+ s__('DeploymentTarget|Infrastructure provider (Terraform, Cloudformation, and so on)'),
+ s__('DeploymentTarget|Serverless backend (Lambda, Cloud functions)'),
+ s__('DeploymentTarget|GitLab Pages'),
+ s__('DeploymentTarget|Other hosting service'),
+ s__('DeploymentTarget|No deployment planned'),
+];
+
+export const NEW_PROJECT_FORM = 'new_project';
+export const DEPLOYMENT_TARGET_LABEL = 'new_project_deployment_target';
+export const DEPLOYMENT_TARGET_EVENT = 'select_deployment_target';
diff --git a/app/assets/javascripts/projects/new/index.js b/app/assets/javascripts/projects/new/index.js
index 010c6a29ae3..4de9b8a6f47 100644
--- a/app/assets/javascripts/projects/new/index.js
+++ b/app/assets/javascripts/projects/new/index.js
@@ -4,6 +4,7 @@ import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import NewProjectCreationApp from './components/app.vue';
import NewProjectUrlSelect from './components/new_project_url_select.vue';
+import DeploymentTargetSelect from './components/deployment_target_select.vue';
export function initNewProjectCreation() {
const el = document.querySelector('.js-new-project-creation');
@@ -64,3 +65,16 @@ export function initNewProjectUrlSelect() {
}),
);
}
+
+export function initDeploymentTargetSelect() {
+ const el = document.querySelector('.js-deployment-target-select');
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ render: (createElement) => createElement(DeploymentTargetSelect),
+ });
+}
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();
}
});
diff --git a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
index 91d8fca0487..aa3235b1515 100644
--- a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
+++ b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
@@ -2,6 +2,7 @@
import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { __, s__ } from '~/locale';
+import { CC_VALIDATION_REQUIRED_ERROR } from '../constants';
const DEFAULT_ERROR_MESSAGE = __('An error occurred while updating the configuration.');
const REQUIRES_VALIDATION_TEXT = s__(
@@ -47,11 +48,13 @@ export default {
};
},
computed: {
- showCreditCardValidation() {
+ ccRequiredError() {
+ return this.errorMessage === CC_VALIDATION_REQUIRED_ERROR && !this.ccAlertDismissed;
+ },
+ genericError() {
return (
- this.isCreditCardValidationRequired &&
- !this.isSharedRunnerEnabled &&
- !this.successfulValidation &&
+ this.errorMessage &&
+ this.errorMessage !== CC_VALIDATION_REQUIRED_ERROR &&
!this.ccAlertDismissed
);
},
@@ -62,6 +65,7 @@ export default {
},
toggleSharedRunners() {
this.isLoading = true;
+ this.ccAlertDismissed = false;
this.errorMessage = null;
axios
@@ -82,20 +86,19 @@ export default {
<template>
<div>
<section class="gl-mt-5">
- <gl-alert v-if="errorMessage" class="gl-mb-3" variant="danger" :dismissible="false">
- {{ errorMessage }}
- </gl-alert>
-
<cc-validation-required-alert
- v-if="showCreditCardValidation"
+ v-if="ccRequiredError"
class="gl-pb-5"
:custom-message="$options.i18n.REQUIRES_VALIDATION_TEXT"
@verifiedCreditCard="creditCardValidated"
@dismiss="ccAlertDismissed = true"
/>
+ <gl-alert v-if="genericError" class="gl-mb-3" variant="danger" :dismissible="false">
+ {{ errorMessage }}
+ </gl-alert>
+
<gl-toggle
- v-else
ref="sharedRunnersToggle"
:disabled="isDisabledAndUnoverridable"
:is-loading="isLoading"
diff --git a/app/assets/javascripts/projects/settings/components/transfer_project_form.vue b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue
index b98e1101884..fe968e74c6d 100644
--- a/app/assets/javascripts/projects/settings/components/transfer_project_form.vue
+++ b/app/assets/javascripts/projects/settings/components/transfer_project_form.vue
@@ -11,8 +11,12 @@ export default {
ConfirmDanger,
},
props: {
- namespaces: {
- type: Object,
+ groupNamespaces: {
+ type: Array,
+ required: true,
+ },
+ userNamespaces: {
+ type: Array,
required: true,
},
confirmationPhrase: {
@@ -44,10 +48,10 @@ export default {
<div>
<gl-form-group>
<namespace-select
- class="qa-namespaces-list"
data-testid="transfer-project-namespace"
:full-width="true"
- :data="namespaces"
+ :group-namespaces="groupNamespaces"
+ :user-namespaces="userNamespaces"
:selected-namespace="selectedNamespace"
@select="handleSelect"
/>
diff --git a/app/assets/javascripts/projects/settings/constants.js b/app/assets/javascripts/projects/settings/constants.js
index f5591c43dc4..9cf1afd334f 100644
--- a/app/assets/javascripts/projects/settings/constants.js
+++ b/app/assets/javascripts/projects/settings/constants.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
@@ -18,3 +20,8 @@ export const ACCESS_LEVELS = {
};
export const ACCESS_LEVEL_NONE = 0;
+
+// must match shared_runners_setting in update_service.rb
+export const CC_VALIDATION_REQUIRED_ERROR = __(
+ 'Shared runners enabled cannot be enabled until a valid credit card is on file',
+);
diff --git a/app/assets/javascripts/projects/settings/init_transfer_project_form.js b/app/assets/javascripts/projects/settings/init_transfer_project_form.js
index 47b49031dc9..a5f720bffaa 100644
--- a/app/assets/javascripts/projects/settings/init_transfer_project_form.js
+++ b/app/assets/javascripts/projects/settings/init_transfer_project_form.js
@@ -3,10 +3,14 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import TransferProjectForm from './components/transfer_project_form.vue';
const prepareNamespaces = (rawNamespaces = '') => {
+ if (!rawNamespaces) {
+ return { groupNamespaces: [], userNamespaces: [] };
+ }
+
const data = JSON.parse(rawNamespaces);
return {
- group: data?.group.map(convertObjectPropsToCamelCase),
- user: data?.user.map(convertObjectPropsToCamelCase),
+ groupNamespaces: data?.group?.map(convertObjectPropsToCamelCase) || [],
+ userNamespaces: data?.user?.map(convertObjectPropsToCamelCase) || [],
};
};
@@ -35,7 +39,7 @@ export default () => {
props: {
confirmButtonText,
confirmationPhrase,
- namespaces: prepareNamespaces(namespaces),
+ ...prepareNamespaces(namespaces),
},
on: {
selectNamespace: (id) => {