Commit e1686c6f authored by Andrew Fontaine's avatar Andrew Fontaine

Remove Index Module from Feature Flags Store

This allows us to pass initialization data into the store and remove a
few simple props and actions completely.
parent 6617a2ba
<script> <script>
import { createNamespacedHelpers } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { GlButton, GlModalDirective, GlTabs } from '@gitlab/ui'; import { GlButton, GlModalDirective, GlTabs } from '@gitlab/ui';
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants'; import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../constants';
import FeatureFlagsTab from './feature_flags_tab.vue'; import FeatureFlagsTab from './feature_flags_tab.vue';
import FeatureFlagsTable from './feature_flags_table.vue'; import FeatureFlagsTable from './feature_flags_table.vue';
import UserListsTable from './user_lists_table.vue'; import UserListsTable from './user_lists_table.vue';
import store from '../store';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { import {
...@@ -17,12 +16,9 @@ import { ...@@ -17,12 +16,9 @@ import {
import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue'; import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
const { mapState, mapActions } = createNamespacedHelpers('index');
const SCOPES = { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE }; const SCOPES = { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE };
export default { export default {
store,
components: { components: {
FeatureFlagsTable, FeatureFlagsTable,
UserListsTable, UserListsTable,
...@@ -36,14 +32,6 @@ export default { ...@@ -36,14 +32,6 @@ export default {
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
props: { props: {
endpoint: {
type: String,
required: true,
},
projectId: {
type: String,
required: true,
},
csrfToken: { csrfToken: {
type: String, type: String,
required: true, required: true,
...@@ -56,19 +44,10 @@ export default { ...@@ -56,19 +44,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
rotateInstanceIdPath: {
type: String,
required: false,
default: '',
},
unleashApiUrl: { unleashApiUrl: {
type: String, type: String,
required: true, required: true,
}, },
unleashApiInstanceId: {
type: String,
required: true,
},
canUserConfigure: { canUserConfigure: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -144,23 +123,15 @@ export default { ...@@ -144,23 +123,15 @@ export default {
}, },
}, },
created() { created() {
this.setFeatureFlagsEndpoint(this.endpoint);
this.setFeatureFlagsOptions({ scope: this.scope, page: this.page }); this.setFeatureFlagsOptions({ scope: this.scope, page: this.page });
this.setProjectId(this.projectId);
this.fetchFeatureFlags(); this.fetchFeatureFlags();
this.fetchUserLists(); this.fetchUserLists();
this.setInstanceId(this.unleashApiInstanceId);
this.setInstanceIdEndpoint(this.rotateInstanceIdPath);
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'setFeatureFlagsEndpoint',
'setFeatureFlagsOptions', 'setFeatureFlagsOptions',
'fetchFeatureFlags', 'fetchFeatureFlags',
'fetchUserLists', 'fetchUserLists',
'setInstanceIdEndpoint',
'setInstanceId',
'setProjectId',
'rotateInstanceId', 'rotateInstanceId',
'toggleFeatureFlag', 'toggleFeatureFlag',
'deleteUserList', 'deleteUserList',
......
import Vue from 'vue'; import Vue from 'vue';
import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue'; import Vuex from 'vuex';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import FeatureFlagsComponent from './components/feature_flags.vue';
import createStore from './store/modules/index';
export default () => Vue.use(Vuex);
new Vue({
el: '#feature-flags-vue', export default () => {
components: { const el = document.querySelector('#feature-flags-vue');
FeatureFlagsComponent,
}, const {
data() { projectName,
return { featureFlagsHelpPagePath,
dataset: document.querySelector(this.$options.el).dataset, errorStateSvgPath,
}; endpoint,
}, projectId,
unleashApiInstanceId,
rotateInstanceIdPath,
} = el.dataset;
return new Vue({
el,
store: createStore({ endpoint, projectId, unleashApiInstanceId, rotateInstanceIdPath }),
provide() { provide() {
return { return {
projectName: this.dataset.projectName, projectName,
featureFlagsHelpPagePath: this.dataset.featureFlagsHelpPagePath, featureFlagsHelpPagePath,
errorStateSvgPath: this.dataset.errorStateSvgPath, errorStateSvgPath,
}; };
}, },
render(createElement) { render(createElement) {
return createElement('feature-flags-component', { return createElement(FeatureFlagsComponent, {
props: { props: {
endpoint: this.dataset.endpoint, featureFlagsClientLibrariesHelpPagePath:
projectId: this.dataset.projectId, el.dataset.featureFlagsClientLibrariesHelpPagePath,
featureFlagsClientLibrariesHelpPagePath: this.dataset featureFlagsClientExampleHelpPagePath: el.dataset.featureFlagsClientExampleHelpPagePath,
.featureFlagsClientLibrariesHelpPagePath, unleashApiUrl: el.dataset.unleashApiUrl,
featureFlagsClientExampleHelpPagePath: this.dataset.featureFlagsClientExampleHelpPagePath,
unleashApiUrl: this.dataset.unleashApiUrl,
unleashApiInstanceId: this.dataset.unleashApiInstanceId || '',
csrfToken: csrf.token, csrfToken: csrf.token,
canUserConfigure: this.dataset.canUserAdminFeatureFlag, canUserConfigure: el.dataset.canUserAdminFeatureFlag,
newFeatureFlagPath: this.dataset.newFeatureFlagPath, newFeatureFlagPath: el.dataset.newFeatureFlagPath,
rotateInstanceIdPath: this.dataset.rotateInstanceIdPath, newUserListPath: el.dataset.newUserListPath,
newUserListPath: this.dataset.newUserListPath,
}, },
}); });
}, },
}); });
};
import Vue from 'vue';
import Vuex from 'vuex';
import indexModule from './modules/index';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
index: indexModule,
},
});
export default createStore();
...@@ -2,19 +2,9 @@ import Api from '~/api'; ...@@ -2,19 +2,9 @@ import Api from '~/api';
import * as types from './mutation_types'; import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
export const setFeatureFlagsEndpoint = ({ commit }, endpoint) =>
commit(types.SET_FEATURE_FLAGS_ENDPOINT, endpoint);
export const setFeatureFlagsOptions = ({ commit }, options) => export const setFeatureFlagsOptions = ({ commit }, options) =>
commit(types.SET_FEATURE_FLAGS_OPTIONS, 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 }) => { export const fetchFeatureFlags = ({ state, dispatch }) => {
dispatch('requestFeatureFlags'); dispatch('requestFeatureFlags');
......
import Vuex from 'vuex';
import state from './state'; import state from './state';
import * as actions from './actions'; import * as actions from './actions';
import mutations from './mutations'; import mutations from './mutations';
export default { export default data =>
namespaced: true, new Vuex.Store({
actions, actions,
mutations, mutations,
state: state(), state: state(data),
}; });
export const SET_FEATURE_FLAGS_ENDPOINT = 'SET_FEATURE_FLAGS_ENDPOINT';
export const SET_FEATURE_FLAGS_OPTIONS = 'SET_FEATURE_FLAGS_OPTIONS'; 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 REQUEST_FEATURE_FLAGS = 'REQUEST_FEATURE_FLAGS';
export const RECEIVE_FEATURE_FLAGS_SUCCESS = 'RECEIVE_FEATURE_FLAGS_SUCCESS'; export const RECEIVE_FEATURE_FLAGS_SUCCESS = 'RECEIVE_FEATURE_FLAGS_SUCCESS';
......
...@@ -23,21 +23,9 @@ const createPaginationInfo = (state, headers) => { ...@@ -23,21 +23,9 @@ const createPaginationInfo = (state, headers) => {
}; };
export default { export default {
[types.SET_FEATURE_FLAGS_ENDPOINT](state, endpoint) {
state.endpoint = endpoint;
},
[types.SET_FEATURE_FLAGS_OPTIONS](state, options = {}) { [types.SET_FEATURE_FLAGS_OPTIONS](state, options = {}) {
state.options = 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) { [types.REQUEST_FEATURE_FLAGS](state) {
state.isLoading = true; state.isLoading = true;
}, },
......
import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants'; import { FEATURE_FLAG_SCOPE, USER_LIST_SCOPE } from '../../../constants';
export default () => ({ export default ({ endpoint, projectId, unleashApiInstanceId, rotateInstanceIdPath }) => ({
[FEATURE_FLAG_SCOPE]: [], [FEATURE_FLAG_SCOPE]: [],
[USER_LIST_SCOPE]: [], [USER_LIST_SCOPE]: [],
alerts: [], alerts: [],
...@@ -8,11 +8,11 @@ export default () => ({ ...@@ -8,11 +8,11 @@ export default () => ({
pageInfo: { [FEATURE_FLAG_SCOPE]: {}, [USER_LIST_SCOPE]: {} }, pageInfo: { [FEATURE_FLAG_SCOPE]: {}, [USER_LIST_SCOPE]: {} },
isLoading: true, isLoading: true,
hasError: false, hasError: false,
endpoint: null, endpoint,
rotateEndpoint: null, rotateEndpoint: rotateInstanceIdPath,
instanceId: '', instanceId: unleashApiInstanceId,
isRotating: false, isRotating: false,
hasRotateError: false, hasRotateError: false,
options: {}, options: {},
projectId: '', projectId,
}); });
import initFeatureFlags from '~/feature_flags'; import initFeatureFlags from '~/feature_flags';
document.addEventListener('DOMContentLoaded', initFeatureFlags); initFeatureFlags();
import { shallowMount } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api'; import Api from '~/api';
import { createStore } from '~/feature_flags/store'; import createStore from '~/feature_flags/store/modules/index';
import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue'; import FeatureFlagsTab from '~/feature_flags/components/feature_flags_tab.vue';
import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue'; import FeatureFlagsComponent from '~/feature_flags/components/feature_flags.vue';
import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue'; import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
...@@ -14,19 +15,25 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination ...@@ -14,19 +15,25 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { getRequestData, userList } from '../mock_data'; import { getRequestData, userList } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Feature flags', () => { describe('Feature flags', () => {
const mockData = { const mockData = {
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken', csrfToken: 'testToken',
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients', featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example', featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
unleashApiUrl: `${TEST_HOST}/api/unleash`, unleashApiUrl: `${TEST_HOST}/api/unleash`,
unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
canUserConfigure: true, canUserConfigure: true,
canUserRotateToken: true, canUserRotateToken: true,
newFeatureFlagPath: 'feature-flags/new', newFeatureFlagPath: 'feature-flags/new',
newUserListPath: '/user-list/new', newUserListPath: '/user-list/new',
};
const mockState = {
endpoint: `${TEST_HOST}/endpoint.json`,
projectId: '8', projectId: '8',
unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
}; };
let wrapper; let wrapper;
...@@ -34,8 +41,9 @@ describe('Feature flags', () => { ...@@ -34,8 +41,9 @@ describe('Feature flags', () => {
let store; let store;
const factory = (propsData = mockData, fn = shallowMount) => { const factory = (propsData = mockData, fn = shallowMount) => {
store = createStore(); store = createStore(mockState);
wrapper = fn(FeatureFlagsComponent, { wrapper = fn(FeatureFlagsComponent, {
localVue,
store, store,
propsData, propsData,
provide: { provide: {
...@@ -76,7 +84,6 @@ describe('Feature flags', () => { ...@@ -76,7 +84,6 @@ describe('Feature flags', () => {
describe('without permissions', () => { describe('without permissions', () => {
const propsData = { const propsData = {
endpoint: `${TEST_HOST}/endpoint.json`,
csrfToken: 'testToken', csrfToken: 'testToken',
errorStateSvgPath: '/assets/illustrations/feature_flag.svg', errorStateSvgPath: '/assets/illustrations/feature_flag.svg',
featureFlagsHelpPagePath: '/help/feature-flags', featureFlagsHelpPagePath: '/help/feature-flags',
...@@ -85,8 +92,6 @@ describe('Feature flags', () => { ...@@ -85,8 +92,6 @@ describe('Feature flags', () => {
featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients', featureFlagsClientLibrariesHelpPagePath: '/help/feature-flags#unleash-clients',
featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example', featureFlagsClientExampleHelpPagePath: '/help/feature-flags#client-example',
unleashApiUrl: `${TEST_HOST}/api/unleash`, unleashApiUrl: `${TEST_HOST}/api/unleash`,
unleashApiInstanceId: 'oP6sCNRqtRHmpy1gw2-F',
projectId: '8',
}; };
beforeEach(done => { beforeEach(done => {
...@@ -134,7 +139,7 @@ describe('Feature flags', () => { ...@@ -134,7 +139,7 @@ describe('Feature flags', () => {
let emptyState; let emptyState;
beforeEach(async () => { beforeEach(async () => {
mock.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply( mock.onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }).reply(
200, 200,
{ {
feature_flags: [], feature_flags: [],
...@@ -154,8 +159,6 @@ describe('Feature flags', () => { ...@@ -154,8 +159,6 @@ describe('Feature flags', () => {
}); });
it('should render the empty state', async () => { it('should render the empty state', async () => {
await axios.waitForAll();
emptyState = wrapper.find(GlEmptyState);
expect(emptyState.exists()).toBe(true); expect(emptyState.exists()).toBe(true);
}); });
...@@ -182,7 +185,7 @@ describe('Feature flags', () => { ...@@ -182,7 +185,7 @@ describe('Feature flags', () => {
describe('with paginated feature flags', () => { describe('with paginated feature flags', () => {
beforeEach(done => { beforeEach(done => {
mock mock
.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
.replyOnce(200, getRequestData, { .replyOnce(200, getRequestData, {
'x-next-page': '2', 'x-next-page': '2',
'x-page': '1', 'x-page': '1',
...@@ -218,7 +221,7 @@ describe('Feature flags', () => { ...@@ -218,7 +221,7 @@ describe('Feature flags', () => {
const [flag] = table.props(FEATURE_FLAG_SCOPE); const [flag] = table.props(FEATURE_FLAG_SCOPE);
table.vm.$emit('toggle-flag', flag); table.vm.$emit('toggle-flag', flag);
expect(store.dispatch).toHaveBeenCalledWith('index/toggleFeatureFlag', flag); expect(store.dispatch).toHaveBeenCalledWith('toggleFeatureFlag', flag);
}); });
it('renders configure button', () => { it('renders configure button', () => {
...@@ -287,7 +290,7 @@ describe('Feature flags', () => { ...@@ -287,7 +290,7 @@ describe('Feature flags', () => {
describe('unsuccessful request', () => { describe('unsuccessful request', () => {
beforeEach(done => { beforeEach(done => {
mock mock
.onGet(mockData.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } }) .onGet(mockState.endpoint, { params: { scope: FEATURE_FLAG_SCOPE, page: '1' } })
.replyOnce(500, {}); .replyOnce(500, {});
Api.fetchFeatureFlagUserLists.mockRejectedValueOnce(); Api.fetchFeatureFlagUserLists.mockRejectedValueOnce();
......
...@@ -7,10 +7,7 @@ import { ...@@ -7,10 +7,7 @@ import {
receiveFeatureFlagsSuccess, receiveFeatureFlagsSuccess,
receiveFeatureFlagsError, receiveFeatureFlagsError,
fetchFeatureFlags, fetchFeatureFlags,
setFeatureFlagsEndpoint,
setFeatureFlagsOptions, setFeatureFlagsOptions,
setInstanceIdEndpoint,
setInstanceId,
rotateInstanceId, rotateInstanceId,
requestRotateInstanceId, requestRotateInstanceId,
receiveRotateInstanceIdSuccess, receiveRotateInstanceIdSuccess,
...@@ -39,20 +36,7 @@ describe('Feature flags actions', () => { ...@@ -39,20 +36,7 @@ describe('Feature flags actions', () => {
let mockedState; let mockedState;
beforeEach(() => { beforeEach(() => {
mockedState = state(); 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', () => { describe('setFeatureFlagsOptions', () => {
...@@ -68,32 +52,6 @@ describe('Feature flags actions', () => { ...@@ -68,32 +52,6 @@ describe('Feature flags actions', () => {
}); });
}); });
describe('setInstanceIdEndpoint', () => {
it('should commit SET_INSTANCE_ID_ENDPOINT mutation', done => {
testAction(
setInstanceIdEndpoint,
'instance_id.json',
mockedState,
[{ type: types.SET_INSTANCE_ID_ENDPOINT, payload: 'instance_id.json' }],
[],
done,
);
});
});
describe('setInstanceId', () => {
it('should commit SET_INSTANCE_ID mutation', done => {
testAction(
setInstanceId,
'test_instance_id',
mockedState,
[{ type: types.SET_INSTANCE_ID, payload: 'test_instance_id' }],
[],
done,
);
});
});
describe('fetchFeatureFlags', () => { describe('fetchFeatureFlags', () => {
let mock; let mock;
......
...@@ -9,15 +9,7 @@ describe('Feature flags store Mutations', () => { ...@@ -9,15 +9,7 @@ describe('Feature flags store Mutations', () => {
let stateCopy; let stateCopy;
beforeEach(() => { beforeEach(() => {
stateCopy = state(); 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', () => { describe('SET_FEATURE_FLAGS_OPTIONS', () => {
...@@ -27,23 +19,6 @@ describe('Feature flags store Mutations', () => { ...@@ -27,23 +19,6 @@ describe('Feature flags store Mutations', () => {
expect(stateCopy.options).toEqual({ page: '1', scope: 'all' }); expect(stateCopy.options).toEqual({ page: '1', scope: 'all' });
}); });
}); });
describe('SET_INSTANCE_ID_ENDPOINT', () => {
it('should set provided endpoint', () => {
mutations[types.SET_INSTANCE_ID_ENDPOINT](stateCopy, 'rotate_token.json');
expect(stateCopy.rotateEndpoint).toEqual('rotate_token.json');
});
});
describe('SET_INSTANCE_ID', () => {
it('should set provided token', () => {
mutations[types.SET_INSTANCE_ID](stateCopy, rotateData.token);
expect(stateCopy.instanceId).toEqual(rotateData.token);
});
});
describe('REQUEST_FEATURE_FLAGS', () => { describe('REQUEST_FEATURE_FLAGS', () => {
it('should set isLoading to true', () => { it('should set isLoading to true', () => {
mutations[types.REQUEST_FEATURE_FLAGS](stateCopy); mutations[types.REQUEST_FEATURE_FLAGS](stateCopy);
......
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