Commit d463f84b authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '225453-rolling-out-feature-flag-environment-by-environment-is-tricky' into 'master'

Add docs links to Feature Flags strategy settings

See merge request gitlab-org/gitlab!41910
parents 6b639561 304f8405
...@@ -11,4 +11,6 @@ ...@@ -11,4 +11,6 @@
user_callouts_path: user_callouts_path, user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION, user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
show_user_callout: show_feature_flags_new_version?.to_s, show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),
feature_flag_issues_endpoint: feature_flag_issues_links_endpoint(@project, @feature_flag, current_user) } } feature_flag_issues_endpoint: feature_flag_issues_links_endpoint(@project, @feature_flag, current_user) } }
...@@ -9,4 +9,6 @@ ...@@ -9,4 +9,6 @@
user_callouts_path: user_callouts_path, user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION, user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
show_user_callout: show_feature_flags_new_version?.to_s, show_user_callout: show_feature_flags_new_version?.to_s,
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
environments_scope_docs_path: help_page_path('ci/environments', anchor: 'scoping-environments-with-specs'),
project_id: @project.id } } project_id: @project.id } }
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
), ),
newHelpText: s__( newHelpText: s__(
'FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies.', 'FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies.',
), ),
noStrategiesText: s__('FeatureFlags|Feature Flag has no strategies'), noStrategiesText: s__('FeatureFlags|Feature Flag has no strategies'),
}, },
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import Vue from 'vue'; import Vue from 'vue';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { import {
GlButton,
GlFormSelect, GlFormSelect,
GlFormInput, GlFormInput,
GlFormTextarea, GlFormTextarea,
GlFormGroup, GlFormGroup,
GlIcon,
GlLink,
GlToken, GlToken,
GlButton,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { import {
...@@ -22,18 +24,28 @@ import NewEnvironmentsDropdown from './new_environments_dropdown.vue'; ...@@ -22,18 +24,28 @@ import NewEnvironmentsDropdown from './new_environments_dropdown.vue';
export default { export default {
components: { components: {
GlButton,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlFormTextarea, GlFormTextarea,
GlFormSelect, GlFormSelect,
GlIcon,
GlLink,
GlToken, GlToken,
GlButton,
NewEnvironmentsDropdown, NewEnvironmentsDropdown,
}, },
model: { model: {
prop: 'strategy', prop: 'strategy',
event: 'change', event: 'change',
}, },
inject: {
strategyTypeDocsPagePath: {
type: String,
},
environmentsScopeDocsPath: {
type: String,
},
},
props: { props: {
strategy: { strategy: {
type: Object, type: Object,
...@@ -59,9 +71,10 @@ export default { ...@@ -59,9 +71,10 @@ export default {
ROLLOUT_STRATEGY_USER_ID, ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST, ROLLOUT_STRATEGY_GITLAB_USER_LIST,
translations: { 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'), rolloutPercentageDescription: __('Enter a whole number between 0 and 100'),
rolloutPercentageInvalid: s__( rolloutPercentageInvalid: s__(
'FeatureFlags|Percent rollout must be a whole number between 0 and 100', 'FeatureFlags|Percent rollout must be a whole number between 0 and 100',
...@@ -72,7 +85,7 @@ export default { ...@@ -72,7 +85,7 @@ export default {
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'),
}, },
...@@ -199,14 +212,14 @@ export default { ...@@ -199,14 +212,14 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="border-top py-4"> <div class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-100 gl-py-6">
<div class="flex flex-column flex-md-row flex-md-wrap"> <div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row flex-md-wrap">
<div class="mr-5"> <div class="mr-5">
<gl-form-group <gl-form-group :label="$options.i18n.strategyTypeLabel" :label-for="strategyTypeId">
:label="$options.translations.strategyTypeLabel" <p class="gl-display-inline-block ">{{ $options.i18n.strategyTypeDescription }}</p>
:description="$options.translations.strategyTypeDescription" <gl-link :href="strategyTypeDocsPagePath" target="_blank">
:label-for="strategyTypeId" <gl-icon name="question" />
> </gl-link>
<gl-form-select <gl-form-select
:id="strategyTypeId" :id="strategyTypeId"
v-model="formStrategy.name" v-model="formStrategy.name"
...@@ -219,27 +232,27 @@ export default { ...@@ -219,27 +232,27 @@ export default {
<div data-testid="strategy"> <div data-testid="strategy">
<gl-form-group <gl-form-group
v-if="isPercentRollout" v-if="isPercentRollout"
:label="$options.translations.rolloutPercentageLabel" :label="$options.i18n.rolloutPercentageLabel"
:description="$options.translations.rolloutPercentageDescription" :description="$options.i18n.rolloutPercentageDescription"
:label-for="strategyPercentageId" :label-for="strategyPercentageId"
:invalid-feedback="$options.translations.rolloutPercentageInvalid" :invalid-feedback="$options.i18n.rolloutPercentageInvalid"
> >
<div class="flex align-items-center"> <div class="gl-display-flex gl-align-items-center">
<gl-form-input <gl-form-input
:id="strategyPercentageId" :id="strategyPercentageId"
v-model="formPercentage" v-model="formPercentage"
class="rollout-percentage text-right w-3rem" class="rollout-percentage gl-text-right gl-w-9"
type="number" type="number"
@input="onStrategyChange" @input="onStrategyChange"
/> />
<span class="ml-1">%</span> <span class="gl-ml-2">%</span>
</div> </div>
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="isUserWithId" v-if="isUserWithId"
:label="$options.translations.rolloutUserIdsLabel" :label="$options.i18n.rolloutUserIdsLabel"
:description="$options.translations.rolloutUserIdsDescription" :description="$options.i18n.rolloutUserIdsDescription"
:label-for="strategyUserIdsId" :label-for="strategyUserIdsId"
> >
<gl-form-textarea <gl-form-textarea
...@@ -251,9 +264,9 @@ export default { ...@@ -251,9 +264,9 @@ export default {
<gl-form-group <gl-form-group
v-if="isUserList" v-if="isUserList"
:state="hasUserLists" :state="hasUserLists"
:invalid-feedback="$options.translations.rolloutUserListNoListError" :invalid-feedback="$options.i18n.rolloutUserListNoListError"
:label="$options.translations.rolloutUserListLabel" :label="$options.i18n.rolloutUserListLabel"
:description="$options.translations.rolloutUserListDescription" :description="$options.i18n.rolloutUserListDescription"
:label-for="strategyUserListId" :label-for="strategyUserListId"
> >
<gl-form-select <gl-form-select
...@@ -265,7 +278,9 @@ export default { ...@@ -265,7 +278,9 @@ export default {
</gl-form-group> </gl-form-group>
</div> </div>
<div class="align-self-end align-self-md-stretch order-first offset-md-0 order-md-0 ml-auto"> <div
class="align-self-end align-self-md-stretch order-first offset-md-0 order-md-0 gl-ml-auto"
>
<gl-button <gl-button
data-testid="delete-strategy-button" data-testid="delete-strategy-button"
variant="danger" variant="danger"
...@@ -274,23 +289,31 @@ export default { ...@@ -274,23 +289,31 @@ export default {
/> />
</div> </div>
</div> </div>
<div class="flex flex-column"> <label class="gl-display-block" :for="environmentsDropdownId">{{
<label :for="environmentsDropdownId">{{ $options.translations.environmentsLabel }}</label> $options.i18n.environmentsLabel
<div class="flex flex-column flex-md-row align-items-start align-items-md-center"> }}</label>
<p class="gl-display-inline-block">{{ $options.i18n.environmentsSelectDescription }}</p>
<gl-link :href="environmentsScopeDocsPath" target="_blank">
<gl-icon name="question" />
</gl-link>
<div class="gl-display-flex gl-flex-direction-column">
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row align-items-start gl-md-align-items-center"
>
<new-environments-dropdown <new-environments-dropdown
:id="environmentsDropdownId" :id="environmentsDropdownId"
:endpoint="endpoint" :endpoint="endpoint"
class="mr-2" class="gl-mr-3"
@add="addEnvironment" @add="addEnvironment"
/> />
<span v-if="appliesToAllEnvironments" class="text-secondary mt-2 mt-md-0 ml-md-3"> <span v-if="appliesToAllEnvironments" class="text-secondary gl-mt-3 mt-md-0 ml-md-3">
{{ $options.translations.allEnvironments }} {{ $options.i18n.allEnvironments }}
</span> </span>
<div v-else class="flex align-items-center"> <div v-else class="gl-display-flex gl-align-items-center">
<gl-token <gl-token
v-for="environment in filteredEnvironments" v-for="environment in filteredEnvironments"
:key="environment.id" :key="environment.id"
class="mt-2 mr-2 mt-md-0 mr-md-0 ml-md-2 rounded-pill" class="gl-mt-3 gl-mr-3 mt-md-0 mr-md-0 ml-md-2 rounded-pill"
@close="removeScope(environment)" @close="removeScope(environment)"
> >
{{ environment.environmentScope }} {{ environment.environmentScope }}
......
...@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils'; ...@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default () => { export default () => {
const el = document.querySelector('#js-edit-feature-flag'); const el = document.querySelector('#js-edit-feature-flag');
const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset;
return new Vue({ return new Vue({
el, el,
components: { components: {
EditFeatureFlag, EditFeatureFlag,
}, },
provide: {
environmentsScopeDocsPath,
strategyTypeDocsPagePath,
},
render(createElement) { render(createElement) {
return createElement('edit-feature-flag', { return createElement('edit-feature-flag', {
props: { props: {
......
...@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils'; ...@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default () => { export default () => {
const el = document.querySelector('#js-new-feature-flag'); const el = document.querySelector('#js-new-feature-flag');
const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset;
return new Vue({ return new Vue({
el, el,
components: { components: {
NewFeatureFlag, NewFeatureFlag,
}, },
provide: {
environmentsScopeDocsPath,
strategyTypeDocsPagePath,
},
render(createElement) { render(createElement) {
return createElement('new-feature-flag', { return createElement('new-feature-flag', {
props: { props: {
......
---
title: Add explanation text to FF create/edit sections
merge_request: 41910
author:
type: changed
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlFormSelect, GlFormTextarea, GlFormInput, GlToken, GlButton } from '@gitlab/ui'; import { GlFormSelect, GlFormTextarea, GlFormInput, GlLink, GlToken, GlButton } from '@gitlab/ui';
import { import {
PERCENT_ROLLOUT_GROUP_ID, PERCENT_ROLLOUT_GROUP_ID,
ROLLOUT_STRATEGY_ALL_USERS, ROLLOUT_STRATEGY_ALL_USERS,
...@@ -12,10 +12,16 @@ import NewEnvironmentsDropdown from 'ee/feature_flags/components/new_environment ...@@ -12,10 +12,16 @@ import NewEnvironmentsDropdown from 'ee/feature_flags/components/new_environment
import { userList } from '../mock_data'; import { userList } from '../mock_data';
const provide = {
strategyTypeDocsPagePath: 'link-to-strategy-docs',
environmentsScopeDocsPath: 'link-scope-docs',
};
describe('Feature flags strategy', () => { describe('Feature flags strategy', () => {
let wrapper; let wrapper;
const findStrategy = () => wrapper.find('[data-testid="strategy"]'); const findStrategy = () => wrapper.find('[data-testid="strategy"]');
const findDocsLinks = () => wrapper.findAll(GlLink);
const factory = ( const factory = (
opts = { opts = {
...@@ -25,6 +31,7 @@ describe('Feature flags strategy', () => { ...@@ -25,6 +31,7 @@ describe('Feature flags strategy', () => {
endpoint: '', endpoint: '',
userLists: [userList], userLists: [userList],
}, },
provide,
}, },
) => { ) => {
if (wrapper) { if (wrapper) {
...@@ -41,6 +48,18 @@ describe('Feature flags strategy', () => { ...@@ -41,6 +48,18 @@ describe('Feature flags strategy', () => {
} }
}); });
describe('helper links', () => {
const propsData = { strategy: {}, index: 0, endpoint: '', userLists: [userList] };
factory({ propsData, provide });
it('should display 2 helper links', () => {
const links = findDocsLinks();
expect(links.exists()).toBe(true);
expect(links.at(0).attributes('href')).toContain('docs');
expect(links.at(1).attributes('href')).toContain('docs');
});
});
describe.each` describe.each`
name | parameter | value | newValue | input name | parameter | value | newValue | input
${ROLLOUT_STRATEGY_ALL_USERS} | ${null} | ${null} | ${null} | ${null} ${ROLLOUT_STRATEGY_ALL_USERS} | ${null} | ${null} | ${null} | ${null}
...@@ -56,7 +75,7 @@ describe('Feature flags strategy', () => { ...@@ -56,7 +75,7 @@ describe('Feature flags strategy', () => {
} }
strategy = { name, parameters }; strategy = { name, parameters };
propsData = { strategy, index: 0, endpoint: '' }; propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData }); factory({ propsData, provide });
}); });
it('should set the select to match the strategy name', () => { it('should set the select to match the strategy name', () => {
...@@ -86,13 +105,14 @@ describe('Feature flags strategy', () => { ...@@ -86,13 +105,14 @@ describe('Feature flags strategy', () => {
}); });
} }
}); });
describe('with strategy gitlabUserList', () => { describe('with strategy gitlabUserList', () => {
let propsData; let propsData;
let strategy; let strategy;
beforeEach(() => { beforeEach(() => {
strategy = { name: ROLLOUT_STRATEGY_GITLAB_USER_LIST, userListId: '2', parameters: {} }; strategy = { name: ROLLOUT_STRATEGY_GITLAB_USER_LIST, userListId: '2', parameters: {} };
propsData = { strategy, index: 0, endpoint: '', userLists: [userList] }; propsData = { strategy, index: 0, endpoint: '', userLists: [userList] };
factory({ propsData }); factory({ propsData, provide });
}); });
it('should set the select to match the strategy name', () => { it('should set the select to match the strategy name', () => {
...@@ -150,7 +170,7 @@ describe('Feature flags strategy', () => { ...@@ -150,7 +170,7 @@ describe('Feature flags strategy', () => {
scopes: [{ environmentScope: '*' }], scopes: [{ environmentScope: '*' }],
}; };
const propsData = { strategy, index: 0, endpoint: '' }; const propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData }); factory({ propsData, provide });
}); });
it('should change the parameters if a different strategy is chosen', () => { it('should change the parameters if a different strategy is chosen', () => {
...@@ -225,7 +245,7 @@ describe('Feature flags strategy', () => { ...@@ -225,7 +245,7 @@ describe('Feature flags strategy', () => {
scopes: [], scopes: [],
}; };
const propsData = { strategy, index: 0, endpoint: '' }; const propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData }); factory({ propsData, provide });
}); });
it('should display selected scopes', () => { it('should display selected scopes', () => {
......
...@@ -10785,7 +10785,7 @@ msgstr "" ...@@ -10785,7 +10785,7 @@ msgstr ""
msgid "FeatureFlags|Edit User List" msgid "FeatureFlags|Edit User List"
msgstr "" msgstr ""
msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies." msgid "FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies."
msgstr "" msgstr ""
msgid "FeatureFlags|Environment Spec" msgid "FeatureFlags|Environment Spec"
...@@ -22671,7 +22671,7 @@ msgstr "" ...@@ -22671,7 +22671,7 @@ msgstr ""
msgid "Select status" msgid "Select status"
msgstr "" msgstr ""
msgid "Select strategy activation method" msgid "Select strategy activation method."
msgstr "" msgstr ""
msgid "Select subscription" msgid "Select subscription"
...@@ -22686,6 +22686,9 @@ msgstr "" ...@@ -22686,6 +22686,9 @@ 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 timeframe" msgid "Select timeframe"
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