Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
344cfc45
Commit
344cfc45
authored
Aug 17, 2020
by
Illya Klymov
Committed by
Natalia Tepluhina
Aug 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor import_projects store
* Ensure all user input is stored in Vuex * Cleanup unused Vuex code * Tests
parent
e373e4fd
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
921 additions
and
455 deletions
+921
-455
app/assets/javascripts/import_projects/components/import_projects_table.vue
...ipts/import_projects/components/import_projects_table.vue
+45
-41
app/assets/javascripts/import_projects/components/imported_project_table_row.vue
...import_projects/components/imported_project_table_row.vue
+14
-13
app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue
...mport_projects/components/incompatible_repo_table_row.vue
+3
-3
app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
...ts/import_projects/components/provider_repo_table_row.vue
+43
-46
app/assets/javascripts/import_projects/event_hub.js
app/assets/javascripts/import_projects/event_hub.js
+0
-3
app/assets/javascripts/import_projects/index.js
app/assets/javascripts/import_projects/index.js
+17
-10
app/assets/javascripts/import_projects/store/actions.js
app/assets/javascripts/import_projects/store/actions.js
+67
-22
app/assets/javascripts/import_projects/store/getters.js
app/assets/javascripts/import_projects/store/getters.js
+20
-22
app/assets/javascripts/import_projects/store/index.js
app/assets/javascripts/import_projects/store/index.js
+3
-5
app/assets/javascripts/import_projects/store/mutation_types.js
...ssets/javascripts/import_projects/store/mutation_types.js
+6
-0
app/assets/javascripts/import_projects/store/mutations.js
app/assets/javascripts/import_projects/store/mutations.js
+70
-22
app/assets/javascripts/import_projects/store/state.js
app/assets/javascripts/import_projects/store/state.js
+3
-10
app/assets/javascripts/import_projects/utils.js
app/assets/javascripts/import_projects/utils.js
+7
-0
app/views/import/_githubish_status.html.haml
app/views/import/_githubish_status.html.haml
+1
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/frontend/import_projects/components/import_projects_table_spec.js
.../import_projects/components/import_projects_table_spec.js
+30
-41
spec/frontend/import_projects/components/imported_project_table_row_spec.js
...rt_projects/components/imported_project_table_row_spec.js
+26
-39
spec/frontend/import_projects/components/provider_repo_table_row_spec.js
...mport_projects/components/provider_repo_table_row_spec.js
+60
-60
spec/frontend/import_projects/store/actions_spec.js
spec/frontend/import_projects/store/actions_spec.js
+129
-42
spec/frontend/import_projects/store/getters_spec.js
spec/frontend/import_projects/store/getters_spec.js
+79
-61
spec/frontend/import_projects/store/mutations_spec.js
spec/frontend/import_projects/store/mutations_spec.js
+263
-15
spec/frontend/import_projects/utils_spec.js
spec/frontend/import_projects/utils_spec.js
+32
-0
No files found.
app/assets/javascripts/import_projects/components/import_projects_table.vue
View file @
344cfc45
...
...
@@ -6,7 +6,7 @@ import { __, sprintf } from '~/locale';
import
ImportedProjectTableRow
from
'
./imported_project_table_row.vue
'
;
import
ProviderRepoTableRow
from
'
./provider_repo_table_row.vue
'
;
import
IncompatibleRepoTableRow
from
'
./incompatible_repo_table_row.vue
'
;
import
eventHub
from
'
../event_hub
'
;
import
{
isProjectImportable
}
from
'
../utils
'
;
const
reposFetchThrottleDelay
=
1000
;
...
...
@@ -32,20 +32,29 @@ export default {
},
computed
:
{
...
mapState
([
'
importedProjects
'
,
'
providerRepos
'
,
'
incompatibleRepos
'
,
'
isLoadingRepos
'
,
'
filter
'
,
]),
...
mapState
([
'
filter
'
,
'
repositories
'
,
'
namespaces
'
,
'
defaultTargetNamespace
'
]),
...
mapGetters
([
'
isLoading
'
,
'
isImportingAnyRepo
'
,
'
hasProviderRepos
'
,
'
hasImportedProjects
'
,
'
hasImportableRepos
'
,
'
hasIncompatibleRepos
'
,
]),
availableNamespaces
()
{
const
serializedNamespaces
=
this
.
namespaces
.
map
(({
fullPath
})
=>
({
id
:
fullPath
,
text
:
fullPath
,
}));
return
[
{
text
:
__
(
'
Groups
'
),
children
:
serializedNamespaces
},
{
text
:
__
(
'
Users
'
),
children
:
[{
id
:
this
.
defaultTargetNamespace
,
text
:
this
.
defaultTargetNamespace
}],
},
];
},
importAllButtonText
()
{
return
this
.
hasIncompatibleRepos
?
__
(
'
Import all compatible repositories
'
)
...
...
@@ -64,7 +73,8 @@ export default {
},
mounted
()
{
return
this
.
fetchRepos
();
this
.
fetchNamespaces
();
this
.
fetchRepos
();
},
beforeDestroy
()
{
...
...
@@ -75,17 +85,13 @@ export default {
methods
:
{
...
mapActions
([
'
fetchRepos
'
,
'
fetchReposFiltered
'
,
'
fetchJobs
'
,
'
fetchNamespaces
'
,
'
stopJobsPolling
'
,
'
clearJobsEtagPoll
'
,
'
setFilter
'
,
'
importAll
'
,
]),
importAll
()
{
eventHub
.
$emit
(
'
importAll
'
);
},
handleFilterInput
({
target
})
{
this
.
setFilter
(
target
.
value
);
},
...
...
@@ -93,6 +99,8 @@ export default {
throttledFetchRepos
:
throttle
(
function
fetch
()
{
this
.
fetchRepos
();
},
reposFetchThrottleDelay
),
isProjectImportable
,
},
};
</
script
>
...
...
@@ -103,21 +111,17 @@ export default {
{{
s__
(
'
ImportProjects|Select the projects you want to import
'
)
}}
</p>
<template
v-if=
"hasIncompatibleRepos"
>
<slot
name=
"incompatible-repos-warning"
>
</slot>
<slot
name=
"incompatible-repos-warning"
></slot>
</
template
>
<div
v-if=
"!isLoadingRepos"
class=
"d-flex justify-content-between align-items-end flex-wrap mb-3"
>
<div
v-if=
"!isLoading"
class=
"d-flex justify-content-between align-items-end flex-wrap mb-3"
>
<gl-button
variant=
"success"
:loading=
"isImportingAnyRepo"
:disabled=
"!has
Provider
Repos"
:disabled=
"!has
Importable
Repos"
type=
"button"
@
click=
"importAll"
>
{{ importAllButtonText }}
</gl-button
>
{{ importAllButtonText }}
</gl-button>
<slot
name=
"actions"
></slot>
<form
v-if=
"filterable"
class=
"gl-ml-auto"
novalidate
@
submit
.
prevent
>
<input
...
...
@@ -134,14 +138,11 @@ export default {
</form>
</div>
<gl-loading-icon
v-if=
"isLoading
Repos
"
v-if=
"isLoading"
class=
"js-loading-button-icon import-projects-loading-icon"
size=
"md"
/>
<div
v-else-if=
"hasProviderRepos || hasImportedProjects || hasIncompatibleRepos"
class=
"table-responsive"
>
<div
v-else-if=
"repositories.length"
class=
"table-responsive"
>
<table
class=
"table import-table"
>
<thead>
<th
class=
"import-jobs-from-col"
>
{{ fromHeaderText }}
</th>
...
...
@@ -150,17 +151,20 @@ export default {
<th
class=
"import-jobs-cta-col"
></th>
</thead>
<tbody>
<imported-project-table-row
v-for=
"project in importedProjects"
:key=
"project.id"
:project=
"project"
/>
<provider-repo-table-row
v-for=
"repo in providerRepos"
:key=
"repo.id"
:repo=
"repo"
/>
<incompatible-repo-table-row
v-for=
"repo in incompatibleRepos"
:key=
"repo.id"
:repo=
"repo"
/>
<
template
v-for=
"repo in repositories"
>
<incompatible-repo-table-row
v-if=
"repo.importSource.incompatible"
:key=
"repo.importSource.id"
:repo=
"repo"
/>
<provider-repo-table-row
v-else-if=
"isProjectImportable(repo)"
:key=
"repo.importSource.id"
:repo=
"repo"
:available-namespaces=
"availableNamespaces"
/>
<imported-project-table-row
v-else
:key=
"repo.importSource.id"
:project=
"repo"
/>
</
template
>
</tbody>
</table>
</div>
...
...
app/assets/javascripts/import_projects/components/imported_project_table_row.vue
View file @
344cfc45
...
...
@@ -18,7 +18,7 @@ export default {
computed
:
{
displayFullPath
()
{
return
this
.
project
.
fullPath
.
replace
(
/^
\/
/
,
''
);
return
this
.
project
.
importedProject
.
fullPath
.
replace
(
/^
\/
/
,
''
);
},
isFinished
()
{
...
...
@@ -29,29 +29,30 @@ export default {
</
script
>
<
template
>
<tr
class=
"
js-imported-project
import-row"
>
<tr
class=
"import-row"
>
<td>
<a
:href=
"project.providerLink"
:href=
"project.
importSource.
providerLink"
rel=
"noreferrer noopener"
target=
"_blank"
class=
"js-provider-link"
>
{{
project
.
importSource
}}
<gl-icon
v-if=
"project.providerLink"
name=
"external-link"
/>
data-testid=
"providerLink"
>
{{
project
.
importSource
.
fullName
}}
<gl-icon
v-if=
"project.importSource.providerLink"
name=
"external-link"
/>
</a>
</td>
<td
class=
"js-full-path"
>
{{
displayFullPath
}}
</td>
<td><import-status
:status=
"project.importStatus"
/></td>
<td
data-testid=
"fullPath"
>
{{
displayFullPath
}}
</td>
<td>
<import-status
:status=
"project.importStatus"
/>
</td>
<td>
<a
v-if=
"isFinished"
class=
"btn btn-default js-go-to-project"
:href=
"project.fullPath"
class=
"btn btn-default"
data-testid=
"goToProject"
:href=
"project.importedProject.fullPath"
rel=
"noreferrer noopener"
target=
"_blank"
>
{{
__
(
'
Go to project
'
)
}}
>
{{
__
(
'
Go to project
'
)
}}
</a>
</td>
</tr>
...
...
app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue
View file @
344cfc45
...
...
@@ -18,9 +18,9 @@ export default {
<
template
>
<tr
class=
"import-row"
>
<td>
<a
:href=
"repo.
providerLink"
rel=
"noreferrer noopener"
target=
"_blank"
>
{{
repo
.
fullName
}}
<gl-icon
v-if=
"repo.providerLink"
name=
"external-link"
/>
<a
:href=
"repo.
importSource.providerLink"
rel=
"noreferrer noopener"
target=
"_blank"
>
{{
repo
.
importSource
.
fullName
}}
<gl-icon
v-if=
"repo.
importSource.
providerLink"
name=
"external-link"
/>
</a>
</td>
<td></td>
...
...
app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
View file @
344cfc45
...
...
@@ -3,8 +3,6 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
Select2Select
from
'
~/vue_shared/components/select2_select.vue
'
;
import
{
__
}
from
'
~/locale
'
;
import
eventHub
from
'
../event_hub
'
;
import
{
STATUSES
}
from
'
../constants
'
;
import
ImportStatus
from
'
./import_status.vue
'
;
export
default
{
...
...
@@ -19,19 +17,19 @@ export default {
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
targetNamespace
:
this
.
$store
.
state
.
defaultTargetNamespace
,
newName
:
this
.
repo
.
sanitizedName
,
};
availableNamespaces
:
{
type
:
Array
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
namespaces
'
,
'
reposBeingImported
'
,
'
ciCdOnly
'
]),
...
mapState
([
'
ciCdOnly
'
]),
...
mapGetters
([
'
getImportTarget
'
]),
...
mapGetters
([
'
namespaceSelectOptions
'
]),
importTarget
()
{
return
this
.
getImportTarget
(
this
.
repo
.
importSource
.
id
);
},
importButtonText
()
{
return
this
.
ciCdOnly
?
__
(
'
Connect
'
)
:
__
(
'
Import
'
);
...
...
@@ -39,37 +37,36 @@ export default {
select2Options
()
{
return
{
data
:
this
.
namespaceSelectOptions
,
containerCssClass
:
'
import-namespace-select js-namespace-select qa-project-namespace-select w-auto
'
,
data
:
this
.
availableNamespaces
,
containerCssClass
:
'
import-namespace-select qa-project-namespace-select w-auto
'
,
};
},
isLoadingImport
()
{
return
this
.
reposBeingImported
.
includes
(
this
.
repo
.
id
);
targetNamespaceSelect
:
{
get
()
{
return
this
.
importTarget
.
targetNamespace
;
},
set
(
value
)
{
this
.
updateImportTarget
({
targetNamespace
:
value
});
},
},
status
()
{
return
this
.
isLoadingImport
?
STATUSES
.
SCHEDULING
:
STATUSES
.
NONE
;
newNameInput
:
{
get
()
{
return
this
.
importTarget
.
newName
;
},
set
(
value
)
{
this
.
updateImportTarget
({
newName
:
value
});
},
},
},
created
()
{
eventHub
.
$on
(
'
importAll
'
,
this
.
importRepo
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
importAll
'
,
this
.
importRepo
);
},
methods
:
{
...
mapActions
([
'
fetchImport
'
]),
importRepo
()
{
return
this
.
fetchImport
({
newName
:
this
.
newName
,
targetNamespace
:
this
.
targetNamespace
,
repo
:
this
.
repo
,
...
mapActions
([
'
fetchImport
'
,
'
setImportTarget
'
]),
updateImportTarget
(
changedValues
)
{
this
.
setImportTarget
({
repoId
:
this
.
repo
.
importSource
.
id
,
importTarget
:
{
...
this
.
importTarget
,
...
changedValues
},
});
},
},
...
...
@@ -77,36 +74,36 @@ export default {
</
script
>
<
template
>
<tr
class=
"qa-project-import-row
js-provider-repo
import-row"
>
<tr
class=
"qa-project-import-row import-row"
>
<td>
<a
:href=
"repo.providerLink"
:href=
"repo.
importSource.
providerLink"
rel=
"noreferrer noopener"
target=
"_blank"
class=
"js-provider-link"
>
{{
repo
.
fullName
}}
<gl-icon
v-if=
"repo.providerLink"
name=
"external-link"
/>
data-testid=
"providerLink"
>
{{
repo
.
importSource
.
fullName
}}
<gl-icon
v-if=
"repo.importSource.providerLink"
name=
"external-link"
/>
</a>
</td>
<td
class=
"d-flex flex-wrap flex-lg-nowrap"
>
<select2-select
v-model=
"targetNamespace"
:options=
"select2Options"
/>
<select2-select
v-model=
"targetNamespace
Select
"
:options=
"select2Options"
/>
<span
class=
"px-2 import-slash-divider d-flex justify-content-center align-items-center"
>
/
</span
>
<input
v-model=
"newName"
v-model=
"newName
Input
"
type=
"text"
class=
"form-control import-project-name-input
js-new-name
qa-project-path-field"
class=
"form-control import-project-name-input qa-project-path-field"
/>
</td>
<td><import-status
:status=
"status"
/></td>
<td>
<import-status
:status=
"repo.importStatus"
/>
</td>
<td>
<button
v-if=
"!isLoadingImport"
type=
"button"
class=
"qa-import-button
js-import-button
btn btn-default"
@
click=
"
importRepo
"
class=
"qa-import-button btn btn-default"
@
click=
"
fetchImport(repo.importSource.id)
"
>
{{
importButtonText
}}
</button>
...
...
app/assets/javascripts/import_projects/event_hub.js
deleted
100644 → 0
View file @
e373e4fd
import
createEventHub
from
'
~/helpers/event_hub_factory
'
;
export
default
createEventHub
();
app/assets/javascripts/import_projects/index.js
View file @
344cfc45
...
...
@@ -8,22 +8,29 @@ Vue.use(Translate);
export
function
initStoreFromElement
(
element
)
{
const
{
reposPath
,
provider
,
ciCdOnly
,
canSelectNamespace
,
provider
,
reposPath
,
jobsPath
,
importPath
,
ciCdOnly
,
namespacesPath
,
}
=
element
.
dataset
;
return
createStore
({
reposPath
,
provider
,
jobsPath
,
importPath
,
defaultTargetNamespace
:
gon
.
current_username
,
ciCdOnly
:
parseBoolean
(
ciCdOnly
),
canSelectNamespace
:
parseBoolean
(
canSelectNamespace
),
initialState
:
{
defaultTargetNamespace
:
gon
.
current_username
,
ciCdOnly
:
parseBoolean
(
ciCdOnly
),
canSelectNamespace
:
parseBoolean
(
canSelectNamespace
),
provider
,
},
endpoints
:
{
reposPath
,
jobsPath
,
importPath
,
namespacesPath
,
},
});
}
...
...
app/assets/javascripts/import_projects/store/actions.js
View file @
344cfc45
import
Visibility
from
'
visibilityjs
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
isProjectImportable
}
from
'
../utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
Poll
from
'
~/lib/utils/poll
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
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
pathWithFilter
=
({
path
,
filter
=
''
})
=>
(
filter
?
`
${
path
}
?filter=
${
filter
}
`
:
path
);
export
const
clearJobsEtagPoll
=
()
=>
{
const
isRequired
=
()
=>
{
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
param is required
'
);
};
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
});
export
const
fetchRepos
=
({
state
,
dispatch
,
commit
})
=>
{
const
importAll
=
({
state
,
dispatch
})
=>
{
return
Promise
.
all
(
state
.
repositories
.
filter
(
isProjectImportable
)
.
map
(
r
=>
dispatch
(
'
fetchImport
'
,
r
.
importSource
.
id
)),
);
};
const
fetchReposFactory
=
(
reposPath
=
isRequired
())
=>
({
state
,
dispatch
,
commit
})
=>
{
dispatch
(
'
stopJobsPolling
'
);
commit
(
types
.
REQUEST_REPOS
);
const
{
provider
}
=
state
;
const
{
provider
,
filter
}
=
state
;
return
axios
.
get
(
reposPathWithFilter
(
state
))
.
get
(
pathWithFilter
({
path
:
reposPath
,
filter
}
))
.
then
(({
data
})
=>
commit
(
types
.
RECEIVE_REPOS_SUCCESS
,
convertObjectPropsToCamelCase
(
data
,
{
deep
:
true
})),
)
...
...
@@ -52,22 +71,24 @@ 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
})
=>
commit
(
types
.
RECEIVE_IMPORT_SUCCESS
,
{
importedProject
:
convertObjectPropsToCamelCase
(
data
,
{
deep
:
true
}),
repoId
:
repo
.
id
,
repoId
,
}),
)
.
catch
(
e
=>
{
...
...
@@ -84,14 +105,11 @@ export const fetchImport = ({ state, commit }, { newName, targetNamespace, repo
createFlash
(
flashMessage
);
commit
(
types
.
RECEIVE_IMPORT_ERROR
,
repo
.
i
d
);
commit
(
types
.
RECEIVE_IMPORT_ERROR
,
repo
I
d
);
});
};
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 +119,7 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
eTagPoll
=
new
Poll
({
resource
:
{
fetchJobs
:
()
=>
axios
.
get
(
jobsPathWithFilter
(
state
)),
fetchJobs
:
()
=>
axios
.
get
(
pathWithFilter
({
path
:
jobsPath
,
filter
}
)),
},
method
:
'
fetchJobs
'
,
successCallback
:
({
data
})
=>
...
...
@@ -128,3 +146,30 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
}
});
};
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
);
});
};
export
default
({
endpoints
=
isRequired
()
})
=>
({
clearJobsEtagPoll
,
stopJobsPolling
,
restartJobsPolling
,
setFilter
,
setImportTarget
,
importAll
,
fetchRepos
:
fetchReposFactory
(
endpoints
.
reposPath
),
fetchImport
:
fetchImportFactory
(
endpoints
.
importPath
),
fetchJobs
:
fetchJobsFactory
(
endpoints
.
jobsPath
),
fetchNamespaces
:
fetchNamespacesFactory
(
endpoints
.
namespacesPath
),
});
app/assets/javascripts/import_projects/store/getters.js
View file @
344cfc45
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
,
};
};
app/assets/javascripts/import_projects/store/index.js
View file @
344cfc45
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
})
=>
new
Vuex
.
Store
({
state
:
{
...
state
(),
...
initialState
},
actions
,
actions
:
actionsFactory
({
endpoints
})
,
mutations
,
getters
,
});
app/assets/javascripts/import_projects/store/mutation_types.js
View file @
344cfc45
...
...
@@ -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,5 @@ 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
'
;
app/assets/javascripts/import_projects/store/mutations.js
View file @
344cfc45
import
Vue
from
'
vue
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
STATUSES
}
from
'
../constants
'
;
export
default
{
[
types
.
SET_FILTER
](
state
,
filter
)
{
...
...
@@ -12,48 +13,95 @@ 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
);
}
},
};
app/assets/javascripts/import_projects/store/state.js
View file @
344cfc45
export
default
()
=>
({
reposPath
:
''
,
importPath
:
''
,
jobsPath
:
''
,
currentProjectId
:
''
,
provider
:
''
,
currentUsername
:
''
,
importedProjects
:
[],
providerRepos
:
[],
incompatibleRepos
:
[],
repositories
:
[],
namespaces
:
[],
reposBeingImported
:
[]
,
customImportTargets
:
{}
,
isLoadingRepos
:
false
,
canSelectNamespace
:
false
,
isLoadingNamespaces
:
false
,
ciCdOnly
:
false
,
filter
:
''
,
});
app/assets/javascripts/import_projects/utils.js
0 → 100644
View file @
344cfc45
import
{
STATUSES
}
from
'
./constants
'
;
// Will be expanded in future
// eslint-disable-next-line import/prefer-default-export
export
function
isProjectImportable
(
project
)
{
return
project
.
importStatus
===
STATUSES
.
NONE
&&
!
project
.
importSource
.
incompatible
;
}
app/views/import/_githubish_status.html.haml
View file @
344cfc45
...
...
@@ -6,6 +6,7 @@
#import-projects-mount-element
{
data:
{
provider:
provider
,
provider_title:
provider_title
,
can_select_namespace:
current_user
.
can_select_namespace?
.
to_s
,
ci_cd_only:
has_ci_cd_only_params?
.
to_s
,
namespaces_path:
import_available_namespaces_path
,
repos_path:
url_for
([
:status
,
:import
,
provider
,
format: :json
]),
jobs_path:
url_for
([
:realtime_changes
,
:import
,
provider
,
format: :json
]),
import_path:
url_for
([
:import
,
provider
,
format: :json
]),
...
...
locale/gitlab.pot
View file @
344cfc45
...
...
@@ -12851,6 +12851,9 @@ msgstr ""
msgid "ImportProjects|Importing the project failed: %{reason}"
msgstr ""
msgid "ImportProjects|Requesting namespaces failed"
msgstr ""
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
...
...
spec/frontend/import_projects/components/import_projects_table_spec.js
View file @
344cfc45
...
...
@@ -2,17 +2,14 @@ import { nextTick } from 'vue';
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlLoadingIcon
,
GlButton
}
from
'
@gitlab/ui
'
;
import
{
state
,
getters
}
from
'
~/import_projects/store
'
;
import
eventHub
from
'
~/import_projects/event_hub
'
;
import
state
from
'
~/import_projects/store/state
'
;
import
*
as
getters
from
'
~/import_projects/store/getters
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
import
ImportProjectsTable
from
'
~/import_projects/components/import_projects_table.vue
'
;
import
ImportedProjectTableRow
from
'
~/import_projects/components/imported_project_table_row.vue
'
;
import
ProviderRepoTableRow
from
'
~/import_projects/components/provider_repo_table_row.vue
'
;
import
IncompatibleRepoTableRow
from
'
~/import_projects/components/incompatible_repo_table_row.vue
'
;
jest
.
mock
(
'
~/import_projects/event_hub
'
,
()
=>
({
$emit
:
jest
.
fn
(),
}));
describe
(
'
ImportProjectsTable
'
,
()
=>
{
let
wrapper
;
...
...
@@ -21,13 +18,6 @@ describe('ImportProjectsTable', () => {
const
providerTitle
=
'
THE PROVIDER
'
;
const
providerRepo
=
{
id
:
10
,
sanitizedName
:
'
sanitizedName
'
,
fullName
:
'
fullName
'
};
const
importedProject
=
{
id
:
1
,
fullPath
:
'
fullPath
'
,
importStatus
:
'
started
'
,
providerLink
:
'
providerLink
'
,
importSource
:
'
importSource
'
,
};
const
findImportAllButton
=
()
=>
wrapper
...
...
@@ -35,6 +25,7 @@ describe('ImportProjectsTable', () => {
.
filter
(
w
=>
w
.
props
().
variant
===
'
success
'
)
.
at
(
0
);
const
importAllFn
=
jest
.
fn
();
function
createComponent
({
state
:
initialState
,
getters
:
customGetters
,
...
...
@@ -52,8 +43,9 @@ describe('ImportProjectsTable', () => {
},
actions
:
{
fetchRepos
:
jest
.
fn
(),
fetchReposFiltered
:
jest
.
fn
(),
fetchJobs
:
jest
.
fn
(),
fetchNamespaces
:
jest
.
fn
(),
importAll
:
importAllFn
,
stopJobsPolling
:
jest
.
fn
(),
clearJobsEtagPoll
:
jest
.
fn
(),
setFilter
:
jest
.
fn
(),
...
...
@@ -79,11 +71,13 @@ describe('ImportProjectsTable', () => {
});
it
(
'
renders a loading icon while repos are loading
'
,
()
=>
{
createComponent
({
state
:
{
isLoadingRepos
:
true
,
},
});
createComponent
({
state
:
{
isLoadingRepos
:
true
}
});
expect
(
wrapper
.
contains
(
GlLoadingIcon
)).
toBe
(
true
);
});
it
(
'
renders a loading icon while namespaces are loading
'
,
()
=>
{
createComponent
({
state
:
{
isLoadingNamespaces
:
true
}
});
expect
(
wrapper
.
contains
(
GlLoadingIcon
)).
toBe
(
true
);
});
...
...
@@ -91,10 +85,16 @@ describe('ImportProjectsTable', () => {
it
(
'
renders a table with imported projects and provider repos
'
,
()
=>
{
createComponent
({
state
:
{
importedProjects
:
[
importedProject
],
providerRepos
:
[
providerRepo
],
incompatibleRepos
:
[{
...
providerRepo
,
id
:
11
}],
namespaces
:
[{
path
:
'
path
'
}],
namespaces
:
[{
fullPath
:
'
path
'
}],
repositories
:
[
{
importSource
:
{
id
:
1
},
importedProject
:
null
,
importStatus
:
STATUSES
.
NONE
},
{
importSource
:
{
id
:
2
},
importedProject
:
{},
importStatus
:
STATUSES
.
FINISHED
},
{
importSource
:
{
id
:
3
,
incompatible
:
true
},
importedProject
:
{},
importStatus
:
STATUSES
.
NONE
,
},
],
},
});
...
...
@@ -133,13 +133,7 @@ describe('ImportProjectsTable', () => {
);
it
(
'
renders an empty state if there are no projects available
'
,
()
=>
{
createComponent
({
state
:
{
importedProjects
:
[],
providerRepos
:
[],
incompatibleProjects
:
[],
},
});
createComponent
({
state
:
{
repositories
:
[]
}
});
expect
(
wrapper
.
contains
(
ProviderRepoTableRow
)).
toBe
(
false
);
expect
(
wrapper
.
contains
(
ImportedProjectTableRow
)).
toBe
(
false
);
...
...
@@ -147,34 +141,29 @@ describe('ImportProjectsTable', () => {
});
it
(
'
sends importAll event when import button is clicked
'
,
async
()
=>
{
createComponent
({
state
:
{
providerRepos
:
[
providerRepo
],
},
});
createComponent
({
state
:
{
providerRepos
:
[
providerRepo
]
}
});
findImportAllButton
().
vm
.
$emit
(
'
click
'
);
await
nextTick
();
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
importAll
'
);
expect
(
importAllFn
).
toHaveBeenCalled
();
});
it
(
'
shows loading spinner when import is in progress
'
,
()
=>
{
createComponent
({
getters
:
{
isImportingAnyRepo
:
()
=>
true
,
},
});
createComponent
({
getters
:
{
isImportingAnyRepo
:
()
=>
true
}
});
expect
(
findImportAllButton
().
props
().
loading
).
toBe
(
true
);
});
it
(
'
renders filtering input field by default
'
,
()
=>
{
createComponent
();
expect
(
findFilterField
().
exists
()).
toBe
(
true
);
});
it
(
'
does not render filtering input field when filterable is false
'
,
()
=>
{
createComponent
({
filterable
:
false
});
expect
(
findFilterField
().
exists
()).
toBe
(
false
);
});
...
...
spec/frontend/import_projects/components/imported_project_table_row_spec.js
View file @
344cfc45
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
createStore
from
'
~/import_projects/store
'
;
import
importedProjectTableRow
from
'
~/import_projects/components/imported_project_table_row.vue
'
;
import
STATUS_MAP
from
'
~/import_projects/constants
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
ImportedProjectTableRow
from
'
~/import_projects/components/imported_project_table_row.vue
'
;
import
ImportStatus
from
'
~/import_projects/components/import_status.vue
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
describe
(
'
ImportedProjectTableRow
'
,
()
=>
{
let
vm
;
let
wrapper
;
const
project
=
{
id
:
1
,
fullPath
:
'
fullPath
'
,
importStatus
:
'
finished
'
,
providerLink
:
'
providerLink
'
,
importSource
:
'
importSource
'
,
importSource
:
{
fullName
:
'
fullName
'
,
providerLink
:
'
providerLink
'
,
},
importedProject
:
{
id
:
1
,
fullPath
:
'
fullPath
'
,
importSource
:
'
importSource
'
,
},
importStatus
:
STATUSES
.
FINISHED
,
};
function
mountComponent
()
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
component
=
mount
(
importedProjectTableRow
,
{
localVue
,
store
:
createStore
(),
propsData
:
{
project
:
{
...
project
,
},
},
});
return
component
.
vm
;
wrapper
=
mount
(
ImportedProjectTableRow
,
{
propsData
:
{
project
}
});
}
beforeEach
(()
=>
{
vm
=
mountComponent
();
mountComponent
();
});
afterEach
(()
=>
{
vm
.
$
destroy
();
wrapper
.
destroy
();
});
it
(
'
renders an imported project table row
'
,
()
=>
{
const
providerLink
=
vm
.
$el
.
querySelector
(
'
.js-provider-link
'
);
const
statusObject
=
STATUS_MAP
[
project
.
importStatus
];
expect
(
vm
.
$el
.
classList
.
contains
(
'
js-imported-project
'
)).
toBe
(
true
);
expect
(
providerLink
.
href
).
toMatch
(
project
.
providerLink
);
expect
(
providerLink
.
textContent
).
toMatch
(
project
.
importSource
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-full-path
'
).
textContent
).
toMatch
(
project
.
fullPath
);
expect
(
vm
.
$el
.
querySelector
(
`.
${
statusObject
.
textClass
}
`
).
textContent
).
toMatch
(
statusObject
.
text
,
const
providerLink
=
wrapper
.
find
(
'
[data-testid=providerLink]
'
);
expect
(
providerLink
.
attributes
().
href
).
toMatch
(
project
.
importSource
.
providerLink
);
expect
(
providerLink
.
text
()).
toMatch
(
project
.
importSource
.
fullName
);
expect
(
wrapper
.
find
(
'
[data-testid=fullPath]
'
).
text
()).
toMatch
(
project
.
importedProject
.
fullPath
);
expect
(
wrapper
.
find
(
ImportStatus
).
props
().
status
).
toBe
(
project
.
importStatus
);
expect
(
wrapper
.
find
(
'
[data-testid=goToProject
'
).
attributes
().
href
).
toMatch
(
project
.
importedProject
.
fullPath
,
);
expect
(
vm
.
$el
.
querySelector
(
`.ic-status_
${
statusObject
.
icon
}
`
)).
not
.
toBeNull
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-go-to-project
'
).
href
).
toMatch
(
project
.
fullPath
);
});
});
spec/frontend/import_projects/components/provider_repo_table_row_spec.js
View file @
344cfc45
import
{
nextTick
}
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
{
state
,
actions
,
getters
,
mutations
}
from
'
~/import_projects/store
'
;
import
providerRepoTableRow
from
'
~/import_projects/components/provider_repo_table_row.vue
'
;
import
STATUS_MAP
,
{
STATUSES
}
from
'
~/import_projects/constants
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
ProviderRepoTableRow
from
'
~/import_projects/components/provider_repo_table_row.vue
'
;
import
ImportStatus
from
'
~/import_projects/components/import_status.vue
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
import
Select2Select
from
'
~/vue_shared/components/select2_select.vue
'
;
describe
(
'
ProviderRepoTableRow
'
,
()
=>
{
let
vm
;
let
wrapper
;
const
fetchImport
=
jest
.
fn
();
const
importPath
=
'
/import-path
'
;
const
defaultTargetNamespace
=
'
user
'
;
const
ciCdOnly
=
true
;
const
setImportTarget
=
jest
.
fn
();
const
fakeImportTarget
=
{
targetNamespace
:
'
target
'
,
newName
:
'
newName
'
,
};
const
ciCdOnly
=
false
;
const
repo
=
{
id
:
10
,
sanitizedName
:
'
sanitizedName
'
,
fullName
:
'
fullName
'
,
providerLink
:
'
providerLink
'
,
importSource
:
{
id
:
'
remote-1
'
,
fullName
:
'
fullName
'
,
providerLink
:
'
providerLink
'
,
},
importedProject
:
{
id
:
1
,
fullPath
:
'
fullPath
'
,
importSource
:
'
importSource
'
,
},
importStatus
:
STATUSES
.
FINISHED
,
};
function
initStore
(
initialState
)
{
const
stubbedActions
=
{
...
actions
,
fetchImport
};
const
availableNamespaces
=
[
{
text
:
'
Groups
'
,
children
:
[{
id
:
'
test
'
,
text
:
'
test
'
}]
},
{
text
:
'
Users
'
,
children
:
[{
id
:
'
root
'
,
text
:
'
root
'
}]
},
];
function
initStore
(
initialState
)
{
const
store
=
new
Vuex
.
Store
({
state
:
{
...
state
(),
...
initialState
},
actions
:
stubbedActions
,
mutations
,
getters
,
state
:
initialState
,
getters
:
{
getImportTarget
:
()
=>
()
=>
fakeImportTarget
,
},
actions
:
{
fetchImport
,
setImportTarget
},
});
return
store
;
}
const
findImportButton
=
()
=>
wrapper
.
findAll
(
'
button
'
)
.
filter
(
node
=>
node
.
text
()
===
'
Import
'
)
.
at
(
0
);
function
mountComponent
(
initialState
)
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
store
=
initStore
({
importPath
,
defaultTargetNamespace
,
ciCdOnly
,
...
initialState
});
const
store
=
initStore
({
ciCdOnly
,
...
initialState
});
const
component
=
mount
(
p
roviderRepoTableRow
,
{
wrapper
=
shallowMount
(
P
roviderRepoTableRow
,
{
localVue
,
store
,
propsData
:
{
repo
,
},
propsData
:
{
repo
,
availableNamespaces
},
});
return
component
.
vm
;
}
beforeEach
(()
=>
{
vm
=
mountComponent
();
mountComponent
();
});
afterEach
(()
=>
{
vm
.
$
destroy
();
wrapper
.
destroy
();
});
it
(
'
renders a provider repo table row
'
,
()
=>
{
const
providerLink
=
vm
.
$el
.
querySelector
(
'
.js-provider-link
'
);
const
statusObject
=
STATUS_MAP
[
STATUSES
.
NONE
];
expect
(
vm
.
$el
.
classList
.
contains
(
'
js-provider-repo
'
)).
toBe
(
true
);
expect
(
providerLink
.
href
).
toMatch
(
repo
.
providerLink
);
expect
(
providerLink
.
textContent
).
toMatch
(
repo
.
fullName
);
expect
(
vm
.
$el
.
querySelector
(
`.
${
statusObject
.
textClass
}
`
).
textContent
).
toMatch
(
statusObject
.
text
,
);
expect
(
vm
.
$el
.
querySelector
(
`.ic-status_
${
statusObject
.
icon
}
`
)).
not
.
toBeNull
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-import-button
'
)).
not
.
toBeNull
();
const
providerLink
=
wrapper
.
find
(
'
[data-testid=providerLink]
'
);
expect
(
providerLink
.
attributes
().
href
).
toMatch
(
repo
.
importSource
.
providerLink
);
expect
(
providerLink
.
text
()).
toMatch
(
repo
.
importSource
.
fullName
);
expect
(
wrapper
.
find
(
ImportStatus
).
props
().
status
).
toBe
(
repo
.
importStatus
);
expect
(
wrapper
.
contains
(
'
button
'
)).
toBe
(
true
);
});
it
(
'
renders a select2 namespace select
'
,
()
=>
{
const
dropdownTrigger
=
vm
.
$el
.
querySelector
(
'
.js-namespace-select
'
);
expect
(
dropdownTrigger
).
not
.
toBeNull
();
expect
(
dropdownTrigger
.
classList
.
contains
(
'
select2-container
'
)).
toBe
(
true
);
dropdownTrigger
.
click
();
expect
(
vm
.
$el
.
querySelector
(
'
.select2-drop
'
)).
not
.
toBeNull
();
expect
(
wrapper
.
contains
(
Select2Select
)).
toBe
(
true
);
expect
(
wrapper
.
find
(
Select2Select
).
props
().
options
.
data
).
toBe
(
availableNamespaces
);
});
it
(
'
imports repo when clicking import button
'
,
()
=>
{
vm
.
$el
.
querySelector
(
'
.js-import-button
'
).
click
(
);
it
(
'
imports repo when clicking import button
'
,
async
()
=>
{
findImportButton
().
trigger
(
'
click
'
);
return
vm
.
$nextTick
().
then
(()
=>
{
const
{
calls
}
=
fetchImport
.
mock
;
await
nextTick
();
// Not using .toBeCalledWith because it expects
// an unmatchable and undefined 3rd argument.
expect
(
calls
.
length
).
toBe
(
1
);
expect
(
calls
[
0
][
1
]).
toEqual
({
repo
,
newName
:
repo
.
sanitizedName
,
targetNamespace
:
defaultTargetNamespace
,
});
});
const
{
calls
}
=
fetchImport
.
mock
;
expect
(
calls
).
toHaveLength
(
1
);
expect
(
calls
[
0
][
1
]).
toBe
(
repo
.
importSource
.
id
);
});
});
spec/frontend/import_projects/store/actions_spec.js
View file @
344cfc45
...
...
@@ -12,41 +12,76 @@ import {
RECEIVE_IMPORT_SUCCESS
,
RECEIVE_IMPORT_ERROR
,
RECEIVE_JOBS_SUCCESS
,
REQUEST_NAMESPACES
,
RECEIVE_NAMESPACES_SUCCESS
,
RECEIVE_NAMESPACES_ERROR
,
}
from
'
~/import_projects/store/mutation_types
'
;
import
{
fetchRepos
,
fetchImport
,
receiveJobsSuccess
,
fetchJobs
,
clearJobsEtagPoll
,
stopJobsPolling
,
}
from
'
~/import_projects/store/actions
'
;
import
actionsFactory
from
'
~/import_projects/store/actions
'
;
import
{
getImportTarget
}
from
'
~/import_projects/store/getters
'
;
import
state
from
'
~/import_projects/store/state
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
jest
.
mock
(
'
~/flash
'
);
const
MOCK_ENDPOINT
=
`
${
TEST_HOST
}
/endpoint.json`
;
const
{
clearJobsEtagPoll
,
stopJobsPolling
,
importAll
,
fetchRepos
,
fetchImport
,
fetchJobs
,
fetchNamespaces
,
}
=
actionsFactory
({
endpoints
:
{
reposPath
:
MOCK_ENDPOINT
,
importPath
:
MOCK_ENDPOINT
,
jobsPath
:
MOCK_ENDPOINT
,
namespacesPath
:
MOCK_ENDPOINT
,
},
});
describe
(
'
import_projects store actions
'
,
()
=>
{
let
localState
;
const
repos
=
[{
id
:
1
},
{
id
:
2
}];
const
importPayload
=
{
newName
:
'
newName
'
,
targetNamespace
:
'
targetNamespace
'
,
repo
:
{
id
:
1
}
};
const
importRepoId
=
1
;
const
otherImportRepoId
=
2
;
const
defaultTargetNamespace
=
'
default
'
;
const
sanitizedName
=
'
sanitizedName
'
;
const
defaultImportTarget
=
{
newName
:
sanitizedName
,
targetNamespace
:
defaultTargetNamespace
};
beforeEach
(()
=>
{
localState
=
state
();
localState
=
{
...
state
(),
defaultTargetNamespace
,
repositories
:
[
{
importSource
:
{
id
:
importRepoId
,
sanitizedName
},
importStatus
:
STATUSES
.
NONE
},
{
importSource
:
{
id
:
otherImportRepoId
,
sanitizedName
:
'
s2
'
},
importStatus
:
STATUSES
.
NONE
,
},
{
importSource
:
{
id
:
3
,
sanitizedName
:
'
s3
'
,
incompatible
:
true
},
importStatus
:
STATUSES
.
NONE
,
},
],
};
localState
.
getImportTarget
=
getImportTarget
(
localState
);
});
describe
(
'
fetchRepos
'
,
()
=>
{
let
mock
;
const
payload
=
{
imported_projects
:
[{}],
provider_repos
:
[{}]
,
namespaces
:
[{}]
};
const
payload
=
{
imported_projects
:
[{}],
provider_repos
:
[{}]
};
beforeEach
(()
=>
{
localState
.
reposPath
=
`
${
TEST_HOST
}
/endpoint.json`
;
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
mock
.
restore
());
it
(
'
dispatches stopJobsPolling actions and commits REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request
'
,
()
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
200
,
payload
);
mock
.
onGet
(
MOCK_ENDPOINT
).
reply
(
200
,
payload
);
return
testAction
(
fetchRepos
,
...
...
@@ -64,7 +99,7 @@ describe('import_projects store actions', () => {
});
it
(
'
dispatches stopJobsPolling action and commits REQUEST_REPOS, RECEIVE_REPOS_ERROR mutations on an unsuccessful request
'
,
()
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
500
);
mock
.
onGet
(
MOCK_ENDPOINT
).
reply
(
500
);
return
testAction
(
fetchRepos
,
...
...
@@ -104,7 +139,6 @@ describe('import_projects store actions', () => {
let
mock
;
beforeEach
(()
=>
{
localState
.
importPath
=
`
${
TEST_HOST
}
/endpoint.json`
;
mock
=
new
MockAdapter
(
axios
);
});
...
...
@@ -112,15 +146,17 @@ describe('import_projects store actions', () => {
it
(
'
commits REQUEST_IMPORT and REQUEST_IMPORT_SUCCESS mutations on a successful request
'
,
()
=>
{
const
importedProject
=
{
name
:
'
imported/project
'
};
const
importRepoId
=
importPayload
.
repo
.
id
;
mock
.
onPost
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
200
,
importedProject
);
mock
.
onPost
(
MOCK_ENDPOINT
).
reply
(
200
,
importedProject
);
return
testAction
(
fetchImport
,
import
Payloa
d
,
import
RepoI
d
,
localState
,
[
{
type
:
REQUEST_IMPORT
,
payload
:
importRepoId
},
{
type
:
REQUEST_IMPORT
,
payload
:
{
repoId
:
importRepoId
,
importTarget
:
defaultImportTarget
},
},
{
type
:
RECEIVE_IMPORT_SUCCESS
,
payload
:
{
...
...
@@ -134,15 +170,18 @@ describe('import_projects store actions', () => {
});
it
(
'
commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows generic error message on an unsuccessful request
'
,
async
()
=>
{
mock
.
onPost
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
500
);
mock
.
onPost
(
MOCK_ENDPOINT
).
reply
(
500
);
await
testAction
(
fetchImport
,
import
Payloa
d
,
import
RepoI
d
,
localState
,
[
{
type
:
REQUEST_IMPORT
,
payload
:
importPayload
.
repo
.
id
},
{
type
:
RECEIVE_IMPORT_ERROR
,
payload
:
importPayload
.
repo
.
id
},
{
type
:
REQUEST_IMPORT
,
payload
:
{
repoId
:
importRepoId
,
importTarget
:
defaultImportTarget
},
},
{
type
:
RECEIVE_IMPORT_ERROR
,
payload
:
importRepoId
},
],
[],
);
...
...
@@ -152,15 +191,18 @@ describe('import_projects store actions', () => {
it
(
'
commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows detailed error message on an unsuccessful request with errors fields in response
'
,
async
()
=>
{
const
ERROR_MESSAGE
=
'
dummy
'
;
mock
.
onPost
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
500
,
{
errors
:
ERROR_MESSAGE
});
mock
.
onPost
(
MOCK_ENDPOINT
).
reply
(
500
,
{
errors
:
ERROR_MESSAGE
});
await
testAction
(
fetchImport
,
import
Payloa
d
,
import
RepoI
d
,
localState
,
[
{
type
:
REQUEST_IMPORT
,
payload
:
importPayload
.
repo
.
id
},
{
type
:
RECEIVE_IMPORT_ERROR
,
payload
:
importPayload
.
repo
.
id
},
{
type
:
REQUEST_IMPORT
,
payload
:
{
repoId
:
importRepoId
,
importTarget
:
defaultImportTarget
},
},
{
type
:
RECEIVE_IMPORT_ERROR
,
payload
:
importRepoId
},
],
[],
);
...
...
@@ -169,24 +211,11 @@ describe('import_projects store actions', () => {
});
});
describe
(
'
receiveJobsSuccess
'
,
()
=>
{
it
(
`commits
${
RECEIVE_JOBS_SUCCESS
}
mutation`
,
()
=>
{
return
testAction
(
receiveJobsSuccess
,
repos
,
localState
,
[{
type
:
RECEIVE_JOBS_SUCCESS
,
payload
:
repos
}],
[],
);
});
});
describe
(
'
fetchJobs
'
,
()
=>
{
let
mock
;
const
updatedProjects
=
[{
name
:
'
imported/project
'
},
{
name
:
'
provider/repo
'
}];
beforeEach
(()
=>
{
localState
.
jobsPath
=
`
${
TEST_HOST
}
/endpoint.json`
;
mock
=
new
MockAdapter
(
axios
);
});
...
...
@@ -198,7 +227,7 @@ describe('import_projects store actions', () => {
afterEach
(()
=>
mock
.
restore
());
it
(
'
commits RECEIVE_JOBS_SUCCESS mutation on a successful request
'
,
async
()
=>
{
mock
.
onGet
(
`
${
TEST_HOST
}
/endpoint.json`
).
reply
(
200
,
updatedProjects
);
mock
.
onGet
(
MOCK_ENDPOINT
).
reply
(
200
,
updatedProjects
);
await
testAction
(
fetchJobs
,
...
...
@@ -237,4 +266,62 @@ describe('import_projects store actions', () => {
});
});
});
describe
(
'
fetchNamespaces
'
,
()
=>
{
let
mock
;
const
namespaces
=
[{
full_name
:
'
test/ns1
'
},
{
full_name
:
'
test_ns2
'
}];
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
mock
.
restore
());
it
(
'
commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_SUCCESS on success
'
,
async
()
=>
{
mock
.
onGet
(
MOCK_ENDPOINT
).
reply
(
200
,
namespaces
);
await
testAction
(
fetchNamespaces
,
null
,
localState
,
[
{
type
:
REQUEST_NAMESPACES
},
{
type
:
RECEIVE_NAMESPACES_SUCCESS
,
payload
:
convertObjectPropsToCamelCase
(
namespaces
,
{
deep
:
true
}),
},
],
[],
);
});
it
(
'
commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_ERROR and shows generic error message on an unsuccessful request
'
,
async
()
=>
{
mock
.
onGet
(
MOCK_ENDPOINT
).
reply
(
500
);
await
testAction
(
fetchNamespaces
,
null
,
localState
,
[{
type
:
REQUEST_NAMESPACES
},
{
type
:
RECEIVE_NAMESPACES_ERROR
}],
[],
);
expect
(
createFlash
).
toHaveBeenCalledWith
(
'
Requesting namespaces failed
'
);
});
});
describe
(
'
importAll
'
,
()
=>
{
it
(
'
dispatches multiple fetchImport actions
'
,
async
()
=>
{
await
testAction
(
importAll
,
null
,
localState
,
[],
[
{
type
:
'
fetchImport
'
,
payload
:
importRepoId
},
{
type
:
'
fetchImport
'
,
payload
:
otherImportRepoId
},
],
);
});
});
});
spec/frontend/import_projects/store/getters_spec.js
View file @
344cfc45
import
{
namespaceSelectOptions
,
isLoading
,
isImportingAnyRepo
,
hasProviderRepos
,
hasIncompatibleRepos
,
hasImportedProjects
,
hasImportableRepos
,
getImportTarget
,
}
from
'
~/import_projects/store/getters
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
import
state
from
'
~/import_projects/store/state
'
;
const
IMPORTED_REPO
=
{
importSource
:
{},
importedProject
:
{
fullPath
:
'
some/path
'
},
};
const
IMPORTABLE_REPO
=
{
importSource
:
{
id
:
'
some-id
'
,
sanitizedName
:
'
sanitized
'
},
importedProject
:
null
,
importStatus
:
STATUSES
.
NONE
,
};
const
INCOMPATIBLE_REPO
=
{
importSource
:
{
incompatible
:
true
},
};
describe
(
'
import_projects store getters
'
,
()
=>
{
let
localState
;
...
...
@@ -14,85 +30,87 @@ describe('import_projects store getters', () => {
localState
=
state
();
});
describe
(
'
namespaceSelectOptions
'
,
()
=>
{
const
namespaces
=
[{
fullPath
:
'
namespace-0
'
},
{
fullPath
:
'
namespace-1
'
}];
const
defaultTargetNamespace
=
'
current-user
'
;
it
(
'
returns an options array with a "Users" and "Groups" optgroups
'
,
()
=>
{
localState
.
namespaces
=
namespaces
;
localState
.
defaultTargetNamespace
=
defaultTargetNamespace
;
const
optionsArray
=
namespaceSelectOptions
(
localState
);
const
groupsGroup
=
optionsArray
[
0
];
const
usersGroup
=
optionsArray
[
1
];
expect
(
groupsGroup
.
text
).
toBe
(
'
Groups
'
);
expect
(
usersGroup
.
text
).
toBe
(
'
Users
'
);
groupsGroup
.
children
.
forEach
((
child
,
index
)
=>
{
expect
(
child
.
id
).
toBe
(
namespaces
[
index
].
fullPath
);
expect
(
child
.
text
).
toBe
(
namespaces
[
index
].
fullPath
);
it
.
each
`
isLoadingRepos | isLoadingNamespaces | isLoadingValue
${
false
}
|
${
false
}
|
${
false
}
${
true
}
|
${
false
}
|
${
true
}
${
false
}
|
${
true
}
|
${
true
}
${
true
}
|
${
true
}
|
${
true
}
`
(
'
isLoading returns $isLoadingValue when isLoadingRepos is $isLoadingRepos and isLoadingNamespaces is $isLoadingNamespaces
'
,
({
isLoadingRepos
,
isLoadingNamespaces
,
isLoadingValue
})
=>
{
Object
.
assign
(
localState
,
{
isLoadingRepos
,
isLoadingNamespaces
,
});
expect
(
usersGroup
.
children
.
length
).
toBe
(
1
);
expect
(
usersGroup
.
children
[
0
].
id
).
toBe
(
defaultTargetNamespace
);
expect
(
usersGroup
.
children
[
0
].
text
).
toBe
(
defaultTargetNamespace
);
});
});
describe
(
'
isImportingAnyRepo
'
,
()
=>
{
it
(
'
returns true if there are any reposBeingImported
'
,
()
=>
{
localState
.
reposBeingImported
=
new
Array
(
1
);
expect
(
isImportingAnyRepo
(
localState
)).
toBe
(
true
);
});
expect
(
isLoading
(
localState
)).
toBe
(
isLoadingValue
);
},
);
it
.
each
`
importStatus | value
${
STATUSES
.
NONE
}
|
${
false
}
${
STATUSES
.
SCHEDULING
}
|
${
true
}
${
STATUSES
.
SCHEDULED
}
|
${
true
}
${
STATUSES
.
STARTED
}
|
${
true
}
${
STATUSES
.
FINISHED
}
|
${
false
}
`
(
'
isImportingAnyRepo returns $value when repo with $importStatus status is available
'
,
({
importStatus
,
value
})
=>
{
localState
.
repositories
=
[{
importStatus
}];
expect
(
isImportingAnyRepo
(
localState
)).
toBe
(
value
);
},
);
it
(
'
returns false if there are no reposBeingImported
'
,
()
=>
{
localState
.
reposBeingImported
=
[];
expect
(
isImportingAnyRepo
(
localState
)).
toBe
(
false
);
});
});
describe
(
'
hasProviderRepos
'
,
()
=>
{
it
(
'
returns true if there are any providerRepos
'
,
()
=>
{
localState
.
providerRepos
=
new
Array
(
1
);
describe
(
'
hasIncompatibleRepos
'
,
()
=>
{
it
(
'
returns true if there are any incompatible projects
'
,
()
=>
{
localState
.
repositories
=
[
IMPORTABLE_REPO
,
IMPORTED_REPO
,
INCOMPATIBLE_REPO
];
expect
(
has
Provider
Repos
(
localState
)).
toBe
(
true
);
expect
(
has
Incompatible
Repos
(
localState
)).
toBe
(
true
);
});
it
(
'
returns false if there are no
providerRepo
s
'
,
()
=>
{
localState
.
providerRepos
=
[
];
it
(
'
returns false if there are no
incompatible project
s
'
,
()
=>
{
localState
.
repositories
=
[
IMPORTABLE_REPO
,
IMPORTED_REPO
];
expect
(
has
Provider
Repos
(
localState
)).
toBe
(
false
);
expect
(
has
Incompatible
Repos
(
localState
)).
toBe
(
false
);
});
});
describe
(
'
hasImport
edProject
s
'
,
()
=>
{
it
(
'
returns true if there are any import
edProjects
'
,
()
=>
{
localState
.
importedProjects
=
new
Array
(
1
)
;
describe
(
'
hasImport
ableRepo
s
'
,
()
=>
{
it
(
'
returns true if there are any import
able projects
'
,
()
=>
{
localState
.
repositories
=
[
IMPORTABLE_REPO
,
IMPORTED_REPO
,
INCOMPATIBLE_REPO
]
;
expect
(
hasImport
edProject
s
(
localState
)).
toBe
(
true
);
expect
(
hasImport
ableRepo
s
(
localState
)).
toBe
(
true
);
});
it
(
'
returns false if there are no import
edP
rojects
'
,
()
=>
{
localState
.
importedProjects
=
[
];
it
(
'
returns false if there are no import
able p
rojects
'
,
()
=>
{
localState
.
repositories
=
[
IMPORTED_REPO
,
INCOMPATIBLE_REPO
];
expect
(
hasImport
edProject
s
(
localState
)).
toBe
(
false
);
expect
(
hasImport
ableRepo
s
(
localState
)).
toBe
(
false
);
});
});
describe
(
'
hasIncompatibleRepos
'
,
()
=>
{
it
(
'
returns true if there are any incompatibleProjects
'
,
()
=>
{
localState
.
incompatibleRepos
=
new
Array
(
1
);
describe
(
'
getImportTarget
'
,
()
=>
{
it
(
'
returns default value if no custom target available
'
,
()
=>
{
localState
.
defaultTargetNamespace
=
'
default
'
;
localState
.
repositories
=
[
IMPORTABLE_REPO
];
expect
(
hasIncompatibleRepos
(
localState
)).
toBe
(
true
);
expect
(
getImportTarget
(
localState
)(
IMPORTABLE_REPO
.
importSource
.
id
)).
toStrictEqual
({
newName
:
IMPORTABLE_REPO
.
importSource
.
sanitizedName
,
targetNamespace
:
localState
.
defaultTargetNamespace
,
});
});
it
(
'
returns false if there are no incompatibleProjects
'
,
()
=>
{
localState
.
incompatibleRepos
=
[];
it
(
'
returns custom import target if available
'
,
()
=>
{
const
fakeTarget
=
{
newName
:
'
something
'
,
targetNamespace
:
'
ns
'
};
localState
.
repositories
=
[
IMPORTABLE_REPO
];
localState
.
customImportTargets
[
IMPORTABLE_REPO
.
importSource
.
id
]
=
fakeTarget
;
expect
(
hasIncompatibleRepos
(
localState
)).
toBe
(
false
);
expect
(
getImportTarget
(
localState
)(
IMPORTABLE_REPO
.
importSource
.
id
)).
toStrictEqual
(
fakeTarget
,
);
});
});
});
spec/frontend/import_projects/store/mutations_spec.js
View file @
344cfc45
import
*
as
types
from
'
~/import_projects/store/mutation_types
'
;
import
mutations
from
'
~/import_projects/store/mutations
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
describe
(
'
import_projects store mutations
'
,
()
=>
{
describe
(
`
${
types
.
RECEIVE_IMPORT_SUCCESS
}
`
,
()
=>
{
it
(
'
removes repoId from reposBeingImported and providerRepos, adds to importedProjects
'
,
()
=>
{
const
repoId
=
1
;
const
state
=
{
reposBeingImported
:
[
repoId
],
providerRepos
:
[{
id
:
repoId
}],
let
state
;
const
SOURCE_PROJECT
=
{
id
:
1
,
full_name
:
'
full/name
'
,
sanitized_name
:
'
name
'
,
provider_link
:
'
https://demo.link/full/name
'
,
};
const
IMPORTED_PROJECT
=
{
name
:
'
demo
'
,
importSource
:
'
something
'
,
providerLink
:
'
custom-link
'
,
importStatus
:
'
status
'
,
fullName
:
'
fullName
'
,
};
describe
(
`
${
types
.
SET_FILTER
}
`
,
()
=>
{
it
(
'
overwrites current filter value
'
,
()
=>
{
state
=
{
filter
:
'
some-value
'
};
const
NEW_VALUE
=
'
new-value
'
;
mutations
[
types
.
SET_FILTER
](
state
,
NEW_VALUE
);
expect
(
state
.
filter
).
toBe
(
NEW_VALUE
);
});
});
describe
(
`
${
types
.
REQUEST_REPOS
}
`
,
()
=>
{
it
(
'
sets repos loading flag to true
'
,
()
=>
{
state
=
{};
mutations
[
types
.
REQUEST_REPOS
](
state
);
expect
(
state
.
isLoadingRepos
).
toBe
(
true
);
});
});
describe
(
`
${
types
.
RECEIVE_REPOS_SUCCESS
}
`
,
()
=>
{
describe
(
'
for imported projects
'
,
()
=>
{
const
response
=
{
importedProjects
:
[
IMPORTED_PROJECT
],
providerRepos
:
[],
};
it
(
'
picks import status from response
'
,
()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
IMPORTED_PROJECT
.
importStatus
);
});
it
(
'
recreates importSource from response
'
,
()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
expect
(
state
.
repositories
[
0
].
importSource
).
toStrictEqual
(
expect
.
objectContaining
({
fullName
:
IMPORTED_PROJECT
.
importSource
,
sanitizedName
:
IMPORTED_PROJECT
.
name
,
providerLink
:
IMPORTED_PROJECT
.
providerLink
,
}),
);
});
it
(
'
passes project to importProject
'
,
()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
expect
(
IMPORTED_PROJECT
).
toStrictEqual
(
expect
.
objectContaining
(
state
.
repositories
[
0
].
importedProject
),
);
});
});
describe
(
'
for importable projects
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{};
const
response
=
{
importedProjects
:
[],
providerRepos
:
[
SOURCE_PROJECT
],
};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
});
it
(
'
sets import status to none
'
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
STATUSES
.
NONE
);
});
it
(
'
sets importSource to project
'
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importSource
).
toBe
(
SOURCE_PROJECT
);
});
});
describe
(
'
for incompatible projects
'
,
()
=>
{
const
response
=
{
importedProjects
:
[],
providerRepos
:
[],
incompatibleRepos
:
[
SOURCE_PROJECT
],
};
const
importedProject
=
{
id
:
repoId
};
mutations
[
types
.
RECEIVE_IMPORT_SUCCESS
](
state
,
{
importedProject
,
repoId
});
beforeEach
(()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
});
expect
(
state
.
reposBeingImported
.
includes
(
repoId
)).
toBe
(
false
);
expect
(
state
.
providerRepos
.
some
(
repo
=>
repo
.
id
===
repoId
)).
toBe
(
false
);
expect
(
state
.
importedProjects
.
some
(
repo
=>
repo
.
id
===
repoId
)).
toBe
(
true
);
it
(
'
sets incompatible flag
'
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importSource
.
incompatible
).
toBe
(
true
);
});
it
(
'
sets importSource to project
'
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importSource
).
toStrictEqual
(
expect
.
objectContaining
(
SOURCE_PROJECT
),
);
});
});
it
(
'
sets repos loading flag to false
'
,
()
=>
{
const
response
=
{
importedProjects
:
[],
providerRepos
:
[],
};
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_SUCCESS
](
state
,
response
);
expect
(
state
.
isLoadingRepos
).
toBe
(
false
);
});
});
describe
(
`
${
types
.
RECEIVE_REPOS_ERROR
}
`
,
()
=>
{
it
(
'
sets repos loading flag to false
'
,
()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_REPOS_ERROR
](
state
);
expect
(
state
.
isLoadingRepos
).
toBe
(
false
);
});
});
describe
(
`
${
types
.
REQUEST_IMPORT
}
`
,
()
=>
{
beforeEach
(()
=>
{
const
REPO_ID
=
1
;
const
importTarget
=
{
targetNamespace
:
'
ns
'
,
newName
:
'
name
'
};
state
=
{
repositories
:
[{
importSource
:
{
id
:
REPO_ID
}
}]
};
mutations
[
types
.
REQUEST_IMPORT
](
state
,
{
repoId
:
REPO_ID
,
importTarget
});
});
it
(
`sets status to
${
STATUSES
.
SCHEDULING
}
`
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
STATUSES
.
SCHEDULING
);
});
});
describe
(
`
${
types
.
RECEIVE_IMPORT_SUCCESS
}
`
,
()
=>
{
beforeEach
(()
=>
{
const
REPO_ID
=
1
;
state
=
{
repositories
:
[{
importSource
:
{
id
:
REPO_ID
}
}]
};
mutations
[
types
.
RECEIVE_IMPORT_SUCCESS
](
state
,
{
repoId
:
REPO_ID
,
importedProject
:
IMPORTED_PROJECT
,
});
});
it
(
'
sets import status
'
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
IMPORTED_PROJECT
.
importStatus
);
});
it
(
'
sets imported project
'
,
()
=>
{
expect
(
IMPORTED_PROJECT
).
toStrictEqual
(
expect
.
objectContaining
(
state
.
repositories
[
0
].
importedProject
),
);
});
});
describe
(
`
${
types
.
RECEIVE_IMPORT_ERROR
}
`
,
()
=>
{
beforeEach
(()
=>
{
const
REPO_ID
=
1
;
state
=
{
repositories
:
[{
importSource
:
{
id
:
REPO_ID
}
}]
};
mutations
[
types
.
RECEIVE_IMPORT_ERROR
](
state
,
REPO_ID
);
});
it
(
`resets import status to
${
STATUSES
.
NONE
}
`
,
()
=>
{
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
STATUSES
.
NONE
);
});
});
describe
(
`
${
types
.
RECEIVE_JOBS_SUCCESS
}
`
,
()
=>
{
it
(
'
updates import
Status of existing importedProjects
'
,
()
=>
{
it
(
'
updates import
status of existing project
'
,
()
=>
{
const
repoId
=
1
;
const
state
=
{
importedProjects
:
[{
id
:
repoId
,
importStatus
:
'
started
'
}]
};
const
updatedProjects
=
[{
id
:
repoId
,
importStatus
:
'
finished
'
}];
state
=
{
repositories
:
[{
importedProject
:
{
id
:
repoId
},
importStatus
:
STATUSES
.
STARTED
}],
};
const
updatedProjects
=
[{
id
:
repoId
,
importStatus
:
STATUSES
.
FINISHED
}];
mutations
[
types
.
RECEIVE_JOBS_SUCCESS
](
state
,
updatedProjects
);
expect
(
state
.
importedProjects
[
0
].
importStatus
).
toBe
(
updatedProjects
[
0
].
importStatus
);
expect
(
state
.
repositories
[
0
].
importStatus
).
toBe
(
updatedProjects
[
0
].
importStatus
);
});
});
describe
(
`
${
types
.
REQUEST_NAMESPACES
}
`
,
()
=>
{
it
(
'
sets namespaces loading flag to true
'
,
()
=>
{
state
=
{};
mutations
[
types
.
REQUEST_NAMESPACES
](
state
);
expect
(
state
.
isLoadingNamespaces
).
toBe
(
true
);
});
});
describe
(
`
${
types
.
RECEIVE_NAMESPACES_SUCCESS
}
`
,
()
=>
{
const
response
=
[{
fullPath
:
'
some/path
'
}];
beforeEach
(()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_NAMESPACES_SUCCESS
](
state
,
response
);
});
it
(
'
stores namespaces to state
'
,
()
=>
{
expect
(
state
.
namespaces
).
toStrictEqual
(
response
);
});
it
(
'
sets namespaces loading flag to false
'
,
()
=>
{
expect
(
state
.
isLoadingNamespaces
).
toBe
(
false
);
});
});
describe
(
`
${
types
.
RECEIVE_NAMESPACES_ERROR
}
`
,
()
=>
{
it
(
'
sets namespaces loading flag to false
'
,
()
=>
{
state
=
{};
mutations
[
types
.
RECEIVE_NAMESPACES_ERROR
](
state
);
expect
(
state
.
isLoadingNamespaces
).
toBe
(
false
);
});
});
describe
(
`
${
types
.
SET_IMPORT_TARGET
}
`
,
()
=>
{
const
PROJECT
=
{
id
:
2
,
sanitizedName
:
'
sanitizedName
'
,
};
it
(
'
stores custom target if it differs from defaults
'
,
()
=>
{
state
=
{
customImportTargets
:
{},
repositories
:
[{
importSource
:
PROJECT
}]
};
const
importTarget
=
{
targetNamespace
:
'
ns
'
,
newName
:
'
name
'
};
mutations
[
types
.
SET_IMPORT_TARGET
](
state
,
{
repoId
:
PROJECT
.
id
,
importTarget
});
expect
(
state
.
customImportTargets
[
PROJECT
.
id
]).
toBe
(
importTarget
);
});
it
(
'
removes custom target if it is equal to defaults
'
,
()
=>
{
const
importTarget
=
{
targetNamespace
:
'
ns
'
,
newName
:
'
name
'
};
state
=
{
defaultTargetNamespace
:
'
default
'
,
customImportTargets
:
{
[
PROJECT
.
id
]:
importTarget
,
},
repositories
:
[{
importSource
:
PROJECT
}],
};
mutations
[
types
.
SET_IMPORT_TARGET
](
state
,
{
repoId
:
PROJECT
.
id
,
importTarget
:
{
targetNamespace
:
state
.
defaultTargetNamespace
,
newName
:
PROJECT
.
sanitizedName
,
},
});
expect
(
state
.
customImportTargets
[
SOURCE_PROJECT
.
id
]).
toBeUndefined
();
});
});
});
spec/frontend/import_projects/utils_spec.js
0 → 100644
View file @
344cfc45
import
{
isProjectImportable
}
from
'
~/import_projects/utils
'
;
import
{
STATUSES
}
from
'
~/import_projects/constants
'
;
describe
(
'
import_projects utils
'
,
()
=>
{
describe
(
'
isProjectImportable
'
,
()
=>
{
it
.
each
`
status | result
${
STATUSES
.
FINISHED
}
|
${
false
}
${
STATUSES
.
FAILED
}
|
${
false
}
${
STATUSES
.
SCHEDULED
}
|
${
false
}
${
STATUSES
.
STARTED
}
|
${
false
}
${
STATUSES
.
NONE
}
|
${
true
}
${
STATUSES
.
SCHEDULING
}
|
${
false
}
`
(
'
returns $result when project is compatible and status is $status
'
,
({
status
,
result
})
=>
{
expect
(
isProjectImportable
({
importStatus
:
status
,
importSource
:
{
incompatible
:
false
},
}),
).
toBe
(
result
);
});
it
(
'
returns false if project is not compatible
'
,
()
=>
{
expect
(
isProjectImportable
({
importStatus
:
STATUSES
.
NONE
,
importSource
:
{
incompatible
:
true
},
}),
).
toBe
(
false
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment