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:
Diffstat (limited to 'app/assets/javascripts/import_projects/store')
-rw-r--r--app/assets/javascripts/import_projects/store/actions.js142
-rw-r--r--app/assets/javascripts/import_projects/store/getters.js42
-rw-r--r--app/assets/javascripts/import_projects/store/index.js8
-rw-r--r--app/assets/javascripts/import_projects/store/mutation_types.js10
-rw-r--r--app/assets/javascripts/import_projects/store/mutations.js100
-rw-r--r--app/assets/javascripts/import_projects/store/state.js16
6 files changed, 227 insertions, 91 deletions
diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js
index 8d8d33f5972..af410f411d8 100644
--- a/app/assets/javascripts/import_projects/store/actions.js
+++ b/app/assets/javascripts/import_projects/store/actions.js
@@ -1,41 +1,86 @@
import Visibility from 'visibilityjs';
import * as types from './mutation_types';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { isProjectImportable } from '../utils';
+import {
+ convertObjectPropsToCamelCase,
+ normalizeHeaders,
+ parseIntPagination,
+} from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
-import { visitUrl } from '~/lib/utils/url_utility';
-import createFlash from '~/flash';
+import { visitUrl, objectToQuery } from '~/lib/utils/url_utility';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
-import { jobsPathWithFilter, reposPathWithFilter } from './getters';
let eTagPoll;
const hasRedirectInError = e => e?.response?.data?.error?.redirect;
const redirectToUrlInError = e => visitUrl(e.response.data.error.redirect);
+const pathWithParams = ({ path, ...params }) => {
+ const filteredParams = Object.fromEntries(
+ Object.entries(params).filter(([, value]) => value !== ''),
+ );
+ const queryString = objectToQuery(filteredParams);
+ return queryString ? `${path}?${queryString}` : path;
+};
+
+const isRequired = () => {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('param is required');
+};
-export const clearJobsEtagPoll = () => {
+const clearJobsEtagPoll = () => {
eTagPoll = null;
};
-export const stopJobsPolling = () => {
+
+const stopJobsPolling = () => {
if (eTagPoll) eTagPoll.stop();
};
-export const restartJobsPolling = () => {
+
+const restartJobsPolling = () => {
if (eTagPoll) eTagPoll.restart();
};
-export const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter);
+const setFilter = ({ commit }, filter) => commit(types.SET_FILTER, filter);
+
+const setImportTarget = ({ commit }, { repoId, importTarget }) =>
+ commit(types.SET_IMPORT_TARGET, { repoId, importTarget });
+
+const importAll = ({ state, dispatch }) => {
+ return Promise.all(
+ state.repositories
+ .filter(isProjectImportable)
+ .map(r => dispatch('fetchImport', r.importSource.id)),
+ );
+};
-export const fetchRepos = ({ state, dispatch, commit }) => {
+const fetchReposFactory = ({ reposPath = isRequired(), hasPagination }) => ({
+ state,
+ dispatch,
+ commit,
+}) => {
dispatch('stopJobsPolling');
commit(types.REQUEST_REPOS);
- const { provider } = state;
+ const { provider, filter } = state;
return axios
- .get(reposPathWithFilter(state))
- .then(({ data }) =>
- commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
+ .get(
+ pathWithParams({
+ path: reposPath,
+ filter,
+ page: hasPagination ? state.pageInfo.page.toString() : '',
+ }),
)
+ .then(({ data, headers }) => {
+ const normalizedHeaders = normalizeHeaders(headers);
+
+ if ('X-PAGE' in normalizedHeaders) {
+ commit(types.SET_PAGE_INFO, parseIntPagination(normalizedHeaders));
+ }
+
+ commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true }));
+ })
.then(() => dispatch('fetchJobs'))
.catch(e => {
if (hasRedirectInError(e)) {
@@ -52,24 +97,26 @@ export const fetchRepos = ({ state, dispatch, commit }) => {
});
};
-export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo }) => {
- if (!state.reposBeingImported.includes(repo.id)) {
- commit(types.REQUEST_IMPORT, repo.id);
- }
+const fetchImportFactory = (importPath = isRequired()) => ({ state, commit, getters }, repoId) => {
+ const { ciCdOnly } = state;
+ const importTarget = getters.getImportTarget(repoId);
+
+ commit(types.REQUEST_IMPORT, { repoId, importTarget });
+ const { newName, targetNamespace } = importTarget;
return axios
- .post(state.importPath, {
- ci_cd_only: state.ciCdOnly,
+ .post(importPath, {
+ repo_id: repoId,
+ ci_cd_only: ciCdOnly,
new_name: newName,
- repo_id: repo.id,
target_namespace: targetNamespace,
})
- .then(({ data }) =>
+ .then(({ data }) => {
commit(types.RECEIVE_IMPORT_SUCCESS, {
importedProject: convertObjectPropsToCamelCase(data, { deep: true }),
- repoId: repo.id,
- }),
- )
+ repoId,
+ });
+ })
.catch(e => {
const serverErrorMessage = e?.response?.data?.errors;
const flashMessage = serverErrorMessage
@@ -84,14 +131,11 @@ export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo
createFlash(flashMessage);
- commit(types.RECEIVE_IMPORT_ERROR, repo.id);
+ commit(types.RECEIVE_IMPORT_ERROR, repoId);
});
};
-export const receiveJobsSuccess = ({ commit }, updatedProjects) =>
- commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects);
-
-export const fetchJobs = ({ state, commit, dispatch }) => {
+export const fetchJobsFactory = (jobsPath = isRequired()) => ({ state, commit, dispatch }) => {
const { filter } = state;
if (eTagPoll) {
@@ -101,7 +145,7 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
eTagPoll = new Poll({
resource: {
- fetchJobs: () => axios.get(jobsPathWithFilter(state)),
+ fetchJobs: () => axios.get(pathWithParams({ path: jobsPath, filter })),
},
method: 'fetchJobs',
successCallback: ({ data }) =>
@@ -129,5 +173,39 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
});
};
-// prevent babel-plugin-rewire from generating an invalid default during karma tests
-export default () => {};
+const fetchNamespacesFactory = (namespacesPath = isRequired()) => ({ commit }) => {
+ commit(types.REQUEST_NAMESPACES);
+ axios
+ .get(namespacesPath)
+ .then(({ data }) =>
+ commit(types.RECEIVE_NAMESPACES_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
+ )
+ .catch(() => {
+ createFlash(s__('ImportProjects|Requesting namespaces failed'));
+
+ commit(types.RECEIVE_NAMESPACES_ERROR);
+ });
+};
+
+const setPage = ({ state, commit, dispatch }, page) => {
+ if (page === state.pageInfo.page) {
+ return null;
+ }
+
+ commit(types.SET_PAGE, page);
+ return dispatch('fetchRepos');
+};
+
+export default ({ endpoints = isRequired(), hasPagination }) => ({
+ clearJobsEtagPoll,
+ stopJobsPolling,
+ restartJobsPolling,
+ setFilter,
+ setImportTarget,
+ importAll,
+ setPage,
+ fetchRepos: fetchReposFactory({ reposPath: endpoints.reposPath, hasPagination }),
+ fetchImport: fetchImportFactory(endpoints.importPath),
+ fetchJobs: fetchJobsFactory(endpoints.jobsPath),
+ fetchNamespaces: fetchNamespacesFactory(endpoints.namespacesPath),
+});
diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js
index e6eb8f523de..7d529c94d7d 100644
--- a/app/assets/javascripts/import_projects/store/getters.js
+++ b/app/assets/javascripts/import_projects/store/getters.js
@@ -1,29 +1,27 @@
-import { __ } from '~/locale';
+import { STATUSES } from '../constants';
-export const namespaceSelectOptions = state => {
- const serializedNamespaces = state.namespaces.map(({ fullPath }) => ({
- id: fullPath,
- text: fullPath,
- }));
+export const isLoading = state => state.isLoadingRepos || state.isLoadingNamespaces;
- return [
- { text: __('Groups'), children: serializedNamespaces },
- {
- text: __('Users'),
- children: [{ id: state.defaultTargetNamespace, text: state.defaultTargetNamespace }],
- },
- ];
-};
+export const isImportingAnyRepo = state =>
+ state.repositories.some(repo =>
+ [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.STARTED].includes(repo.importStatus),
+ );
-export const isImportingAnyRepo = state => state.reposBeingImported.length > 0;
+export const hasIncompatibleRepos = state =>
+ state.repositories.some(repo => repo.importSource.incompatible);
-export const hasProviderRepos = state => state.providerRepos.length > 0;
+export const hasImportableRepos = state =>
+ state.repositories.some(repo => repo.importStatus === STATUSES.NONE);
-export const hasImportedProjects = state => state.importedProjects.length > 0;
+export const getImportTarget = state => repoId => {
+ if (state.customImportTargets[repoId]) {
+ return state.customImportTargets[repoId];
+ }
-export const hasIncompatibleRepos = state => state.incompatibleRepos.length > 0;
+ const repo = state.repositories.find(r => r.importSource.id === repoId);
-export const reposPathWithFilter = ({ reposPath, filter = '' }) =>
- filter ? `${reposPath}?filter=${filter}` : reposPath;
-export const jobsPathWithFilter = ({ jobsPath, filter = '' }) =>
- filter ? `${jobsPath}?filter=${filter}` : jobsPath;
+ return {
+ newName: repo.importSource.sanitizedName,
+ targetNamespace: state.defaultTargetNamespace,
+ };
+};
diff --git a/app/assets/javascripts/import_projects/store/index.js b/app/assets/javascripts/import_projects/store/index.js
index 29deb7868ba..7ba12f81eb9 100644
--- a/app/assets/javascripts/import_projects/store/index.js
+++ b/app/assets/javascripts/import_projects/store/index.js
@@ -1,18 +1,16 @@
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
-import * as actions from './actions';
+import actionsFactory from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
-export { state, actions, getters, mutations };
-
-export default initialState =>
+export default ({ initialState, endpoints, hasPagination }) =>
new Vuex.Store({
state: { ...state(), ...initialState },
- actions,
+ actions: actionsFactory({ endpoints, hasPagination }),
mutations,
getters,
});
diff --git a/app/assets/javascripts/import_projects/store/mutation_types.js b/app/assets/javascripts/import_projects/store/mutation_types.js
index a23b7eef986..6adf5e59cff 100644
--- a/app/assets/javascripts/import_projects/store/mutation_types.js
+++ b/app/assets/javascripts/import_projects/store/mutation_types.js
@@ -2,6 +2,10 @@ export const REQUEST_REPOS = 'REQUEST_REPOS';
export const RECEIVE_REPOS_SUCCESS = 'RECEIVE_REPOS_SUCCESS';
export const RECEIVE_REPOS_ERROR = 'RECEIVE_REPOS_ERROR';
+export const REQUEST_NAMESPACES = 'REQUEST_NAMESPACES';
+export const RECEIVE_NAMESPACES_SUCCESS = 'RECEIVE_NAMESPACES_SUCCESS';
+export const RECEIVE_NAMESPACES_ERROR = 'RECEIVE_NAMESPACES_ERROR';
+
export const REQUEST_IMPORT = 'REQUEST_IMPORT';
export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS';
export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
@@ -9,3 +13,9 @@ export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
export const SET_FILTER = 'SET_FILTER';
+
+export const SET_IMPORT_TARGET = 'SET_IMPORT_TARGET';
+
+export const SET_PAGE = 'SET_PAGE';
+
+export const SET_PAGE_INFO = 'SET_PAGE_INFO';
diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js
index ec62d0640ef..b3dbef896a6 100644
--- a/app/assets/javascripts/import_projects/store/mutations.js
+++ b/app/assets/javascripts/import_projects/store/mutations.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import * as types from './mutation_types';
+import { STATUSES } from '../constants';
export default {
[types.SET_FILTER](state, filter) {
@@ -12,48 +13,103 @@ export default {
[types.RECEIVE_REPOS_SUCCESS](
state,
- { importedProjects, providerRepos, incompatibleRepos, namespaces },
+ { importedProjects, providerRepos, incompatibleRepos = [] },
) {
+ // Normalizing structure to support legacy backend format
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/27370#note_379034091 for details
+
state.isLoadingRepos = false;
- state.importedProjects = importedProjects;
- state.providerRepos = providerRepos;
- state.incompatibleRepos = incompatibleRepos ?? [];
- state.namespaces = namespaces;
+ state.repositories = [
+ ...importedProjects.map(({ importSource, providerLink, importStatus, ...project }) => ({
+ importSource: {
+ id: `finished-${project.id}`,
+ fullName: importSource,
+ sanitizedName: project.name,
+ providerLink,
+ },
+ importStatus,
+ importedProject: project,
+ })),
+ ...providerRepos.map(project => ({
+ importSource: project,
+ importStatus: STATUSES.NONE,
+ importedProject: null,
+ })),
+ ...incompatibleRepos.map(project => ({
+ importSource: { ...project, incompatible: true },
+ importStatus: STATUSES.NONE,
+ importedProject: null,
+ })),
+ ];
},
[types.RECEIVE_REPOS_ERROR](state) {
state.isLoadingRepos = false;
},
- [types.REQUEST_IMPORT](state, repoId) {
- state.reposBeingImported.push(repoId);
+ [types.REQUEST_IMPORT](state, { repoId, importTarget }) {
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = STATUSES.SCHEDULING;
+ existingRepo.importedProject = {
+ fullPath: `/${importTarget.targetNamespace}/${importTarget.newName}`,
+ };
},
[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) {
- const existingRepoIndex = state.reposBeingImported.indexOf(repoId);
- if (state.reposBeingImported.includes(repoId))
- state.reposBeingImported.splice(existingRepoIndex, 1);
+ const { importStatus, ...project } = importedProject;
- const providerRepoIndex = state.providerRepos.findIndex(
- providerRepo => providerRepo.id === repoId,
- );
- state.providerRepos.splice(providerRepoIndex, 1);
- state.importedProjects.unshift(importedProject);
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = importStatus;
+ existingRepo.importedProject = project;
},
[types.RECEIVE_IMPORT_ERROR](state, repoId) {
- const repoIndex = state.reposBeingImported.indexOf(repoId);
- if (state.reposBeingImported.includes(repoId)) state.reposBeingImported.splice(repoIndex, 1);
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+ existingRepo.importStatus = STATUSES.NONE;
+ existingRepo.importedProject = null;
},
[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) {
updatedProjects.forEach(updatedProject => {
- const existingProject = state.importedProjects.find(
- importedProject => importedProject.id === updatedProject.id,
- );
-
- Vue.set(existingProject, 'importStatus', updatedProject.importStatus);
+ const repo = state.repositories.find(p => p.importedProject?.id === updatedProject.id);
+ if (repo) {
+ repo.importStatus = updatedProject.importStatus;
+ }
});
},
+
+ [types.REQUEST_NAMESPACES](state) {
+ state.isLoadingNamespaces = true;
+ },
+
+ [types.RECEIVE_NAMESPACES_SUCCESS](state, namespaces) {
+ state.isLoadingNamespaces = false;
+ state.namespaces = namespaces;
+ },
+
+ [types.RECEIVE_NAMESPACES_ERROR](state) {
+ state.isLoadingNamespaces = false;
+ },
+
+ [types.SET_IMPORT_TARGET](state, { repoId, importTarget }) {
+ const existingRepo = state.repositories.find(r => r.importSource.id === repoId);
+
+ if (
+ importTarget.targetNamespace === state.defaultTargetNamespace &&
+ importTarget.newName === existingRepo.importSource.sanitizedName
+ ) {
+ Vue.delete(state.customImportTargets, repoId);
+ } else {
+ Vue.set(state.customImportTargets, repoId, importTarget);
+ }
+ },
+
+ [types.SET_PAGE_INFO](state, pageInfo) {
+ state.pageInfo = pageInfo;
+ },
+
+ [types.SET_PAGE](state, page) {
+ state.pageInfo.page = page;
+ },
};
diff --git a/app/assets/javascripts/import_projects/store/state.js b/app/assets/javascripts/import_projects/store/state.js
index 0418d735b1d..3318181e4af 100644
--- a/app/assets/javascripts/import_projects/store/state.js
+++ b/app/assets/javascripts/import_projects/store/state.js
@@ -1,17 +1,13 @@
export default () => ({
- reposPath: '',
- importPath: '',
- jobsPath: '',
- currentProjectId: '',
provider: '',
- currentUsername: '',
- importedProjects: [],
- providerRepos: [],
- incompatibleRepos: [],
+ repositories: [],
namespaces: [],
- reposBeingImported: [],
+ customImportTargets: {},
isLoadingRepos: false,
- canSelectNamespace: false,
+ isLoadingNamespaces: false,
ciCdOnly: false,
filter: '',
+ pageInfo: {
+ page: 1,
+ },
});