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