Commit 339a9173 authored by Phil Hughes's avatar Phil Hughes

Merge branch '8621-ff-store-new-create' into 'master'

Adds new and edit modules FF store

See merge request gitlab-org/gitlab-ee!9292
parents 2b2ae904 cafbf4c0
import Vue from 'vue';
import Vuex from 'vuex';
import indexModule from './modules/index';
import newModule from './modules/new';
import editModule from './modules/edit';
Vue.use(Vuex);
......@@ -8,6 +10,8 @@ export const createStore = () =>
new Vuex.Store({
modules: {
index: indexModule,
new: newModule,
edit: editModule,
},
});
......
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { parseFeatureFlagsParams } 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: parseFeatureFlagsParams(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!'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import state from './state';
import * as actions from './actions';
import mutations from './mutations';
export default {
namespaced: true,
actions,
mutations,
state: state(),
};
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';
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_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.scopes = state.scopes;
},
[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;
},
};
export default () => ({
endpoint: null,
path: null,
isSendingRequest: false,
error: [],
name: null,
description: null,
scopes: null,
isLoading: false,
hasError: false,
});
// eslint-disable-next-line import/prefer-default-export
export const parseFeatureFlagsParams = params => ({
operations_feature_flags: {
name: params.name,
description: params.description,
active: true,
scopes_attributes: params.scopes.map(scope => ({
environment_scope: scope.name,
active: scope.active,
})),
},
});
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { parseFeatureFlagsParams } 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');
axios
.post(state.endpoint, { params: parseFeatureFlagsParams(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);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import state from './state';
import * as actions from './actions';
import mutations from './mutations';
export default {
namespaced: true,
actions,
mutations,
state: state(),
};
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';
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;
},
};
export default () => ({
endpoint: null,
path: null,
isSendingRequest: false,
error: [],
});
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import actions, {
setEndpoint,
setPath,
updateFeatureFlag,
requestUpdateFeatureFlag,
receiveUpdateFeatureFlagSuccess,
receiveUpdateFeatureFlagError,
fetchFeatureFlag,
requestFeatureFlag,
receiveFeatureFlagSuccess,
receiveFeatureFlagError,
} from 'ee/feature_flags/store/modules/edit/actions';
import state from 'ee/feature_flags/store/modules/edit/state';
import * as types from 'ee/feature_flags/store/modules/edit/mutation_types';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
describe('Feature flags Edit Module actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state();
});
describe('setEndpoint', () => {
it('should commit SET_ENDPOINT mutation', done => {
testAction(
setEndpoint,
'feature_flags.json',
mockedState,
[{ type: types.SET_ENDPOINT, payload: 'feature_flags.json' }],
[],
done,
);
});
});
describe('setPath', () => {
it('should commit SET_PATH mutation', done => {
testAction(
setPath,
'/feature_flags',
mockedState,
[{ type: types.SET_PATH, payload: '/feature_flags' }],
[],
done,
);
});
});
describe('updateFeatureFlag', () => {
let mock;
beforeEach(() => {
mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
spyOnDependency(actions, 'visitUrl');
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagSuccess ', done => {
mock
.onPut(mockedState.endpoint, {
params: {
operations_feature_flags: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(200);
testAction(
updateFeatureFlag,
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
},
mockedState,
[],
[
{
type: 'requestUpdateFeatureFlag',
},
{
type: 'receiveUpdateFeatureFlagSuccess',
},
],
done,
);
});
});
describe('error', () => {
it('dispatches requestUpdateFeatureFlag and receiveUpdateFeatureFlagError ', done => {
mock
.onPut(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(500, { message: [] });
testAction(
updateFeatureFlag,
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
},
mockedState,
[],
[
{
type: 'requestUpdateFeatureFlag',
},
{
type: 'receiveUpdateFeatureFlagError',
payload: { message: [] },
},
],
done,
);
});
});
});
describe('requestUpdateFeatureFlag', () => {
it('should commit REQUEST_UPDATE_FEATURE_FLAG mutation', done => {
testAction(
requestUpdateFeatureFlag,
null,
mockedState,
[{ type: types.REQUEST_UPDATE_FEATURE_FLAG }],
[],
done,
);
});
});
describe('receiveUpdateFeatureFlagSuccess', () => {
it('should commit RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS mutation', done => {
testAction(
receiveUpdateFeatureFlagSuccess,
null,
mockedState,
[
{
type: types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS,
},
],
[],
done,
);
});
});
describe('receiveUpdateFeatureFlagError', () => {
it('should commit RECEIVE_UPDATE_FEATURE_FLAG_ERROR mutation', done => {
testAction(
receiveUpdateFeatureFlagError,
'There was an error',
mockedState,
[{ type: types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR, payload: 'There was an error' }],
[],
done,
);
});
});
describe('fetchFeatureFlag', () => {
let mock;
beforeEach(() => {
mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestFeatureFlag and receiveFeatureFlagSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 1 });
testAction(
fetchFeatureFlag,
{ id: 1 },
mockedState,
[],
[
{
type: 'requestFeatureFlag',
},
{
type: 'receiveFeatureFlagSuccess',
payload: { id: 1 },
},
],
done,
);
});
});
describe('error', () => {
it('dispatches requestFeatureFlag and receiveUpdateFeatureFlagError ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`, {}).replyOnce(500, {});
testAction(
fetchFeatureFlag,
null,
mockedState,
[],
[
{
type: 'requestFeatureFlag',
},
{
type: 'receiveFeatureFlagError',
},
],
done,
);
});
});
});
describe('requestFeatureFlag', () => {
it('should commit REQUEST_FEATURE_FLAG mutation', done => {
testAction(
requestFeatureFlag,
null,
mockedState,
[{ type: types.REQUEST_FEATURE_FLAG }],
[],
done,
);
});
});
describe('receiveFeatureFlagSuccess', () => {
it('should commit RECEIVE_FEATURE_FLAG_SUCCESS mutation', done => {
testAction(
receiveFeatureFlagSuccess,
{ id: 1 },
mockedState,
[{ type: types.RECEIVE_FEATURE_FLAG_SUCCESS, payload: { id: 1 } }],
[],
done,
);
});
});
describe('receiveFeatureFlagError', () => {
it('should commit RECEIVE_FEATURE_FLAG_ERROR mutation', done => {
testAction(
receiveFeatureFlagError,
null,
mockedState,
[
{
type: types.RECEIVE_FEATURE_FLAG_ERROR,
},
],
[],
done,
);
});
});
});
import state from 'ee/feature_flags/store/modules/edit/state';
import mutations from 'ee/feature_flags/store/modules/edit/mutations';
import * as types from 'ee/feature_flags/store/modules/edit/mutation_types';
describe('Feature flags Edit Module Mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('SET_ENDPOINT', () => {
it('should set endpoint', () => {
mutations[types.SET_ENDPOINT](stateCopy, 'feature_flags.json');
expect(stateCopy.endpoint).toEqual('feature_flags.json');
});
});
describe('SET_PATH', () => {
it('should set provided options', () => {
mutations[types.SET_PATH](stateCopy, 'feature_flags');
expect(stateCopy.path).toEqual('feature_flags');
});
});
describe('REQUEST_FEATURE_FLAG', () => {
it('should set isLoading to true', () => {
mutations[types.REQUEST_FEATURE_FLAG](stateCopy);
expect(stateCopy.isLoading).toEqual(true);
});
it('should set error to an empty array', () => {
mutations[types.REQUEST_FEATURE_FLAG](stateCopy);
expect(stateCopy.error).toEqual([]);
});
});
describe('RECEIVE_FEATURE_FLAG_SUCCESS', () => {
const data = {
name: '*',
description: 'All environments',
scopes: [{ id: 1 }],
};
beforeEach(() => {
mutations[types.RECEIVE_FEATURE_FLAG_SUCCESS](stateCopy, data);
});
it('should set isLoading to false', () => {
expect(stateCopy.isLoading).toEqual(false);
});
it('should set hasError to false', () => {
expect(stateCopy.hasError).toEqual(false);
});
it('should set name with the provided one', () => {
expect(stateCopy.name).toEqual(data.name);
});
it('should set description with the provided one', () => {
expect(stateCopy.description).toEqual(data.description);
});
it('should set scope with the provided one', () => {
expect(stateCopy.scope).toEqual(data.scope);
});
});
describe('RECEIVE_FEATURE_FLAG_ERROR', () => {
beforeEach(() => {
mutations[types.RECEIVE_FEATURE_FLAG_ERROR](stateCopy);
});
it('should set isLoading to false', () => {
expect(stateCopy.isLoading).toEqual(false);
});
it('should set hasError to true', () => {
expect(stateCopy.hasError).toEqual(true);
});
});
describe('REQUEST_UPDATE_FEATURE_FLAG', () => {
beforeEach(() => {
mutations[types.REQUEST_UPDATE_FEATURE_FLAG](stateCopy);
});
it('should set isSendingRequest to true', () => {
expect(stateCopy.isSendingRequest).toEqual(true);
});
it('should set error to an empty array', () => {
expect(stateCopy.error).toEqual([]);
});
});
describe('RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS', () => {
it('should set isSendingRequest to false', () => {
mutations[types.RECEIVE_UPDATE_FEATURE_FLAG_SUCCESS](stateCopy);
expect(stateCopy.isSendingRequest).toEqual(false);
});
});
describe('RECEIVE_UPDATE_FEATURE_FLAG_ERROR', () => {
beforeEach(() => {
mutations[types.RECEIVE_UPDATE_FEATURE_FLAG_ERROR](stateCopy, {
message: ['Name is required'],
});
});
it('should set isSendingRequest to false', () => {
expect(stateCopy.isSendingRequest).toEqual(false);
});
it('should set error to the given message', () => {
expect(stateCopy.error).toEqual(['Name is required']);
});
});
});
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import actions, {
setEndpoint,
setPath,
createFeatureFlag,
requestCreateFeatureFlag,
receiveCreateFeatureFlagSuccess,
receiveCreateFeatureFlagError,
} from 'ee/feature_flags/store/modules/new/actions';
import state from 'ee/feature_flags/store/modules/new/state';
import * as types from 'ee/feature_flags/store/modules/new/mutation_types';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
describe('Feature flags New Module Actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state();
});
describe('setEndpoint', () => {
it('should commit SET_ENDPOINT mutation', done => {
testAction(
setEndpoint,
'feature_flags.json',
mockedState,
[{ type: types.SET_ENDPOINT, payload: 'feature_flags.json' }],
[],
done,
);
});
});
describe('setPath', () => {
it('should commit SET_PATH mutation', done => {
testAction(
setPath,
'/feature_flags',
mockedState,
[{ type: types.SET_PATH, payload: '/feature_flags' }],
[],
done,
);
});
});
describe('createFeatureFlag', () => {
let mock;
beforeEach(() => {
mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
spyOnDependency(actions, 'visitUrl');
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagSuccess ', done => {
mock
.onPost(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(200);
testAction(
createFeatureFlag,
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
},
mockedState,
[],
[
{
type: 'requestCreateFeatureFlag',
},
{
type: 'receiveCreateFeatureFlagSuccess',
},
],
done,
);
});
});
describe('error', () => {
it('dispatches requestCreateFeatureFlag and receiveCreateFeatureFlagError ', done => {
mock
.onPost(`${TEST_HOST}/endpoint.json`, {
params: {
operations_feature_flags: {
name: 'feature_flag',
description: 'feature flag',
active: true,
scopes_attributes: [{ environment_scope: '*', active: true }],
},
},
})
.replyOnce(500, { message: [] });
testAction(
createFeatureFlag,
{
name: 'feature_flag',
description: 'feature flag',
scopes: [{ name: '*', active: true }],
},
mockedState,
[],
[
{
type: 'requestCreateFeatureFlag',
},
{
type: 'receiveCreateFeatureFlagError',
payload: { message: [] },
},
],
done,
);
});
});
});
describe('requestCreateFeatureFlag', () => {
it('should commit REQUEST_CREATE_FEATURE_FLAG mutation', done => {
testAction(
requestCreateFeatureFlag,
null,
mockedState,
[{ type: types.REQUEST_CREATE_FEATURE_FLAG }],
[],
done,
);
});
});
describe('receiveCreateFeatureFlagSuccess', () => {
it('should commit RECEIVE_CREATE_FEATURE_FLAG_SUCCESS mutation', done => {
testAction(
receiveCreateFeatureFlagSuccess,
null,
mockedState,
[
{
type: types.RECEIVE_CREATE_FEATURE_FLAG_SUCCESS,
},
],
[],
done,
);
});
});
describe('receiveCreateFeatureFlagError', () => {
it('should commit RECEIVE_CREATE_FEATURE_FLAG_ERROR mutation', done => {
testAction(
receiveCreateFeatureFlagError,
'There was an error',
mockedState,
[{ type: types.RECEIVE_CREATE_FEATURE_FLAG_ERROR, payload: 'There was an error' }],
[],
done,
);
});
});
});
import state from 'ee/feature_flags/store/modules/new/state';
import mutations from 'ee/feature_flags/store/modules/new/mutations';
import * as types from 'ee/feature_flags/store/modules/new/mutation_types';
describe('Feature flags New Module Mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('SET_ENDPOINT', () => {
it('should set endpoint', () => {
mutations[types.SET_ENDPOINT](stateCopy, 'feature_flags.json');
expect(stateCopy.endpoint).toEqual('feature_flags.json');
});
});
describe('SET_PATH', () => {
it('should set provided options', () => {
mutations[types.SET_PATH](stateCopy, 'feature_flags');
expect(stateCopy.path).toEqual('feature_flags');
});
});
describe('REQUEST_CREATE_FEATURE_FLAG', () => {
it('should set isSendingRequest to true', () => {
mutations[types.REQUEST_CREATE_FEATURE_FLAG](stateCopy);
expect(stateCopy.isSendingRequest).toEqual(true);
});
it('should set error to an empty array', () => {
mutations[types.REQUEST_CREATE_FEATURE_FLAG](stateCopy);
expect(stateCopy.error).toEqual([]);
});
});
describe('RECEIVE_CREATE_FEATURE_FLAG_SUCCESS', () => {
it('should set isSendingRequest to false', () => {
mutations[types.RECEIVE_CREATE_FEATURE_FLAG_SUCCESS](stateCopy);
expect(stateCopy.isSendingRequest).toEqual(false);
});
});
describe('RECEIVE_CREATE_FEATURE_FLAG_ERROR', () => {
beforeEach(() => {
mutations[types.RECEIVE_CREATE_FEATURE_FLAG_ERROR](stateCopy, {
message: ['Name is required'],
});
});
it('should set isSendingRequest to false', () => {
expect(stateCopy.isSendingRequest).toEqual(false);
});
it('should set hasError to true', () => {
expect(stateCopy.error).toEqual(['Name is required']);
});
});
});
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