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/store/modules')
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/edit/actions.js75
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/edit/index.js10
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js12
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/edit/mutations.js45
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/edit/state.js18
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/helpers.js213
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/index/actions.js107
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/index/index.js10
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js26
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/index/mutations.js125
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/index/state.js18
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/new/actions.js51
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/new/index.js10
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js6
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/new/mutations.js21
-rw-r--r--app/assets/javascripts/feature_flags/store/modules/new/state.js6
16 files changed, 753 insertions, 0 deletions
diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/actions.js b/app/assets/javascripts/feature_flags/store/modules/edit/actions.js
new file mode 100644
index 00000000000..351f36d8fa6
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/edit/actions.js
@@ -0,0 +1,75 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { __ } from '~/locale';
+import { NEW_VERSION_FLAG } from '../../../constants';
+import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers';
+
+/**
+ * Commits mutation to set the main endpoint
+ * @param {String} endpoint
+ */
+export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint);
+
+/**
+ * Commits mutation to set the feature flag path.
+ * Used to redirect the user after form submission
+ *
+ * @param {String} path
+ */
+export const setPath = ({ commit }, path) => commit(types.SET_PATH, path);
+
+/**
+ * Handles the edition of a feature flag.
+ *
+ * Will dispatch `requestUpdateFeatureFlag`
+ * Serializes the params and makes a put request
+ * Dispatches an action acording to the request status.
+ *
+ * @param {Object} params
+ */
+export const updateFeatureFlag = ({ state, dispatch }, params) => {
+ dispatch('requestUpdateFeatureFlag');
+
+ axios
+ .put(
+ state.endpoint,
+ params.version === NEW_VERSION_FLAG
+ ? mapStrategiesToRails(params)
+ : mapFromScopesViewModel(params),
+ )
+ .then(() => {
+ dispatch('receiveUpdateFeatureFlagSuccess');
+ visitUrl(state.path);
+ })
+ .catch(error => dispatch('receiveUpdateFeatureFlagError', error.response.data));
+};
+
+export const requestUpdateFeatureFlag = ({ commit }) => commit(types.REQUEST_UPDATE_FEATURE_FLAG);
+export const receiveUpdateFeatureFlagSuccess = ({ commit }) =>
+ commit(types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS);
+export const receiveUpdateFeatureFlagError = ({ commit }, error) =>
+ commit(types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR, error);
+
+/**
+ * Fetches the feature flag data for the edit form
+ */
+export const fetchFeatureFlag = ({ state, dispatch }) => {
+ dispatch('requestFeatureFlag');
+
+ axios
+ .get(state.endpoint)
+ .then(({ data }) => dispatch('receiveFeatureFlagSuccess', data))
+ .catch(() => dispatch('receiveFeatureFlagError'));
+};
+
+export const requestFeatureFlag = ({ commit }) => commit(types.REQUEST_FEATURE_FLAG);
+export const receiveFeatureFlagSuccess = ({ commit }, response) =>
+ commit(types.RECEIVE_FEATURE_FLAG_SUCCESS, response);
+export const receiveFeatureFlagError = ({ commit }) => {
+ commit(types.RECEIVE_FEATURE_FLAG_ERROR);
+ createFlash(__('Something went wrong on our end. Please try again!'));
+};
+
+export const toggleActive = ({ commit }, active) => commit(types.TOGGLE_ACTIVE, active);
diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/index.js b/app/assets/javascripts/feature_flags/store/modules/edit/index.js
new file mode 100644
index 00000000000..665bb29a17e
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/edit/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+export default {
+ namespaced: true,
+ actions,
+ mutations,
+ state: state(),
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js b/app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js
new file mode 100644
index 00000000000..b2715e501f4
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/edit/mutation_types.js
@@ -0,0 +1,12 @@
+export const SET_ENDPOINT = 'SET_ENDPOINT';
+export const SET_PATH = 'SET_PATH';
+
+export const REQUEST_UPDATE_FEATURE_FLAG = 'REQUEST_UPDATE_FEATURE_FLAG';
+export const RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS = 'RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS';
+export const RECEIVE_UPDATE_FEATURE_FLAG_ERROR = 'RECEIVE_UPDATE_FEATURE_FLAG_ERROR';
+
+export const REQUEST_FEATURE_FLAG = 'REQUEST_FEATURE_FLAG';
+export const RECEIVE_FEATURE_FLAG_SUCCESS = 'RECEIVE_FEATURE_FLAG_SUCCESS';
+export const RECEIVE_FEATURE_FLAG_ERROR = 'RECEIVE_FEATURE_FLAG_ERROR';
+
+export const TOGGLE_ACTIVE = 'TOGGLE_ACTIVE';
diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/mutations.js b/app/assets/javascripts/feature_flags/store/modules/edit/mutations.js
new file mode 100644
index 00000000000..1d2721e037d
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/edit/mutations.js
@@ -0,0 +1,45 @@
+import * as types from './mutation_types';
+import { mapToScopesViewModel, mapStrategiesToViewModel } from '../helpers';
+import { LEGACY_FLAG } from '../../../constants';
+
+export default {
+ [types.SET_ENDPOINT](state, endpoint) {
+ state.endpoint = endpoint;
+ },
+ [types.SET_PATH](state, path) {
+ state.path = path;
+ },
+ [types.REQUEST_FEATURE_FLAG](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_FEATURE_FLAG_SUCCESS](state, response) {
+ state.isLoading = false;
+ state.hasError = false;
+
+ state.name = response.name;
+ state.description = response.description;
+ state.iid = response.iid;
+ state.active = response.active;
+ state.scopes = mapToScopesViewModel(response.scopes);
+ state.strategies = mapStrategiesToViewModel(response.strategies);
+ state.version = response.version || LEGACY_FLAG;
+ },
+ [types.RECEIVE_FEATURE_FLAG_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+ [types.REQUEST_UPDATE_FEATURE_FLAG](state) {
+ state.isSendingRequest = true;
+ state.error = [];
+ },
+ [types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS](state) {
+ state.isSendingRequest = false;
+ },
+ [types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR](state, error) {
+ state.isSendingRequest = false;
+ state.error = error.message || [];
+ },
+ [types.TOGGLE_ACTIVE](state, active) {
+ state.active = active;
+ },
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/edit/state.js b/app/assets/javascripts/feature_flags/store/modules/edit/state.js
new file mode 100644
index 00000000000..7de05b49482
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/edit/state.js
@@ -0,0 +1,18 @@
+import { LEGACY_FLAG } from '../../../constants';
+
+export default () => ({
+ endpoint: null,
+ path: null,
+ isSendingRequest: false,
+ error: [],
+
+ name: null,
+ description: null,
+ scopes: [],
+ isLoading: false,
+ hasError: false,
+ iid: null,
+ active: true,
+ strategies: [],
+ version: LEGACY_FLAG,
+});
diff --git a/app/assets/javascripts/feature_flags/store/modules/helpers.js b/app/assets/javascripts/feature_flags/store/modules/helpers.js
new file mode 100644
index 00000000000..5a8d7bc6af3
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/helpers.js
@@ -0,0 +1,213 @@
+import { isEmpty, uniqueId, isString } from 'lodash';
+import {
+ ROLLOUT_STRATEGY_ALL_USERS,
+ ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
+ ROLLOUT_STRATEGY_USER_ID,
+ ROLLOUT_STRATEGY_GITLAB_USER_LIST,
+ INTERNAL_ID_PREFIX,
+ DEFAULT_PERCENT_ROLLOUT,
+ PERCENT_ROLLOUT_GROUP_ID,
+ fetchPercentageParams,
+ fetchUserIdParams,
+ LEGACY_FLAG,
+} from '../../constants';
+
+/**
+ * Converts raw scope objects fetched from the API into an array of scope
+ * objects that is easier/nicer to bind to in Vue.
+ * @param {Array} scopesFromRails An array of scope objects fetched from the API
+ */
+export const mapToScopesViewModel = scopesFromRails =>
+ (scopesFromRails || []).map(s => {
+ const percentStrategy = (s.strategies || []).find(
+ strat => strat.name === ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
+ );
+
+ const rolloutPercentage = fetchPercentageParams(percentStrategy) || DEFAULT_PERCENT_ROLLOUT;
+
+ const userStrategy = (s.strategies || []).find(
+ strat => strat.name === ROLLOUT_STRATEGY_USER_ID,
+ );
+
+ const rolloutStrategy =
+ (percentStrategy && percentStrategy.name) ||
+ (userStrategy && userStrategy.name) ||
+ ROLLOUT_STRATEGY_ALL_USERS;
+
+ const rolloutUserIds = (fetchUserIdParams(userStrategy) || '')
+ .split(',')
+ .filter(id => id)
+ .join(', ');
+
+ return {
+ id: s.id,
+ environmentScope: s.environment_scope,
+ active: Boolean(s.active),
+ canUpdate: Boolean(s.can_update),
+ protected: Boolean(s.protected),
+ rolloutStrategy,
+ rolloutPercentage,
+ rolloutUserIds,
+
+ // eslint-disable-next-line no-underscore-dangle
+ shouldBeDestroyed: Boolean(s._destroy),
+ shouldIncludeUserIds: rolloutUserIds.length > 0 && percentStrategy !== null,
+ };
+ });
+/**
+ * Converts the parameters emitted by the Vue component into
+ * the shape that the Rails API expects.
+ * @param {Array} scopesFromVue An array of scope objects from the Vue component
+ */
+export const mapFromScopesViewModel = params => {
+ const scopes = (params.scopes || []).map(s => {
+ const parameters = {};
+ if (s.rolloutStrategy === ROLLOUT_STRATEGY_PERCENT_ROLLOUT) {
+ parameters.groupId = PERCENT_ROLLOUT_GROUP_ID;
+ parameters.percentage = s.rolloutPercentage;
+ } else if (s.rolloutStrategy === ROLLOUT_STRATEGY_USER_ID) {
+ parameters.userIds = (s.rolloutUserIds || '').replace(/, /g, ',');
+ }
+
+ const userIdParameters = {};
+
+ if (s.shouldIncludeUserIds && s.rolloutStrategy !== ROLLOUT_STRATEGY_USER_ID) {
+ userIdParameters.userIds = (s.rolloutUserIds || '').replace(/, /g, ',');
+ }
+
+ // Strip out any internal IDs
+ const id = isString(s.id) && s.id.startsWith(INTERNAL_ID_PREFIX) ? undefined : s.id;
+
+ const strategies = [
+ {
+ name: s.rolloutStrategy,
+ parameters,
+ },
+ ];
+
+ if (!isEmpty(userIdParameters)) {
+ strategies.push({ name: ROLLOUT_STRATEGY_USER_ID, parameters: userIdParameters });
+ }
+
+ return {
+ id,
+ environment_scope: s.environmentScope,
+ active: s.active,
+ can_update: s.canUpdate,
+ protected: s.protected,
+ _destroy: s.shouldBeDestroyed,
+ strategies,
+ };
+ });
+
+ const model = {
+ operations_feature_flag: {
+ name: params.name,
+ description: params.description,
+ active: params.active,
+ scopes_attributes: scopes,
+ version: LEGACY_FLAG,
+ },
+ };
+
+ return model;
+};
+
+/**
+ * Creates a new feature flag environment scope object for use
+ * in a Vue component. An optional parameter can be passed to
+ * override the property values that are created by default.
+ *
+ * @param {Object} overrides An optional object whose
+ * property values will be used to override the default values.
+ *
+ */
+export const createNewEnvironmentScope = (overrides = {}, featureFlagPermissions = false) => {
+ const defaultScope = {
+ environmentScope: '',
+ active: false,
+ id: uniqueId(INTERNAL_ID_PREFIX),
+ rolloutStrategy: ROLLOUT_STRATEGY_ALL_USERS,
+ rolloutPercentage: DEFAULT_PERCENT_ROLLOUT,
+ rolloutUserIds: '',
+ };
+
+ const newScope = {
+ ...defaultScope,
+ ...overrides,
+ };
+
+ if (featureFlagPermissions) {
+ newScope.canUpdate = true;
+ newScope.protected = false;
+ }
+
+ return newScope;
+};
+
+const mapStrategyScopesToRails = scopes =>
+ scopes.length === 0
+ ? [{ environment_scope: '*' }]
+ : scopes.map(s => ({
+ id: s.id,
+ _destroy: s.shouldBeDestroyed,
+ environment_scope: s.environmentScope,
+ }));
+
+const mapStrategyScopesToView = scopes =>
+ scopes.map(s => ({
+ id: s.id,
+ // eslint-disable-next-line no-underscore-dangle
+ shouldBeDestroyed: Boolean(s._destroy),
+ environmentScope: s.environment_scope,
+ }));
+
+const mapStrategiesParametersToViewModel = params => {
+ if (params.userIds) {
+ return { ...params, userIds: params.userIds.split(',').join(', ') };
+ }
+ return params;
+};
+
+export const mapStrategiesToViewModel = strategiesFromRails =>
+ (strategiesFromRails || []).map(s => ({
+ id: s.id,
+ name: s.name,
+ parameters: mapStrategiesParametersToViewModel(s.parameters),
+ userListId: s.user_list?.id,
+ // eslint-disable-next-line no-underscore-dangle
+ shouldBeDestroyed: Boolean(s._destroy),
+ scopes: mapStrategyScopesToView(s.scopes),
+ }));
+
+const mapStrategiesParametersToRails = params => {
+ if (params.userIds) {
+ return { ...params, userIds: params.userIds.split(', ').join(',') };
+ }
+ return params;
+};
+
+const mapStrategyToRails = strategy => {
+ const mappedStrategy = {
+ id: strategy.id,
+ name: strategy.name,
+ _destroy: strategy.shouldBeDestroyed,
+ scopes_attributes: mapStrategyScopesToRails(strategy.scopes || []),
+ parameters: mapStrategiesParametersToRails(strategy.parameters),
+ };
+
+ if (strategy.name === ROLLOUT_STRATEGY_GITLAB_USER_LIST) {
+ mappedStrategy.user_list_id = strategy.userListId;
+ }
+ return mappedStrategy;
+};
+
+export const mapStrategiesToRails = params => ({
+ operations_feature_flag: {
+ name: params.name,
+ description: params.description,
+ version: params.version,
+ active: params.active,
+ strategies_attributes: (params.strategies || []).map(mapStrategyToRails),
+ },
+});
diff --git a/app/assets/javascripts/feature_flags/store/modules/index/actions.js b/app/assets/javascripts/feature_flags/store/modules/index/actions.js
new file mode 100644
index 00000000000..ed41dd34e4d
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/index/actions.js
@@ -0,0 +1,107 @@
+import Api from '~/api';
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+
+export const setFeatureFlagsEndpoint = ({ commit }, endpoint) =>
+ commit(types.SET_FEATURE_FLAGS_ENDPOINT, endpoint);
+
+export const setFeatureFlagsOptions = ({ commit }, options) =>
+ commit(types.SET_FEATURE_FLAGS_OPTIONS, options);
+
+export const setInstanceIdEndpoint = ({ commit }, endpoint) =>
+ commit(types.SET_INSTANCE_ID_ENDPOINT, endpoint);
+
+export const setProjectId = ({ commit }, endpoint) => commit(types.SET_PROJECT_ID, endpoint);
+
+export const setInstanceId = ({ commit }, instanceId) => commit(types.SET_INSTANCE_ID, instanceId);
+
+export const fetchFeatureFlags = ({ state, dispatch }) => {
+ dispatch('requestFeatureFlags');
+
+ axios
+ .get(state.endpoint, {
+ params: state.options,
+ })
+ .then(response =>
+ dispatch('receiveFeatureFlagsSuccess', {
+ data: response.data || {},
+ headers: response.headers,
+ }),
+ )
+ .catch(() => dispatch('receiveFeatureFlagsError'));
+};
+
+export const requestFeatureFlags = ({ commit }) => commit(types.REQUEST_FEATURE_FLAGS);
+export const receiveFeatureFlagsSuccess = ({ commit }, response) =>
+ commit(types.RECEIVE_FEATURE_FLAGS_SUCCESS, response);
+export const receiveFeatureFlagsError = ({ commit }) => commit(types.RECEIVE_FEATURE_FLAGS_ERROR);
+
+export const fetchUserLists = ({ state, dispatch }) => {
+ dispatch('requestUserLists');
+
+ return Api.fetchFeatureFlagUserLists(state.projectId, state.options.page)
+ .then(({ data, headers }) => dispatch('receiveUserListsSuccess', { data, headers }))
+ .catch(() => dispatch('receiveUserListsError'));
+};
+
+export const requestUserLists = ({ commit }) => commit(types.REQUEST_USER_LISTS);
+export const receiveUserListsSuccess = ({ commit }, response) =>
+ commit(types.RECEIVE_USER_LISTS_SUCCESS, response);
+export const receiveUserListsError = ({ commit }) => commit(types.RECEIVE_USER_LISTS_ERROR);
+
+export const toggleFeatureFlag = ({ dispatch }, flag) => {
+ dispatch('updateFeatureFlag', flag);
+
+ axios
+ .put(flag.update_path, {
+ operations_feature_flag: flag,
+ })
+ .then(response => dispatch('receiveUpdateFeatureFlagSuccess', response.data))
+ .catch(() => dispatch('receiveUpdateFeatureFlagError', flag.id));
+};
+
+export const updateFeatureFlag = ({ commit }, flag) => commit(types.UPDATE_FEATURE_FLAG, flag);
+
+export const receiveUpdateFeatureFlagSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS, data);
+export const receiveUpdateFeatureFlagError = ({ commit }, id) =>
+ commit(types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR, id);
+
+export const deleteUserList = ({ state, dispatch }, list) => {
+ dispatch('requestDeleteUserList', list);
+
+ return Api.deleteFeatureFlagUserList(state.projectId, list.iid)
+ .then(() => dispatch('fetchUserLists'))
+ .catch(error =>
+ dispatch('receiveDeleteUserListError', {
+ list,
+ error: error?.response?.data ?? error,
+ }),
+ );
+};
+
+export const requestDeleteUserList = ({ commit }, list) =>
+ commit(types.REQUEST_DELETE_USER_LIST, list);
+
+export const receiveDeleteUserListError = ({ commit }, { error, list }) => {
+ commit(types.RECEIVE_DELETE_USER_LIST_ERROR, { error, list });
+};
+
+export const rotateInstanceId = ({ state, dispatch }) => {
+ dispatch('requestRotateInstanceId');
+
+ axios
+ .post(state.rotateEndpoint)
+ .then(({ data = {}, headers }) => dispatch('receiveRotateInstanceIdSuccess', { data, headers }))
+ .catch(() => dispatch('receiveRotateInstanceIdError'));
+};
+
+export const requestRotateInstanceId = ({ commit }) => commit(types.REQUEST_ROTATE_INSTANCE_ID);
+export const receiveRotateInstanceIdSuccess = ({ commit }, response) =>
+ commit(types.RECEIVE_ROTATE_INSTANCE_ID_SUCCESS, response);
+export const receiveRotateInstanceIdError = ({ commit }) =>
+ commit(types.RECEIVE_ROTATE_INSTANCE_ID_ERROR);
+
+export const clearAlert = ({ commit }, index) => {
+ commit(types.RECEIVE_CLEAR_ALERT, index);
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/index/index.js b/app/assets/javascripts/feature_flags/store/modules/index/index.js
new file mode 100644
index 00000000000..665bb29a17e
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/index/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+export default {
+ namespaced: true,
+ actions,
+ mutations,
+ state: state(),
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js b/app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js
new file mode 100644
index 00000000000..4a4bd13c945
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/index/mutation_types.js
@@ -0,0 +1,26 @@
+export const SET_FEATURE_FLAGS_ENDPOINT = 'SET_FEATURE_FLAGS_ENDPOINT';
+export const SET_FEATURE_FLAGS_OPTIONS = 'SET_FEATURE_FLAGS_OPTIONS';
+export const SET_INSTANCE_ID_ENDPOINT = 'SET_INSTANCE_ID_ENDPOINT';
+export const SET_INSTANCE_ID = 'SET_INSTANCE_ID';
+export const SET_PROJECT_ID = 'SET_PROJECT_ID';
+
+export const REQUEST_FEATURE_FLAGS = 'REQUEST_FEATURE_FLAGS';
+export const RECEIVE_FEATURE_FLAGS_SUCCESS = 'RECEIVE_FEATURE_FLAGS_SUCCESS';
+export const RECEIVE_FEATURE_FLAGS_ERROR = 'RECEIVE_FEATURE_FLAGS_ERROR';
+
+export const REQUEST_USER_LISTS = 'REQUEST_USER_LISTS';
+export const RECEIVE_USER_LISTS_SUCCESS = 'RECEIVE_USER_LISTS_SUCCESS';
+export const RECEIVE_USER_LISTS_ERROR = 'RECEIVE_USER_LISTS_ERROR';
+
+export const REQUEST_DELETE_USER_LIST = 'REQUEST_DELETE_USER_LIST';
+export const RECEIVE_DELETE_USER_LIST_ERROR = 'RECEIVE_DELETE_USER_LIST_ERROR';
+
+export const UPDATE_FEATURE_FLAG = 'UPDATE_FEATURE_FLAG';
+export const RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS = 'RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS';
+export const RECEIVE_UPDATE_FEATURE_FLAG_ERROR = 'RECEIVE_UPDATE_FEATURE_FLAG_ERROR';
+
+export const REQUEST_ROTATE_INSTANCE_ID = 'REQUEST_ROTATE_INSTANCE_ID';
+export const RECEIVE_ROTATE_INSTANCE_ID_SUCCESS = 'RECEIVE_ROTATE_INSTANCE_ID_SUCCESS';
+export const RECEIVE_ROTATE_INSTANCE_ID_ERROR = 'RECEIVE_ROTATE_INSTANCE_ID_ERROR';
+
+export const RECEIVE_CLEAR_ALERT = 'RECEIVE_CLEAR_ALERT';
diff --git a/app/assets/javascripts/feature_flags/store/modules/index/mutations.js b/app/assets/javascripts/feature_flags/store/modules/index/mutations.js
new file mode 100644
index 00000000000..948786a3533
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/index/mutations.js
@@ -0,0 +1,125 @@
+import Vue from 'vue';
+import * as types from './mutation_types';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants';
+import { mapToScopesViewModel } from '../helpers';
+
+const mapFlag = flag => ({ ...flag, scopes: mapToScopesViewModel(flag.scopes || []) });
+
+const updateFlag = (state, flag) => {
+ const index = state[FEATURE_FLAG_SCOPE].findIndex(({ id }) => id === flag.id);
+ Vue.set(state[FEATURE_FLAG_SCOPE], index, flag);
+};
+
+const createPaginationInfo = (state, headers) => {
+ let paginationInfo;
+ if (Object.keys(headers).length) {
+ const normalizedHeaders = normalizeHeaders(headers);
+ paginationInfo = parseIntPagination(normalizedHeaders);
+ } else {
+ paginationInfo = headers;
+ }
+ return paginationInfo;
+};
+
+export default {
+ [types.SET_FEATURE_FLAGS_ENDPOINT](state, endpoint) {
+ state.endpoint = endpoint;
+ },
+ [types.SET_FEATURE_FLAGS_OPTIONS](state, options = {}) {
+ state.options = options;
+ },
+ [types.SET_INSTANCE_ID_ENDPOINT](state, endpoint) {
+ state.rotateEndpoint = endpoint;
+ },
+ [types.SET_INSTANCE_ID](state, instance) {
+ state.instanceId = instance;
+ },
+ [types.SET_PROJECT_ID](state, project) {
+ state.projectId = project;
+ },
+ [types.REQUEST_FEATURE_FLAGS](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_FEATURE_FLAGS_SUCCESS](state, response) {
+ state.isLoading = false;
+ state.hasError = false;
+ state[FEATURE_FLAG_SCOPE] = (response.data.feature_flags || []).map(mapFlag);
+
+ const paginationInfo = createPaginationInfo(state, response.headers);
+ state.count = {
+ ...state.count,
+ [FEATURE_FLAG_SCOPE]: paginationInfo?.total ?? state[FEATURE_FLAG_SCOPE].length,
+ };
+ state.pageInfo = {
+ ...state.pageInfo,
+ [FEATURE_FLAG_SCOPE]: paginationInfo,
+ };
+ },
+ [types.RECEIVE_FEATURE_FLAGS_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+ [types.REQUEST_USER_LISTS](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_USER_LISTS_SUCCESS](state, response) {
+ state.isLoading = false;
+ state.hasError = false;
+ state[USER_LIST_SCOPE] = response.data || [];
+
+ const paginationInfo = createPaginationInfo(state, response.headers);
+ state.count = {
+ ...state.count,
+ [USER_LIST_SCOPE]: paginationInfo?.total ?? state[USER_LIST_SCOPE].length,
+ };
+ state.pageInfo = {
+ ...state.pageInfo,
+ [USER_LIST_SCOPE]: paginationInfo,
+ };
+ },
+ [types.RECEIVE_USER_LISTS_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+ [types.REQUEST_ROTATE_INSTANCE_ID](state) {
+ state.isRotating = true;
+ state.hasRotateError = false;
+ },
+ [types.RECEIVE_ROTATE_INSTANCE_ID_SUCCESS](
+ state,
+ {
+ data: { token },
+ },
+ ) {
+ state.isRotating = false;
+ state.instanceId = token;
+ state.hasRotateError = false;
+ },
+ [types.RECEIVE_ROTATE_INSTANCE_ID_ERROR](state) {
+ state.isRotating = false;
+ state.hasRotateError = true;
+ },
+ [types.UPDATE_FEATURE_FLAG](state, flag) {
+ updateFlag(state, flag);
+ },
+ [types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS](state, data) {
+ updateFlag(state, mapFlag(data));
+ },
+ [types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR](state, i) {
+ const flag = state[FEATURE_FLAG_SCOPE].find(({ id }) => i === id);
+ updateFlag(state, { ...flag, active: !flag.active });
+ },
+ [types.REQUEST_DELETE_USER_LIST](state, list) {
+ state.userLists = state.userLists.filter(l => l !== list);
+ },
+ [types.RECEIVE_DELETE_USER_LIST_ERROR](state, { error, list }) {
+ state.isLoading = false;
+ state.hasError = false;
+ state.alerts = [].concat(error.message);
+ state.userLists = state.userLists.concat(list).sort((l1, l2) => l1.iid - l2.iid);
+ },
+ [types.RECEIVE_CLEAR_ALERT](state, index) {
+ state.alerts.splice(index, 1);
+ },
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/index/state.js b/app/assets/javascripts/feature_flags/store/modules/index/state.js
new file mode 100644
index 00000000000..443a12d485d
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/index/state.js
@@ -0,0 +1,18 @@
+import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants';
+
+export default () => ({
+ [FEATURE_FLAG_SCOPE]: [],
+ [USER_LIST_SCOPE]: [],
+ alerts: [],
+ count: {},
+ pageInfo: { [FEATURE_FLAG_SCOPE]: {}, [USER_LIST_SCOPE]: {} },
+ isLoading: true,
+ hasError: false,
+ endpoint: null,
+ rotateEndpoint: null,
+ instanceId: '',
+ isRotating: false,
+ hasRotateError: false,
+ options: {},
+ projectId: '',
+});
diff --git a/app/assets/javascripts/feature_flags/store/modules/new/actions.js b/app/assets/javascripts/feature_flags/store/modules/new/actions.js
new file mode 100644
index 00000000000..d2159d55d53
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/new/actions.js
@@ -0,0 +1,51 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { NEW_VERSION_FLAG } from '../../../constants';
+import { mapFromScopesViewModel, mapStrategiesToRails } from '../helpers';
+
+/**
+ * Commits mutation to set the main endpoint
+ * @param {String} endpoint
+ */
+export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint);
+
+/**
+ * Commits mutation to set the feature flag path.
+ * Used to redirect the user after form submission
+ *
+ * @param {String} path
+ */
+export const setPath = ({ commit }, path) => commit(types.SET_PATH, path);
+
+/**
+ * Handles the creation of a new feature flag.
+ *
+ * Will dispatch `requestCreateFeatureFlag`
+ * Serializes the params and makes a post request
+ * Dispatches an action acording to the request status.
+ *
+ * @param {Object} params
+ */
+export const createFeatureFlag = ({ state, dispatch }, params) => {
+ dispatch('requestCreateFeatureFlag');
+
+ return axios
+ .post(
+ state.endpoint,
+ params.version === NEW_VERSION_FLAG
+ ? mapStrategiesToRails(params)
+ : mapFromScopesViewModel(params),
+ )
+ .then(() => {
+ dispatch('receiveCreateFeatureFlagSuccess');
+ visitUrl(state.path);
+ })
+ .catch(error => dispatch('receiveCreateFeatureFlagError', error.response.data));
+};
+
+export const requestCreateFeatureFlag = ({ commit }) => commit(types.REQUEST_CREATE_FEATURE_FLAG);
+export const receiveCreateFeatureFlagSuccess = ({ commit }) =>
+ commit(types.RECEIVE_CREATE_FEATURE_FLAG_SUCCESS);
+export const receiveCreateFeatureFlagError = ({ commit }, error) =>
+ commit(types.RECEIVE_CREATE_FEATURE_FLAG_ERROR, error);
diff --git a/app/assets/javascripts/feature_flags/store/modules/new/index.js b/app/assets/javascripts/feature_flags/store/modules/new/index.js
new file mode 100644
index 00000000000..665bb29a17e
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/new/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+export default {
+ namespaced: true,
+ actions,
+ mutations,
+ state: state(),
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js b/app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js
new file mode 100644
index 00000000000..317f3689dfd
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/new/mutation_types.js
@@ -0,0 +1,6 @@
+export const SET_ENDPOINT = 'SET_ENDPOINT';
+export const SET_PATH = 'SET_PATH';
+
+export const REQUEST_CREATE_FEATURE_FLAG = 'REQUEST_CREATE_FEATURE_FLAG';
+export const RECEIVE_CREATE_FEATURE_FLAG_SUCCESS = 'RECEIVE_CREATE_FEATURE_FLAG_SUCCESS';
+export const RECEIVE_CREATE_FEATURE_FLAG_ERROR = 'RECEIVE_CREATE_FEATURE_FLAG_ERROR';
diff --git a/app/assets/javascripts/feature_flags/store/modules/new/mutations.js b/app/assets/javascripts/feature_flags/store/modules/new/mutations.js
new file mode 100644
index 00000000000..06e467c04f1
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/new/mutations.js
@@ -0,0 +1,21 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_ENDPOINT](state, endpoint) {
+ state.endpoint = endpoint;
+ },
+ [types.SET_PATH](state, path) {
+ state.path = path;
+ },
+ [types.REQUEST_CREATE_FEATURE_FLAG](state) {
+ state.isSendingRequest = true;
+ state.error = [];
+ },
+ [types.RECEIVE_CREATE_FEATURE_FLAG_SUCCESS](state) {
+ state.isSendingRequest = false;
+ },
+ [types.RECEIVE_CREATE_FEATURE_FLAG_ERROR](state, error) {
+ state.isSendingRequest = false;
+ state.error = error.message || [];
+ },
+};
diff --git a/app/assets/javascripts/feature_flags/store/modules/new/state.js b/app/assets/javascripts/feature_flags/store/modules/new/state.js
new file mode 100644
index 00000000000..6f9263dbb2a
--- /dev/null
+++ b/app/assets/javascripts/feature_flags/store/modules/new/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+ endpoint: null,
+ path: null,
+ isSendingRequest: false,
+ error: [],
+});