Commit cebc9ec1 authored by Enrique Alcántara's avatar Enrique Alcántara Committed by Fatih Acet

Cluster dropdown Vuex store

Abstract cluster dropdown data fetching pattern
into an independent Vuex store module
parent cf10130a
<script>
import { mapActions, mapState } from 'vuex';
import { createNamespacedHelpers, mapState, mapActions } from 'vuex';
import RegionDropdown from './region_dropdown.vue';
import RoleNameDropdown from './role_name_dropdown.vue';
import SecurityGroupDropdown from './security_group_dropdown.vue';
import SubnetDropdown from './subnet_dropdown.vue';
import VPCDropdown from './vpc_dropdown.vue';
import VpcDropdown from './vpc_dropdown.vue';
const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers(
'regions',
);
export default {
components: {
......@@ -13,31 +16,39 @@ export default {
RoleNameDropdown,
SecurityGroupDropdown,
SubnetDropdown,
VPCDropdown,
VpcDropdown,
},
computed: {
...mapState(['isLoadingRegions', 'loadingRegionsError', 'regions', 'selectedRegion']),
...mapState(['selectedRegion']),
...mapRegionsState({
regions: 'items',
isLoadingRegions: 'isLoadingItems',
loadingRegionsError: 'loadingItemsError',
}),
},
mounted() {
this.fetchRegions();
},
methods: {
...mapActions(['fetchRegions', 'setRegion']),
...mapActions(['setRegion']),
...mapRegionsActions({
fetchRegions: 'fetchItems',
}),
},
};
</script>
<template>
<form name="eks-cluster-configuration-form">
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">
{{ s__('ClusterIntegration|Role name') }}
</label>
<label class="label-bold" name="role" for="eks-role">{{
s__('ClusterIntegration|Role name')
}}</label>
<role-name-dropdown />
</div>
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">
{{ s__('ClusterIntegration|Region') }}
</label>
<label class="label-bold" name="role" for="eks-role">{{
s__('ClusterIntegration|Region')
}}</label>
<region-dropdown
:value="selectedRegion"
:regions="regions"
......
import * as awsServices from '../services/aws_services_facade';
import * as types from './mutation_types';
export const requestRegions = ({ commit }) => commit(types.REQUEST_REGIONS);
export const receiveRegionsSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_REGIONS_SUCCESS, payload);
};
export const receiveRegionsError = ({ commit }, payload) => {
commit(types.RECEIVE_REGIONS_ERROR, payload);
};
export const fetchRegions = ({ dispatch }) => {
dispatch('requestRegions');
return awsServices
.fetchRegions()
.then(regions => dispatch('receiveRegionsSuccess', { regions }))
.catch(error => dispatch('receiveRegionsError', { error }));
};
export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload);
};
......
import * as types from './mutation_types';
export default fetchItems => ({
requestItems: ({ commit }) => commit(types.REQUEST_ITEMS),
receiveItemsSuccess: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_SUCCESS, payload),
receiveItemsError: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_ERROR, payload),
fetchItems: ({ dispatch }) => {
dispatch('requestItems');
return fetchItems()
.then(items => dispatch('receiveItemsSuccess', { items }))
.catch(error => dispatch('receiveItemsError', { error }));
},
});
import * as getters from './getters';
import actions from './actions';
import mutations from './mutations';
import state from './state';
const createStore = fetchFn => ({
actions: actions(fetchFn),
getters,
mutations,
state: state(),
});
export default createStore;
export const REQUEST_ITEMS = 'REQUEST_ITEMS';
export const RECEIVE_ITEMS_SUCCESS = 'REQUEST_ITEMS_SUCCESS';
export const RECEIVE_ITEMS_ERROR = 'RECEIVE_ITEMS_ERROR';
import * as types from './mutation_types';
export default {
[types.REQUEST_ITEMS](state) {
state.isLoadingItems = true;
state.loadingItemsError = null;
},
[types.RECEIVE_ITEMS_SUCCESS](state, { items }) {
state.isLoadingItems = false;
state.items = items;
},
[types.RECEIVE_ITEMS_ERROR](state, { error }) {
state.isLoadingItems = false;
state.loadingItemsError = error;
},
};
export default () => ({
isLoadingItems: false,
items: [],
loadingItemsError: null,
});
......@@ -4,12 +4,22 @@ import * as getters from './getters';
import mutations from './mutations';
import state from './state';
import clusterDropdownStore from './cluster_dropdown';
import * as awsServices from '../services/aws_services_facade';
const createStore = () =>
new Vuex.Store({
actions,
getters,
mutations,
state: state(),
modules: {
regions: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchRegions),
},
},
});
export default createStore;
export const REQUEST_REGIONS = 'REQUEST_REGIONS';
export const RECEIVE_REGIONS_SUCCESS = 'REQUEST_REGIONS_SUCCESS';
export const RECEIVE_REGIONS_ERROR = 'RECEIVE_REGIONS_ERROR';
// eslint-disable-next-line import/prefer-default-export
export const SET_REGION = 'SET_REGION';
import * as types from './mutation_types';
export default {
[types.REQUEST_REGIONS](state) {
state.isLoadingRegions = true;
state.loadingRegionsError = null;
},
[types.RECEIVE_REGIONS_SUCCESS](state, { regions }) {
state.isLoadingRegions = false;
state.regions = regions;
},
[types.RECEIVE_REGIONS_ERROR](state, { error }) {
state.isLoadingRegions = false;
state.loadingRegionsError = error;
},
[types.SET_REGION](state, { region }) {
state.selectedRegion = region;
},
......
......@@ -2,27 +2,9 @@ export default () => ({
isValidatingCredentials: false,
validCredentials: false,
isLoadingRegions: false,
isLoadingRoles: false,
isLoadingVPCs: false,
isLoadingSubnets: false,
isLoadingSecurityGroups: false,
regions: [],
roles: [],
vpcs: [],
subnets: [],
securityGroups: [],
loadingRegionsError: null,
loadingRolesError: null,
loadingVPCsError: null,
loadingSubnetsError: null,
loadingSecurityGroupsError: null,
selectedRegion: '',
selectedRole: '',
selectedVPC: '',
selectedVpc: '',
selectedSubnet: '',
selectedSecurityGroup: '',
});
......@@ -4,28 +4,38 @@ import Vue from 'vue';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('EksClusterConfigurationForm', () => {
let store;
let actions;
let state;
let regionsState;
let regionsActions;
let vm;
beforeEach(() => {
actions = {
fetchRegions: jest.fn(),
setRegion: jest.fn(),
setVpc: jest.fn(),
};
regionsActions = {
fetchItems: jest.fn(),
};
state = {
regions: [{ name: 'region 1' }],
isLoadingRegions: false,
loadingRegionsError: { message: '' },
regionsState = {
...clusterDropdownStoreState(),
};
store = new Vuex.Store({
state,
actions,
modules: {
regions: {
namespaced: true,
state: regionsState,
actions: regionsActions,
},
},
});
});
......@@ -44,31 +54,35 @@ describe('EksClusterConfigurationForm', () => {
describe('when mounted', () => {
it('fetches available regions', () => {
expect(actions.fetchRegions).toHaveBeenCalled();
expect(regionsActions.fetchItems).toHaveBeenCalled();
});
});
it('sets isLoadingRegions to RegionDropdown loading property', () => {
state.isLoadingRegions = true;
regionsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findRegionDropdown().props('loading')).toEqual(state.isLoadingRegions);
expect(findRegionDropdown().props('loading')).toEqual(regionsState.isLoadingItems);
});
});
it('sets regions to RegionDropdown regions property', () => {
expect(findRegionDropdown().props('regions')).toEqual(state.regions);
expect(findRegionDropdown().props('regions')).toEqual(regionsState.items);
});
it('sets loadingRegionsError to RegionDropdown error property', () => {
expect(findRegionDropdown().props('error')).toEqual(state.loadingRegionsError);
expect(findRegionDropdown().props('error')).toEqual(regionsState.loadingItemsError);
});
it('dispatches setRegion action when region is selected', () => {
const region = { region: 'us-west-2' };
describe('when region is selected', () => {
const region = { name: 'us-west-2' };
beforeEach(() => {
findRegionDropdown().vm.$emit('input', region);
});
it('dispatches setRegion action', () => {
expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
});
});
});
import testAction from 'helpers/vuex_action_helper';
import * as awsServicesFacade from '~/create_cluster/eks_cluster/services/aws_services_facade';
import createState from '~/create_cluster/eks_cluster/store/state';
import * as types from '~/create_cluster/eks_cluster/store/mutation_types';
import * as actions from '~/create_cluster/eks_cluster/store/actions';
describe('EKS Cluster Store Actions', () => {
const regions = [{ name: 'region 1' }];
describe('fetchRegions', () => {
describe('on success', () => {
beforeEach(() => {
jest.spyOn(awsServicesFacade, 'fetchRegions').mockResolvedValueOnce(regions);
});
it('dispatches success with received regions', () =>
testAction(
actions.fetchRegions,
null,
createState(),
[],
[
{ type: 'requestRegions' },
{
type: 'receiveRegionsSuccess',
payload: { regions },
},
],
));
});
describe('on failure', () => {
const error = new Error('Could not fetch regions');
beforeEach(() => {
jest.spyOn(awsServicesFacade, 'fetchRegions').mockRejectedValueOnce(error);
});
it('dispatches success with received regions', () =>
testAction(
actions.fetchRegions,
null,
createState(),
[],
[
{ type: 'requestRegions' },
{
type: 'receiveRegionsError',
payload: { error },
},
],
));
});
});
describe('requestRegions', () => {
it(`commits ${types.REQUEST_REGIONS} mutation`, () =>
testAction(actions.requestRegions, null, createState(), [{ type: types.REQUEST_REGIONS }]));
});
describe('receiveRegionsSuccess', () => {
it(`commits ${types.RECEIVE_REGIONS_SUCCESS} mutation`, () =>
testAction(actions.receiveRegionsSuccess, { regions }, createState(), [
{
type: types.RECEIVE_REGIONS_SUCCESS,
payload: {
regions,
},
},
]));
});
describe('receiveRegionsError', () => {
it(`commits ${types.RECEIVE_REGIONS_ERROR} mutation`, () => {
const error = new Error('Error fetching regions');
testAction(actions.receiveRegionsError, { error }, createState(), [
{
type: types.RECEIVE_REGIONS_ERROR,
payload: {
error,
},
},
]);
});
});
describe('setRegion', () => {
it(`commits ${types.SET_REGION} mutation`, () => {
const region = { name: 'west-1' };
......
import testAction from 'helpers/vuex_action_helper';
import createState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state';
import * as types from '~/create_cluster/eks_cluster/store/cluster_dropdown/mutation_types';
import actionsFactory from '~/create_cluster/eks_cluster/store/cluster_dropdown/actions';
describe('Cluster dropdown Store Actions', () => {
const items = [{ name: 'item 1' }];
let fetchFn;
let actions;
beforeEach(() => {
fetchFn = jest.fn();
actions = actionsFactory(fetchFn);
});
describe('fetchItems', () => {
describe('on success', () => {
beforeEach(() => {
fetchFn.mockResolvedValueOnce(items);
actions = actionsFactory(fetchFn);
});
it('dispatches success with received items', () =>
testAction(
actions.fetchItems,
null,
createState(),
[],
[
{ type: 'requestItems' },
{
type: 'receiveItemsSuccess',
payload: { items },
},
],
));
});
describe('on failure', () => {
const error = new Error('Could not fetch items');
beforeEach(() => {
fetchFn.mockRejectedValueOnce(error);
});
it('dispatches success with received items', () =>
testAction(
actions.fetchItems,
null,
createState(),
[],
[
{ type: 'requestItems' },
{
type: 'receiveItemsError',
payload: { error },
},
],
));
});
});
describe('requestItems', () => {
it(`commits ${types.REQUEST_ITEMS} mutation`, () =>
testAction(actions.requestItems, null, createState(), [{ type: types.REQUEST_ITEMS }]));
});
describe('receiveItemsSuccess', () => {
it(`commits ${types.RECEIVE_ITEMS_SUCCESS} mutation`, () =>
testAction(actions.receiveItemsSuccess, { items }, createState(), [
{
type: types.RECEIVE_ITEMS_SUCCESS,
payload: {
items,
},
},
]));
});
describe('receiveItemsError', () => {
it(`commits ${types.RECEIVE_ITEMS_ERROR} mutation`, () => {
const error = new Error('Error fetching items');
testAction(actions.receiveItemsError, { error }, createState(), [
{
type: types.RECEIVE_ITEMS_ERROR,
payload: {
error,
},
},
]);
});
});
});
import {
REQUEST_ITEMS,
RECEIVE_ITEMS_SUCCESS,
RECEIVE_ITEMS_ERROR,
} from '~/create_cluster/eks_cluster/store/cluster_dropdown/mutation_types';
import createState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state';
import mutations from '~/create_cluster/eks_cluster/store/cluster_dropdown/mutations';
describe('Cluster dropdown store mutations', () => {
let state;
let emptyPayload;
let items;
let error;
beforeEach(() => {
emptyPayload = {};
items = [{ name: 'item 1' }];
error = new Error('could not load error');
state = createState();
});
it.each`
mutation | mutatedProperty | payload | expectedValue | expectedValueDescription
${REQUEST_ITEMS} | ${'isLoadingItems'} | ${emptyPayload} | ${true} | ${true}
${REQUEST_ITEMS} | ${'loadingItemsError'} | ${emptyPayload} | ${null} | ${null}
${RECEIVE_ITEMS_SUCCESS} | ${'isLoadingItems'} | ${{ items }} | ${false} | ${false}
${RECEIVE_ITEMS_SUCCESS} | ${'items'} | ${{ items }} | ${items} | ${'items payload'}
${RECEIVE_ITEMS_ERROR} | ${'isLoadingItems'} | ${{ error }} | ${false} | ${false}
${RECEIVE_ITEMS_ERROR} | ${'error'} | ${{ error }} | ${error} | ${'received error object'}
`(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
const { mutation, mutatedProperty, payload, expectedValue } = data;
mutations[mutation](state, payload);
expect(state[mutatedProperty]).toBe(expectedValue);
});
});
import {
REQUEST_REGIONS,
RECEIVE_REGIONS_ERROR,
RECEIVE_REGIONS_SUCCESS,
SET_REGION,
} from '~/create_cluster/eks_cluster/store/mutation_types';
import { SET_REGION } from '~/create_cluster/eks_cluster/store/mutation_types';
import createState from '~/create_cluster/eks_cluster/store/state';
import mutations from '~/create_cluster/eks_cluster/store/mutations';
describe('Create EKS cluster store mutations', () => {
let state;
let emptyPayload;
let regions;
let region;
let error;
beforeEach(() => {
emptyPayload = {};
region = { name: 'regions-1' };
regions = [region];
error = new Error('could not load error');
state = createState();
});
it.each`
mutation | mutatedProperty | payload | expectedValue | expectedValueDescription
${REQUEST_REGIONS} | ${'isLoadingRegions'} | ${emptyPayload} | ${true} | ${true}
${REQUEST_REGIONS} | ${'loadingRegionsError'} | ${emptyPayload} | ${null} | ${null}
${RECEIVE_REGIONS_SUCCESS} | ${'isLoadingRegions'} | ${{ regions }} | ${false} | ${false}
${RECEIVE_REGIONS_SUCCESS} | ${'regions'} | ${{ regions }} | ${regions} | ${'regions payload'}
${RECEIVE_REGIONS_ERROR} | ${'isLoadingRegions'} | ${{ error }} | ${false} | ${false}
${RECEIVE_REGIONS_ERROR} | ${'error'} | ${{ error }} | ${error} | ${'received error object'}
${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'}
`(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
const { mutation, mutatedProperty, payload, expectedValue } = data;
......
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