Commit 8560f234 authored by Mark Florian's avatar Mark Florian Committed by Fatih Acet

Refactor Dependency List Vuex store

This is part of the work to implement the display of Dependency Scanning
results in the Dependency List[1].

The bulk of this is simply moving the existing Vuex store to a reusable
module. The purpose of this is to facilitate adding a second instance of
the module, so that there can be two independent dependency lists at
once, as specified in the [feature's issue][1].

[1]: https://gitlab.com/gitlab-org/gitlab-ee/issues/10077
parent 25154035
...@@ -6,6 +6,7 @@ import DependenciesActions from './dependencies_actions.vue'; ...@@ -6,6 +6,7 @@ import DependenciesActions from './dependencies_actions.vue';
import DependenciesTable from './dependencies_table.vue'; import DependenciesTable from './dependencies_table.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue'; import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue'; import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue';
import { DEPENDENCY_LIST_TYPES } from '../store/constants';
export default { export default {
name: 'DependenciesApp', name: 'DependenciesApp',
...@@ -45,8 +46,9 @@ export default { ...@@ -45,8 +46,9 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters(['isJobNotSetUp', 'isJobFailed', 'isIncomplete']), ...mapState(['currentList']),
...mapState([ ...mapGetters(DEPENDENCY_LIST_TYPES.all, ['isJobNotSetUp', 'isJobFailed', 'isIncomplete']),
...mapState(DEPENDENCY_LIST_TYPES.all, [
'initialized', 'initialized',
'isLoading', 'isLoading',
'errorLoading', 'errorLoading',
...@@ -63,7 +65,7 @@ export default { ...@@ -63,7 +65,7 @@ export default {
this.fetchDependencies(); this.fetchDependencies();
}, },
methods: { methods: {
...mapActions(['setDependenciesEndpoint', 'fetchDependencies']), ...mapActions(DEPENDENCY_LIST_TYPES.all, ['setDependenciesEndpoint', 'fetchDependencies']),
fetchPage(page) { fetchPage(page) {
this.fetchDependencies({ page }); this.fetchDependencies({ page });
}, },
...@@ -109,7 +111,7 @@ export default { ...@@ -109,7 +111,7 @@ export default {
<gl-badge v-if="pageInfo.total" pill>{{ pageInfo.total }}</gl-badge> <gl-badge v-if="pageInfo.total" pill>{{ pageInfo.total }}</gl-badge>
</h4> </h4>
<dependencies-actions /> <dependencies-actions :namespace="currentList" />
</div> </div>
<dependencies-table :dependencies="dependencies" :is-loading="isLoading" /> <dependencies-table :dependencies="dependencies" :is-loading="isLoading" />
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { GlButton, GlDropdown, GlDropdownItem, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlDropdown, GlDropdownItem, GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { SORT_ORDER } from '../store/constants'; import { DEPENDENCY_LIST_TYPES } from '../store/constants';
import { SORT_ORDER } from '../store/modules/list/constants';
export default { export default {
name: 'DependenciesActions', name: 'DependenciesActions',
...@@ -15,9 +16,28 @@ export default { ...@@ -15,9 +16,28 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
props: {
namespace: {
type: String,
required: true,
validator: value => Object.values(DEPENDENCY_LIST_TYPES).includes(value),
},
},
computed: { computed: {
...mapState(['sortField', 'sortFields', 'sortOrder']), ...mapState({
...mapGetters(['downloadEndpoint']), sortField(state) {
return state[this.namespace].sortField;
},
sortFields(state) {
return state[this.namespace].sortFields;
},
sortOrder(state) {
return state[this.namespace].sortOrder;
},
downloadEndpoint(state, getters) {
return getters[`${this.namespace}/downloadEndpoint`];
},
}),
sortFieldName() { sortFieldName() {
return this.sortFields[this.sortField]; return this.sortFields[this.sortField];
}, },
...@@ -26,7 +46,14 @@ export default { ...@@ -26,7 +46,14 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['setSortField', 'toggleSortOrder']), ...mapActions({
setSortField(dispatch, field) {
dispatch(`${this.namespace}/setSortField`, field);
},
toggleSortOrder(dispatch) {
dispatch(`${this.namespace}/toggleSortOrder`);
},
}),
isCurrentSortField(id) { isCurrentSortField(id) {
return id === this.sortField; return id === this.sortField;
}, },
......
import { __, s__ } from '~/locale'; // eslint-disable-next-line import/prefer-default-export
export const DEPENDENCY_LIST_TYPES = {
export const SORT_FIELDS = { all: 'allDependencies',
name: s__('Dependencies|Component name'),
packager: s__('Dependencies|Packager'),
}; };
export const SORT_ORDER = {
ascending: 'asc',
descending: 'desc',
};
export const REPORT_STATUS = {
ok: 'ok',
jobNotSetUp: 'job_not_set_up',
jobFailed: 'job_failed',
noDependencies: 'no_dependencies',
incomplete: 'no_dependency_files',
};
export const FETCH_ERROR_MESSAGE = __(
'Error fetching the dependency list. Please check your network connection and try again.',
);
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions'; import listModule from './modules/list';
import * as getters from './getters';
import mutations from './mutations';
import state from './state'; import state from './state';
Vue.use(Vuex); Vue.use(Vuex);
export default () => export default () => {
new Vuex.Store({ const allDependencies = listModule();
actions,
getters, return new Vuex.Store({
mutations, modules: {
allDependencies,
},
state, state,
}); });
};
import { __, s__ } from '~/locale';
export const SORT_FIELDS = {
name: s__('Dependencies|Component name'),
packager: s__('Dependencies|Packager'),
};
export const SORT_ORDER = {
ascending: 'asc',
descending: 'desc',
};
export const REPORT_STATUS = {
ok: 'ok',
jobNotSetUp: 'job_not_set_up',
jobFailed: 'job_failed',
noDependencies: 'no_dependencies',
incomplete: 'no_dependency_files',
};
export const FETCH_ERROR_MESSAGE = __(
'Error fetching the dependency list. Please check your network connection and try again.',
);
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
export default () => ({
namespaced: true,
actions,
getters,
mutations,
state,
});
import { REPORT_STATUS, SORT_FIELDS, SORT_ORDER } from './constants';
export default () => ({
endpoint: '',
initialized: false,
isLoading: false,
errorLoading: false,
dependencies: [],
pageInfo: {},
reportInfo: {
status: REPORT_STATUS.ok,
jobPath: '',
},
sortField: 'name',
sortFields: SORT_FIELDS,
sortOrder: SORT_ORDER.ascending,
});
import { REPORT_STATUS, SORT_FIELDS, SORT_ORDER } from './constants'; import { DEPENDENCY_LIST_TYPES } from './constants';
export default () => ({ export default () => ({
endpoint: '', currentList: DEPENDENCY_LIST_TYPES.all,
initialized: false,
isLoading: false,
errorLoading: false,
dependencies: [],
pageInfo: {},
reportInfo: {
status: REPORT_STATUS.ok,
jobPath: '',
},
sortField: 'name',
sortFields: SORT_FIELDS,
sortOrder: SORT_ORDER.ascending,
}); });
...@@ -22,7 +22,9 @@ exports[`DependenciesApp component on creation given a dependency list which is ...@@ -22,7 +22,9 @@ exports[`DependenciesApp component on creation given a dependency list which is
</glbadge-stub> </glbadge-stub>
</h4> </h4>
<dependenciesactions-stub /> <dependenciesactions-stub
namespace="allDependencies"
/>
</div> </div>
<dependenciestable-stub <dependenciestable-stub
...@@ -55,7 +57,9 @@ exports[`DependenciesApp component on creation given a fetch error matches the s ...@@ -55,7 +57,9 @@ exports[`DependenciesApp component on creation given a fetch error matches the s
<!----> <!---->
</h4> </h4>
<dependenciesactions-stub /> <dependenciesactions-stub
namespace="allDependencies"
/>
</div> </div>
<dependenciestable-stub <dependenciestable-stub
...@@ -88,7 +92,9 @@ exports[`DependenciesApp component on creation given a list of dependencies and ...@@ -88,7 +92,9 @@ exports[`DependenciesApp component on creation given a list of dependencies and
</glbadge-stub> </glbadge-stub>
</h4> </h4>
<dependenciesactions-stub /> <dependenciesactions-stub
namespace="allDependencies"
/>
</div> </div>
<dependenciestable-stub <dependenciestable-stub
...@@ -123,7 +129,9 @@ exports[`DependenciesApp component on creation given the dependency list job fai ...@@ -123,7 +129,9 @@ exports[`DependenciesApp component on creation given the dependency list job fai
<!----> <!---->
</h4> </h4>
<dependenciesactions-stub /> <dependenciesactions-stub
namespace="allDependencies"
/>
</div> </div>
<dependenciestable-stub <dependenciestable-stub
......
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/dependencies/store'; import createStore from 'ee/dependencies/store';
import { REPORT_STATUS } from 'ee/dependencies/store/constants'; import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue'; import DependenciesApp from 'ee/dependencies/components/app.vue';
import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue'; import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue';
import DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue'; import DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue';
...@@ -11,6 +12,7 @@ import Pagination from '~/vue_shared/components/pagination_links.vue'; ...@@ -11,6 +12,7 @@ import Pagination from '~/vue_shared/components/pagination_links.vue';
describe('DependenciesApp component', () => { describe('DependenciesApp component', () => {
let store; let store;
let wrapper; let wrapper;
const listType = DEPENDENCY_LIST_TYPES.all;
const basicAppProps = { const basicAppProps = {
endpoint: '/foo', endpoint: '/foo',
...@@ -51,8 +53,8 @@ describe('DependenciesApp component', () => { ...@@ -51,8 +53,8 @@ describe('DependenciesApp component', () => {
it('dispatches the correct initial actions', () => { it('dispatches the correct initial actions', () => {
expect(store.dispatch.mock.calls).toEqual([ expect(store.dispatch.mock.calls).toEqual([
['setDependenciesEndpoint', basicAppProps.endpoint], [`${listType}/setDependenciesEndpoint`, basicAppProps.endpoint],
['fetchDependencies'], [`${listType}/fetchDependencies`, undefined],
]); ]);
}); });
...@@ -64,13 +66,13 @@ describe('DependenciesApp component', () => { ...@@ -64,13 +66,13 @@ describe('DependenciesApp component', () => {
beforeEach(() => { beforeEach(() => {
dependencies = ['foo', 'bar']; dependencies = ['foo', 'bar'];
Object.assign(store.state, { Object.assign(store.state[listType], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
dependencies, dependencies,
}); });
store.state.pageInfo.total = 100; store.state[listType].pageInfo.total = 100;
store.state.reportInfo.status = REPORT_STATUS.ok; store.state[listType].reportInfo.status = REPORT_STATUS.ok;
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -88,7 +90,7 @@ describe('DependenciesApp component', () => { ...@@ -88,7 +90,7 @@ describe('DependenciesApp component', () => {
it('passes the correct props to the pagination', () => { it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, { expectComponentWithProps(Pagination, {
pageInfo: store.state.pageInfo, pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage, change: wrapper.vm.fetchPage,
}); });
}); });
...@@ -98,13 +100,13 @@ describe('DependenciesApp component', () => { ...@@ -98,13 +100,13 @@ describe('DependenciesApp component', () => {
beforeEach(() => { beforeEach(() => {
dependencies = []; dependencies = [];
Object.assign(store.state, { Object.assign(store.state[listType], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
dependencies, dependencies,
}); });
store.state.pageInfo.total = 0; store.state[listType].pageInfo.total = 0;
store.state.reportInfo.status = REPORT_STATUS.jobNotSetUp; store.state[listType].reportInfo.status = REPORT_STATUS.jobNotSetUp;
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -118,14 +120,14 @@ describe('DependenciesApp component', () => { ...@@ -118,14 +120,14 @@ describe('DependenciesApp component', () => {
beforeEach(() => { beforeEach(() => {
dependencies = []; dependencies = [];
Object.assign(store.state, { Object.assign(store.state[listType], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
dependencies, dependencies,
}); });
store.state.pageInfo.total = 0; store.state[listType].pageInfo.total = 0;
store.state.reportInfo.status = REPORT_STATUS.jobFailed; store.state[listType].reportInfo.status = REPORT_STATUS.jobFailed;
store.state.reportInfo.jobPath = '/jobs/foo/321'; store.state[listType].reportInfo.jobPath = '/jobs/foo/321';
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -136,7 +138,7 @@ describe('DependenciesApp component', () => { ...@@ -136,7 +138,7 @@ describe('DependenciesApp component', () => {
it('passes the correct props to the job failure alert', () => { it('passes the correct props to the job failure alert', () => {
expectComponentWithProps(DependencyListJobFailedAlert, { expectComponentWithProps(DependencyListJobFailedAlert, {
jobPath: store.state.reportInfo.jobPath, jobPath: store.state[listType].reportInfo.jobPath,
}); });
}); });
...@@ -168,13 +170,13 @@ describe('DependenciesApp component', () => { ...@@ -168,13 +170,13 @@ describe('DependenciesApp component', () => {
beforeEach(() => { beforeEach(() => {
dependencies = ['foo', 'bar']; dependencies = ['foo', 'bar'];
Object.assign(store.state, { Object.assign(store.state[listType], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
dependencies, dependencies,
}); });
store.state.pageInfo.total = 100; store.state[listType].pageInfo.total = 100;
store.state.reportInfo.status = REPORT_STATUS.incomplete; store.state[listType].reportInfo.status = REPORT_STATUS.incomplete;
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -197,7 +199,7 @@ describe('DependenciesApp component', () => { ...@@ -197,7 +199,7 @@ describe('DependenciesApp component', () => {
it('passes the correct props to the pagination', () => { it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, { expectComponentWithProps(Pagination, {
pageInfo: store.state.pageInfo, pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage, change: wrapper.vm.fetchPage,
}); });
}); });
...@@ -219,7 +221,7 @@ describe('DependenciesApp component', () => { ...@@ -219,7 +221,7 @@ describe('DependenciesApp component', () => {
beforeEach(() => { beforeEach(() => {
dependencies = []; dependencies = [];
Object.assign(store.state, { Object.assign(store.state[listType], {
initialized: true, initialized: true,
isLoading: false, isLoading: false,
errorLoading: true, errorLoading: true,
......
...@@ -2,14 +2,16 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; ...@@ -2,14 +2,16 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui'; import { GlDropdownItem } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import createStore from 'ee/dependencies/store'; import createStore from 'ee/dependencies/store';
import { SORT_FIELDS } from 'ee/dependencies/store/constants'; import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { SORT_FIELDS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesActions from 'ee/dependencies/components/dependencies_actions.vue'; import DependenciesActions from 'ee/dependencies/components/dependencies_actions.vue';
describe('DependenciesActions component', () => { describe('DependenciesActions component', () => {
let store; let store;
let wrapper; let wrapper;
const listType = DEPENDENCY_LIST_TYPES.all;
const factory = () => { const factory = (props = {}) => {
const localVue = createLocalVue(); const localVue = createLocalVue();
store = createStore(); store = createStore();
...@@ -19,12 +21,13 @@ describe('DependenciesActions component', () => { ...@@ -19,12 +21,13 @@ describe('DependenciesActions component', () => {
localVue, localVue,
store, store,
sync: false, sync: false,
propsData: { ...props },
}); });
}; };
beforeEach(() => { beforeEach(() => {
factory(); factory({ namespace: listType });
store.state.endpoint = `${TEST_HOST}/dependencies`; store.state[listType].endpoint = `${TEST_HOST}/dependencies`;
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -46,21 +49,23 @@ describe('DependenciesActions component', () => { ...@@ -46,21 +49,23 @@ describe('DependenciesActions component', () => {
}); });
expect(store.dispatch.mock.calls).toEqual( expect(store.dispatch.mock.calls).toEqual(
expect.arrayContaining(Object.keys(SORT_FIELDS).map(field => ['setSortField', field])), expect.arrayContaining(
Object.keys(SORT_FIELDS).map(field => [`${listType}/setSortField`, field]),
),
); );
}); });
it('dispatches the toggleSortOrder action on clicking the sort order button', () => { it('dispatches the toggleSortOrder action on clicking the sort order button', () => {
const sortButton = wrapper.find('.js-sort-order'); const sortButton = wrapper.find('.js-sort-order');
sortButton.vm.$emit('click'); sortButton.vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith('toggleSortOrder'); expect(store.dispatch).toHaveBeenCalledWith(`${listType}/toggleSortOrder`);
}); });
it('has a button to export the dependency list', () => { it('has a button to export the dependency list', () => {
const download = wrapper.find('.js-download'); const download = wrapper.find('.js-download');
expect(download.attributes()).toEqual( expect(download.attributes()).toEqual(
expect.objectContaining({ expect.objectContaining({
href: store.getters.downloadEndpoint, href: store.getters[`${listType}/downloadEndpoint`],
download: expect.any(String), download: expect.any(String),
}), }),
); );
......
...@@ -3,10 +3,10 @@ import axios from 'axios'; ...@@ -3,10 +3,10 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import * as actions from 'ee/dependencies/store/actions'; import * as actions from 'ee/dependencies/store/modules/list/actions';
import * as types from 'ee/dependencies/store/mutation_types'; import * as types from 'ee/dependencies/store/modules/list/mutation_types';
import getInitialState from 'ee/dependencies/store/state'; import getInitialState from 'ee/dependencies/store/modules/list/state';
import { SORT_ORDER, FETCH_ERROR_MESSAGE } from 'ee/dependencies/store/constants'; import { SORT_ORDER, FETCH_ERROR_MESSAGE } from 'ee/dependencies/store/modules/list/constants';
import createFlash from '~/flash'; import createFlash from '~/flash';
import mockDependenciesResponse from './data/mock_dependencies'; import mockDependenciesResponse from './data/mock_dependencies';
......
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import * as getters from 'ee/dependencies/store/getters'; import * as getters from 'ee/dependencies/store/modules/list/getters';
import { REPORT_STATUS } from 'ee/dependencies/store/constants'; import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
describe('Dependencies getters', () => { describe('Dependencies getters', () => {
describe.each` describe.each`
......
import * as types from 'ee/dependencies/store/mutation_types'; import * as types from 'ee/dependencies/store/modules/list/mutation_types';
import mutations from 'ee/dependencies/store/mutations'; import mutations from 'ee/dependencies/store/modules/list/mutations';
import getInitialState from 'ee/dependencies/store/state'; import getInitialState from 'ee/dependencies/store/modules/list/state';
import { REPORT_STATUS, SORT_ORDER } from 'ee/dependencies/store/constants'; import { REPORT_STATUS, SORT_ORDER } from 'ee/dependencies/store/modules/list/constants';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
describe('Dependencies mutations', () => { describe('Dependencies mutations', () => {
......
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