Commit bd454b94 authored by Enrique Alcantara's avatar Enrique Alcantara

Load and display EKS AWS VPCs

- Implement VPC dropdown component
- Include VPC dropdown component in form
- Implement vuex store flow to fetch VPCs
parent 9fb74ec5
...@@ -25,15 +25,28 @@ export default { ...@@ -25,15 +25,28 @@ export default {
isLoadingRegions: 'isLoadingItems', isLoadingRegions: 'isLoadingItems',
loadingRegionsError: 'loadingItemsError', loadingRegionsError: 'loadingItemsError',
}), }),
...mapState('vpcs', {
vpcs: ({ items }) => items,
isLoadingVpcs: ({ isLoadingItems }) => isLoadingItems,
loadingVpcsError: ({ loadingItemsError }) => loadingItemsError,
}),
vpcDropdownDisabled() {
return !Boolean(this.selectedRegion);
},
}, },
mounted() { mounted() {
this.fetchRegions(); this.fetchRegions();
}, },
methods: { methods: {
...mapActions(['setRegion']), ...mapActions(['setRegion', 'setVpc']),
...mapRegionsActions({ ...mapActions({
fetchRegions: 'fetchItems', fetchRegions: 'regions/fetchItems',
fetchVpcs: 'vpcs/fetchItems',
}), }),
setRegionAndFetchVpcs(region) {
this.setRegion({ region });
this.fetchVpcs({ region });
},
}, },
}; };
</script> </script>
...@@ -54,7 +67,20 @@ export default { ...@@ -54,7 +67,20 @@ export default {
:regions="regions" :regions="regions"
:error="loadingRegionsError" :error="loadingRegionsError"
:loading="isLoadingRegions" :loading="isLoadingRegions"
@input="setRegion({ region: $event })" @input="setRegionAndFetchVpcs($event)"
/>
</div>
<div class="form-group">
<label class="label-bold" name="role" for="eks-role">{{
s__('ClusterIntegration|VPC')
}}</label>
<vpc-dropdown
:input="selectedVpc"
:vpcs="vpcs"
:error="loadingVpcsError"
:loading="isLoadingVpcs"
:disabled="vpcDropdownDisabled"
@input="setVpc({ vpc: $event })"
/> />
</div> </div>
</form> </form>
......
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
vpcs: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
disabled: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
default: null,
},
},
computed: {
hasErrors() {
return Boolean(this.error);
},
helpText() {
return sprintf(
s__(
'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#vpc" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-vpc"
field-name="eks-vpc"
:items="vpcs"
:loading="loading"
:disabled="disabled"
:disabled-text="s__('ClusterIntegration|Select a region to choose a VPC')"
:loading-text="s__('ClusterIntegration|Loading VPCs')"
:placeholder="s__('ClusterIntergation|Select a VPC')"
:search-field-placeholder="s__('ClusterIntegration|Search VPCs')"
:empty-text="s__('ClusterIntegration|No VPCs found')"
:has-errors="hasErrors"
:error-message="s__('ClusterIntegration|Could not load VPCs for the selected region')"
v-bind="$attrs"
v-on="$listeners"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>
...@@ -12,6 +12,7 @@ export default () => ...@@ -12,6 +12,7 @@ export default () =>
components: { components: {
CreateEksCluster, CreateEksCluster,
}, },
data() {},
render(createElement) { render(createElement) {
return createElement('create-eks-cluster'); return createElement('create-eks-cluster');
}, },
......
...@@ -17,4 +17,21 @@ export const fetchRegions = () => ...@@ -17,4 +17,21 @@ export const fetchRegions = () =>
.send(); .send();
}); });
export const fetchVpcs = () =>
new Promise((resolve, reject) => {
const ec2 = new EC2();
ec2
.describeVpcs()
.on('success', ({ data: { Vpcs: vpcs } }) => {
const transformedVpcs = vpcs.map(({ VpcId: name }) => ({ name }));
resolve(transformedVpcs);
})
.on('error', error => {
reject(error);
})
.send();
});
export default () => {}; export default () => {};
...@@ -4,4 +4,8 @@ export const setRegion = ({ commit }, payload) => { ...@@ -4,4 +4,8 @@ export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload); commit(types.SET_REGION, payload);
}; };
export const setVpc = ({ commit }, payload) => {
commit(types.SET_VPC, payload);
};
export default () => {}; export default () => {};
...@@ -19,6 +19,10 @@ const createStore = () => ...@@ -19,6 +19,10 @@ const createStore = () =>
namespaced: true, namespaced: true,
...clusterDropdownStore(awsServices.fetchRegions), ...clusterDropdownStore(awsServices.fetchRegions),
}, },
vpcs: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchVpcs),
},
}, },
}); });
......
// eslint-disable-next-line import/prefer-default-export
export const SET_REGION = 'SET_REGION'; export const SET_REGION = 'SET_REGION';
export const SET_VPC = 'SET_VPC';
...@@ -4,4 +4,7 @@ export default { ...@@ -4,4 +4,7 @@ export default {
[types.SET_REGION](state, { region }) { [types.SET_REGION](state, { region }) {
state.selectedRegion = region; state.selectedRegion = region;
}, },
[types.SET_VPC](state, { vpc }) {
state.selectedVpc = vpc;
},
}; };
...@@ -3,6 +3,7 @@ import Vuex from 'vuex'; ...@@ -3,6 +3,7 @@ import Vuex from 'vuex';
import Vue from 'vue'; import Vue from 'vue';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.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 RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
import VpcDropdown from '~/create_cluster/eks_cluster/components/vpc_dropdown.vue';
import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state'; import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state';
...@@ -13,6 +14,8 @@ describe('EksClusterConfigurationForm', () => { ...@@ -13,6 +14,8 @@ describe('EksClusterConfigurationForm', () => {
let store; let store;
let actions; let actions;
let regionsState; let regionsState;
let vpcsState;
let vpcsActions;
let regionsActions; let regionsActions;
let vm; let vm;
...@@ -24,12 +27,23 @@ describe('EksClusterConfigurationForm', () => { ...@@ -24,12 +27,23 @@ describe('EksClusterConfigurationForm', () => {
regionsActions = { regionsActions = {
fetchItems: jest.fn(), fetchItems: jest.fn(),
}; };
vpcsActions = {
fetchItems: jest.fn(),
};
regionsState = { regionsState = {
...clusterDropdownStoreState(), ...clusterDropdownStoreState(),
}; };
vpcsState = {
...clusterDropdownStoreState(),
};
store = new Vuex.Store({ store = new Vuex.Store({
actions, actions,
modules: { modules: {
vpcs: {
namespaced: true,
state: vpcsState,
actions: vpcsActions,
},
regions: { regions: {
namespaced: true, namespaced: true,
state: regionsState, state: regionsState,
...@@ -51,6 +65,7 @@ describe('EksClusterConfigurationForm', () => { ...@@ -51,6 +65,7 @@ describe('EksClusterConfigurationForm', () => {
}); });
const findRegionDropdown = () => vm.find(RegionDropdown); const findRegionDropdown = () => vm.find(RegionDropdown);
const findVpcDropdown = () => vm.find(VpcDropdown);
describe('when mounted', () => { describe('when mounted', () => {
it('fetches available regions', () => { it('fetches available regions', () => {
...@@ -84,5 +99,37 @@ describe('EksClusterConfigurationForm', () => { ...@@ -84,5 +99,37 @@ describe('EksClusterConfigurationForm', () => {
it('dispatches setRegion action', () => { it('dispatches setRegion action', () => {
expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined); expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
}); });
it('fetches available vpcs', () => {
expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
});
});
it('disables VpcDropdown when no region is selected', () => {
expect(findVpcDropdown().props('disabled')).toEqual(true);
});
it('sets isLoadingVpcs to VpcDropdown loading property', () => {
vpcsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findVpcDropdown().props('loading')).toEqual(vpcsState.isLoadingItems);
});
});
it('sets vpcs to VpcDropdown vpcs property', () => {
expect(findVpcDropdown().props('vpcs')).toEqual(vpcsState.items);
}); });
it('sets loadingVpcsError to VpcDropdown error property', () => {
expect(findVpcDropdown().props('error')).toEqual(vpcsState.loadingItemsError);
});
it('dispatches setVpc action when vpc is selected', () => {
const vpc = { name: 'vpc-1' };
findVpcDropdown().vm.$emit('input', vpc);
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined);
})
}); });
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import VpcDropdown from '~/create_cluster/eks_cluster/components/vpc_dropdown.vue';
describe('VpcDropdown', () => {
let vm;
const getClusterFormDropdown = () => vm.find(ClusterFormDropdown);
beforeEach(() => {
vm = shallowMount(VpcDropdown);
});
afterEach(() => vm.destroy());
it('renders a cluster-form-dropdown', () => {
expect(getClusterFormDropdown().exists()).toBe(true);
});
it('sets vpcs to cluster-form-dropdown items property', () => {
const vpcs = [{ name: 'basic' }];
vm.setProps({ vpcs });
expect(getClusterFormDropdown().props('items')).toEqual(vpcs);
});
it('maps loading property to cluster-form-dropdown loading property', () => {
const loading = true;
vm.setProps({ loading });
expect(getClusterFormDropdown().props('loading')).toEqual(loading);
});
it('sets a loading text', () => {
expect(getClusterFormDropdown().props('loadingText')).toEqual('Loading VPCs');
});
it('maps disabled property to cluster-form-dropdown disabled property', () => {
const disabled = true;
vm.setProps({ disabled });
expect(getClusterFormDropdown().props('disabled')).toEqual(disabled);
});
it('sets a disabled text', () => {
expect(getClusterFormDropdown().props('disabledText')).toEqual(
'Select a region to choose a VPC',
);
});
it('sets a placeholder', () => {
expect(getClusterFormDropdown().props('placeholder')).toEqual('Select a VPC');
});
it('sets an empty results text', () => {
expect(getClusterFormDropdown().props('emptyText')).toEqual('No VPCs found');
});
it('sets a search field placeholder', () => {
expect(getClusterFormDropdown().props('searchFieldPlaceholder')).toEqual('Search VPCs');
});
it('sets hasErrors property', () => {
vm.setProps({ error: {} });
expect(getClusterFormDropdown().props('hasErrors')).toEqual(true);
});
it('sets an error message', () => {
expect(getClusterFormDropdown().props('errorMessage')).toEqual(
'Could not load VPCs for the selected region',
);
});
});
...@@ -14,4 +14,14 @@ describe('EKS Cluster Store Actions', () => { ...@@ -14,4 +14,14 @@ describe('EKS Cluster Store Actions', () => {
]); ]);
}); });
}); });
describe('setVpc', () => {
it(`commits ${types.SET_VPC} mutation`, () => {
const vpc = { name: 'west-1' };
testAction(actions.setVpc, { vpc }, createState(), [
{ type: types.SET_VPC, payload: { vpc } },
]);
});
});
}); });
import { SET_REGION } from '~/create_cluster/eks_cluster/store/mutation_types'; import { SET_REGION, SET_VPC } from '~/create_cluster/eks_cluster/store/mutation_types';
import createState from '~/create_cluster/eks_cluster/store/state'; import createState from '~/create_cluster/eks_cluster/store/state';
import mutations from '~/create_cluster/eks_cluster/store/mutations'; import mutations from '~/create_cluster/eks_cluster/store/mutations';
describe('Create EKS cluster store mutations', () => { describe('Create EKS cluster store mutations', () => {
let state; let state;
let region; let region;
let vpc;
beforeEach(() => { beforeEach(() => {
region = { name: 'regions-1' }; region = { name: 'regions-1' };
vpc = { name: 'vpc-1' };
state = createState(); state = createState();
}); });
it.each` it.each`
mutation | mutatedProperty | payload | expectedValue | expectedValueDescription mutation | mutatedProperty | payload | expectedValue | expectedValueDescription
${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'} ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'}
${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'}
`(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => { `(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
const { mutation, mutatedProperty, payload, expectedValue } = 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