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 @@
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
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) } }
......@@ -9,4 +9,6 @@
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERISION,
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 } }
......@@ -107,7 +107,7 @@ export default {
),
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'),
},
......
......@@ -2,12 +2,14 @@
import Vue from 'vue';
import { isNumber } from 'lodash';
import {
GlButton,
GlFormSelect,
GlFormInput,
GlFormTextarea,
GlFormGroup,
GlIcon,
GlLink,
GlToken,
GlButton,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
import {
......@@ -22,18 +24,28 @@ import NewEnvironmentsDropdown from './new_environments_dropdown.vue';
export default {
components: {
GlButton,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlFormSelect,
GlIcon,
GlLink,
GlToken,
GlButton,
NewEnvironmentsDropdown,
},
model: {
prop: 'strategy',
event: 'change',
},
inject: {
strategyTypeDocsPagePath: {
type: String,
},
environmentsScopeDocsPath: {
type: String,
},
},
props: {
strategy: {
type: Object,
......@@ -59,9 +71,10 @@ export default {
ROLLOUT_STRATEGY_USER_ID,
ROLLOUT_STRATEGY_GITLAB_USER_LIST,
translations: {
i18n: {
allEnvironments: __('All 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',
......@@ -72,7 +85,7 @@ export default {
rolloutUserListLabel: s__('FeatureFlag|List'),
rolloutUserListDescription: s__('FeatureFlag|Select a user list'),
rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'),
strategyTypeDescription: __('Select strategy activation method'),
strategyTypeDescription: __('Select strategy activation method.'),
strategyTypeLabel: s__('FeatureFlag|Type'),
},
......@@ -199,14 +212,14 @@ export default {
};
</script>
<template>
<div class="border-top py-4">
<div class="flex flex-column flex-md-row flex-md-wrap">
<div class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-100 gl-py-6">
<div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row flex-md-wrap">
<div class="mr-5">
<gl-form-group
:label="$options.translations.strategyTypeLabel"
:description="$options.translations.strategyTypeDescription"
:label-for="strategyTypeId"
>
<gl-form-group :label="$options.i18n.strategyTypeLabel" :label-for="strategyTypeId">
<p class="gl-display-inline-block ">{{ $options.i18n.strategyTypeDescription }}</p>
<gl-link :href="strategyTypeDocsPagePath" target="_blank">
<gl-icon name="question" />
</gl-link>
<gl-form-select
:id="strategyTypeId"
v-model="formStrategy.name"
......@@ -219,27 +232,27 @@ export default {
<div data-testid="strategy">
<gl-form-group
v-if="isPercentRollout"
:label="$options.translations.rolloutPercentageLabel"
:description="$options.translations.rolloutPercentageDescription"
:label="$options.i18n.rolloutPercentageLabel"
:description="$options.i18n.rolloutPercentageDescription"
: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
:id="strategyPercentageId"
v-model="formPercentage"
class="rollout-percentage text-right w-3rem"
class="rollout-percentage gl-text-right gl-w-9"
type="number"
@input="onStrategyChange"
/>
<span class="ml-1">%</span>
<span class="gl-ml-2">%</span>
</div>
</gl-form-group>
<gl-form-group
v-if="isUserWithId"
:label="$options.translations.rolloutUserIdsLabel"
:description="$options.translations.rolloutUserIdsDescription"
:label="$options.i18n.rolloutUserIdsLabel"
:description="$options.i18n.rolloutUserIdsDescription"
:label-for="strategyUserIdsId"
>
<gl-form-textarea
......@@ -251,9 +264,9 @@ export default {
<gl-form-group
v-if="isUserList"
:state="hasUserLists"
:invalid-feedback="$options.translations.rolloutUserListNoListError"
:label="$options.translations.rolloutUserListLabel"
:description="$options.translations.rolloutUserListDescription"
:invalid-feedback="$options.i18n.rolloutUserListNoListError"
:label="$options.i18n.rolloutUserListLabel"
:description="$options.i18n.rolloutUserListDescription"
:label-for="strategyUserListId"
>
<gl-form-select
......@@ -265,7 +278,9 @@ export default {
</gl-form-group>
</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
data-testid="delete-strategy-button"
variant="danger"
......@@ -274,23 +289,31 @@ export default {
/>
</div>
</div>
<div class="flex flex-column">
<label :for="environmentsDropdownId">{{ $options.translations.environmentsLabel }}</label>
<div class="flex flex-column flex-md-row align-items-start align-items-md-center">
<label class="gl-display-block" :for="environmentsDropdownId">{{
$options.i18n.environmentsLabel
}}</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
:id="environmentsDropdownId"
:endpoint="endpoint"
class="mr-2"
class="gl-mr-3"
@add="addEnvironment"
/>
<span v-if="appliesToAllEnvironments" class="text-secondary mt-2 mt-md-0 ml-md-3">
{{ $options.translations.allEnvironments }}
<span v-if="appliesToAllEnvironments" class="text-secondary gl-mt-3 mt-md-0 ml-md-3">
{{ $options.i18n.allEnvironments }}
</span>
<div v-else class="flex align-items-center">
<div v-else class="gl-display-flex gl-align-items-center">
<gl-token
v-for="environment in filteredEnvironments"
: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)"
>
{{ environment.environmentScope }}
......
......@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default () => {
const el = document.querySelector('#js-edit-feature-flag');
const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset;
return new Vue({
el,
components: {
EditFeatureFlag,
},
provide: {
environmentsScopeDocsPath,
strategyTypeDocsPagePath,
},
render(createElement) {
return createElement('edit-feature-flag', {
props: {
......
......@@ -4,12 +4,17 @@ import { parseBoolean } from '~/lib/utils/common_utils';
export default () => {
const el = document.querySelector('#js-new-feature-flag');
const { environmentsScopeDocsPath, strategyTypeDocsPagePath } = el.dataset;
return new Vue({
el,
components: {
NewFeatureFlag,
},
provide: {
environmentsScopeDocsPath,
strategyTypeDocsPagePath,
},
render(createElement) {
return createElement('new-feature-flag', {
props: {
......
---
title: Add explanation text to FF create/edit sections
merge_request: 41910
author:
type: changed
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 {
PERCENT_ROLLOUT_GROUP_ID,
ROLLOUT_STRATEGY_ALL_USERS,
......@@ -12,10 +12,16 @@ import NewEnvironmentsDropdown from 'ee/feature_flags/components/new_environment
import { userList } from '../mock_data';
const provide = {
strategyTypeDocsPagePath: 'link-to-strategy-docs',
environmentsScopeDocsPath: 'link-scope-docs',
};
describe('Feature flags strategy', () => {
let wrapper;
const findStrategy = () => wrapper.find('[data-testid="strategy"]');
const findDocsLinks = () => wrapper.findAll(GlLink);
const factory = (
opts = {
......@@ -25,6 +31,7 @@ describe('Feature flags strategy', () => {
endpoint: '',
userLists: [userList],
},
provide,
},
) => {
if (wrapper) {
......@@ -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`
name | parameter | value | newValue | input
${ROLLOUT_STRATEGY_ALL_USERS} | ${null} | ${null} | ${null} | ${null}
......@@ -56,7 +75,7 @@ describe('Feature flags strategy', () => {
}
strategy = { name, parameters };
propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData });
factory({ propsData, provide });
});
it('should set the select to match the strategy name', () => {
......@@ -86,13 +105,14 @@ describe('Feature flags strategy', () => {
});
}
});
describe('with strategy gitlabUserList', () => {
let propsData;
let strategy;
beforeEach(() => {
strategy = { name: ROLLOUT_STRATEGY_GITLAB_USER_LIST, userListId: '2', parameters: {} };
propsData = { strategy, index: 0, endpoint: '', userLists: [userList] };
factory({ propsData });
factory({ propsData, provide });
});
it('should set the select to match the strategy name', () => {
......@@ -150,7 +170,7 @@ describe('Feature flags strategy', () => {
scopes: [{ environmentScope: '*' }],
};
const propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData });
factory({ propsData, provide });
});
it('should change the parameters if a different strategy is chosen', () => {
......@@ -225,7 +245,7 @@ describe('Feature flags strategy', () => {
scopes: [],
};
const propsData = { strategy, index: 0, endpoint: '' };
factory({ propsData });
factory({ propsData, provide });
});
it('should display selected scopes', () => {
......
......@@ -10785,7 +10785,7 @@ msgstr ""
msgid "FeatureFlags|Edit User List"
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 ""
msgid "FeatureFlags|Environment Spec"
......@@ -22671,7 +22671,7 @@ msgstr ""
msgid "Select status"
msgstr ""
msgid "Select strategy activation method"
msgid "Select strategy activation method."
msgstr ""
msgid "Select subscription"
......@@ -22686,6 +22686,9 @@ msgstr ""
msgid "Select the custom project template source group."
msgstr ""
msgid "Select the environment scope for this feature flag."
msgstr ""
msgid "Select timeframe"
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