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
ef2643f7
Commit
ef2643f7
authored
Dec 10, 2020
by
Illya Klymov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement displaying source groups for bulk import
MVC version of group import without actual import being run
parent
a30bdc7f
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
920 additions
and
0 deletions
+920
-0
app/assets/javascripts/import_entities/import_groups/components/import_table.vue
...import_entities/import_groups/components/import_table.vue
+78
-0
app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
...rt_entities/import_groups/components/import_table_row.vue
+105
-0
app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
...s/import_entities/import_groups/graphql/client_factory.js
+66
-0
app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
.../fragments/bulk_import_source_group_item.fragment.graphql
+8
-0
app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
...rt_groups/graphql/mutations/import_group.mutation.graphql
+3
-0
app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql
...rt_groups/graphql/mutations/set_new_name.mutation.graphql
+3
-0
app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql
...s/graphql/mutations/set_target_namespace.mutation.graphql
+3
-0
app/assets/javascripts/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql
...groups/graphql/queries/available_namespaces.query.graphql
+6
-0
app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql
...s/graphql/queries/bulk_import_source_groups.query.graphql
+7
-0
app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
...s/import_groups/graphql/services/source_groups_manager.js
+45
-0
app/assets/javascripts/import_entities/import_groups/index.js
...assets/javascripts/import_entities/import_groups/index.js
+31
-0
app/assets/javascripts/pages/import/bulk_imports/index.js
app/assets/javascripts/pages/import/bulk_imports/index.js
+4
-0
app/assets/stylesheets/page_bundles/import.scss
app/assets/stylesheets/page_bundles/import.scss
+24
-0
app/views/import/bulk_imports/status.html.haml
app/views/import/bulk_imports/status.html.haml
+4
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
...ntities/import_groups/components/import_table_row_spec.js
+112
-0
spec/frontend/import_entities/import_groups/components/import_table_spec.js
...rt_entities/import_groups/components/import_table_spec.js
+103
-0
spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
...ort_entities/import_groups/graphql/client_factory_spec.js
+179
-0
spec/frontend/import_entities/import_groups/graphql/fixtures.js
...rontend/import_entities/import_groups/graphql/fixtures.js
+51
-0
spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
...ort_groups/graphql/services/source_groups_manager_spec.js
+82
-0
No files found.
app/assets/javascripts/import_entities/import_groups/components/import_table.vue
0 → 100644
View file @
ef2643f7
<
script
>
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
bulkImportSourceGroupsQuery
from
'
../graphql/queries/bulk_import_source_groups.query.graphql
'
;
import
availableNamespacesQuery
from
'
../graphql/queries/available_namespaces.query.graphql
'
;
import
setTargetNamespaceMutation
from
'
../graphql/mutations/set_target_namespace.mutation.graphql
'
;
import
setNewNameMutation
from
'
../graphql/mutations/set_new_name.mutation.graphql
'
;
import
importGroupMutation
from
'
../graphql/mutations/import_group.mutation.graphql
'
;
import
ImportTableRow
from
'
./import_table_row.vue
'
;
const
mapApolloMutations
=
mutations
=>
Object
.
fromEntries
(
Object
.
entries
(
mutations
).
map
(([
key
,
mutation
])
=>
[
key
,
function
mutate
(
config
)
{
return
this
.
$apollo
.
mutate
({
mutation
,
...
config
,
});
},
]),
);
export
default
{
components
:
{
GlLoadingIcon
,
ImportTableRow
,
},
apollo
:
{
bulkImportSourceGroups
:
bulkImportSourceGroupsQuery
,
availableNamespaces
:
availableNamespacesQuery
,
},
methods
:
{
...
mapApolloMutations
({
setTargetNamespace
:
setTargetNamespaceMutation
,
setNewName
:
setNewNameMutation
,
importGroup
:
importGroupMutation
,
}),
},
};
</
script
>
<
template
>
<div>
<gl-loading-icon
v-if=
"$apollo.loading"
size=
"md"
class=
"gl-mt-5"
/>
<div
v-else-if=
"bulkImportSourceGroups.length"
>
<table
class=
"gl-w-full"
>
<thead
class=
"gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1"
>
<th
class=
"gl-py-4 import-jobs-from-col"
>
{{
s__
(
'
BulkImport|From source group
'
)
}}
</th>
<th
class=
"gl-py-4 import-jobs-to-col"
>
{{
s__
(
'
BulkImport|To new group
'
)
}}
</th>
<th
class=
"gl-py-4 import-jobs-status-col"
>
{{
__
(
'
Status
'
)
}}
</th>
<th
class=
"gl-py-4 import-jobs-cta-col"
></th>
</thead>
<tbody>
<template
v-for=
"group in bulkImportSourceGroups"
>
<import-table-row
:key=
"group.id"
:group=
"group"
:available-namespaces=
"availableNamespaces"
@
update-target-namespace=
"
setTargetNamespace(
{
variables: { sourceGroupId: group.id, targetNamespace: $event },
})
"
@update-new-name="
setNewName({
variables: { sourceGroupId: group.id, newName: $event },
})
"
@import-group="importGroup({ variables: { sourceGroupId: group.id } })"
/>
</
template
>
</tbody>
</table>
</div>
</div>
</template>
app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
0 → 100644
View file @
ef2643f7
<
script
>
import
{
GlButton
,
GlIcon
,
GlLink
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
Select2Select
from
'
~/vue_shared/components/select2_select.vue
'
;
import
ImportStatus
from
'
../../components/import_status.vue
'
;
import
{
STATUSES
}
from
'
../../constants
'
;
export
default
{
components
:
{
Select2Select
,
ImportStatus
,
GlButton
,
GlLink
,
GlIcon
,
GlFormInput
,
},
props
:
{
group
:
{
type
:
Object
,
required
:
true
,
},
availableNamespaces
:
{
type
:
Array
,
required
:
true
,
},
},
computed
:
{
isDisabled
()
{
return
this
.
group
.
status
!==
STATUSES
.
NONE
;
},
isFinished
()
{
return
this
.
group
.
status
===
STATUSES
.
FINISHED
;
},
select2Options
()
{
return
{
data
:
this
.
availableNamespaces
.
map
(
namespace
=>
({
id
:
namespace
.
full_path
,
text
:
namespace
.
full_path
,
})),
};
},
},
methods
:
{
getPath
(
group
)
{
return
`
${
group
.
import_target
.
target_namespace
}
/
${
group
.
import_target
.
new_name
}
`
;
},
getFullPath
(
group
)
{
return
`
${
gon
.
relative_url_root
||
''
}
/
${
this
.
getPath
(
group
)}
`
;
},
},
};
</
script
>
<
template
>
<tr
class=
"gl-border-gray-200 gl-border-0 gl-border-b-1"
>
<td
class=
"gl-p-4"
>
<a
:href=
"group.web_url"
rel=
"noreferrer noopener"
target=
"_blank"
>
{{
group
.
full_path
}}
<gl-icon
name=
"external-link"
/>
</a>
</td>
<td
class=
"gl-p-4"
>
<gl-link
v-if=
"isFinished"
:href=
"getFullPath(group)"
>
{{
getPath
(
group
)
}}
</gl-link>
<div
v-else
class=
"import-entities-target-select gl-display-flex gl-align-items-stretch"
:class=
"
{
disabled: isDisabled,
}"
>
<select2-select
:disabled=
"isDisabled"
:options=
"select2Options"
:value=
"group.import_target.target_namespace"
@
input=
"$emit('update-target-namespace', $event)"
/>
<div
class=
"import-entities-target-select-separator gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
>
/
</div>
<gl-form-input
class=
"gl-rounded-top-left-none gl-rounded-bottom-left-none"
:disabled=
"isDisabled"
:value=
"group.import_target.new_name"
@
input=
"$emit('update-new-name', $event)"
/>
</div>
</td>
<td
class=
"gl-p-4 gl-white-space-nowrap"
>
<import-status
:status=
"group.status"
/>
</td>
<td
class=
"gl-p-4"
>
<gl-button
v-if=
"!isDisabled"
variant=
"success"
category=
"secondary"
@
click=
"$emit('import-group')"
>
{{
__
(
'
Import
'
)
}}
</gl-button
>
</td>
</tr>
</
template
>
app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
0 → 100644
View file @
ef2643f7
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
{
STATUSES
}
from
'
../../constants
'
;
import
availableNamespacesQuery
from
'
./queries/available_namespaces.query.graphql
'
;
import
{
SourceGroupsManager
}
from
'
./services/source_groups_manager
'
;
export
const
clientTypenames
=
{
BulkImportSourceGroup
:
'
ClientBulkImportSourceGroup
'
,
AvailableNamespace
:
'
ClientAvailableNamespace
'
,
};
export
function
createResolvers
({
endpoints
})
{
return
{
Query
:
{
async
bulkImportSourceGroups
(
_
,
__
,
{
client
})
{
const
{
data
:
{
availableNamespaces
},
}
=
await
client
.
query
({
query
:
availableNamespacesQuery
});
return
axios
.
get
(
endpoints
.
status
).
then
(({
data
})
=>
{
return
data
.
importable_data
.
map
(
group
=>
({
__typename
:
clientTypenames
.
BulkImportSourceGroup
,
...
group
,
status
:
STATUSES
.
NONE
,
import_target
:
{
new_name
:
group
.
full_path
,
target_namespace
:
availableNamespaces
[
0
].
full_path
,
},
}));
});
},
availableNamespaces
:
()
=>
axios
.
get
(
endpoints
.
availableNamespaces
).
then
(({
data
})
=>
data
.
map
(
namespace
=>
({
__typename
:
clientTypenames
.
AvailableNamespace
,
...
namespace
,
})),
),
},
Mutation
:
{
setTargetNamespace
(
_
,
{
targetNamespace
,
sourceGroupId
},
{
client
})
{
new
SourceGroupsManager
({
client
}).
updateById
(
sourceGroupId
,
sourceGroup
=>
{
// eslint-disable-next-line no-param-reassign
sourceGroup
.
import_target
.
target_namespace
=
targetNamespace
;
});
},
setNewName
(
_
,
{
newName
,
sourceGroupId
},
{
client
})
{
new
SourceGroupsManager
({
client
}).
updateById
(
sourceGroupId
,
sourceGroup
=>
{
// eslint-disable-next-line no-param-reassign
sourceGroup
.
import_target
.
new_name
=
newName
;
});
},
async
importGroup
(
_
,
{
sourceGroupId
},
{
client
})
{
const
groupManager
=
new
SourceGroupsManager
({
client
});
const
group
=
groupManager
.
findById
(
sourceGroupId
);
groupManager
.
setImportStatus
(
group
,
STATUSES
.
SCHEDULING
);
},
},
};
}
export
const
createApolloClient
=
({
endpoints
})
=>
createDefaultClient
(
createResolvers
({
endpoints
}),
{
assumeImmutableResults
:
true
});
app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
0 → 100644
View file @
ef2643f7
fragment
BulkImportSourceGroupItem
on
ClientBulkImportSourceGroup
{
id
web_url
full_path
full_name
status
import_target
}
app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
0 → 100644
View file @
ef2643f7
mutation
importGroup
(
$sourceGroupId
:
String
!)
{
importGroup
(
sourceGroupId
:
$sourceGroupId
)
@client
}
app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql
0 → 100644
View file @
ef2643f7
mutation
setNewName
(
$newName
:
String
!,
$sourceGroupId
:
String
!)
{
setNewName
(
newName
:
$newName
,
sourceGroupId
:
$sourceGroupId
)
@client
}
app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql
0 → 100644
View file @
ef2643f7
mutation
setTargetNamespace
(
$targetNamespace
:
String
!,
$sourceGroupId
:
String
!)
{
setTargetNamespace
(
targetNamespace
:
$targetNamespace
,
sourceGroupId
:
$sourceGroupId
)
@client
}
app/assets/javascripts/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql
0 → 100644
View file @
ef2643f7
query
availableNamespaces
{
availableNamespaces
@client
{
id
full_path
}
}
app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql
0 → 100644
View file @
ef2643f7
#import "../fragments/bulk_import_source_group_item.fragment.graphql"
query
bulkImportSourceGroups
{
bulkImportSourceGroups
@client
{
...
BulkImportSourceGroupItem
}
}
app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
0 → 100644
View file @
ef2643f7
import
{
defaultDataIdFromObject
}
from
'
apollo-cache-inmemory
'
;
import
produce
from
'
immer
'
;
import
ImportSourceGroupFragment
from
'
../fragments/bulk_import_source_group_item.fragment.graphql
'
;
function
extractTypeConditionFromFragment
(
fragment
)
{
return
fragment
.
definitions
[
0
]?.
typeCondition
.
name
.
value
;
}
function
generateGroupId
(
id
)
{
return
defaultDataIdFromObject
({
__typename
:
extractTypeConditionFromFragment
(
ImportSourceGroupFragment
),
id
,
});
}
export
class
SourceGroupsManager
{
constructor
({
client
})
{
this
.
client
=
client
;
}
findById
(
id
)
{
const
cacheId
=
generateGroupId
(
id
);
return
this
.
client
.
readFragment
({
fragment
:
ImportSourceGroupFragment
,
id
:
cacheId
});
}
update
(
group
,
fn
)
{
this
.
client
.
writeFragment
({
fragment
:
ImportSourceGroupFragment
,
id
:
generateGroupId
(
group
.
id
),
data
:
produce
(
group
,
fn
),
});
}
updateById
(
id
,
fn
)
{
const
group
=
this
.
findById
(
id
);
this
.
update
(
group
,
fn
);
}
setImportStatus
(
group
,
status
)
{
this
.
update
(
group
,
sourceGroup
=>
{
// eslint-disable-next-line no-param-reassign
sourceGroup
.
status
=
status
;
});
}
}
app/assets/javascripts/import_entities/import_groups/index.js
0 → 100644
View file @
ef2643f7
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
Translate
from
'
~/vue_shared/translate
'
;
import
{
createApolloClient
}
from
'
./graphql/client_factory
'
;
import
ImportTable
from
'
./components/import_table.vue
'
;
Vue
.
use
(
Translate
);
Vue
.
use
(
VueApollo
);
export
function
mountImportGroupsApp
(
mountElement
)
{
if
(
!
mountElement
)
return
undefined
;
const
{
statusPath
,
availableNamespacesPath
,
createBulkImportPath
}
=
mountElement
.
dataset
;
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createApolloClient
({
endpoints
:
{
status
:
statusPath
,
availableNamespaces
:
availableNamespacesPath
,
createBulkImport
:
createBulkImportPath
,
},
}),
});
return
new
Vue
({
el
:
mountElement
,
apolloProvider
,
render
(
createElement
)
{
return
createElement
(
ImportTable
);
},
});
}
app/assets/javascripts/pages/import/bulk_imports/index.js
0 → 100644
View file @
ef2643f7
import
{
mountImportGroupsApp
}
from
'
~/import_entities/import_groups
'
;
const
mountElement
=
document
.
getElementById
(
'
import-groups-mount-element
'
);
mountImportGroupsApp
(
mountElement
);
app/assets/stylesheets/page_bundles/import.scss
View file @
ef2643f7
...
...
@@ -43,9 +43,33 @@
}
.import-entities-target-select
{
&
.disabled
{
.import-entities-target-select-separator
,
.select2-container.select2-container-disabled
.select2-choice
{
color
:
var
(
--
gray-400
,
$gray-400
);
border-color
:
var
(
--
gray-100
,
$gray-100
);
background-color
:
var
(
--
gray-10
,
$gray-10
);
}
.select2-container.select2-container-disabled
.select2-choice
.select2-arrow
{
background-color
:
var
(
--
gray-10
,
$gray-10
);
}
}
.import-entities-target-select-separator
{
border-color
:
var
(
--
gray-200
,
$gray-200
);
background-color
:
var
(
--
gray-10
,
$gray-10
);
}
.select2-container
{
>
.select2-choice
{
.select2-arrow
{
background-color
:
var
(
--
white
,
$white
);
}
border-color
:
var
(
--
gray-200
,
$gray-200
);
color
:
var
(
--
gray-900
,
$gray-900
)
!
important
;
background-color
:
var
(
--
white
,
$white
)
!
important
;
border-top-right-radius
:
0
;
border-bottom-right-radius
:
0
;
}
...
...
app/views/import/bulk_imports/status.html.haml
View file @
ef2643f7
...
...
@@ -6,3 +6,7 @@
=
s_
(
'ImportGroups|Import groups from GitLab'
)
%p
.gl-my-0.gl-py-5.gl-border-solid.gl-border-gray-200.gl-border-0.gl-border-b-1
=
s_
(
'ImportGroups|Importing groups from %{link}'
).
html_safe
%
{
link:
external_link
(
@source_url
,
@source_url
)
}
#import-groups-mount-element
{
data:
{
status_path:
status_import_bulk_imports_path
(
format: :json
),
available_namespaces_path:
import_available_namespaces_path
(
format: :json
),
create_bulk_import_path:
import_bulk_imports_path
(
format: :json
)
}
}
locale/gitlab.pot
View file @
ef2643f7
...
...
@@ -4774,6 +4774,12 @@ msgstr ""
msgid "Bulk request concurrency"
msgstr ""
msgid "BulkImport|From source group"
msgstr ""
msgid "BulkImport|To new group"
msgstr ""
msgid "BulkImport|expected an associated Group but has an associated Project"
msgstr ""
...
...
spec/frontend/import_entities/import_groups/components/import_table_row_spec.js
0 → 100644
View file @
ef2643f7
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlButton
,
GlLink
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
Select2Select
from
'
~/vue_shared/components/select2_select.vue
'
;
import
ImportTableRow
from
'
~/import_entities/import_groups/components/import_table_row.vue
'
;
import
{
STATUSES
}
from
'
~/import_entities/constants
'
;
import
{
availableNamespacesFixture
}
from
'
../graphql/fixtures
'
;
const
getFakeGroup
=
status
=>
({
web_url
:
'
https://fake.host/
'
,
full_path
:
'
fake_group_1
'
,
full_name
:
'
fake_name_1
'
,
import_target
:
{
target_namespace
:
'
root
'
,
new_name
:
'
group1
'
,
},
id
:
1
,
status
,
});
describe
(
'
import table row
'
,
()
=>
{
let
wrapper
;
let
group
;
const
findByText
=
(
cmp
,
text
)
=>
{
return
wrapper
.
findAll
(
cmp
).
wrappers
.
find
(
node
=>
node
.
text
().
indexOf
(
text
)
===
0
);
};
const
findImportButton
=
()
=>
findByText
(
GlButton
,
'
Import
'
);
const
findNameInput
=
()
=>
wrapper
.
find
(
GlFormInput
);
const
findNamespaceDropdown
=
()
=>
wrapper
.
find
(
Select2Select
);
const
createComponent
=
props
=>
{
wrapper
=
shallowMount
(
ImportTableRow
,
{
propsData
:
{
availableNamespaces
:
availableNamespacesFixture
,
...
props
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
describe
(
'
events
'
,
()
=>
{
beforeEach
(()
=>
{
group
=
getFakeGroup
(
STATUSES
.
NONE
);
createComponent
({
group
});
});
it
.
each
`
selector | sourceEvent | payload | event
${
findNamespaceDropdown
}
|
${
'
input
'
}
|
${
'
demo
'
}
|
${
'
update-target-namespace
'
}
${
findNameInput
}
|
${
'
input
'
}
|
${
'
demo
'
}
|
${
'
update-new-name
'
}
${
findImportButton
}
|
${
'
click
'
}
|
${
undefined
}
|
${
'
import-group
'
}
`
(
'
invokes $event
'
,
({
selector
,
sourceEvent
,
payload
,
event
})
=>
{
selector
().
vm
.
$emit
(
sourceEvent
,
payload
);
expect
(
wrapper
.
emitted
(
event
)).
toBeDefined
();
expect
(
wrapper
.
emitted
(
event
)[
0
][
0
]).
toBe
(
payload
);
});
});
describe
(
'
when entity status is NONE
'
,
()
=>
{
beforeEach
(()
=>
{
group
=
getFakeGroup
(
STATUSES
.
NONE
);
createComponent
({
group
});
});
it
(
'
renders Import button
'
,
()
=>
{
expect
(
findByText
(
GlButton
,
'
Import
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders namespace dropdown as not disabled
'
,
()
=>
{
expect
(
findNamespaceDropdown
().
attributes
(
'
disabled
'
)).
toBe
(
undefined
);
});
});
describe
(
'
when entity status is SCHEDULING
'
,
()
=>
{
beforeEach
(()
=>
{
group
=
getFakeGroup
(
STATUSES
.
SCHEDULING
);
createComponent
({
group
});
});
it
(
'
does not render Import button
'
,
()
=>
{
expect
(
findByText
(
GlButton
,
'
Import
'
)).
toBe
(
undefined
);
});
it
(
'
renders namespace dropdown as disabled
'
,
()
=>
{
expect
(
findNamespaceDropdown
().
attributes
(
'
disabled
'
)).
toBe
(
'
true
'
);
});
});
describe
(
'
when entity status is FINISHED
'
,
()
=>
{
beforeEach
(()
=>
{
group
=
getFakeGroup
(
STATUSES
.
FINISHED
);
createComponent
({
group
});
});
it
(
'
does not render Import button
'
,
()
=>
{
expect
(
findByText
(
GlButton
,
'
Import
'
)).
toBe
(
undefined
);
});
it
(
'
does not render namespace dropdown
'
,
()
=>
{
expect
(
findNamespaceDropdown
().
exists
()).
toBe
(
false
);
});
it
(
'
renders target as link
'
,
()
=>
{
const
TARGET_LINK
=
`
${
group
.
import_target
.
target_namespace
}
/
${
group
.
import_target
.
new_name
}
`
;
expect
(
findByText
(
GlLink
,
TARGET_LINK
).
exists
()).
toBe
(
true
);
});
});
});
spec/frontend/import_entities/import_groups/components/import_table_spec.js
0 → 100644
View file @
ef2643f7
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
createMockApollo
from
'
jest/helpers/mock_apollo_helper
'
;
import
ImportTableRow
from
'
~/import_entities/import_groups/components/import_table_row.vue
'
;
import
ImportTable
from
'
~/import_entities/import_groups/components/import_table.vue
'
;
import
setTargetNamespaceMutation
from
'
~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql
'
;
import
setNewNameMutation
from
'
~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql
'
;
import
importGroupMutation
from
'
~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
'
;
import
{
STATUSES
}
from
'
~/import_entities/constants
'
;
import
{
availableNamespacesFixture
,
generateFakeEntry
}
from
'
../graphql/fixtures
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
describe
(
'
import table
'
,
()
=>
{
let
wrapper
;
let
apolloProvider
;
const
createComponent
=
({
bulkImportSourceGroups
})
=>
{
apolloProvider
=
createMockApollo
([],
{
Query
:
{
availableNamespaces
:
()
=>
availableNamespacesFixture
,
bulkImportSourceGroups
,
},
Mutation
:
{
setTargetNamespace
:
jest
.
fn
(),
setNewName
:
jest
.
fn
(),
importGroup
:
jest
.
fn
(),
},
});
wrapper
=
shallowMount
(
ImportTable
,
{
localVue
,
apolloProvider
,
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
renders loading icon while performing request
'
,
async
()
=>
{
createComponent
({
bulkImportSourceGroups
:
()
=>
new
Promise
(()
=>
{}),
});
await
waitForPromises
();
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
});
it
(
'
does not renders loading icon when request is completed
'
,
async
()
=>
{
createComponent
({
bulkImportSourceGroups
:
()
=>
[],
});
await
waitForPromises
();
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
false
);
});
it
(
'
renders import row for each group in response
'
,
async
()
=>
{
const
FAKE_GROUPS
=
[
generateFakeEntry
({
id
:
1
,
status
:
STATUSES
.
NONE
}),
generateFakeEntry
({
id
:
2
,
status
:
STATUSES
.
FINISHED
}),
];
createComponent
({
bulkImportSourceGroups
:
()
=>
FAKE_GROUPS
,
});
await
waitForPromises
();
expect
(
wrapper
.
findAll
(
ImportTableRow
).
length
).
toBe
(
FAKE_GROUPS
.
length
);
});
describe
(
'
converts row events to mutation invocations
'
,
()
=>
{
const
FAKE_GROUP
=
generateFakeEntry
({
id
:
1
,
status
:
STATUSES
.
NONE
});
beforeEach
(()
=>
{
createComponent
({
bulkImportSourceGroups
:
()
=>
[
FAKE_GROUP
],
});
return
waitForPromises
();
});
it
.
each
`
event | payload | mutation | variables
${
'
update-target-namespace
'
}
|
${
'
new-namespace
'
}
|
${
setTargetNamespaceMutation
}
|
${{
sourceGroupId
:
FAKE_GROUP
.
id
,
targetNamespace
:
'
new-namespace
'
}
}
${
'
update-new-name
'
}
|
${
'
new-name
'
}
|
${
setNewNameMutation
}
|
${{
sourceGroupId
:
FAKE_GROUP
.
id
,
newName
:
'
new-name
'
}
}
${
'
import-group
'
}
|
${
undefined
}
|
${
importGroupMutation
}
|
${{
sourceGroupId
:
FAKE_GROUP
.
id
}
}
`
(
'
correctly maps $event to mutation
'
,
async
({
event
,
payload
,
mutation
,
variables
})
=>
{
jest
.
spyOn
(
apolloProvider
.
defaultClient
,
'
mutate
'
);
wrapper
.
find
(
ImportTableRow
).
vm
.
$emit
(
event
,
payload
);
await
waitForPromises
();
expect
(
apolloProvider
.
defaultClient
.
mutate
).
toHaveBeenCalledWith
({
mutation
,
variables
,
});
});
});
});
spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
0 → 100644
View file @
ef2643f7
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
InMemoryCache
}
from
'
apollo-cache-inmemory
'
;
import
{
createMockClient
}
from
'
mock-apollo-client
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
clientTypenames
,
createResolvers
,
}
from
'
~/import_entities/import_groups/graphql/client_factory
'
;
import
{
STATUSES
}
from
'
~/import_entities/constants
'
;
import
bulkImportSourceGroupsQuery
from
'
~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql
'
;
import
availableNamespacesQuery
from
'
~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql
'
;
import
setTargetNamespaceMutation
from
'
~/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql
'
;
import
setNewNameMutation
from
'
~/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql
'
;
import
importGroupMutation
from
'
~/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
import
{
statusEndpointFixture
,
availableNamespacesFixture
}
from
'
./fixtures
'
;
const
FAKE_ENDPOINTS
=
{
status
:
'
/fake_status_url
'
,
availableNamespaces
:
'
/fake_available_namespaces
'
,
createBulkImport
:
'
/fake_create_bulk_import
'
,
};
describe
(
'
Bulk import resolvers
'
,
()
=>
{
let
axiosMockAdapter
;
let
client
;
beforeEach
(()
=>
{
axiosMockAdapter
=
new
MockAdapter
(
axios
);
client
=
createMockClient
({
cache
:
new
InMemoryCache
({
fragmentMatcher
:
{
match
:
()
=>
true
},
addTypename
:
false
,
}),
resolvers
:
createResolvers
({
endpoints
:
FAKE_ENDPOINTS
}),
});
});
afterEach
(()
=>
{
axiosMockAdapter
.
restore
();
});
describe
(
'
queries
'
,
()
=>
{
describe
(
'
availableNamespaces
'
,
()
=>
{
let
results
;
beforeEach
(
async
()
=>
{
axiosMockAdapter
.
onGet
(
FAKE_ENDPOINTS
.
availableNamespaces
)
.
reply
(
httpStatus
.
OK
,
availableNamespacesFixture
);
const
response
=
await
client
.
query
({
query
:
availableNamespacesQuery
});
results
=
response
.
data
.
availableNamespaces
;
});
it
(
'
mirrors REST endpoint response fields
'
,
()
=>
{
const
MIRRORED_FIELDS
=
[
'
id
'
,
'
full_path
'
];
expect
(
results
.
every
((
r
,
idx
)
=>
MIRRORED_FIELDS
.
every
(
field
=>
r
[
field
]
===
availableNamespacesFixture
[
idx
][
field
]),
),
).
toBe
(
true
);
});
});
describe
(
'
bulkImportSourceGroups
'
,
()
=>
{
let
results
;
beforeEach
(
async
()
=>
{
axiosMockAdapter
.
onGet
(
FAKE_ENDPOINTS
.
status
).
reply
(
httpStatus
.
OK
,
statusEndpointFixture
);
axiosMockAdapter
.
onGet
(
FAKE_ENDPOINTS
.
availableNamespaces
)
.
reply
(
httpStatus
.
OK
,
availableNamespacesFixture
);
const
response
=
await
client
.
query
({
query
:
bulkImportSourceGroupsQuery
});
results
=
response
.
data
.
bulkImportSourceGroups
;
});
it
(
'
mirrors REST endpoint response fields
'
,
()
=>
{
const
MIRRORED_FIELDS
=
[
'
id
'
,
'
full_name
'
,
'
full_path
'
,
'
web_url
'
];
expect
(
results
.
every
((
r
,
idx
)
=>
MIRRORED_FIELDS
.
every
(
field
=>
r
[
field
]
===
statusEndpointFixture
.
importable_data
[
idx
][
field
],
),
),
).
toBe
(
true
);
});
it
(
'
populates each result instance with status field default to none
'
,
()
=>
{
expect
(
results
.
every
(
r
=>
r
.
status
===
STATUSES
.
NONE
)).
toBe
(
true
);
});
it
(
'
populates each result instance with import_target defaulted to first available namespace
'
,
()
=>
{
expect
(
results
.
every
(
r
=>
r
.
import_target
.
target_namespace
===
availableNamespacesFixture
[
0
].
full_path
,
),
).
toBe
(
true
);
});
});
});
describe
(
'
mutations
'
,
()
=>
{
let
results
;
const
GROUP_ID
=
1
;
beforeEach
(()
=>
{
client
.
writeQuery
({
query
:
bulkImportSourceGroupsQuery
,
data
:
{
bulkImportSourceGroups
:
[
{
__typename
:
clientTypenames
.
BulkImportSourceGroup
,
id
:
GROUP_ID
,
status
:
STATUSES
.
NONE
,
web_url
:
'
https://fake.host/1
'
,
full_path
:
'
fake_group_1
'
,
full_name
:
'
fake_name_1
'
,
import_target
:
{
target_namespace
:
'
root
'
,
new_name
:
'
group1
'
,
},
},
],
},
});
client
.
watchQuery
({
query
:
bulkImportSourceGroupsQuery
,
fetchPolicy
:
'
cache-only
'
,
})
.
subscribe
(({
data
})
=>
{
results
=
data
.
bulkImportSourceGroups
;
});
});
it
(
'
setTargetNamespaces updates group target namespace
'
,
async
()
=>
{
const
NEW_TARGET_NAMESPACE
=
'
target
'
;
await
client
.
mutate
({
mutation
:
setTargetNamespaceMutation
,
variables
:
{
sourceGroupId
:
GROUP_ID
,
targetNamespace
:
NEW_TARGET_NAMESPACE
},
});
expect
(
results
[
0
].
import_target
.
target_namespace
).
toBe
(
NEW_TARGET_NAMESPACE
);
});
it
(
'
setNewName updates group target name
'
,
async
()
=>
{
const
NEW_NAME
=
'
new
'
;
await
client
.
mutate
({
mutation
:
setNewNameMutation
,
variables
:
{
sourceGroupId
:
GROUP_ID
,
newName
:
NEW_NAME
},
});
expect
(
results
[
0
].
import_target
.
new_name
).
toBe
(
NEW_NAME
);
});
describe
(
'
importGroup
'
,
()
=>
{
it
(
'
sets status to SCHEDULING when request initiates
'
,
async
()
=>
{
axiosMockAdapter
.
onPost
(
FAKE_ENDPOINTS
.
createBulkImport
).
reply
(()
=>
new
Promise
(()
=>
{}));
client
.
mutate
({
mutation
:
importGroupMutation
,
variables
:
{
sourceGroupId
:
GROUP_ID
},
});
await
waitForPromises
();
const
{
bulkImportSourceGroups
:
intermediateResults
}
=
client
.
readQuery
({
query
:
bulkImportSourceGroupsQuery
,
});
expect
(
intermediateResults
[
0
].
status
).
toBe
(
STATUSES
.
SCHEDULING
);
});
});
});
});
spec/frontend/import_entities/import_groups/graphql/fixtures.js
0 → 100644
View file @
ef2643f7
import
{
clientTypenames
}
from
'
~/import_entities/import_groups/graphql/client_factory
'
;
export
const
generateFakeEntry
=
({
id
,
status
,
...
rest
})
=>
({
__typename
:
clientTypenames
.
BulkImportSourceGroup
,
web_url
:
`https://fake.host/
${
id
}
`
,
full_path
:
`fake_group_
${
id
}
`
,
full_name
:
`fake_name_
${
id
}
`
,
import_target
:
{
target_namespace
:
'
root
'
,
new_name
:
`group
${
id
}
`
,
},
id
,
status
,
...
rest
,
});
export
const
statusEndpointFixture
=
{
importable_data
:
[
{
id
:
2595438
,
full_name
:
'
AutoBreakfast
'
,
full_path
:
'
auto-breakfast
'
,
web_url
:
'
https://gitlab.com/groups/auto-breakfast
'
,
},
{
id
:
4347861
,
full_name
:
'
GitLab Data
'
,
full_path
:
'
gitlab-data
'
,
web_url
:
'
https://gitlab.com/groups/gitlab-data
'
,
},
{
id
:
5723700
,
full_name
:
'
GitLab Services
'
,
full_path
:
'
gitlab-services
'
,
web_url
:
'
https://gitlab.com/groups/gitlab-services
'
,
},
{
id
:
349181
,
full_name
:
'
GitLab-examples
'
,
full_path
:
'
gitlab-examples
'
,
web_url
:
'
https://gitlab.com/groups/gitlab-examples
'
,
},
],
};
export
const
availableNamespacesFixture
=
[
{
id
:
24
,
full_path
:
'
Commit451
'
},
{
id
:
22
,
full_path
:
'
gitlab-org
'
},
{
id
:
23
,
full_path
:
'
gnuwget
'
},
{
id
:
25
,
full_path
:
'
jashkenas
'
},
];
spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js
0 → 100644
View file @
ef2643f7
import
{
defaultDataIdFromObject
}
from
'
apollo-cache-inmemory
'
;
import
{
SourceGroupsManager
}
from
'
~/import_entities/import_groups/graphql/services/source_groups_manager
'
;
import
ImportSourceGroupFragment
from
'
~/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
'
;
import
{
clientTypenames
}
from
'
~/import_entities/import_groups/graphql/client_factory
'
;
describe
(
'
SourceGroupsManager
'
,
()
=>
{
let
manager
;
let
client
;
const
getFakeGroup
=
()
=>
({
__typename
:
clientTypenames
.
BulkImportSourceGroup
,
id
:
5
,
});
beforeEach
(()
=>
{
client
=
{
readFragment
:
jest
.
fn
(),
writeFragment
:
jest
.
fn
(),
};
manager
=
new
SourceGroupsManager
({
client
});
});
it
(
'
finds item by group id
'
,
()
=>
{
const
ID
=
5
;
const
FAKE_GROUP
=
getFakeGroup
();
client
.
readFragment
.
mockReturnValue
(
FAKE_GROUP
);
const
group
=
manager
.
findById
(
ID
);
expect
(
group
).
toBe
(
FAKE_GROUP
);
expect
(
client
.
readFragment
).
toHaveBeenCalledWith
({
fragment
:
ImportSourceGroupFragment
,
id
:
defaultDataIdFromObject
(
getFakeGroup
()),
});
});
it
(
'
updates group with provided function
'
,
()
=>
{
const
UPDATED_GROUP
=
{};
const
fn
=
jest
.
fn
().
mockReturnValue
(
UPDATED_GROUP
);
manager
.
update
(
getFakeGroup
(),
fn
);
expect
(
client
.
writeFragment
).
toHaveBeenCalledWith
({
fragment
:
ImportSourceGroupFragment
,
id
:
defaultDataIdFromObject
(
getFakeGroup
()),
data
:
UPDATED_GROUP
,
});
});
it
(
'
updates group by id with provided function
'
,
()
=>
{
const
UPDATED_GROUP
=
{};
const
fn
=
jest
.
fn
().
mockReturnValue
(
UPDATED_GROUP
);
client
.
readFragment
.
mockReturnValue
(
getFakeGroup
());
manager
.
updateById
(
getFakeGroup
().
id
,
fn
);
expect
(
client
.
readFragment
).
toHaveBeenCalledWith
({
fragment
:
ImportSourceGroupFragment
,
id
:
defaultDataIdFromObject
(
getFakeGroup
()),
});
expect
(
client
.
writeFragment
).
toHaveBeenCalledWith
({
fragment
:
ImportSourceGroupFragment
,
id
:
defaultDataIdFromObject
(
getFakeGroup
()),
data
:
UPDATED_GROUP
,
});
});
it
(
'
sets import status when group is provided
'
,
()
=>
{
client
.
readFragment
.
mockReturnValue
(
getFakeGroup
());
const
NEW_STATUS
=
'
NEW_STATUS
'
;
manager
.
setImportStatus
(
getFakeGroup
(),
NEW_STATUS
);
expect
(
client
.
writeFragment
).
toHaveBeenCalledWith
({
fragment
:
ImportSourceGroupFragment
,
id
:
defaultDataIdFromObject
(
getFakeGroup
()),
data
:
{
...
getFakeGroup
(),
status
:
NEW_STATUS
,
},
});
});
});
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