Skip to content
Snippets Groups Projects
Commit 532d80b2 authored by Peter Hegman's avatar Peter Hegman :red_circle:
Browse files

Add support for editing a group in the admin area

- Don't change `Group URL`
- Add warning alert
- Add `Group ID` input
parent 65636e48
No related branches found
No related tags found
1 merge request!89670Add support for editing a group in the admin area
<script>
import { GlFormGroup, GlFormInput, GlFormInputGroup, GlInputGroupText } from '@gitlab/ui';
import {
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlInputGroupText,
GlLink,
GlAlert,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { s__, __ } from '~/locale';
......@@ -33,23 +40,36 @@ export default {
),
validFeedback: s__('Groups|Group path is available.'),
},
groupId: {
label: s__('Groups|Group ID'),
},
},
apiLoadingMessage: s__('Groups|Checking group URL availability...'),
apiErrorMessage: __(
'An error occurred while checking group path. Please refresh and try again.',
),
changingUrlWarningMessage: s__('Groups|Changing group URL can have unintended side effects.'),
learnMore: s__('Groups|Learn more'),
},
nameInputSize: { md: 'lg' },
changingGroupPathHelpPagePath: helpPagePath('user/group/index', {
anchor: 'change-a-groups-path',
}),
mattermostDataBindName: 'create_chat_team',
components: { GlFormGroup, GlFormInput, GlFormInputGroup, GlInputGroupText },
components: {
GlFormGroup,
GlFormInput,
GlFormInputGroup,
GlInputGroupText,
GlLink,
GlAlert,
},
inject: ['fields', 'basePath', 'mattermostEnabled'],
data() {
return {
name: this.fields.name.value,
path: this.fields.path.value,
hasPathBeenManuallySet: false,
apiSuggestedPath: '',
apiLoading: false,
nameFeedbackState: null,
......@@ -65,16 +85,23 @@ export default {
pathDescription() {
return this.apiLoading ? this.$options.i18n.apiLoadingMessage : '';
},
isEditingGroup() {
return this.fields.groupId.value !== '';
},
},
watch: {
name: [
function updatePath(newName) {
if (this.isEditingGroup || this.hasPathBeenManuallySet) return;
this.nameFeedbackState = null;
this.pathFeedbackState = null;
this.apiSuggestedPath = '';
this.path = slugify(newName);
},
debounce(async function updatePathWithSuggestions() {
if (this.isEditingGroup || this.hasPathBeenManuallySet) return;
try {
const { suggests } = await this.checkPathAvailability();
......@@ -134,10 +161,13 @@ export default {
handlePathInput(value) {
this.pathFeedbackState = null;
this.apiSuggestedPath = '';
this.hasPathBeenManuallySet = true;
this.path = value;
this.debouncedValidatePath();
},
debouncedValidatePath: debounce(async function validatePath() {
if (this.isEditingGroup && this.path === this.fields.path.value) return;
try {
const {
exists,
......@@ -228,5 +258,22 @@ export default {
/>
</gl-form-input-group>
</gl-form-group>
<template v-if="isEditingGroup">
<gl-alert class="gl-mb-5" :dismissible="false" variant="warning">
{{ $options.i18n.changingUrlWarningMessage }}
<gl-link :href="$options.changingGroupPathHelpPagePath"
>{{ $options.i18n.learnMore }}
</gl-link>
</gl-alert>
<gl-form-group :label="$options.i18n.inputs.groupId.label" :label-for="fields.groupId.id">
<gl-form-input
:id="fields.groupId.id"
:value="fields.groupId.value"
:name="fields.groupId.name"
size="sm"
readonly
/>
</gl-form-group>
</template>
</div>
</template>
......@@ -2,3 +2,4 @@
= f.hidden_field :name, data: { js_name: 'name' }
= f.hidden_field :path, maxlength: ::Namespace::URL_MAX_LENGTH, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, data: { js_name: 'path' }
= f.hidden_field :parent_id, data: { js_name: 'parentId' }
= f.hidden_field :id, data: { js_name: 'groupId' }
......@@ -42,8 +42,6 @@
end
it 'creates new group' do
stub_feature_flags(group_name_path_vue: false)
visit admin_groups_path
page.within '#content-body' do
......@@ -96,8 +94,6 @@
end
it 'when entering in group path, group name does not change anymore' do
stub_feature_flags(group_name_path_vue: false)
visit admin_groups_path
click_link "New group"
group_path = 'my-gitlab-project'
......
import { merge } from 'lodash';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { GlAlert } from '@gitlab/ui';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import GroupNameAndPath from '~/groups/components/group_name_and_path.vue';
import { getGroupPathAvailability } from '~/rest_api';
import { createAlert } from '~/flash';
import { helpPagePath } from '~/helpers/help_page_helper';
jest.mock('~/flash');
jest.mock('~/rest_api', () => ({
......@@ -37,9 +39,15 @@ describe('GroupNameAndPath', () => {
const createComponent = ({ provide = {} } = {}) => {
wrapper = mountExtended(GroupNameAndPath, { provide: merge({}, defaultProvide, provide) });
};
const createComponentEditGroup = ({ path = mockGroupUrl } = {}) => {
createComponent({
provide: { fields: { groupId: { value: '1' }, path: { value: path } } },
});
};
const findGroupNameField = () => wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.name.label);
const findGroupUrlField = () => wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.path.label);
const findAlert = () => extendedWrapper(wrapper.findComponent(GlAlert));
const apiMockAvailablePath = () => {
getGroupPathAvailability.mockResolvedValue({
......@@ -60,12 +68,45 @@ describe('GroupNameAndPath', () => {
};
describe('when user types in the `Group name` field', () => {
it('updates `Group URL` field as user types', async () => {
createComponent();
describe('when creating a new group', () => {
it('updates `Group URL` field as user types', async () => {
createComponent();
await findGroupNameField().setValue(mockGroupName);
await findGroupNameField().setValue(mockGroupName);
expect(findGroupUrlField().element.value).toBe(mockGroupUrl);
});
});
describe('when editing a group', () => {
it('does not update `Group URL` field and does not call API', async () => {
const groupUrl = 'foo-bar';
createComponentEditGroup({ path: groupUrl });
await findGroupNameField().setValue(mockGroupName);
expect(findGroupUrlField().element.value).toBe(mockGroupUrl);
expect(findGroupUrlField().element.value).toBe(groupUrl);
expect(getGroupPathAvailability).not.toHaveBeenCalled();
});
});
describe('when `Group URL` field has been manually entered', () => {
it('does not update `Group URL` field and does not call API', async () => {
apiMockAvailablePath();
createComponent();
await findGroupUrlField().setValue(mockGroupUrl);
await waitForPromises();
getGroupPathAvailability.mockClear();
await findGroupNameField().setValue('Foo bar');
expect(findGroupUrlField().element.value).toBe(mockGroupUrl);
expect(getGroupPathAvailability).not.toHaveBeenCalled();
});
});
it('shows loading message', async () => {
......@@ -227,6 +268,30 @@ describe('GroupNameAndPath', () => {
expect(findGroupUrlField().element.value).toBe(mockGroupUrlSuggested);
});
});
describe('when editing a group', () => {
it('calls API if `Group URL` does not equal the original `Group URL`', async () => {
const groupUrl = 'foo-bar';
apiMockAvailablePath();
createComponentEditGroup({ path: groupUrl });
await findGroupUrlField().setValue('foo-bar1');
await waitForPromises();
expect(getGroupPathAvailability).toHaveBeenCalled();
expect(wrapper.findByText(GroupNameAndPath.i18n.inputs.path.validFeedback).exists()).toBe(
true,
);
getGroupPathAvailability.mockClear();
await findGroupUrlField().setValue('foo-bar');
expect(getGroupPathAvailability).not.toHaveBeenCalled();
});
});
});
describe('when `Group URL` field is invalid', () => {
......@@ -258,4 +323,25 @@ describe('GroupNameAndPath', () => {
expect(findGroupUrlField().attributes('data-bind-in')).toBeUndefined();
});
});
describe('when editing a group', () => {
it('shows warning alert with `Learn more` link', () => {
createComponentEditGroup();
expect(findAlert().exists()).toBe(true);
expect(findAlert().findByRole('link', { name: 'Learn more' }).attributes('href')).toBe(
helpPagePath('user/group/index', {
anchor: 'change-a-groups-path',
}),
);
});
it('shows `Group ID` field', () => {
createComponentEditGroup();
expect(
wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.groupId.label).element.value,
).toBe('1');
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment