Commit fea17513 authored by Andrew Fontaine's avatar Andrew Fontaine Committed by Martin Wortschack

Refactor Strategy Component to Break Out Parameter

By splitting out strategy into one sub-component per strategy, we should
be able to make it easier to add more strategies down the road.

Strategy sub-components are responsible for knowing how they alter the
strategy object, and emit it upward when changes are made.

The parent strategy component keeps a map between strategy types and
their components, utilizing Vue's `component` component to dynamically
display the correct sub-component in the DOM.
parent abc46947
...@@ -3,7 +3,7 @@ import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle, GlIcon } from ...@@ -3,7 +3,7 @@ import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle, GlIcon } from
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ROLLOUT_STRATEGY_PERCENT_ROLLOUT, NEW_VERSION_FLAG, LEGACY_FLAG } from '../constants'; import { ROLLOUT_STRATEGY_PERCENT_ROLLOUT, NEW_VERSION_FLAG, LEGACY_FLAG } from '../constants';
import labelForStrategy from '../utils'; import { labelForStrategy } from '../utils';
export default { export default {
components: { components: {
......
...@@ -477,7 +477,7 @@ export default { ...@@ -477,7 +477,7 @@ export default {
<label class="sr-only" :for="rolloutPercentageId(index)"> <label class="sr-only" :for="rolloutPercentageId(index)">
{{ s__('FeatureFlags|Rollout Percentage') }} {{ s__('FeatureFlags|Rollout Percentage') }}
</label> </label>
<div class="w-3rem"> <div class="gl-w-9">
<input <input
:id="rolloutPercentageId(index)" :id="rolloutPercentageId(index)"
v-model="scope.rolloutPercentage" v-model="scope.rolloutPercentage"
......
<script>
export default {
mounted() {
this.$emit('change', { parameters: {} });
},
render() {
return this.$slots.default;
},
};
</script>
<script>
import { GlFormSelect } from '@gitlab/ui';
import { s__ } from '~/locale';
import ParameterFormGroup from './parameter_form_group.vue';
export default {
components: {
GlFormSelect,
ParameterFormGroup,
},
props: {
strategy: {
required: true,
type: Object,
},
userLists: {
required: false,
type: Array,
default: () => [],
},
},
translations: {
rolloutUserListLabel: s__('FeatureFlag|List'),
rolloutUserListDescription: s__('FeatureFlag|Select a user list'),
rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'),
},
computed: {
userListOptions() {
return this.userLists.map(({ name, id }) => ({ value: id, text: name }));
},
hasUserLists() {
return this.userListOptions.length > 0;
},
userListId() {
return this.strategy?.userListId ?? '';
},
},
methods: {
onUserListChange(list) {
this.$emit('change', {
userListId: list,
});
},
},
};
</script>
<template>
<parameter-form-group
:state="hasUserLists"
:invalid-feedback="$options.translations.rolloutUserListNoListError"
:label="$options.translations.rolloutUserListLabel"
:description="$options.translations.rolloutUserListDescription"
>
<template #default="{ inputId }">
<gl-form-select
:id="inputId"
:value="userListId"
:options="userListOptions"
@change="onUserListChange"
/>
</template>
</parameter-form-group>
</template>
<script>
import { uniqueId } from 'lodash';
import { GlFormGroup } from '@gitlab/ui';
export default {
components: {
GlFormGroup,
},
props: {
inputId: {
required: false,
type: String,
default: () => uniqueId('feature_flag_strategies_'),
},
},
};
</script>
<template>
<gl-form-group :label-for="inputId" v-bind="$attrs">
<slot v-bind="{ inputId }"></slot>
</gl-form-group>
</template>
<script>
import { GlFormInput } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { PERCENT_ROLLOUT_GROUP_ID } from '../../constants';
import ParameterFormGroup from './parameter_form_group.vue';
export default {
components: {
GlFormInput,
ParameterFormGroup,
},
props: {
strategy: {
required: true,
type: Object,
},
},
translations: {
rolloutPercentageDescription: __('Enter a whole number between 0 and 100'),
rolloutPercentageInvalid: s__(
'FeatureFlags|Percent rollout must be a whole number between 0 and 100',
),
rolloutPercentageLabel: s__('FeatureFlag|Percentage'),
},
computed: {
isValid() {
return Number(this.percentage) >= 0 && Number(this.percentage) <= 100;
},
percentage() {
return this.strategy?.parameters?.percentage ?? '';
},
},
methods: {
onPercentageChange(value) {
this.$emit('change', {
parameters: {
percentage: value,
groupId: PERCENT_ROLLOUT_GROUP_ID,
},
});
},
},
};
</script>
<template>
<parameter-form-group
:label="$options.translations.rolloutPercentageLabel"
:description="$options.translations.rolloutPercentageDescription"
:invalid-feedback="$options.translations.rolloutPercentageInvalid"
:state="isValid"
>
<template #default="{ inputId }">
<div class="gl-display-flex gl-align-items-center">
<gl-form-input
:id="inputId"
:value="percentage"
:state="isValid"
class="rollout-percentage gl-text-right gl-w-9"
type="number"
min="0"
max="100"
@input="onPercentageChange"
/>
<span class="gl-ml-2">%</span>
</div>
</template>
</parameter-form-group>
</template>
<script>
import { GlFormTextarea } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import ParameterFormGroup from './parameter_form_group.vue';
export default {
components: {
ParameterFormGroup,
GlFormTextarea,
},
props: {
strategy: {
required: true,
type: Object,
},
},
translations: {
rolloutUserIdsDescription: __('Enter one or more user ID separated by commas'),
rolloutUserIdsLabel: s__('FeatureFlag|User IDs'),
},
computed: {
userIds() {
return this.strategy?.parameters?.userIds ?? '';
},
},
methods: {
onUserIdsChange(value) {
this.$emit('change', {
parameters: {
userIds: value,
},
});
},
},
};
</script>
<template>
<parameter-form-group
:label="$options.translations.rolloutUserIdsLabel"
:description="$options.translations.rolloutUserIdsDescription"
>
<template #default="{ inputId }">
<gl-form-textarea :id="inputId" :value="userIds" @input="onUserIdsChange" />
</template>
</parameter-form-group>
</template>
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { import { GlButton, GlFormSelect, GlFormGroup, GlIcon, GlLink, GlToken } from '@gitlab/ui';
GlButton,
GlFormSelect,
GlFormInput,
GlFormTextarea,
GlFormGroup,
GlIcon,
GlLink,
GlToken,
} from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { import { EMPTY_PARAMETERS, STRATEGY_SELECTIONS } from '../constants';
PERCENT_ROLLOUT_GROUP_ID,
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
} from '../constants';
import NewEnvironmentsDropdown from './new_environments_dropdown.vue'; import NewEnvironmentsDropdown from './new_environments_dropdown.vue';
import StrategyParameters from './strategy_parameters.vue';
export default { export default {
components: { components: {
GlButton, GlButton,
GlFormGroup, GlFormGroup,
GlFormInput,
GlFormTextarea,
GlFormSelect, GlFormSelect,
GlIcon, GlIcon,
GlLink, GlLink,
GlToken, GlToken,
NewEnvironmentsDropdown, NewEnvironmentsDropdown,
}, StrategyParameters,
model: {
prop: 'strategy',
event: 'change',
}, },
inject: { inject: {
strategyTypeDocsPagePath: { strategyTypeDocsPagePath: {
...@@ -66,86 +47,35 @@ export default { ...@@ -66,86 +47,35 @@ export default {
default: () => [], default: () => [],
}, },
}, },
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
i18n: { i18n: {
allEnvironments: __('All environments'), allEnvironments: __('All environments'),
environmentsLabel: __('Environments'), environmentsLabel: __('Environments'),
environmentsSelectDescription: __('Select the environment scope for this feature flag.'),
rolloutPercentageDescription: __('Enter a whole number between 0 and 100'),
rolloutPercentageInvalid: s__(
'FeatureFlags|Percent rollout must be a whole number between 0 and 100',
),
rolloutPercentageLabel: s__('FeatureFlag|Percentage'),
rolloutUserIdsDescription: __('Enter one or more user ID separated by commas'),
rolloutUserIdsLabel: s__('FeatureFlag|User IDs'),
rolloutUserListLabel: s__('FeatureFlag|List'), rolloutUserListLabel: s__('FeatureFlag|List'),
rolloutUserListDescription: s__('FeatureFlag|Select a user list'), rolloutUserListDescription: s__('FeatureFlag|Select a user list'),
rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'), rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'),
strategyTypeDescription: __('Select strategy activation method.'), strategyTypeDescription: __('Select strategy activation method.'),
strategyTypeLabel: s__('FeatureFlag|Type'), strategyTypeLabel: s__('FeatureFlag|Type'),
environmentsSelectDescription: s__(
'FeatureFlag|Select the environment scope for this feature flag.',
),
}, },
strategies: STRATEGY_SELECTIONS,
data() { data() {
return { return {
environments: this.strategy.scopes || [], environments: this.strategy.scopes || [],
formStrategy: { ...this.strategy }, formStrategy: { ...this.strategy },
formPercentage:
this.strategy.name === ROLLOUT_STRATEGY_PERCENT_ROLLOUT
? this.strategy.parameters.percentage
: '',
formUserIds:
this.strategy.name === ROLLOUT_STRATEGY_USER_ID ? this.strategy.parameters.userIds : '',
formUserListId:
this.strategy.name === ROLLOUT_STRATEGY_GITLAB_USER_LIST ? this.strategy.userListId : '',
strategies: [
{
value: ROLLOUT_STRATEGY_ALL_USERS,
text: __('All users'),
},
{
value: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
text: __('Percent of users'),
},
{
value: ROLLOUT_STRATEGY_USER_ID,
text: __('User IDs'),
},
{
value: ROLLOUT_STRATEGY_GITLAB_USER_LIST,
text: __('User List'),
},
],
}; };
}, },
computed: { computed: {
strategyTypeId() { strategyTypeId() {
return `strategy-type-${this.index}`; return `strategy-type-${this.index}`;
}, },
strategyPercentageId() {
return `strategy-percentage-${this.index}`;
},
strategyUserIdsId() {
return `strategy-user-ids-${this.index}`;
},
strategyUserListId() {
return `strategy-user-list-${this.index}`;
},
environmentsDropdownId() { environmentsDropdownId() {
return `environments-dropdown-${this.index}`; return `environments-dropdown-${this.index}`;
}, },
isPercentRollout() {
return this.isStrategyType(ROLLOUT_STRATEGY_PERCENT_ROLLOUT);
},
isUserWithId() {
return this.isStrategyType(ROLLOUT_STRATEGY_USER_ID);
},
isUserList() {
return this.isStrategyType(ROLLOUT_STRATEGY_GITLAB_USER_LIST);
},
appliesToAllEnvironments() { appliesToAllEnvironments() {
return ( return (
this.filteredEnvironments.length === 1 && this.filteredEnvironments.length === 1 &&
...@@ -155,12 +85,6 @@ export default { ...@@ -155,12 +85,6 @@ export default {
filteredEnvironments() { filteredEnvironments() {
return this.environments.filter(e => !e.shouldBeDestroyed); return this.environments.filter(e => !e.shouldBeDestroyed);
}, },
userListOptions() {
return this.userLists.map(({ name, id }) => ({ value: id, text: name }));
},
hasUserLists() {
return this.userListOptions.length > 0;
},
}, },
methods: { methods: {
addEnvironment(environment) { addEnvironment(environment) {
...@@ -169,33 +93,19 @@ export default { ...@@ -169,33 +93,19 @@ export default {
allEnvironmentsScope.shouldBeDestroyed = true; allEnvironmentsScope.shouldBeDestroyed = true;
} }
this.environments.push({ environmentScope: environment }); this.environments.push({ environmentScope: environment });
this.onStrategyChange(); this.onStrategyChange({ ...this.formStrategy, scopes: this.environments });
}, },
onStrategyChange() { onStrategyTypeChange(name) {
const parameters = {}; this.onStrategyChange({
const strategy = {
...this.formStrategy, ...this.formStrategy,
scopes: this.environments, ...EMPTY_PARAMETERS,
}; name,
switch (this.formStrategy.name) {
case ROLLOUT_STRATEGY_PERCENT_ROLLOUT:
parameters.percentage = this.formPercentage;
parameters.groupId = PERCENT_ROLLOUT_GROUP_ID;
break;
case ROLLOUT_STRATEGY_USER_ID:
parameters.userIds = this.formUserIds;
break;
case ROLLOUT_STRATEGY_GITLAB_USER_LIST:
strategy.userListId = this.formUserListId;
break;
default:
break;
}
this.$emit('change', {
...strategy,
parameters,
}); });
}, },
onStrategyChange(s) {
this.$emit('change', s);
this.formStrategy = s;
},
removeScope(environment) { removeScope(environment) {
if (isNumber(environment.id)) { if (isNumber(environment.id)) {
Vue.set(environment, 'shouldBeDestroyed', true); Vue.set(environment, 'shouldBeDestroyed', true);
...@@ -205,10 +115,7 @@ export default { ...@@ -205,10 +115,7 @@ export default {
if (this.filteredEnvironments.length === 0) { if (this.filteredEnvironments.length === 0) {
this.environments.push({ environmentScope: '*' }); this.environments.push({ environmentScope: '*' });
} }
this.onStrategyChange(); this.onStrategyChange({ ...this.formStrategy, scopes: this.environments });
},
isStrategyType(type) {
return this.formStrategy.name === type;
}, },
}, },
}; };
...@@ -224,60 +131,19 @@ export default { ...@@ -224,60 +131,19 @@ export default {
</gl-link> </gl-link>
<gl-form-select <gl-form-select
:id="strategyTypeId" :id="strategyTypeId"
v-model="formStrategy.name" :value="formStrategy.name"
:options="strategies" :options="$options.strategies"
@change="onStrategyChange" @change="onStrategyTypeChange"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
<div data-testid="strategy"> <div data-testid="strategy">
<gl-form-group <strategy-parameters
v-if="isPercentRollout" :strategy="strategy"
:label="$options.i18n.rolloutPercentageLabel" :user-lists="userLists"
:description="$options.i18n.rolloutPercentageDescription" @change="onStrategyChange"
:label-for="strategyPercentageId" />
:invalid-feedback="$options.i18n.rolloutPercentageInvalid"
>
<div class="gl-display-flex gl-align-items-center">
<gl-form-input
:id="strategyPercentageId"
v-model="formPercentage"
class="rollout-percentage gl-text-right gl-w-9"
type="number"
@input="onStrategyChange"
/>
<span class="gl-ml-2">%</span>
</div>
</gl-form-group>
<gl-form-group
v-if="isUserWithId"
:label="$options.i18n.rolloutUserIdsLabel"
:description="$options.i18n.rolloutUserIdsDescription"
:label-for="strategyUserIdsId"
>
<gl-form-textarea
:id="strategyUserIdsId"
v-model="formUserIds"
@input="onStrategyChange"
/>
</gl-form-group>
<gl-form-group
v-if="isUserList"
:state="hasUserLists"
:invalid-feedback="$options.i18n.rolloutUserListNoListError"
:label="$options.i18n.rolloutUserListLabel"
:description="$options.i18n.rolloutUserListDescription"
:label-for="strategyUserListId"
>
<gl-form-select
:id="strategyUserListId"
v-model="formUserListId"
:options="userListOptions"
@change="onStrategyChange"
/>
</gl-form-group>
</div> </div>
<div <div
......
<script>
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
} from '../constants';
import Default from './strategies/default.vue';
import PercentRollout from './strategies/percent_rollout.vue';
import UsersWithId from './strategies/users_with_id.vue';
import GitlabUserList from './strategies/gitlab_user_list.vue';
const STRATEGIES = Object.freeze({
[ROLLOUT_STRATEGY_ALL_USERS]: Default,
[ROLLOUT_STRATEGY_PERCENT_ROLLOUT]: PercentRollout,
[ROLLOUT_STRATEGY_USER_ID]: UsersWithId,
[ROLLOUT_STRATEGY_GITLAB_USER_LIST]: GitlabUserList,
});
export default {
props: {
strategy: {
type: Object,
required: true,
},
},
computed: {
strategyComponent() {
return STRATEGIES[(this.strategy?.name)];
},
},
methods: {
onChange(value) {
this.$emit('change', {
...this.strategy,
...value,
});
},
},
};
</script>
<template>
<component
:is="strategyComponent"
v-if="strategyComponent"
:strategy="strategy"
v-bind="$attrs"
@change="onChange"
/>
</template>
...@@ -26,3 +26,24 @@ export const NEW_FLAG_ALERT = s__( ...@@ -26,3 +26,24 @@ export const NEW_FLAG_ALERT = s__(
export const FEATURE_FLAG_SCOPE = 'featureFlags'; export const FEATURE_FLAG_SCOPE = 'featureFlags';
export const USER_LIST_SCOPE = 'userLists'; export const USER_LIST_SCOPE = 'userLists';
export const EMPTY_PARAMETERS = { parameters: {}, userListId: undefined };
export const STRATEGY_SELECTIONS = [
{
value: ROLLOUT_STRATEGY_ALL_USERS,
text: s__('FeatureFlags|All users'),
},
{
value: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
text: s__('FeatureFlags|Percent of users'),
},
{
value: ROLLOUT_STRATEGY_USER_ID,
text: s__('FeatureFlags|User IDs'),
},
{
value: ROLLOUT_STRATEGY_GITLAB_USER_LIST,
text: s__('FeatureFlags|User List'),
},
];
...@@ -182,7 +182,7 @@ export const mapStrategiesToViewModel = strategiesFromRails => ...@@ -182,7 +182,7 @@ export const mapStrategiesToViewModel = strategiesFromRails =>
const mapStrategiesParametersToRails = params => { const mapStrategiesParametersToRails = params => {
if (params.userIds) { if (params.userIds) {
return { ...params, userIds: params.userIds.split(', ').join(',') }; return { ...params, userIds: params.userIds.replace(/\s*,\s*/g, ',') };
} }
return params; return params;
}; };
......
...@@ -30,7 +30,7 @@ const badgeTextByType = { ...@@ -30,7 +30,7 @@ const badgeTextByType = {
const scopeName = ({ environment_scope: scope }) => const scopeName = ({ environment_scope: scope }) =>
scope === ALL_ENVIRONMENTS_NAME ? s__('FeatureFlags|All Environments') : scope; scope === ALL_ENVIRONMENTS_NAME ? s__('FeatureFlags|All Environments') : scope;
export default strategy => { export const labelForStrategy = strategy => {
const { name, parameters } = badgeTextByType[strategy.name]; const { name, parameters } = badgeTextByType[strategy.name];
if (parameters) { if (parameters) {
......
...@@ -428,7 +428,6 @@ img.emoji { ...@@ -428,7 +428,6 @@ img.emoji {
/** COMMON SIZING CLASSES **/ /** COMMON SIZING CLASSES **/
.w-0 { width: 0; } .w-0 { width: 0; }
.w-8em { width: 8em; } .w-8em { width: 8em; }
.w-3rem { width: 3rem; }
.w-15p { width: 15%; } .w-15p { width: 15%; }
.w-30p { width: 30%; } .w-30p { width: 30%; }
.w-60p { width: 60%; } .w-60p { width: 60%; }
......
...@@ -2548,9 +2548,6 @@ msgstr "" ...@@ -2548,9 +2548,6 @@ msgstr ""
msgid "All threads resolved" msgid "All threads resolved"
msgstr "" msgstr ""
msgid "All users"
msgstr ""
msgid "All users must have a name." msgid "All users must have a name."
msgstr "" msgstr ""
...@@ -11125,6 +11122,9 @@ msgstr "" ...@@ -11125,6 +11122,9 @@ msgstr ""
msgid "FeatureFlag|Select a user list" msgid "FeatureFlag|Select a user list"
msgstr "" msgstr ""
msgid "FeatureFlag|Select the environment scope for this feature flag."
msgstr ""
msgid "FeatureFlag|There are no configured user lists" msgid "FeatureFlag|There are no configured user lists"
msgstr "" msgstr ""
...@@ -18578,9 +18578,6 @@ msgstr "" ...@@ -18578,9 +18578,6 @@ msgstr ""
msgid "People without permission will never get a notification." msgid "People without permission will never get a notification."
msgstr "" msgstr ""
msgid "Percent of users"
msgstr ""
msgid "Percentage" msgid "Percentage"
msgstr "" msgstr ""
...@@ -23132,9 +23129,6 @@ msgstr "" ...@@ -23132,9 +23129,6 @@ msgstr ""
msgid "Select the custom project template source group." msgid "Select the custom project template source group."
msgstr "" msgstr ""
msgid "Select the environment scope for this feature flag."
msgstr ""
msgid "Select timezone" msgid "Select timezone"
msgstr "" msgstr ""
...@@ -27965,12 +27959,6 @@ msgstr "" ...@@ -27965,12 +27959,6 @@ msgstr ""
msgid "User %{username} was successfully removed." msgid "User %{username} was successfully removed."
msgstr "" msgstr ""
msgid "User IDs"
msgstr ""
msgid "User List"
msgstr ""
msgid "User OAuth applications" msgid "User OAuth applications"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import Default from '~/feature_flags/components/strategies/default.vue';
describe('~/feature_flags/components/strategies/default.vue', () => {
it('should emit an empty parameter object on mount', () => {
const wrapper = shallowMount(Default);
expect(wrapper.emitted('change')).toEqual([[{ parameters: {} }]]);
});
});
import { mount } from '@vue/test-utils';
import { GlFormSelect } from '@gitlab/ui';
import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
import { userListStrategy, userList } from '../../mock_data';
const DEFAULT_PROPS = {
strategy: userListStrategy,
userLists: [userList],
};
describe('~/feature_flags/components/strategies/gitlab_user_list.vue', () => {
let wrapper;
const factory = (props = {}) =>
mount(GitlabUserList, { propsData: { ...DEFAULT_PROPS, ...props } });
describe('with user lists', () => {
beforeEach(() => {
wrapper = factory();
});
it('should show the input for userListId with the correct value', () => {
const inputWrapper = wrapper.find(GlFormSelect);
expect(inputWrapper.exists()).toBe(true);
expect(inputWrapper.element.value).toBe('2');
});
it('should emit a change event when altering the userListId', () => {
const inputWrapper = wrapper.find(GitlabUserList);
inputWrapper.vm.$emit('change', {
userListId: '3',
});
expect(wrapper.emitted('change')).toEqual([
[
{
userListId: '3',
},
],
]);
});
});
describe('without user lists', () => {
beforeEach(() => {
wrapper = factory({ userLists: [] });
});
it('should display a message that there are no user lists', () => {
expect(wrapper.text()).toContain('There are no configured user lists');
});
});
});
import { mount } from '@vue/test-utils';
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
describe('~/feature_flags/strategies/parameter_form_group.vue', () => {
let wrapper;
let formGroup;
let slot;
beforeEach(() => {
wrapper = mount(ParameterFormGroup, {
propsData: { inputId: 'test-id', label: 'test' },
attrs: { description: 'test description' },
scopedSlots: {
default(props) {
return this.$createElement(GlFormInput, {
attrs: { id: props.inputId, 'data-testid': 'slot' },
});
},
},
});
formGroup = wrapper.find(GlFormGroup);
slot = wrapper.find('[data-testid="slot"]');
});
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = null;
});
it('should display the default slot', () => {
expect(slot.exists()).toBe(true);
});
it('should bind the input id to the slot', () => {
expect(slot.attributes('id')).toBe('test-id');
});
it('should bind the label-for to the input id', () => {
expect(formGroup.find('[for="test-id"]').exists()).toBe(true);
});
it('should bind extra attributes to the form group', () => {
expect(formGroup.attributes('description')).toBe('test description');
});
});
import { mount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
import { PERCENT_ROLLOUT_GROUP_ID } from '~/feature_flags/constants';
import { percentRolloutStrategy } from '../../mock_data';
const DEFAULT_PROPS = {
strategy: percentRolloutStrategy,
};
describe('~/feature_flags/components/strategies/percent_rollout.vue', () => {
let wrapper;
let input;
let formGroup;
const factory = (props = {}) =>
mount(PercentRollout, { propsData: { ...DEFAULT_PROPS, ...props } });
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = null;
});
describe('with valid percentage', () => {
beforeEach(() => {
wrapper = factory();
input = wrapper.find(GlFormInput);
formGroup = wrapper.find(ParameterFormGroup);
});
it('displays the current value', () => {
expect(input.element.value).toBe(percentRolloutStrategy.parameters.percentage);
});
it('emits a change when the value changes', async () => {
input.setValue('75');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('change')).toEqual([
[{ parameters: { percentage: '75', groupId: PERCENT_ROLLOUT_GROUP_ID } }],
]);
});
it('does not show errors', () => {
expect(formGroup.attributes('state')).toBe('true');
});
});
describe('with percentage that is out of range', () => {
beforeEach(() => {
wrapper = factory({ strategy: { parameters: { percentage: '101' } } });
input = wrapper.find(GlFormInput);
formGroup = wrapper.find(ParameterFormGroup);
});
it('shows errors', () => {
expect(formGroup.attributes('state')).toBeUndefined();
});
});
});
import { mount } from '@vue/test-utils';
import { GlFormTextarea } from '@gitlab/ui';
import UsersWithId from '~/feature_flags/components/strategies/users_with_id.vue';
import { usersWithIdStrategy } from '../../mock_data';
const DEFAULT_PROPS = {
strategy: usersWithIdStrategy,
};
describe('~/feature_flags/components/users_with_id.vue', () => {
let wrapper;
let textarea;
const factory = (props = {}) => mount(UsersWithId, { propsData: { ...DEFAULT_PROPS, ...props } });
beforeEach(() => {
wrapper = factory();
textarea = wrapper.find(GlFormTextarea);
});
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = null;
});
it('should display the current value of the parameters', () => {
expect(textarea.element.value).toBe(usersWithIdStrategy.parameters.userIds);
});
it('should emit a change event when the IDs change', () => {
textarea.setValue('4,5,6');
expect(wrapper.emitted('change')).toEqual([[{ parameters: { userIds: '4,5,6' } }]]);
});
});
import { shallowMount } from '@vue/test-utils';
import { last } from 'lodash';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
} from '~/feature_flags/constants';
import Default from '~/feature_flags/components/strategies/default.vue';
import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
import UsersWithId from '~/feature_flags/components/strategies/users_with_id.vue';
import StrategyParameters from '~/feature_flags/components/strategy_parameters.vue';
import { allUsersStrategy, userList } from '../mock_data';
const DEFAULT_PROPS = {
strategy: allUsersStrategy,
userLists: [userList],
};
describe('~/feature_flags/components/strategy_parameters.vue', () => {
let wrapper;
const factory = (props = {}) =>
shallowMount(StrategyParameters, {
propsData: {
...DEFAULT_PROPS,
...props,
},
});
afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = null;
});
describe.each`
name | component
${ROLLOUT_STRATEGY_ALL_USERS} | ${Default}
${ROLLOUT_STRATEGY_PERCENT_ROLLOUT} | ${PercentRollout}
${ROLLOUT_STRATEGY_USER_ID} | ${UsersWithId}
${ROLLOUT_STRATEGY_GITLAB_USER_LIST} | ${GitlabUserList}
`('with $name', ({ name, component }) => {
let strategy;
beforeEach(() => {
strategy = { name, parameters: {} };
wrapper = factory({ strategy });
});
it('should show the correct component', () => {
expect(wrapper.contains(component)).toBe(true);
});
it('should emit changes from the lower component', () => {
const strategyParameterWrapper = wrapper.find(component);
strategyParameterWrapper.vm.$emit('change', { parameters: { foo: 'bar' } });
expect(last(wrapper.emitted('change'))).toEqual([
{
name,
parameters: { foo: 'bar' },
},
]);
});
});
describe('pass through props', () => {
it('should pass through any extra props that might be needed', () => {
wrapper = factory({
strategy: {
name: ROLLOUT_STRATEGY_GITLAB_USER_LIST,
},
});
expect(wrapper.find(GitlabUserList).props('userLists')).toEqual([userList]);
});
});
});
import { import {
ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT, ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
ROLLOUT_STRATEGY_USER_ID,
} from '~/feature_flags/constants'; } from '~/feature_flags/constants';
export const featureFlag = { export const featureFlag = {
...@@ -102,6 +104,25 @@ export const userList = { ...@@ -102,6 +104,25 @@ export const userList = {
edit_path: '/path/to/user/list/edit', edit_path: '/path/to/user/list/edit',
}; };
export const userListStrategy = {
name: ROLLOUT_STRATEGY_GITLAB_USER_LIST,
parameters: {},
scopes: [],
userListId: userList.id,
};
export const percentRolloutStrategy = {
name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: { percentage: '50', groupId: 'default' },
scopes: [],
};
export const usersWithIdStrategy = {
name: ROLLOUT_STRATEGY_USER_ID,
parameters: { userIds: '1,2,3' },
scopes: [],
};
export const allUsersStrategy = { export const allUsersStrategy = {
name: ROLLOUT_STRATEGY_ALL_USERS, name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {}, parameters: {},
......
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