Commit ef2643f7 authored by Illya Klymov's avatar Illya Klymov

Implement displaying source groups for bulk import

MVC version of group import without actual import being run
parent a30bdc7f
<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>
<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>
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 });
fragment BulkImportSourceGroupItem on ClientBulkImportSourceGroup {
id
web_url
full_path
full_name
status
import_target
}
mutation importGroup($sourceGroupId: String!) {
importGroup(sourceGroupId: $sourceGroupId) @client
}
mutation setNewName($newName: String!, $sourceGroupId: String!) {
setNewName(newName: $newName, sourceGroupId: $sourceGroupId) @client
}
mutation setTargetNamespace($targetNamespace: String!, $sourceGroupId: String!) {
setTargetNamespace(targetNamespace: $targetNamespace, sourceGroupId: $sourceGroupId) @client
}
query availableNamespaces {
availableNamespaces @client {
id
full_path
}
}
#import "../fragments/bulk_import_source_group_item.fragment.graphql"
query bulkImportSourceGroups {
bulkImportSourceGroups @client {
...BulkImportSourceGroupItem
}
}
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;
});
}
}
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);
},
});
}
import { mountImportGroupsApp } from '~/import_entities/import_groups';
const mountElement = document.getElementById('import-groups-mount-element');
mountImportGroupsApp(mountElement);
......@@ -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;
}
......
......@@ -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) } }
......@@ -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 ""
......
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);
});
});
});
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,
});
});
});
});
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);
});
});
});
});
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' },
];
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,
},
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment