Commit 921bae09 authored by Phil Hughes's avatar Phil Hughes

Merge branch '8621-move-index-vuex-store' into 'master'

Transforms Feature Flag store into a module

See merge request gitlab-org/gitlab-ee!9214
parents e6b5f452 a4db6ef7
<script>
import { mapState, mapActions } from 'vuex';
import { createNamespacedHelpers } from 'vuex';
import _ from 'underscore';
import { GlEmptyState, GlLoadingIcon, GlButton } from '@gitlab/ui';
import FeatureFlagsTable from './feature_flags_table.vue';
......@@ -13,6 +13,8 @@ import {
buildUrlWithCurrentLocation,
} from '~/lib/utils/common_utils';
const { mapState, mapActions } = createNamespacedHelpers('index');
export default {
store,
components: {
......
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
import mutations from './mutations';
import indexModule from './modules/index';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
actions,
mutations,
state,
modules: {
index: indexModule,
},
});
export default createStore();
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
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 }, error) =>
commit(types.RECEIVE_FEATURE_FLAGS_ERROR, error);
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 fetchFeatureFlags = ({ state, dispatch }) => {
dispatch('requestFeatureFlags');
......@@ -14,15 +14,19 @@ export const fetchFeatureFlags = ({ state, dispatch }) => {
.get(state.endpoint, {
params: state.options,
})
.then(response => dispatch('receiveFeatureFlagsSuccess', response))
.catch(error => dispatch('receiveFeatureFlagsError', error));
.then(response =>
dispatch('receiveFeatureFlagsSuccess', {
data: response.data || {},
headers: response.headers,
}),
)
.catch(() => dispatch('receiveFeatureFlagsError'));
};
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 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);
// 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(),
};
......@@ -13,6 +13,7 @@ export default {
},
[types.RECEIVE_FEATURE_FLAGS_SUCCESS](state, response) {
state.isLoading = false;
state.hasError = false;
state.featureFlags = response.data.feature_flags;
state.count = response.data.count;
......
......@@ -2,13 +2,13 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import featureFlagsComponent from 'ee/feature_flags/components/feature_flags.vue';
import { createStore } from 'ee/feature_flags/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { featureFlag } from './mock_data';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
import { getRequestData } from '../mock_data';
describe('Feature Flags', () => {
const mockData = {
endpoint: 'feature_flags.json',
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
featureFlagsHelpPagePath: '/help/feature-flags',
......@@ -16,7 +16,6 @@ describe('Feature Flags', () => {
newFeatureFlagPath: 'feature-flags/new',
};
let store;
let FeatureFlagsComponent;
let component;
let mock;
......@@ -28,13 +27,13 @@ describe('Feature Flags', () => {
});
afterEach(() => {
component.$destroy();
mock.restore();
component.$destroy();
});
describe('without permissions', () => {
const props = {
endpoint: 'feature_flags.json',
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
featureFlagsHelpPagePath: '/help/feature-flags',
......@@ -42,18 +41,11 @@ describe('Feature Flags', () => {
};
beforeEach(done => {
mock.onGet(mockData.endpoint).reply(200, {
feature_flags: [],
count: {
all: 0,
enabled: 0,
disabled: 0,
},
});
component = mountComponentWithStore(FeatureFlagsComponent, {
store,
props,
});
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: 'all', page: '1' } })
.reply(200, getRequestData, {});
component = mountComponent(FeatureFlagsComponent, props);
setTimeout(() => {
done();
......@@ -71,19 +63,11 @@ describe('Feature Flags', () => {
describe('loading state', () => {
it('renders a loading icon', done => {
mock.onGet(mockData.endpoint).reply(200, {
feature_flags: [],
count: {
all: 0,
enabled: 0,
disabled: 0,
},
});
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { scope: 'all', page: '1' } })
.replyOnce(200, getRequestData, {});
component = mountComponentWithStore(FeatureFlagsComponent, {
store,
props: mockData,
});
component = mountComponent(FeatureFlagsComponent, mockData);
const loadingElement = component.$el.querySelector('.js-loading-state');
......@@ -101,19 +85,20 @@ describe('Feature Flags', () => {
describe('successful request', () => {
describe('without feature flags', () => {
beforeEach(done => {
mock.onGet(mockData.endpoint).reply(200, {
mock.onGet(mockData.endpoint, { params: { scope: 'all', page: '1' } }).replyOnce(
200,
{
feature_flags: [],
count: {
all: 0,
enabled: 0,
disabled: 0,
},
});
},
{},
);
component = mountComponentWithStore(FeatureFlagsComponent, {
store,
props: mockData,
});
component = mountComponent(FeatureFlagsComponent, mockData);
setTimeout(() => {
done();
......@@ -135,33 +120,18 @@ describe('Feature Flags', () => {
describe('with paginated feature flags', () => {
beforeEach(done => {
mock.onGet(mockData.endpoint).reply(
200,
{
feature_flags: [featureFlag],
count: {
all: 37,
enabled: 5,
disabled: 32,
},
},
{
'X-nExt-pAge': '2',
mock
.onGet(mockData.endpoint, { params: { scope: 'all', page: '1' } })
.replyOnce(200, getRequestData, {
'x-next-page': '2',
'x-page': '1',
'X-Per-Page': '1',
'X-Per-Page': '2',
'X-Prev-Page': '',
'X-TOTAL': '37',
'X-Total-Pages': '2',
},
);
store = createStore();
component = mountComponentWithStore(FeatureFlagsComponent, {
store,
props: mockData,
'X-Total-Pages': '5',
});
component = mountComponent(FeatureFlagsComponent, mockData);
setTimeout(() => {
done();
}, 0);
......@@ -170,11 +140,11 @@ describe('Feature Flags', () => {
it('should render a table with feature flags', () => {
expect(component.$el.querySelectorAll('.js-feature-flag-table')).not.toBeNull();
expect(component.$el.querySelector('.feature-flag-name').textContent.trim()).toEqual(
featureFlag.name,
getRequestData.feature_flags[0].name,
);
expect(component.$el.querySelector('.feature-flag-description').textContent.trim()).toEqual(
featureFlag.description,
getRequestData.feature_flags[0].description,
);
});
......@@ -188,7 +158,7 @@ describe('Feature Flags', () => {
describe('pagination', () => {
it('should render pagination', () => {
expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(5);
expect(component.$el.querySelectorAll('.gl-pagination')).not.toBeNull();
});
it('should make an API request when page is clicked', done => {
......@@ -198,7 +168,7 @@ describe('Feature Flags', () => {
expect(component.updateFeatureFlagOptions).toHaveBeenCalledWith({
scope: 'all',
page: '2',
page: '4',
});
done();
}, 0);
......@@ -222,13 +192,9 @@ describe('Feature Flags', () => {
describe('unsuccessful request', () => {
beforeEach(done => {
mock.onGet(mockData.endpoint).reply(500, {});
mock.onGet(mockData.endpoint, { params: { scope: 'all', page: '1' } }).replyOnce(500, {});
store = createStore();
component = mountComponentWithStore(FeatureFlagsComponent, {
store,
props: mockData,
});
component = mountComponent(FeatureFlagsComponent, mockData);
setTimeout(() => {
done();
......
import Vue from 'vue';
import featureFlagsTableComponent from 'ee/feature_flags/components/feature_flags_table.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { featureFlag } from './mock_data';
import { featureFlag } from '../mock_data';
describe('Feature Flag table', () => {
let Component;
......
......@@ -37,3 +37,46 @@ export const featureFlag = {
},
],
};
export const getRequestData = {
feature_flags: [
{
id: 3,
active: true,
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
name: 'ci_live_trace',
description: 'For the new live trace architecture',
edit_path: '/root/per-environment-feature-flags/-/feature_flags/3/edit',
destroy_path: '/root/per-environment-feature-flags/-/feature_flags/3',
scopes: [
{
id: 1,
active: true,
environment_scope: '*',
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
},
{
id: 2,
active: false,
environment_scope: 'production',
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
},
{
id: 3,
active: false,
environment_scope: 'review/*',
created_at: '2019-01-14T06:41:40.987Z',
updated_at: '2019-01-14T06:41:40.987Z',
},
],
},
],
count: {
all: 1,
disabled: 1,
enabled: 0,
},
};
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
requestFeatureFlags,
receiveFeatureFlagsSuccess,
receiveFeatureFlagsError,
fetchFeatureFlags,
setFeatureFlagsEndpoint,
setFeatureFlagsOptions,
} from 'ee/feature_flags/store/modules/index/actions';
import state from 'ee/feature_flags/store/modules/index/state';
import * as types from 'ee/feature_flags/store/modules/index/mutation_types';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import { getRequestData } from '../../mock_data';
describe('Feature flags actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state();
});
describe('setFeatureFlagsEndpoint', () => {
it('should commit SET_FEATURE_FLAGS_ENDPOINT mutation', done => {
testAction(
setFeatureFlagsEndpoint,
'feature_flags.json',
mockedState,
[{ type: types.SET_FEATURE_FLAGS_ENDPOINT, payload: 'feature_flags.json' }],
[],
done,
);
});
});
describe('setFeatureFlagsOptions', () => {
it('should commit SET_FEATURE_FLAGS_OPTIONS mutation', done => {
testAction(
setFeatureFlagsOptions,
{ page: '1', scope: 'all' },
mockedState,
[{ type: types.SET_FEATURE_FLAGS_OPTIONS, payload: { page: '1', scope: 'all' } }],
[],
done,
);
});
});
describe('fetchFeatureFlags', () => {
let mock;
beforeEach(() => {
mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestFeatureFlags and receiveFeatureFlagsSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, getRequestData, {});
testAction(
fetchFeatureFlags,
null,
mockedState,
[],
[
{
type: 'requestFeatureFlags',
},
{
payload: { data: getRequestData, headers: {} },
type: 'receiveFeatureFlagsSuccess',
},
],
done,
);
});
});
describe('error', () => {
it('dispatches requestFeatureFlags and receiveFeatureFlagsError ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`, {}).replyOnce(500, {});
testAction(
fetchFeatureFlags,
null,
mockedState,
[],
[
{
type: 'requestFeatureFlags',
},
{
type: 'receiveFeatureFlagsError',
},
],
done,
);
});
});
});
describe('requestFeatureFlags', () => {
it('should commit RECEIVE_FEATURE_FLAGS_SUCCESS mutation', done => {
testAction(
requestFeatureFlags,
null,
mockedState,
[{ type: types.REQUEST_FEATURE_FLAGS }],
[],
done,
);
});
});
describe('receiveFeatureFlagsSuccess', () => {
it('should commit RECEIVE_FEATURE_FLAGS_SUCCESS mutation', done => {
testAction(
receiveFeatureFlagsSuccess,
{ data: getRequestData, headers: {} },
mockedState,
[
{
type: types.RECEIVE_FEATURE_FLAGS_SUCCESS,
payload: { data: getRequestData, headers: {} },
},
],
[],
done,
);
});
});
describe('receiveFeatureFlagsError', () => {
it('should commit RECEIVE_FEATURE_FLAGS_ERROR mutation', done => {
testAction(
receiveFeatureFlagsError,
null,
mockedState,
[{ type: types.RECEIVE_FEATURE_FLAGS_ERROR }],
[],
done,
);
});
});
});
import state from 'ee/feature_flags/store/modules/index/state';
import mutations from 'ee/feature_flags/store/modules/index/mutations';
import * as types from 'ee/feature_flags/store/modules/index/mutation_types';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { getRequestData } from '../../mock_data';
describe('Feature flags store Mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('SET_FEATURE_FLAGS_ENDPOINT', () => {
it('should set endpoint', () => {
mutations[types.SET_FEATURE_FLAGS_ENDPOINT](stateCopy, 'feature_flags.json');
expect(stateCopy.endpoint).toEqual('feature_flags.json');
});
});
describe('SET_FEATURE_FLAGS_OPTIONS', () => {
it('should set provided options', () => {
mutations[types.SET_FEATURE_FLAGS_OPTIONS](stateCopy, { page: '1', scope: 'all' });
expect(stateCopy.options).toEqual({ page: '1', scope: 'all' });
});
});
describe('REQUEST_FEATURE_FLAGS', () => {
it('should set isLoading to true', () => {
mutations[types.REQUEST_FEATURE_FLAGS](stateCopy);
expect(stateCopy.isLoading).toEqual(true);
});
});
describe('RECEIVE_FEATURE_FLAGS_SUCCESS', () => {
const headers = {
'x-next-page': '2',
'x-page': '1',
'X-Per-Page': '2',
'X-Prev-Page': '',
'X-TOTAL': '37',
'X-Total-Pages': '5',
};
beforeEach(() => {
mutations[types.RECEIVE_FEATURE_FLAGS_SUCCESS](stateCopy, { data: getRequestData, headers });
});
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 featureFlags with the given data', () => {
expect(stateCopy.featureFlags).toEqual(getRequestData.feature_flags);
});
it('should set count with the given data', () => {
expect(stateCopy.count).toEqual(getRequestData.count);
});
it('should set pagination', () => {
expect(stateCopy.pageInfo).toEqual(parseIntPagination(normalizeHeaders(headers)));
});
});
describe('RECEIVE_FEATURE_FLAGS_ERROR', () => {
beforeEach(() => {
mutations[types.RECEIVE_FEATURE_FLAGS_ERROR](stateCopy);
});
it('should set isLoading to false', () => {
expect(stateCopy.isLoading).toEqual(false);
});
it('should set hasError to true', () => {
expect(stateCopy.hasError).toEqual(true);
});
});
});
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