Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/feature_flags/components/form.vue')
-rw-r--r--app/assets/javascripts/feature_flags/components/form.vue428
1 files changed, 26 insertions, 402 deletions
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"