form.vue 6.38 KB
Newer Older
1
<script>
2 3
import { GlButton } from '@gitlab/ui';
import { memoize, cloneDeep, isNumber, uniqueId } from 'lodash';
4
import Vue from 'vue';
5
import { s__ } from '~/locale';
6
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
7
import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
8 9 10
import {
  ROLLOUT_STRATEGY_ALL_USERS,
  ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
11
  ROLLOUT_STRATEGY_USER_ID,
12
  ALL_ENVIRONMENTS_NAME,
13
  NEW_VERSION_FLAG,
14
} from '../constants';
15
import Strategy from './strategy.vue';
16 17

export default {
Coung Ngo's avatar
Coung Ngo committed
18
  i18n: {
19
    removeLabel: s__('FeatureFlags|Remove'),
Coung Ngo's avatar
Coung Ngo committed
20 21
    statusLabel: s__('FeatureFlags|Status'),
  },
22
  components: {
23
    GlButton,
24
    Strategy,
25
    RelatedIssuesRoot,
26
  },
27
  mixins: [featureFlagsMixin()],
28 29 30 31 32
  inject: {
    featureFlagIssuesEndpoint: {
      default: '',
    },
  },
33
  props: {
34 35 36 37 38
    active: {
      type: Boolean,
      required: false,
      default: true,
    },
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    name: {
      type: String,
      required: false,
      default: '',
    },
    description: {
      type: String,
      required: false,
      default: '',
    },
    cancelPath: {
      type: String,
      required: true,
    },
    submitText: {
      type: String,
      required: true,
    },
57 58 59 60 61
    strategies: {
      type: Array,
      required: false,
      default: () => [],
    },
62
  },
63 64
  translations: {
    allEnvironmentsText: s__('FeatureFlags|* (All Environments)'),
65

66
    helpText: s__(
67
      'FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}.',
68
    ),
69 70

    newHelpText: s__(
71
      'FeatureFlags|Enable features for specific users and environments by configuring feature flag strategies.',
72 73 74
    ),
    noStrategiesText: s__('FeatureFlags|Feature Flag has no strategies'),
  },
75 76 77

  ROLLOUT_STRATEGY_ALL_USERS,
  ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
78
  ROLLOUT_STRATEGY_USER_ID,
79 80 81 82 83 84 85 86 87

  // Matches numbers 0 through 100
  rolloutPercentageRegex: /^[0-9]$|^[1-9][0-9]$|^100$/,

  data() {
    return {
      formName: this.name,
      formDescription: this.description,

88
      formStrategies: cloneDeep(this.strategies),
89 90 91 92

      newScope: '',
    };
  },
93
  computed: {
94
    filteredStrategies() {
95
      return this.formStrategies.filter((s) => !s.shouldBeDestroyed);
96
    },
97
    showRelatedIssues() {
98
      return Boolean(this.featureFlagIssuesEndpoint);
99
    },
100 101
  },
  methods: {
102 103 104 105 106 107 108 109
    keyFor(strategy) {
      if (strategy.id) {
        return strategy.id;
      }

      return uniqueId('strategy_');
    },

110
    addStrategy() {
111
      this.formStrategies.push({ name: ROLLOUT_STRATEGY_ALL_USERS, parameters: {}, scopes: [] });
112 113 114
    },

    deleteStrategy(s) {
115
      if (isNumber(s.id)) {
116 117
        Vue.set(s, 'shouldBeDestroyed', true);
      } else {
118
        this.formStrategies = this.formStrategies.filter((strategy) => strategy !== s);
119 120 121
      }
    },

122
    isAllEnvironment(name) {
123
      return name === ALL_ENVIRONMENTS_NAME;
124
    },
125 126 127 128
    /**
     * When the user clicks the submit button
     * it triggers an event with the form data
     */
129
    handleSubmit() {
130
      const flag = {
131 132
        name: this.formName,
        description: this.formDescription,
133
        active: this.active,
134 135
        version: NEW_VERSION_FLAG,
        strategies: this.formStrategies,
136 137 138
      };

      this.$emit('handleSubmit', flag);
139
    },
140

141
    isRolloutPercentageInvalid: memoize(function isRolloutPercentageInvalid(percentage) {
142 143
      return !this.$options.rolloutPercentageRegex.test(percentage);
    }),
144
    onFormStrategyChange(strategy, index) {
145 146 147
      const currentUserListId = this.filteredStrategies[index]?.userList?.id;
      const newUserListId = strategy?.userList?.id;

148
      Object.assign(this.filteredStrategies[index], strategy);
149 150 151 152

      if (currentUserListId !== newUserListId) {
        this.formStrategies = [...this.formStrategies];
      }
153
    },
154 155 156 157
  },
};
</script>
<template>
158
  <form class="feature-flags-form">
159 160 161
    <fieldset>
      <div class="row">
        <div class="form-group col-md-4">
162
          <label for="feature-flag-name" class="label-bold">{{ s__('FeatureFlags|Name') }} *</label>
163
          <input id="feature-flag-name" v-model="formName" class="form-control" />
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
        </div>
      </div>

      <div class="row">
        <div class="form-group col-md-4">
          <label for="feature-flag-description" class="label-bold">
            {{ s__('FeatureFlags|Description') }}
          </label>
          <textarea
            id="feature-flag-description"
            v-model="formDescription"
            class="form-control"
            rows="4"
          ></textarea>
        </div>
      </div>

181 182 183 184
      <related-issues-root
        v-if="showRelatedIssues"
        :endpoint="featureFlagIssuesEndpoint"
        :can-admin="true"
185
        :show-categorized-issues="false"
186 187
      />

188 189 190 191 192 193 194 195
      <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>
196 197 198
          </div>
        </div>
      </div>
199 200 201 202 203 204 205 206 207 208 209 210 211
      <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>
212 213 214
    </fieldset>

    <div class="form-actions">
215
      <gl-button
216
        ref="submitButton"
217
        type="button"
218
        variant="confirm"
219 220
        class="js-ff-submit col-xs-12"
        @click="handleSubmit"
221
        >{{ submitText }}</gl-button
222
      >
223
      <gl-button :href="cancelPath" class="js-ff-cancel col-xs-12 float-right">
224
        {{ __('Cancel') }}
225
      </gl-button>
226 227 228
    </div>
  </form>
</template>