Commit 959dc60e authored by peterhegman's avatar peterhegman

Implement token selector component

Refactor "Restrict membership by email" field to use GitLab UI token
selector component instead of a comma separated list.
parent 81be8aed
...@@ -554,7 +554,7 @@ Some domains cannot be restricted. These are the most popular public email domai ...@@ -554,7 +554,7 @@ Some domains cannot be restricted. These are the most popular public email domai
To enable this feature: To enable this feature:
1. Navigate to the group's **Settings > General** page. 1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and enter the domain names into **Restrict membership by email** field. You can enter multiple domains by separating each domain with a comma (,). 1. Expand the **Permissions, LFS, 2FA** section, and enter the domain names into **Restrict membership by email** field.
1. Click **Save changes**. 1. Click **Save changes**.
This will enable the domain-checking for all new users added to the group from this moment on. This will enable the domain-checking for all new users added to the group from this moment on.
......
import Vue from 'vue';
import { __, sprintf } from '~/locale';
import CommaSeparatedListTokenSelector from '../components/comma_separated_list_token_selector.vue';
export default () => {
// eslint-disable-next-line no-new
new Vue({
el: '.js-allowed-email-domains',
components: {
CommaSeparatedListTokenSelector,
},
data() {
const { dataset } = document.querySelector(this.$options.el);
return {
hiddenInputId: dataset.hiddenInputId,
labelId: dataset.labelId,
};
},
render(createElement) {
return createElement('comma-separated-list-token-selector', {
props: {
hiddenInputId: this.hiddenInputId,
ariaLabelledby: this.labelId,
placeholder: __('Enter domain'),
},
scopedSlots: {
'user-defined-token-content': ({ inputText: value }) => {
return sprintf(__('Add "%{value}" to allowlist'), { value });
},
},
});
},
});
};
<script>
import { GlTokenSelector } from '@gitlab/ui';
export default {
name: 'CommaSeparatedListTokenSelector',
hiddenInput: null,
components: { GlTokenSelector },
props: {
hiddenInputId: {
type: String,
required: true,
},
ariaLabelledby: {
type: String,
required: true,
},
placeholder: {
type: String,
required: false,
default: null,
},
},
data() {
return {
selectedTokens: [],
};
},
watch: {
selectedTokens(newValue) {
this.$options.hiddenInput.value = newValue.map(token => token.name).join(',');
// Dispatch `input` event so form submit button becomes active
this.$options.hiddenInput.dispatchEvent(
new Event('input', {
bubbles: true,
cancelable: true,
}),
);
},
},
mounted() {
const hiddenInput = document.getElementById(this.hiddenInputId);
this.$options.hiddenInput = hiddenInput;
if (hiddenInput.value === '') {
return;
}
this.selectedTokens = hiddenInput.value.split(/,\s*/).map((token, index) => ({
id: index,
name: token,
}));
},
methods: {
handleEnter(event) {
// Prevent form from submitting when adding a token
if (event.target.value !== '') {
event.preventDefault();
}
},
},
};
</script>
<template>
<gl-token-selector
v-model="selectedTokens"
container-class="gl-h-auto!"
allow-user-defined-tokens
hide-dropdown-with-no-items
:aria-labelledby="ariaLabelledby"
:placeholder="placeholder"
@keydown.enter="handleEnter"
>
<template #user-defined-token-content="{ inputText }">
<slot name="user-defined-token-content" :input-text="inputText"></slot>
</template>
</gl-token-selector>
</template>
import '~/pages/groups/edit';
import initAllowedEmailDomains from 'ee/groups/settings/allowed_email_domains';
document.addEventListener('DOMContentLoaded', () => {
initAllowedEmailDomains();
});
- return if !group.feature_available?(:group_allowed_email_domains) || group.parent_id.present? - return if !group.feature_available?(:group_allowed_email_domains) || group.parent_id.present?
- hidden_input_id = 'group_allowed_email_domains_list'
%h5= _('Restrict membership by email') - label_id = "#{hidden_input_id}_label"
.form-group .form-group
= f.text_field :allowed_email_domains_list, class: 'form-control', placeholder: _('Enter domain') %label{ id: label_id }
= _('Restrict membership by email')
.js-allowed-email-domains{ data: { hidden_input_id: hidden_input_id, label_id: label_id } }
= f.hidden_field :allowed_email_domains_list, id: hidden_input_id
.form-text.text-muted .form-text.text-muted
- read_more_link = link_to(_('Read more'), help_page_path('user/group/index', anchor: 'allowed-domain-restriction-premium')) - read_more_link = link_to(_('Read more'), help_page_path('user/group/index', anchor: 'allowed-domain-restriction-premium'))
= _('Only verified users with an email address in any of these domains can be added to the group.') = _('Only verified users with an email address in any of these domains can be added to the group.')
%br %br
= _('Multiple domains are supported with comma delimiters.')
%br
= _('Example: <code>acme.com,acme.co.in,acme.uk</code>.').html_safe
%br
= _('Some common domains are not allowed. %{read_more_link}.').html_safe % { read_more_link: read_more_link } = _('Some common domains are not allowed. %{read_more_link}.').html_safe % { read_more_link: read_more_link }
---
title: Change "Restrict membership by email" field from a comma separated list to
the GitLab UI Token Selector component
merge_request: 35543
author:
type: changed
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import { GlToken, GlTokenSelector } from '@gitlab/ui';
import CommaSeparatedListTokenSelector from 'ee/groups/settings/components/comma_separated_list_token_selector.vue';
describe('CommaSeparatedListTokenSelector', () => {
let wrapper;
let div;
let input;
const defaultProps = {
hiddenInputId: 'comma-separated-list',
ariaLabelledby: 'comma-separated-list-label',
};
const createComponent = options => {
wrapper = mount(CommaSeparatedListTokenSelector, {
attachTo: div,
...options,
propsData: {
...defaultProps,
...(options?.propsData || {}),
},
});
};
beforeEach(() => {
div = document.createElement('div');
input = document.createElement('input');
input.setAttribute('type', 'text');
input.id = 'comma-separated-list';
document.body.appendChild(div);
div.appendChild(input);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
div.remove();
});
describe('when component is mounted', () => {
it.each`
inputValue | expectedTokens
${'gitlab.com,gitlab.org,gitlab.ninja'} | ${['gitlab.com', 'gitlab.org', 'gitlab.ninja']}
${'gitlab.com, gitlab.org, gitlab.ninja'} | ${['gitlab.com', 'gitlab.org', 'gitlab.ninja']}
${'foo bar, baz'} | ${['foo bar', 'baz']}
${'192.168.0.0/24,192.168.1.0/24'} | ${['192.168.0.0/24', '192.168.1.0/24']}
`(
'parses comma separated list ($inputValue) into tokens',
async ({ inputValue, expectedTokens }) => {
input.value = inputValue;
createComponent();
await nextTick();
wrapper.findAll(GlToken).wrappers.forEach((tokenWrapper, index) => {
expect(tokenWrapper.text()).toBe(expectedTokens[index]);
});
},
);
});
describe('when selected tokens changes', () => {
const setup = async () => {
const tokens = [
{
id: 1,
name: 'gitlab.com',
},
{
id: 2,
name: 'gitlab.org',
},
{
id: 3,
name: 'gitlab.ninja',
},
];
createComponent();
await wrapper.setData({
selectedTokens: tokens,
});
};
it('sets input value ', async () => {
await setup();
expect(input.value).toBe('gitlab.com,gitlab.org,gitlab.ninja');
});
it('fires `input` event', async () => {
const dispatchEvent = jest.spyOn(input, 'dispatchEvent');
await setup();
expect(dispatchEvent).toHaveBeenCalledWith(
new Event('input', {
bubbles: true,
cancelable: true,
}),
);
});
});
describe('when enter key is pressed', () => {
it('does not submit the form if token selector text input has a value', async () => {
createComponent();
const tokenSelectorInput = wrapper.find(GlTokenSelector).find('input[type="text"]');
tokenSelectorInput.element.value = 'foo bar';
const event = { preventDefault: jest.fn() };
await tokenSelectorInput.trigger('keydown.enter', event);
expect(event.preventDefault).toHaveBeenCalled();
});
});
});
...@@ -83,7 +83,8 @@ RSpec.describe 'groups/edit.html.haml' do ...@@ -83,7 +83,8 @@ RSpec.describe 'groups/edit.html.haml' do
expect(rendered).to render_template('groups/settings/_allowed_email_domain') expect(rendered).to render_template('groups/settings/_allowed_email_domain')
expect(rendered).to(have_field('group_allowed_email_domains_list', expect(rendered).to(have_field('group_allowed_email_domains_list',
{ disabled: false, { disabled: false,
with: domains.join(",") })) with: domains.join(","),
type: :hidden }))
end end
end end
......
...@@ -1284,6 +1284,9 @@ msgstr "" ...@@ -1284,6 +1284,9 @@ msgstr ""
msgid "Add" msgid "Add"
msgstr "" msgstr ""
msgid "Add \"%{value}\" to allowlist"
msgstr ""
msgid "Add %d issue" msgid "Add %d issue"
msgid_plural "Add %d issues" msgid_plural "Add %d issues"
msgstr[0] "" msgstr[0] ""
...@@ -9275,9 +9278,6 @@ msgstr "" ...@@ -9275,9 +9278,6 @@ msgstr ""
msgid "Exactly one of %{attributes} is required" msgid "Exactly one of %{attributes} is required"
msgstr "" msgstr ""
msgid "Example: <code>acme.com,acme.co.in,acme.uk</code>."
msgstr ""
msgid "Example: @sub\\.company\\.com$" msgid "Example: @sub\\.company\\.com$"
msgstr "" msgstr ""
...@@ -14799,9 +14799,6 @@ msgstr "" ...@@ -14799,9 +14799,6 @@ msgstr ""
msgid "MrDeploymentActions|Stop environment" msgid "MrDeploymentActions|Stop environment"
msgstr "" msgstr ""
msgid "Multiple domains are supported with comma delimiters."
msgstr ""
msgid "Multiple issue boards" msgid "Multiple issue boards"
msgstr "" msgstr ""
......
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