summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/feature_flags/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/feature_flags/components')
-rw-r--r--app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue1
-rw-r--r--app/assets/javascripts/feature_flags/components/edit_feature_flag.vue37
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags.vue7
-rw-r--r--app/assets/javascripts/feature_flags/components/feature_flags_table.vue68
-rw-r--r--app/assets/javascripts/feature_flags/components/form.vue428
-rw-r--r--app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue2
-rw-r--r--app/assets/javascripts/feature_flags/components/new_feature_flag.vue40
-rw-r--r--app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue2
8 files changed, 41 insertions, 544 deletions
diff --git a/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue b/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue
index 77e40039b43..d86e13ce722 100644
--- a/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue
+++ b/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue
@@ -196,6 +196,7 @@ export default {
/>
<gl-loading-icon
v-if="isRotating"
+ size="sm"
class="gl-absolute gl-align-self-center gl-right-5 gl-mr-7"
/>
diff --git a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
index e7f4b51c964..dde021b67be 100644
--- a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
+++ b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue
@@ -1,10 +1,8 @@
<script>
import { GlAlert, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
-import axios from '~/lib/utils/axios_utils';
import { sprintf, s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { LEGACY_FLAG } from '../constants';
import FeatureFlagForm from './form.vue';
export default {
@@ -15,59 +13,29 @@ export default {
FeatureFlagForm,
},
mixins: [glFeatureFlagMixin()],
- inject: {
- showUserCallout: {},
- userCalloutId: {
- default: '',
- },
- userCalloutsPath: {
- default: '',
- },
- },
- data() {
- return {
- userShouldSeeNewFlagAlert: this.showUserCallout,
- };
- },
- translations: {
- legacyReadOnlyFlagAlert: s__(
- 'FeatureFlags|GitLab is moving to a new way of managing feature flags. This feature flag is read-only, and it will be removed in 14.0. Please create a new feature flag.',
- ),
- },
computed: {
...mapState([
'path',
'error',
'name',
'description',
- 'scopes',
'strategies',
'isLoading',
'hasError',
'iid',
'active',
- 'version',
]),
title() {
return this.iid
? `^${this.iid} ${this.name}`
: sprintf(s__('Edit %{name}'), { name: this.name });
},
- deprecated() {
- return this.version === LEGACY_FLAG;
- },
},
created() {
return this.fetchFeatureFlag();
},
methods: {
...mapActions(['updateFeatureFlag', 'fetchFeatureFlag', 'toggleActive']),
- dismissNewVersionFlagAlert() {
- this.userShouldSeeNewFlagAlert = false;
- axios.post(this.userCalloutsPath, {
- feature_name: this.userCalloutId,
- });
- },
},
};
</script>
@@ -76,9 +44,6 @@ export default {
<gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-7" />
<template v-else-if="!isLoading && !hasError">
- <gl-alert v-if="deprecated" variant="warning" :dismissible="false" class="gl-my-5">{{
- $options.translations.legacyReadOnlyFlagAlert
- }}</gl-alert>
<div class="gl-display-flex gl-align-items-center gl-mb-4 gl-mt-4">
<gl-toggle
:value="active"
@@ -100,12 +65,10 @@ export default {
<feature-flag-form
:name="name"
:description="description"
- :scopes="scopes"
:strategies="strategies"
:cancel-path="path"
:submit-text="__('Save changes')"
:active="active"
- :version="version"
@handleSubmit="(data) => updateFeatureFlag(data)"
/>
</template>
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags.vue b/app/assets/javascripts/feature_flags/components/feature_flags.vue
index d08e8d2b3a1..53909dcf42e 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags.vue
@@ -3,11 +3,8 @@ import { GlAlert, GlBadge, GlButton, GlModalDirective, GlSprintf } from '@gitlab
import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex';
-import {
- buildUrlWithCurrentLocation,
- getParameterByName,
- historyPushState,
-} from '~/lib/utils/common_utils';
+import { buildUrlWithCurrentLocation, historyPushState } from '~/lib/utils/common_utils';
+import { getParameterByName } from '~/lib/utils/url_utility';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
import EmptyState from './empty_state.vue';
diff --git a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
index 9220077af71..cfd838bf5a1 100644
--- a/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
+++ b/app/assets/javascripts/feature_flags/components/feature_flags_table.vue
@@ -1,8 +1,7 @@
<script>
-import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle, GlIcon } from '@gitlab/ui';
+import { GlBadge, GlButton, GlTooltipDirective, GlModal, GlToggle } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { ROLLOUT_STRATEGY_PERCENT_ROLLOUT, NEW_VERSION_FLAG, LEGACY_FLAG } from '../constants';
import { labelForStrategy } from '../utils';
export default {
@@ -14,7 +13,6 @@ export default {
components: {
GlBadge,
GlButton,
- GlIcon,
GlModal,
GlToggle,
},
@@ -35,13 +33,7 @@ export default {
deleteFeatureFlagName: null,
};
},
- translations: {
- legacyFlagReadOnlyAlert: s__('FeatureFlags|Flag is read-only'),
- },
computed: {
- permissions() {
- return this.glFeatures.featureFlagPermissions;
- },
modalTitle() {
return sprintf(s__('FeatureFlags|Delete %{name}?'), {
name: this.deleteFeatureFlagName,
@@ -57,12 +49,6 @@ export default {
},
},
methods: {
- isLegacyFlag(flag) {
- return flag.version !== NEW_VERSION_FLAG;
- },
- statusToggleDisabled(flag) {
- return flag.version === LEGACY_FLAG;
- },
scopeTooltipText(scope) {
return !scope.active
? sprintf(s__('FeatureFlags|Inactive flag for %{scope}'), {
@@ -70,22 +56,6 @@ export default {
})
: '';
},
- badgeText(scope) {
- const displayName =
- scope.environmentScope === '*'
- ? s__('FeatureFlags|* (All environments)')
- : scope.environmentScope;
-
- const displayPercentage =
- scope.rolloutStrategy === ROLLOUT_STRATEGY_PERCENT_ROLLOUT
- ? `: ${scope.rolloutPercentage}%`
- : '';
-
- return `${displayName}${displayPercentage}`;
- },
- badgeVariant(scope) {
- return scope.active ? 'info' : 'muted';
- },
strategyBadgeText(strategy) {
return labelForStrategy(strategy);
},
@@ -142,7 +112,6 @@ export default {
<gl-toggle
v-if="featureFlag.update_path"
:value="featureFlag.active"
- :disabled="statusToggleDisabled(featureFlag)"
:label="$options.i18n.toggleLabel"
label-position="hidden"
data-testid="feature-flag-status-toggle"
@@ -169,12 +138,6 @@ export default {
<div class="feature-flag-name text-monospace text-truncate">
{{ featureFlag.name }}
</div>
- <gl-icon
- v-if="isLegacyFlag(featureFlag)"
- v-gl-tooltip.hover="$options.translations.legacyFlagReadOnlyAlert"
- class="gl-ml-3"
- name="information-o"
- />
</div>
<div class="feature-flag-description text-secondary text-truncate">
{{ featureFlag.description }}
@@ -189,27 +152,14 @@ export default {
<div
class="table-mobile-content d-flex flex-wrap justify-content-end justify-content-md-start js-feature-flag-environments"
>
- <template v-if="isLegacyFlag(featureFlag)">
- <gl-badge
- v-for="scope in featureFlag.scopes"
- :key="scope.id"
- v-gl-tooltip.hover="scopeTooltipText(scope)"
- :variant="badgeVariant(scope)"
- :data-qa-selector="`feature-flag-scope-${badgeVariant(scope)}-badge`"
- class="gl-mr-3 gl-mt-2"
- >{{ badgeText(scope) }}</gl-badge
- >
- </template>
- <template v-else>
- <gl-badge
- v-for="strategy in featureFlag.strategies"
- :key="strategy.id"
- data-testid="strategy-badge"
- variant="info"
- class="gl-mr-3 gl-mt-2 gl-white-space-normal gl-text-left gl-px-5"
- >{{ strategyBadgeText(strategy) }}</gl-badge
- >
- </template>
+ <gl-badge
+ v-for="strategy in featureFlag.strategies"
+ :key="strategy.id"
+ data-testid="strategy-badge"
+ variant="info"
+ class="gl-mr-3 gl-mt-2 gl-white-space-normal gl-text-left gl-px-5"
+ >{{ strategyBadgeText(strategy) }}</gl-badge
+ >
</div>
</div>
diff --git a/app/assets/javascripts/feature_flags/components/form.vue b/app/assets/javascripts/feature_flags/components/form.vue
index 67ddceaf080..f7ad2c1f106 100644
--- a/app/assets/javascripts/feature_flags/components/form.vue
+++ b/app/assets/javascripts/feature_flags/components/form.vue
@@ -1,16 +1,6 @@
<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"
diff --git a/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue b/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue
index c59e3178b09..5575c6567b5 100644
--- a/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue
+++ b/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue
@@ -80,7 +80,7 @@ export default {
@focus="fetchEnvironments"
@keyup="fetchEnvironments"
/>
- <gl-loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" size="sm" />
<gl-dropdown-item
v-for="environment in results"
v-else-if="results.length"
diff --git a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue
index 19be57f9d27..865c1e677cd 100644
--- a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue
+++ b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue
@@ -1,10 +1,8 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
-import axios from '~/lib/utils/axios_utils';
import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { NEW_VERSION_FLAG, ROLLOUT_STRATEGY_ALL_USERS } from '../constants';
-import { createNewEnvironmentScope } from '../store/helpers';
+import { ROLLOUT_STRATEGY_ALL_USERS } from '../constants';
import FeatureFlagForm from './form.vue';
export default {
@@ -13,48 +11,14 @@ export default {
GlAlert,
},
mixins: [featureFlagsMixin()],
- inject: {
- showUserCallout: {},
- userCalloutId: {
- default: '',
- },
- userCalloutsPath: {
- default: '',
- },
- },
- data() {
- return {
- userShouldSeeNewFlagAlert: this.showUserCallout,
- };
- },
computed: {
...mapState(['error', 'path']),
- scopes() {
- return [
- createNewEnvironmentScope(
- {
- environmentScope: '*',
- active: true,
- },
- this.glFeatures.featureFlagsPermissions,
- ),
- ];
- },
- version() {
- return NEW_VERSION_FLAG;
- },
strategies() {
return [{ name: ROLLOUT_STRATEGY_ALL_USERS, parameters: {}, scopes: [] }];
},
},
methods: {
...mapActions(['createFeatureFlag']),
- dismissNewVersionFlagAlert() {
- this.userShouldSeeNewFlagAlert = false;
- axios.post(this.userCalloutsPath, {
- feature_name: this.userCalloutId,
- });
- },
},
};
</script>
@@ -69,9 +33,7 @@ export default {
<feature-flag-form
:cancel-path="path"
:submit-text="s__('FeatureFlags|Create feature flag')"
- :scopes="scopes"
:strategies="strategies"
- :version="version"
@handleSubmit="(data) => createFeatureFlag(data)"
/>
</div>
diff --git a/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue b/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue
index 45fc37da747..9dbffe75f6b 100644
--- a/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue
+++ b/app/assets/javascripts/feature_flags/components/strategies/gitlab_user_list.vue
@@ -76,7 +76,7 @@ export default {
@focus="fetchUserLists"
@keyup="fetchUserLists"
/>
- <gl-loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" size="sm" />
<gl-dropdown-item
v-for="list in userLists"
:key="list.id"