Commit 2a55152c authored by Andrew Fontaine's avatar Andrew Fontaine

Remove Legacy Flags from Form

As legacy flags are no longer visible, they no longer need to be viewed.
We can now delete all the code that was used to manage them.
parent a6ae996d
<script>
import {
GlButton,
GlBadge,
GlTooltip,
GlTooltipDirective,
GlFormTextarea,
GlFormCheckbox,
GlSprintf,
GlIcon,
GlToggle,
} from '@gitlab/ui';
import { memoize, isString, cloneDeep, isNumber, uniqueId } from 'lodash';
import { GlButton } from '@gitlab/ui';
import { memoize, cloneDeep, isNumber, uniqueId } from 'lodash';
import Vue from 'vue';
import { s__ } from '~/locale';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
......@@ -20,12 +10,8 @@ import {
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID,
ALL_ENVIRONMENTS_NAME,
INTERNAL_ID_PREFIX,
NEW_VERSION_FLAG,
LEGACY_FLAG,
} from '../constants';
import { createNewEnvironmentScope } from '../store/helpers';
import EnvironmentsDropdown from './environments_dropdown.vue';
import Strategy from './strategy.vue';
export default {
......@@ -35,20 +21,9 @@ export default {
},
components: {
GlButton,
GlBadge,
GlFormTextarea,
GlFormCheckbox,
GlTooltip,
GlSprintf,
GlIcon,
GlToggle,
EnvironmentsDropdown,
Strategy,
RelatedIssuesRoot,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [featureFlagsMixin()],
inject: {
featureFlagIssuesEndpoint: {
......@@ -71,11 +46,6 @@ export default {
required: false,
default: '',
},
scopes: {
type: Array,
required: false,
default: () => [],
},
cancelPath: {
type: String,
required: true,
......@@ -89,11 +59,6 @@ export default {
required: false,
default: () => [],
},
version: {
type: String,
required: false,
default: LEGACY_FLAG,
},
},
translations: {
allEnvironmentsText: s__('FeatureFlags|* (All Environments)'),
......@@ -120,35 +85,18 @@ export default {
formName: this.name,
formDescription: this.description,
// operate on a clone to avoid mutating props
formScopes: this.scopes.map((s) => ({ ...s })),
formStrategies: cloneDeep(this.strategies),
newScope: '',
};
},
computed: {
filteredScopes() {
return this.formScopes.filter((scope) => !scope.shouldBeDestroyed);
},
filteredStrategies() {
return this.formStrategies.filter((s) => !s.shouldBeDestroyed);
},
canUpdateFlag() {
return !this.permissionsFlag || (this.formScopes || []).every((scope) => scope.canUpdate);
},
permissionsFlag() {
return this.glFeatures.featureFlagPermissions;
},
supportsStrategies() {
return this.version === NEW_VERSION_FLAG;
},
showRelatedIssues() {
return this.featureFlagIssuesEndpoint.length > 0;
},
readOnly() {
return this.version === LEGACY_FLAG;
},
},
methods: {
keyFor(strategy) {
......@@ -174,37 +122,6 @@ export default {
isAllEnvironment(name) {
return name === ALL_ENVIRONMENTS_NAME;
},
/**
* When the user clicks the remove button we delete the scope
*
* If the scope has an ID, we need to add the `shouldBeDestroyed` flag.
* If the scope does *not* have an ID, we can just remove it.
*
* This flag will be used when submitting the data to the backend
* to determine which records to delete (via a "_destroy" property).
*
* @param {Object} scope
*/
removeScope(scope) {
if (isString(scope.id) && scope.id.startsWith(INTERNAL_ID_PREFIX)) {
this.formScopes = this.formScopes.filter((s) => s !== scope);
} else {
Vue.set(scope, 'shouldBeDestroyed', true);
}
},
/**
* Creates a new scope and adds it to the list of scopes
*
* @param overrides An object whose properties will
* be used override the default scope options
*/
createNewScope(overrides) {
this.formScopes.push(createNewEnvironmentScope(overrides, this.permissionsFlag));
this.newScope = '';
},
/**
* When the user clicks the submit button
* it triggers an event with the form data
......@@ -214,61 +131,16 @@ export default {
name: this.formName,
description: this.formDescription,
active: this.active,
version: this.version,
version: NEW_VERSION_FLAG,
strategies: this.formStrategies,
};
if (this.version === LEGACY_FLAG) {
flag.scopes = this.formScopes;
} else {
flag.strategies = this.formStrategies;
}
this.$emit('handleSubmit', flag);
},
canUpdateScope(scope) {
return !this.permissionsFlag || scope.canUpdate;
},
isRolloutPercentageInvalid: memoize(function isRolloutPercentageInvalid(percentage) {
return !this.$options.rolloutPercentageRegex.test(percentage);
}),
/**
* Generates a unique ID for the strategy based on the v-for index
*
* @param index The index of the strategy
*/
rolloutStrategyId(index) {
return `rollout-strategy-${index}`;
},
/**
* Generates a unique ID for the percentage based on the v-for index
*
* @param index The index of the percentage
*/
rolloutPercentageId(index) {
return `rollout-percentage-${index}`;
},
rolloutUserId(index) {
return `rollout-user-id-${index}`;
},
shouldDisplayIncludeUserIds(scope) {
return ![ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_USER_ID].includes(
scope.rolloutStrategy,
);
},
shouldDisplayUserIds(scope) {
return scope.rolloutStrategy === ROLLOUT_STRATEGY_USER_ID || scope.shouldIncludeUserIds;
},
onStrategyChange(index) {
const scope = this.filteredScopes[index];
scope.shouldIncludeUserIds =
scope.rolloutUserIds.length > 0 &&
scope.rolloutStrategy === ROLLOUT_STRATEGY_PERCENT_ROLLOUT;
},
onFormStrategyChange(strategy, index) {
Object.assign(this.filteredStrategies[index], strategy);
},
......@@ -281,12 +153,7 @@ export default {
<div class="row">
<div class="form-group col-md-4">
<label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }} *</label>
<input
id="feature-flag-name"
v-model="formName"
:disabled="!canUpdateFlag"
class="form-control"
/>
<input id="feature-flag-name" v-model="formName" class="form-control" />
</div>
</div>
......@@ -298,7 +165,6 @@ export default {
<textarea
id="feature-flag-description"
v-model="formDescription"
:disabled="!canUpdateFlag"
class="form-control"
rows="4"
></textarea>
......@@ -312,277 +178,35 @@ export default {
:show-categorized-issues="false"
/>
<template v-if="supportsStrategies">
<div class="row">
<div class="col-md-12">
<h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between">
<p class="mr-3">{{ $options.translations.newHelpText }}</p>
<gl-button variant="confirm" category="secondary" @click="addStrategy">
{{ s__('FeatureFlags|Add strategy') }}
</gl-button>
</div>
</div>
</div>
<div v-if="filteredStrategies.length > 0" data-testid="feature-flag-strategies">
<strategy
v-for="(strategy, index) in filteredStrategies"
:key="keyFor(strategy)"
:strategy="strategy"
:index="index"
@change="onFormStrategyChange($event, index)"
@delete="deleteStrategy(strategy)"
/>
</div>
<div v-else class="flex justify-content-center border-top py-4 w-100">
<span>{{ $options.translations.noStrategiesText }}</span>
</div>
</template>
<div v-else class="row">
<div class="form-group col-md-12">
<h4>{{ s__('FeatureFlags|Target environments') }}</h4>
<gl-sprintf :message="$options.translations.helpText">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
<template #bold="{ content }">
<b>{{ content }}</b>
</template>
</gl-sprintf>
<div class="js-scopes-table gl-mt-3">
<div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-30" role="columnheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-section section-20 text-center" role="columnheader">
{{ s__('FeatureFlags|Status') }}
</div>
<div class="table-section section-40" role="columnheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
</div>
<div
v-for="(scope, index) in filteredScopes"
:key="scope.id"
ref="scopeRow"
class="gl-responsive-table-row"
role="row"
>
<div class="table-section section-30" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div
class="table-mobile-content gl-display-flex gl-align-items-center gl-justify-content-start"
>
<p v-if="isAllEnvironment(scope.environmentScope)" class="js-scope-all pl-3">
{{ $options.translations.allEnvironmentsText }}
</p>
<environments-dropdown
v-else
class="col-12"
:value="scope.environmentScope"
:disabled="!canUpdateScope(scope) || scope.environmentScope !== ''"
@selectEnvironment="(env) => (scope.environmentScope = env)"
@createClicked="(env) => (scope.environmentScope = env)"
@clearInput="(env) => (scope.environmentScope = '')"
/>
<gl-badge v-if="permissionsFlag && scope.protected" variant="success">
{{ s__('FeatureFlags|Protected') }}
</gl-badge>
</div>
</div>
<div class="table-section section-20 text-center" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ $options.i18n.statusLabel }}
</div>
<div class="table-mobile-content gl-display-flex gl-justify-content-center">
<gl-toggle
:value="scope.active"
:disabled="!active || !canUpdateScope(scope)"
:label="$options.i18n.statusLabel"
label-position="hidden"
@change="(status) => (scope.active = status)"
/>
</div>
</div>
<div class="table-section section-40" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
<div class="table-mobile-content js-rollout-strategy form-inline">
<label class="sr-only" :for="rolloutStrategyId(index)">
{{ s__('FeatureFlags|Rollout Strategy') }}
</label>
<div class="select-wrapper col-12 col-md-8 p-0">
<select
:id="rolloutStrategyId(index)"
v-model="scope.rolloutStrategy"
:disabled="!scope.active"
class="form-control select-control w-100 js-rollout-strategy"
@change="onStrategyChange(index)"
>
<option :value="$options.ROLLOUT_STRATEGY_ALL_USERS">
{{ s__('FeatureFlags|All users') }}
</option>
<option :value="$options.ROLLOUT_STRATEGY_PERCENT_ROLLOUT">
{{ s__('FeatureFlags|Percent rollout (logged in users)') }}
</option>
<option :value="$options.ROLLOUT_STRATEGY_USER_ID">
{{ s__('FeatureFlags|User IDs') }}
</option>
</select>
<gl-icon
name="chevron-down"
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500"
:size="16"
/>
</div>
<div
v-if="scope.rolloutStrategy === $options.ROLLOUT_STRATEGY_PERCENT_ROLLOUT"
class="d-flex-center mt-2 mt-md-0 ml-md-2"
>
<label class="sr-only" :for="rolloutPercentageId(index)">
{{ s__('FeatureFlags|Rollout Percentage') }}
</label>
<div class="gl-w-9">
<input
:id="rolloutPercentageId(index)"
v-model="scope.rolloutPercentage"
:disabled="!scope.active"
:class="{
'is-invalid': isRolloutPercentageInvalid(scope.rolloutPercentage),
}"
type="number"
min="0"
max="100"
:pattern="$options.rolloutPercentageRegex.source"
class="rollout-percentage js-rollout-percentage form-control text-right w-100"
/>
</div>
<gl-tooltip
v-if="isRolloutPercentageInvalid(scope.rolloutPercentage)"
:target="rolloutPercentageId(index)"
>
{{
s__(
'FeatureFlags|Percent rollout must be an integer number between 0 and 100',
)
}}
</gl-tooltip>
<span class="ml-1">%</span>
</div>
<div class="d-flex flex-column align-items-start mt-2 w-100">
<gl-form-checkbox
v-if="shouldDisplayIncludeUserIds(scope)"
v-model="scope.shouldIncludeUserIds"
>{{ s__('FeatureFlags|Include additional user IDs') }}</gl-form-checkbox
>
<template v-if="shouldDisplayUserIds(scope)">
<label :for="rolloutUserId(index)" class="mb-2">
{{ s__('FeatureFlags|User IDs') }}
</label>
<gl-form-textarea
:id="rolloutUserId(index)"
v-model="scope.rolloutUserIds"
class="w-100"
/>
</template>
</div>
</div>
</div>
<div class="table-section section-10 text-right" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Remove') }}
</div>
<div class="table-mobile-content">
<gl-button
v-if="!isAllEnvironment(scope.environmentScope) && canUpdateScope(scope)"
v-gl-tooltip
:title="$options.i18n.removeLabel"
:aria-label="$options.i18n.removeLabel"
class="js-delete-scope btn-transparent pr-3 pl-3"
icon="clear"
data-testid="feature-flag-delete"
@click="removeScope(scope)"
/>
</div>
</div>
</div>
<div class="gl-responsive-table-row" role="row" data-testid="add-new-scope">
<div class="table-section section-30" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Environment Spec') }}
</div>
<div class="table-mobile-content">
<environments-dropdown
class="js-new-scope-name col-12"
:value="newScope"
@selectEnvironment="(env) => createNewScope({ environmentScope: env })"
@createClicked="(env) => createNewScope({ environmentScope: env })"
/>
</div>
</div>
<div class="table-section section-20 text-center" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ $options.i18n.statusLabel }}
</div>
<div class="table-mobile-content gl-display-flex gl-justify-content-center">
<gl-toggle
:disabled="!active"
:label="$options.i18n.statusLabel"
label-position="hidden"
:value="false"
@change="createNewScope({ active: true })"
/>
</div>
</div>
<div class="table-section section-40" role="gridcell">
<div class="table-mobile-header" role="rowheader">
{{ s__('FeatureFlags|Rollout Strategy') }}
</div>
<div class="table-mobile-content js-rollout-strategy form-inline">
<label class="sr-only" for="new-rollout-strategy-placeholder">{{
s__('FeatureFlags|Rollout Strategy')
}}</label>
<div class="select-wrapper col-12 col-md-8 p-0">
<select
id="new-rollout-strategy-placeholder"
disabled
class="form-control select-control w-100"
>
<option>{{ s__('FeatureFlags|All users') }}</option>
</select>
<gl-icon
name="chevron-down"
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500"
:size="16"
/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between">
<p class="mr-3">{{ $options.translations.newHelpText }}</p>
<gl-button variant="confirm" category="secondary" @click="addStrategy">
{{ s__('FeatureFlags|Add strategy') }}
</gl-button>
</div>
</div>
</div>
<div v-if="filteredStrategies.length > 0" data-testid="feature-flag-strategies">
<strategy
v-for="(strategy, index) in filteredStrategies"
:key="keyFor(strategy)"
:strategy="strategy"
:index="index"
@change="onFormStrategyChange($event, index)"
@delete="deleteStrategy(strategy)"
/>
</div>
<div v-else class="flex justify-content-center border-top py-4 w-100">
<span>{{ $options.translations.noStrategiesText }}</span>
</div>
</fieldset>
<div class="form-actions">
<gl-button
ref="submitButton"
:disabled="readOnly"
type="button"
variant="confirm"
class="js-ff-submit col-xs-12"
......
......@@ -13619,9 +13619,6 @@ msgstr ""
msgid "FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies."
msgstr ""
msgid "FeatureFlags|Environment Spec"
msgstr ""
msgid "FeatureFlags|Environment Specs"
msgstr ""
......@@ -13667,9 +13664,6 @@ msgstr ""
msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr ""
msgid "FeatureFlags|Include additional user IDs"
msgstr ""
msgid "FeatureFlags|Install a %{docsLinkAnchoredStart}compatible client library%{docsLinkAnchoredEnd} and specify the API URL, application name, and instance ID during the configuration setup. %{docsLinkStart}More Information%{docsLinkEnd}"
msgstr ""
......@@ -13709,24 +13703,12 @@ msgstr ""
msgid "FeatureFlags|Percent rollout"
msgstr ""
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
msgid "FeatureFlags|Percent rollout must be an integer number between 0 and 100"
msgstr ""
msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Remove"
msgstr ""
msgid "FeatureFlags|Rollout Percentage"
msgstr ""
msgid "FeatureFlags|Rollout Strategy"
msgstr ""
msgid "FeatureFlags|Set the Unleash client application name to the name of the environment your application runs in. This value is used to match environment scopes. See the %{linkStart}example client configuration%{linkEnd}."
msgstr ""
......@@ -13736,9 +13718,6 @@ msgstr ""
msgid "FeatureFlags|Strategies"
msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr ""
......
......@@ -115,7 +115,7 @@ describe('Edit feature flag form', () => {
});
it('should set the version of the form from the feature flag', () => {
expect(wrapper.find(Form).props('version')).toBe(LEGACY_FLAG);
expect(wrapper.find(Form).attributes('version')).toBe(LEGACY_FLAG);
mock.resetHandlers();
......@@ -136,7 +136,7 @@ describe('Edit feature flag form', () => {
factory();
return axios.waitForAll().then(() => {
expect(wrapper.find(Form).props('version')).toBe(NEW_VERSION_FLAG);
expect(wrapper.find(Form).attributes('version')).toBe(NEW_VERSION_FLAG);
});
});
......
import { GlFormTextarea, GlFormCheckbox, GlButton, GlToggle } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { uniqueId } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Api from '~/api';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
import Form from '~/feature_flags/components/form.vue';
import Strategy from '~/feature_flags/components/strategy.vue';
import {
ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
INTERNAL_ID_PREFIX,
DEFAULT_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
import { featureFlag, userList, allUsersStrategy } from '../mock_data';
......@@ -29,15 +23,8 @@ describe('feature flag form', () => {
const requiredInjections = {
environmentsEndpoint: '/environments.json',
projectId: '1',
glFeatures: {
featureFlagPermissions: true,
featureFlagsNewVersion: true,
},
};
const findAddNewScopeRow = () => wrapper.findByTestId('add-new-scope');
const findGlToggle = () => wrapper.find(GlToggle);
const factory = (props = {}, provide = {}) => {
wrapper = extendedWrapper(
shallowMount(Form, {
......@@ -100,328 +87,6 @@ describe('feature flag form', () => {
it('should render description textarea', () => {
expect(wrapper.find('#feature-flag-description').exists()).toBe(true);
});
describe('scopes', () => {
it('should render scopes table', () => {
expect(wrapper.find('.js-scopes-table').exists()).toBe(true);
});
it('should render scopes table with a new row ', () => {
expect(findAddNewScopeRow().exists()).toBe(true);
});
describe('status toggle', () => {
describe('without filled text input', () => {
it('should add a new scope with the text value empty and the status', () => {
findGlToggle().vm.$emit('change', true);
expect(wrapper.vm.formScopes).toHaveLength(1);
expect(wrapper.vm.formScopes[0].active).toEqual(true);
expect(wrapper.vm.formScopes[0].environmentScope).toEqual('');
expect(wrapper.vm.newScope).toEqual('');
});
});
it('has label', () => {
expect(findGlToggle().props('label')).toBe(Form.i18n.statusLabel);
});
it('should be disabled if the feature flag is not active', (done) => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
});
});
});
describe('with provided data', () => {
beforeEach(() => {
factory({
...requiredProps,
name: featureFlag.name,
description: featureFlag.description,
active: true,
version: LEGACY_FLAG,
scopes: [
{
id: 1,
active: true,
environmentScope: 'scope',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '54',
rolloutUserIds: '123',
shouldIncludeUserIds: true,
},
{
id: 2,
active: true,
environmentScope: 'scope',
canUpdate: false,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '54',
rolloutUserIds: '123',
shouldIncludeUserIds: true,
},
],
});
});
describe('scopes', () => {
it('should be possible to remove a scope', () => {
expect(wrapper.findByTestId('feature-flag-delete').exists()).toEqual(true);
});
it('renders empty row to add a new scope', () => {
expect(findAddNewScopeRow().exists()).toEqual(true);
});
it('renders the user id checkbox', () => {
expect(wrapper.find(GlFormCheckbox).exists()).toBe(true);
});
it('renders the user id text area', () => {
expect(wrapper.find(GlFormTextarea).exists()).toBe(true);
expect(wrapper.find(GlFormTextarea).vm.value).toBe('123');
});
describe('update scope', () => {
describe('on click on toggle', () => {
it('should update the scope', () => {
findGlToggle().vm.$emit('change', false);
expect(wrapper.vm.formScopes[0].active).toBe(false);
});
it('should be disabled if the feature flag is not active', (done) => {
wrapper.setProps({ active: false });
wrapper.vm.$nextTick(() => {
expect(findGlToggle().props('disabled')).toBe(true);
done();
});
});
});
describe('on strategy change', () => {
it('should not include user IDs if All Users is selected', () => {
const scope = wrapper.find({ ref: 'scopeRow' });
scope.find('select').setValue(ROLLOUT_STRATEGY_ALL_USERS);
return wrapper.vm.$nextTick().then(() => {
expect(scope.find('#rollout-user-id-0').exists()).toBe(false);
});
});
});
});
describe('deleting an existing scope', () => {
beforeEach(() => {
wrapper.find('.js-delete-scope').vm.$emit('click');
});
it('should add `shouldBeDestroyed` key the clicked scope', () => {
expect(wrapper.vm.formScopes[0].shouldBeDestroyed).toBe(true);
});
it('should not render deleted scopes', () => {
expect(wrapper.vm.filteredScopes).toEqual([expect.objectContaining({ id: 2 })]);
});
});
describe('deleting a new scope', () => {
it('should remove the scope from formScopes', () => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environmentScope: 'new_scope',
active: false,
id: uniqueId(INTERNAL_ID_PREFIX),
canUpdate: true,
protected: false,
strategies: [
{
name: ROLLOUT_STRATEGY_ALL_USERS,
parameters: {},
},
],
},
],
});
wrapper.find('.js-delete-scope').vm.$emit('click');
expect(wrapper.vm.formScopes).toEqual([]);
});
});
describe('with * scope', () => {
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
description: 'this is a feature flag',
scopes: [
{
environmentScope: '*',
active: false,
canUpdate: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
},
],
});
});
it('renders read-only name', () => {
expect(wrapper.find('.js-scope-all').exists()).toEqual(true);
});
});
describe('without permission to update', () => {
it('should have the flag name input disabled', () => {
const input = wrapper.find('#feature-flag-name');
expect(input.element.disabled).toBe(true);
});
it('should have the flag discription text area disabled', () => {
const textarea = wrapper.find('#feature-flag-description');
expect(textarea.element.disabled).toBe(true);
});
it('should have the scope that cannot be updated be disabled', () => {
const row = wrapper.findAll('.gl-responsive-table-row').at(2);
expect(row.find(EnvironmentsDropdown).vm.disabled).toBe(true);
expect(row.find(GlToggle).props('disabled')).toBe(true);
expect(row.find('.js-delete-scope').exists()).toBe(false);
});
});
});
describe('on submit', () => {
const selectFirstRolloutStrategyOption = (dropdownIndex) => {
wrapper
.findAll('select.js-rollout-strategy')
.at(dropdownIndex)
.findAll('option')
.at(1)
.setSelected();
};
beforeEach(() => {
factory({
...requiredProps,
name: 'feature_flag_1',
active: true,
description: 'this is a feature flag',
scopes: [
{
id: 1,
environmentScope: 'production',
canUpdate: true,
protected: true,
active: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
},
],
});
return wrapper.vm.$nextTick();
});
it('should emit handleSubmit with the updated data', () => {
wrapper.find('#feature-flag-name').setValue('feature_flag_2');
return wrapper.vm
.$nextTick()
.then(() => {
wrapper
.find('.js-new-scope-name')
.find(EnvironmentsDropdown)
.vm.$emit('selectEnvironment', 'review');
return wrapper.vm.$nextTick();
})
.then(() => {
findAddNewScopeRow().find(GlToggle).vm.$emit('change', true);
})
.then(() => {
findGlToggle().vm.$emit('change', true);
return wrapper.vm.$nextTick();
})
.then(() => {
selectFirstRolloutStrategyOption(0);
return wrapper.vm.$nextTick();
})
.then(() => {
selectFirstRolloutStrategyOption(2);
return wrapper.vm.$nextTick();
})
.then(() => {
wrapper.find('.js-rollout-percentage').setValue('55');
return wrapper.vm.$nextTick();
})
.then(() => {
wrapper.find({ ref: 'submitButton' }).vm.$emit('click');
const data = wrapper.emitted().handleSubmit[0][0];
expect(data.name).toEqual('feature_flag_2');
expect(data.description).toEqual('this is a feature flag');
expect(data.active).toBe(true);
expect(data.scopes).toEqual([
{
id: 1,
active: true,
environmentScope: 'production',
canUpdate: true,
protected: true,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: '55',
rolloutUserIds: '',
shouldIncludeUserIds: false,
},
{
id: expect.any(String),
active: false,
environmentScope: 'review',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
},
{
id: expect.any(String),
active: true,
environmentScope: '',
canUpdate: true,
protected: false,
rolloutStrategy: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
rolloutUserIds: '',
shouldIncludeUserIds: false,
},
]);
});
});
});
});
describe('with strategies', () => {
......@@ -432,7 +97,6 @@ describe('feature flag form', () => {
name: featureFlag.name,
description: featureFlag.description,
active: true,
version: NEW_VERSION_FLAG,
strategies: [
{
type: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
......
......@@ -81,8 +81,6 @@ describe('New feature flag form', () => {
rolloutUserIds: '',
};
expect(wrapper.vm.scopes).toEqual([defaultScope]);
expect(wrapper.find(Form).props('scopes')).toContainEqual(defaultScope);
});
it('has an all users strategy by default', () => {
......
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