Commit 96f736c9 authored by Simon Knox's avatar Simon Knox

Merge branch 'afontaine/remove-legacy-flags-frontend-form' into 'master'

Remove Legacy Flags from Feature Flag Form

See merge request gitlab-org/gitlab!64006
parents 566467f3 2a55152c
<script> <script>
import { import { GlButton } from '@gitlab/ui';
GlButton, import { memoize, cloneDeep, isNumber, uniqueId } from 'lodash';
GlBadge,
GlTooltip,
GlTooltipDirective,
GlFormTextarea,
GlFormCheckbox,
GlSprintf,
GlIcon,
GlToggle,
} from '@gitlab/ui';
import { memoize, isString, cloneDeep, isNumber, uniqueId } from 'lodash';
import Vue from 'vue'; import Vue from 'vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue'; import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
...@@ -20,12 +10,8 @@ import { ...@@ -20,12 +10,8 @@ import {
ROLLOUT_STRATEGY_PERCENT_ROLLOUT, ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
ROLLOUT_STRATEGY_USER_ID, ROLLOUT_STRATEGY_USER_ID,
ALL_ENVIRONMENTS_NAME, ALL_ENVIRONMENTS_NAME,
INTERNAL_ID_PREFIX,
NEW_VERSION_FLAG, NEW_VERSION_FLAG,
LEGACY_FLAG,
} from '../constants'; } from '../constants';
import { createNewEnvironmentScope } from '../store/helpers';
import EnvironmentsDropdown from './environments_dropdown.vue';
import Strategy from './strategy.vue'; import Strategy from './strategy.vue';
export default { export default {
...@@ -35,20 +21,9 @@ export default { ...@@ -35,20 +21,9 @@ export default {
}, },
components: { components: {
GlButton, GlButton,
GlBadge,
GlFormTextarea,
GlFormCheckbox,
GlTooltip,
GlSprintf,
GlIcon,
GlToggle,
EnvironmentsDropdown,
Strategy, Strategy,
RelatedIssuesRoot, RelatedIssuesRoot,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [featureFlagsMixin()], mixins: [featureFlagsMixin()],
inject: { inject: {
featureFlagIssuesEndpoint: { featureFlagIssuesEndpoint: {
...@@ -71,11 +46,6 @@ export default { ...@@ -71,11 +46,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
scopes: {
type: Array,
required: false,
default: () => [],
},
cancelPath: { cancelPath: {
type: String, type: String,
required: true, required: true,
...@@ -89,11 +59,6 @@ export default { ...@@ -89,11 +59,6 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
version: {
type: String,
required: false,
default: LEGACY_FLAG,
},
}, },
translations: { translations: {
allEnvironmentsText: s__('FeatureFlags|* (All Environments)'), allEnvironmentsText: s__('FeatureFlags|* (All Environments)'),
...@@ -120,35 +85,18 @@ export default { ...@@ -120,35 +85,18 @@ export default {
formName: this.name, formName: this.name,
formDescription: this.description, formDescription: this.description,
// operate on a clone to avoid mutating props
formScopes: this.scopes.map((s) => ({ ...s })),
formStrategies: cloneDeep(this.strategies), formStrategies: cloneDeep(this.strategies),
newScope: '', newScope: '',
}; };
}, },
computed: { computed: {
filteredScopes() {
return this.formScopes.filter((scope) => !scope.shouldBeDestroyed);
},
filteredStrategies() { filteredStrategies() {
return this.formStrategies.filter((s) => !s.shouldBeDestroyed); 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() { showRelatedIssues() {
return this.featureFlagIssuesEndpoint.length > 0; return this.featureFlagIssuesEndpoint.length > 0;
}, },
readOnly() {
return this.version === LEGACY_FLAG;
},
}, },
methods: { methods: {
keyFor(strategy) { keyFor(strategy) {
...@@ -174,37 +122,6 @@ export default { ...@@ -174,37 +122,6 @@ export default {
isAllEnvironment(name) { isAllEnvironment(name) {
return name === ALL_ENVIRONMENTS_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 * When the user clicks the submit button
* it triggers an event with the form data * it triggers an event with the form data
...@@ -214,61 +131,16 @@ export default { ...@@ -214,61 +131,16 @@ export default {
name: this.formName, name: this.formName,
description: this.formDescription, description: this.formDescription,
active: this.active, 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); this.$emit('handleSubmit', flag);
}, },
canUpdateScope(scope) {
return !this.permissionsFlag || scope.canUpdate;
},
isRolloutPercentageInvalid: memoize(function isRolloutPercentageInvalid(percentage) { isRolloutPercentageInvalid: memoize(function isRolloutPercentageInvalid(percentage) {
return !this.$options.rolloutPercentageRegex.test(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) { onFormStrategyChange(strategy, index) {
Object.assign(this.filteredStrategies[index], strategy); Object.assign(this.filteredStrategies[index], strategy);
}, },
...@@ -281,12 +153,7 @@ export default { ...@@ -281,12 +153,7 @@ export default {
<div class="row"> <div class="row">
<div class="form-group col-md-4"> <div class="form-group col-md-4">
<label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }} *</label> <label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }} *</label>
<input <input id="feature-flag-name" v-model="formName" class="form-control" />
id="feature-flag-name"
v-model="formName"
:disabled="!canUpdateFlag"
class="form-control"
/>
</div> </div>
</div> </div>
...@@ -298,7 +165,6 @@ export default { ...@@ -298,7 +165,6 @@ export default {
<textarea <textarea
id="feature-flag-description" id="feature-flag-description"
v-model="formDescription" v-model="formDescription"
:disabled="!canUpdateFlag"
class="form-control" class="form-control"
rows="4" rows="4"
></textarea> ></textarea>
...@@ -312,277 +178,35 @@ export default { ...@@ -312,277 +178,35 @@ export default {
:show-categorized-issues="false" :show-categorized-issues="false"
/> />
<template v-if="supportsStrategies"> <div class="row">
<div class="row"> <div class="col-md-12">
<div class="col-md-12"> <h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<h4>{{ s__('FeatureFlags|Strategies') }}</h4> <div class="flex align-items-baseline justify-content-between">
<div class="flex align-items-baseline justify-content-between"> <p class="mr-3">{{ $options.translations.newHelpText }}</p>
<p class="mr-3">{{ $options.translations.newHelpText }}</p> <gl-button variant="confirm" category="secondary" @click="addStrategy">
<gl-button variant="confirm" category="secondary" @click="addStrategy"> {{ s__('FeatureFlags|Add strategy') }}
{{ s__('FeatureFlags|Add strategy') }} </gl-button>
</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> </div>
</div> </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> </fieldset>
<div class="form-actions"> <div class="form-actions">
<gl-button <gl-button
ref="submitButton" ref="submitButton"
:disabled="readOnly"
type="button" type="button"
variant="confirm" variant="confirm"
class="js-ff-submit col-xs-12" class="js-ff-submit col-xs-12"
......
...@@ -13610,9 +13610,6 @@ msgstr "" ...@@ -13610,9 +13610,6 @@ msgstr ""
msgid "FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies." msgid "FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies."
msgstr "" msgstr ""
msgid "FeatureFlags|Environment Spec"
msgstr ""
msgid "FeatureFlags|Environment Specs" msgid "FeatureFlags|Environment Specs"
msgstr "" msgstr ""
...@@ -13658,9 +13655,6 @@ msgstr "" ...@@ -13658,9 +13655,6 @@ msgstr ""
msgid "FeatureFlags|Inactive flag for %{scope}" msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr "" 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}" 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 "" msgstr ""
...@@ -13700,24 +13694,12 @@ msgstr "" ...@@ -13700,24 +13694,12 @@ msgstr ""
msgid "FeatureFlags|Percent rollout" msgid "FeatureFlags|Percent rollout"
msgstr "" msgstr ""
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
msgid "FeatureFlags|Percent rollout must be an integer number between 0 and 100" msgid "FeatureFlags|Percent rollout must be an integer number between 0 and 100"
msgstr "" msgstr ""
msgid "FeatureFlags|Protected"
msgstr ""
msgid "FeatureFlags|Remove" msgid "FeatureFlags|Remove"
msgstr "" 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}." 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 "" msgstr ""
...@@ -13727,9 +13709,6 @@ msgstr "" ...@@ -13727,9 +13709,6 @@ msgstr ""
msgid "FeatureFlags|Strategies" msgid "FeatureFlags|Strategies"
msgstr "" msgstr ""
msgid "FeatureFlags|Target environments"
msgstr ""
msgid "FeatureFlags|There was an error fetching the feature flags." msgid "FeatureFlags|There was an error fetching the feature flags."
msgstr "" msgstr ""
......
...@@ -115,7 +115,7 @@ describe('Edit feature flag form', () => { ...@@ -115,7 +115,7 @@ describe('Edit feature flag form', () => {
}); });
it('should set the version of the form from the feature flag', () => { 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(); mock.resetHandlers();
...@@ -136,7 +136,7 @@ describe('Edit feature flag form', () => { ...@@ -136,7 +136,7 @@ describe('Edit feature flag form', () => {
factory(); factory();
return axios.waitForAll().then(() => { 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 { shallowMount } from '@vue/test-utils';
import { uniqueId } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Api from '~/api'; import Api from '~/api';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
import Form from '~/feature_flags/components/form.vue'; import Form from '~/feature_flags/components/form.vue';
import Strategy from '~/feature_flags/components/strategy.vue'; import Strategy from '~/feature_flags/components/strategy.vue';
import { import {
ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_ALL_USERS,
ROLLOUT_STRATEGY_PERCENT_ROLLOUT, ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
INTERNAL_ID_PREFIX,
DEFAULT_PERCENT_ROLLOUT,
LEGACY_FLAG,
NEW_VERSION_FLAG,
} from '~/feature_flags/constants'; } from '~/feature_flags/constants';
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue'; import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
import { featureFlag, userList, allUsersStrategy } from '../mock_data'; import { featureFlag, userList, allUsersStrategy } from '../mock_data';
...@@ -29,15 +23,8 @@ describe('feature flag form', () => { ...@@ -29,15 +23,8 @@ describe('feature flag form', () => {
const requiredInjections = { const requiredInjections = {
environmentsEndpoint: '/environments.json', environmentsEndpoint: '/environments.json',
projectId: '1', projectId: '1',
glFeatures: {
featureFlagPermissions: true,
featureFlagsNewVersion: true,
},
}; };
const findAddNewScopeRow = () => wrapper.findByTestId('add-new-scope');
const findGlToggle = () => wrapper.find(GlToggle);
const factory = (props = {}, provide = {}) => { const factory = (props = {}, provide = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(Form, { shallowMount(Form, {
...@@ -100,328 +87,6 @@ describe('feature flag form', () => { ...@@ -100,328 +87,6 @@ describe('feature flag form', () => {
it('should render description textarea', () => { it('should render description textarea', () => {
expect(wrapper.find('#feature-flag-description').exists()).toBe(true); 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', () => { describe('with strategies', () => {
...@@ -432,7 +97,6 @@ describe('feature flag form', () => { ...@@ -432,7 +97,6 @@ describe('feature flag form', () => {
name: featureFlag.name, name: featureFlag.name,
description: featureFlag.description, description: featureFlag.description,
active: true, active: true,
version: NEW_VERSION_FLAG,
strategies: [ strategies: [
{ {
type: ROLLOUT_STRATEGY_PERCENT_ROLLOUT, type: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
......
...@@ -81,8 +81,6 @@ describe('New feature flag form', () => { ...@@ -81,8 +81,6 @@ describe('New feature flag form', () => {
rolloutUserIds: '', rolloutUserIds: '',
}; };
expect(wrapper.vm.scopes).toEqual([defaultScope]); expect(wrapper.vm.scopes).toEqual([defaultScope]);
expect(wrapper.find(Form).props('scopes')).toContainEqual(defaultScope);
}); });
it('has an all users strategy by default', () => { 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