Commit 53b33a09 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch...

Merge branch '330885-group-migration-ensure-group-validation-produce-correct-error-message-when-name-is-already' into 'master'

Group Migration: Ensure group validation produce correct error message when name is already taken by project

See merge request gitlab-org/gitlab!63076
parents 053e04fd 6b063be7
...@@ -15,7 +15,7 @@ import ImportStatus from '../../components/import_status.vue'; ...@@ -15,7 +15,7 @@ import ImportStatus from '../../components/import_status.vue';
import { STATUSES } from '../../constants'; import { STATUSES } from '../../constants';
import addValidationErrorMutation from '../graphql/mutations/add_validation_error.mutation.graphql'; import addValidationErrorMutation from '../graphql/mutations/add_validation_error.mutation.graphql';
import removeValidationErrorMutation from '../graphql/mutations/remove_validation_error.mutation.graphql'; import removeValidationErrorMutation from '../graphql/mutations/remove_validation_error.mutation.graphql';
import groupQuery from '../graphql/queries/group.query.graphql'; import groupAndProjectQuery from '../graphql/queries/groupAndProject.query.graphql';
const DEBOUNCE_INTERVAL = 300; const DEBOUNCE_INTERVAL = 300;
...@@ -47,21 +47,21 @@ export default { ...@@ -47,21 +47,21 @@ export default {
}, },
apollo: { apollo: {
existingGroup: { existingGroupAndProject: {
query: groupQuery, query: groupAndProjectQuery,
debounce: DEBOUNCE_INTERVAL, debounce: DEBOUNCE_INTERVAL,
variables() { variables() {
return { return {
fullPath: this.fullPath, fullPath: this.fullPath,
}; };
}, },
update({ existingGroup }) { update({ existingGroup, existingProject }) {
const variables = { const variables = {
field: 'new_name', field: 'new_name',
sourceGroupId: this.group.id, sourceGroupId: this.group.id,
}; };
if (!existingGroup) { if (!existingGroup && !existingProject) {
this.$apollo.mutate({ this.$apollo.mutate({
mutation: removeValidationErrorMutation, mutation: removeValidationErrorMutation,
variables, variables,
...@@ -71,7 +71,7 @@ export default { ...@@ -71,7 +71,7 @@ export default {
mutation: addValidationErrorMutation, mutation: addValidationErrorMutation,
variables: { variables: {
...variables, ...variables,
message: s__('BulkImport|Name already exists.'), message: this.$options.i18n.NAME_ALREADY_EXISTS,
}, },
}); });
} }
...@@ -115,6 +115,10 @@ export default { ...@@ -115,6 +115,10 @@ export default {
return joinPaths(gon.relative_url_root || '/', this.fullPath); return joinPaths(gon.relative_url_root || '/', this.fullPath);
}, },
}, },
i18n: {
NAME_ALREADY_EXISTS: s__('BulkImport|Name already exists.'),
},
}; };
</script> </script>
......
query group($fullPath: ID!) { query groupAndProject($fullPath: ID!) {
existingGroup: group(fullPath: $fullPath) { existingGroup: group(fullPath: $fullPath) {
id id
} }
existingProject: project(fullPath: $fullPath) {
id
}
} }
...@@ -5,11 +5,15 @@ import VueApollo from 'vue-apollo'; ...@@ -5,11 +5,15 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { STATUSES } from '~/import_entities/constants'; import { STATUSES } from '~/import_entities/constants';
import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue'; import ImportTableRow from '~/import_entities/import_groups/components/import_table_row.vue';
import groupQuery from '~/import_entities/import_groups/graphql/queries/group.query.graphql'; import addValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql';
import removeValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql';
import groupAndProjectQuery from '~/import_entities/import_groups/graphql/queries/groupAndProject.query.graphql';
import { availableNamespacesFixture } from '../graphql/fixtures'; import { availableNamespacesFixture } from '../graphql/fixtures';
Vue.use(VueApollo); Vue.use(VueApollo);
const { i18n: I18N } = ImportTableRow;
const getFakeGroup = (status) => ({ const getFakeGroup = (status) => ({
web_url: 'https://fake.host/', web_url: 'https://fake.host/',
full_path: 'fake_group_1', full_path: 'fake_group_1',
...@@ -25,6 +29,7 @@ const getFakeGroup = (status) => ({ ...@@ -25,6 +29,7 @@ const getFakeGroup = (status) => ({
const EXISTING_GROUP_TARGET_NAMESPACE = 'existing-group'; const EXISTING_GROUP_TARGET_NAMESPACE = 'existing-group';
const EXISTING_GROUP_PATH = 'existing-path'; const EXISTING_GROUP_PATH = 'existing-path';
const EXISTING_PROJECT_PATH = 'existing-project-path';
describe('import table row', () => { describe('import table row', () => {
let wrapper; let wrapper;
...@@ -41,13 +46,19 @@ describe('import table row', () => { ...@@ -41,13 +46,19 @@ describe('import table row', () => {
const createComponent = (props) => { const createComponent = (props) => {
apolloProvider = createMockApollo([ apolloProvider = createMockApollo([
[ [
groupQuery, groupAndProjectQuery,
({ fullPath }) => { ({ fullPath }) => {
const existingGroup = const existingGroup =
fullPath === `${EXISTING_GROUP_TARGET_NAMESPACE}/${EXISTING_GROUP_PATH}` fullPath === `${EXISTING_GROUP_TARGET_NAMESPACE}/${EXISTING_GROUP_PATH}`
? { id: 1 } ? { id: 1 }
: null; : null;
return Promise.resolve({ data: { existingGroup } });
const existingProject =
fullPath === `${EXISTING_GROUP_TARGET_NAMESPACE}/${EXISTING_PROJECT_PATH}`
? { id: 1 }
: null;
return Promise.resolve({ data: { existingGroup, existingProject } });
}, },
], ],
]); ]);
...@@ -173,7 +184,7 @@ describe('import table row', () => { ...@@ -173,7 +184,7 @@ describe('import table row', () => {
}); });
describe('validations', () => { describe('validations', () => {
it('Reports invalid group name when name is not matching regex', () => { it('reports invalid group name when name is not matching regex', () => {
createComponent({ createComponent({
group: { group: {
...getFakeGroup(STATUSES.NONE), ...getFakeGroup(STATUSES.NONE),
...@@ -188,7 +199,7 @@ describe('import table row', () => { ...@@ -188,7 +199,7 @@ describe('import table row', () => {
expect(wrapper.text()).toContain('Please choose a group URL with no special characters.'); expect(wrapper.text()).toContain('Please choose a group URL with no special characters.');
}); });
it('Reports invalid group name if relevant validation error exists', async () => { it('reports invalid group name if relevant validation error exists', async () => {
const FAKE_ERROR_MESSAGE = 'fake error'; const FAKE_ERROR_MESSAGE = 'fake error';
createComponent({ createComponent({
...@@ -208,5 +219,101 @@ describe('import table row', () => { ...@@ -208,5 +219,101 @@ describe('import table row', () => {
expect(wrapper.text()).toContain(FAKE_ERROR_MESSAGE); expect(wrapper.text()).toContain(FAKE_ERROR_MESSAGE);
}); });
it('sets validation error when targetting existing group', async () => {
const testGroup = getFakeGroup(STATUSES.NONE);
createComponent({
group: {
...testGroup,
import_target: {
target_namespace: EXISTING_GROUP_TARGET_NAMESPACE,
new_name: EXISTING_GROUP_PATH,
},
},
});
jest.spyOn(wrapper.vm.$apollo, 'mutate');
jest.runOnlyPendingTimers();
await nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: addValidationErrorMutation,
variables: {
field: 'new_name',
message: I18N.NAME_ALREADY_EXISTS,
sourceGroupId: testGroup.id,
},
});
});
it('sets validation error when targetting existing project', async () => {
const testGroup = getFakeGroup(STATUSES.NONE);
createComponent({
group: {
...testGroup,
import_target: {
target_namespace: EXISTING_GROUP_TARGET_NAMESPACE,
new_name: EXISTING_PROJECT_PATH,
},
},
});
jest.spyOn(wrapper.vm.$apollo, 'mutate');
jest.runOnlyPendingTimers();
await nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: addValidationErrorMutation,
variables: {
field: 'new_name',
message: I18N.NAME_ALREADY_EXISTS,
sourceGroupId: testGroup.id,
},
});
});
it('clears validation error when target is updated', async () => {
const testGroup = getFakeGroup(STATUSES.NONE);
createComponent({
group: {
...testGroup,
import_target: {
target_namespace: EXISTING_GROUP_TARGET_NAMESPACE,
new_name: EXISTING_PROJECT_PATH,
},
},
});
jest.runOnlyPendingTimers();
await nextTick();
jest.spyOn(wrapper.vm.$apollo, 'mutate');
await wrapper.setProps({
group: {
...testGroup,
import_target: {
target_namespace: 'valid_namespace',
new_name: 'valid_path',
},
},
});
jest.runOnlyPendingTimers();
await nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: removeValidationErrorMutation,
variables: {
field: 'new_name',
sourceGroupId: testGroup.id,
},
});
});
}); });
}); });
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