Commit f9da3a1c authored by Enrique Alcantara's avatar Enrique Alcantara

Validate that users selects at least two subnets

When a user is creating a EKS Cluster, they should
select at least two subnets.
parent 4c5be4df
<script>
import { createNamespacedHelpers, mapState, mapActions } from 'vuex';
import { createNamespacedHelpers, mapState, mapActions, mapGetters } from 'vuex';
import { escape as esc } from 'lodash';
import { GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
......@@ -61,6 +61,7 @@ export default {
'gitlabManagedCluster',
'isCreatingCluster',
]),
...mapGetters(['subnetValid']),
...mapRolesState({
roles: 'items',
isLoadingRoles: 'isLoadingItems',
......@@ -119,7 +120,7 @@ export default {
!this.selectedRegion ||
!this.selectedKeyPair ||
!this.selectedVpc ||
!this.selectedSubnet ||
!this.subnetValid ||
!this.selectedRole ||
!this.selectedSecurityGroup ||
!this.selectedInstanceType ||
......@@ -127,6 +128,9 @@ export default {
this.isCreatingCluster
);
},
displaySubnetError() {
return Boolean(this.loadingSubnetsError) || this.selectedSubnet?.length === 1;
},
createClusterButtonLabel() {
return this.isCreatingCluster
? s__('ClusterIntegration|Creating Kubernetes cluster')
......@@ -216,6 +220,13 @@ export default {
false,
);
},
subnetValidationErrorText() {
if (this.loadingSubnetsError) {
return s__('ClusterIntegration|Could not load subnets for the selected VPC');
}
return s__('ClusterIntegration|You should select at least two subnets');
},
securityGroupDropdownHelpText() {
return sprintf(
s__(
......@@ -289,14 +300,14 @@ export default {
this.setRegion({ region });
this.setVpc({ vpc: null });
this.setKeyPair({ keyPair: null });
this.setSubnet({ subnet: null });
this.setSubnet({ subnet: [] });
this.setSecurityGroup({ securityGroup: null });
this.fetchVpcs({ region });
this.fetchKeyPairs({ region });
},
setVpcAndFetchSubnets(vpc) {
this.setVpc({ vpc });
this.setSubnet({ subnet: null });
this.setSubnet({ subnet: [] });
this.setSecurityGroup({ securityGroup: null });
this.fetchSubnets({ vpc, region: this.selectedRegion });
this.fetchSecurityGroups({ vpc, region: this.selectedRegion });
......@@ -436,8 +447,8 @@ export default {
:placeholder="s__('ClusterIntergation|Select a subnet')"
:search-field-placeholder="s__('ClusterIntegration|Search subnets')"
:empty-text="s__('ClusterIntegration|No subnet found')"
:has-errors="Boolean(loadingSubnetsError)"
:error-message="s__('ClusterIntegration|Could not load subnets for the selected VPC')"
:has-errors="displaySubnetError"
:error-message="subnetValidationErrorText"
@input="setSubnet({ subnet: $event })"
/>
<p class="form-text text-muted" v-html="subnetDropdownHelpText"></p>
......
// eslint-disable-next-line import/prefer-default-export
export const subnetValid = ({ selectedSubnet }) =>
Array.isArray(selectedSubnet) && selectedSubnet.length >= 2;
......@@ -21,7 +21,7 @@ export default () => ({
selectedRole: '',
selectedKeyPair: '',
selectedVpc: '',
selectedSubnet: '',
selectedSubnet: [],
selectedSecurityGroup: '',
selectedInstanceType: 'm5.large',
nodeCount: '3',
......
......@@ -4822,6 +4822,9 @@ msgstr ""
msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
msgstr ""
msgid "ClusterIntegration|You should select at least two subnets"
msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
......
......@@ -13,6 +13,7 @@ localVue.use(Vuex);
describe('EksClusterConfigurationForm', () => {
let store;
let actions;
let getters;
let state;
let rolesState;
let regionsState;
......@@ -29,8 +30,7 @@ describe('EksClusterConfigurationForm', () => {
let securityGroupsActions;
let vm;
beforeEach(() => {
state = eksClusterFormState();
const createStore = (config = {}) => {
actions = {
createCluster: jest.fn(),
setClusterName: jest.fn(),
......@@ -64,29 +64,44 @@ describe('EksClusterConfigurationForm', () => {
securityGroupsActions = {
fetchItems: jest.fn(),
};
state = {
...eksClusterFormState(),
...config.initialState,
};
rolesState = {
...clusterDropdownStoreState(),
...config.rolesState,
};
regionsState = {
...clusterDropdownStoreState(),
...config.regionsState,
};
vpcsState = {
...clusterDropdownStoreState(),
...config.vpcsState,
};
subnetsState = {
...clusterDropdownStoreState(),
...config.subnetsState,
};
keyPairsState = {
...clusterDropdownStoreState(),
...config.keyPairsState,
};
securityGroupsState = {
...clusterDropdownStoreState(),
...config.securityGroupsState,
};
instanceTypesState = {
...clusterDropdownStoreState(),
...config.instanceTypesState,
};
getters = {
subnetValid: config?.getters?.subnetValid || (() => false),
};
store = new Vuex.Store({
state,
getters,
actions,
modules: {
vpcs: {
......@@ -125,9 +140,29 @@ describe('EksClusterConfigurationForm', () => {
},
},
});
};
const createValidStateStore = initialState => {
createStore({
initialState: {
clusterName: 'cluster name',
environmentScope: '*',
selectedRegion: 'region',
selectedRole: 'role',
selectedKeyPair: 'key pair',
selectedVpc: 'vpc',
selectedSubnet: ['subnet 1', 'subnet 2'],
selectedSecurityGroup: 'group',
selectedInstanceType: 'small-1',
...initialState,
},
getters: {
subnetValid: () => true,
},
});
};
beforeEach(() => {
const buildWrapper = () => {
vm = shallowMount(EksClusterConfigurationForm, {
localVue,
store,
......@@ -137,27 +172,17 @@ describe('EksClusterConfigurationForm', () => {
externalLinkIcon: '',
},
});
};
beforeEach(() => {
createStore();
buildWrapper();
});
afterEach(() => {
vm.destroy();
});
const setAllConfigurationFields = () => {
store.replaceState({
...state,
clusterName: 'cluster name',
environmentScope: '*',
selectedRegion: 'region',
selectedRole: 'role',
selectedKeyPair: 'key pair',
selectedVpc: 'vpc',
selectedSubnet: 'subnet',
selectedSecurityGroup: 'group',
selectedInstanceType: 'small-1',
});
};
const findCreateClusterButton = () => vm.find('.js-create-cluster');
const findClusterNameInput = () => vm.find('[id=eks-cluster-name]');
const findEnvironmentScopeInput = () => vm.find('[id=eks-environment-scope]');
......@@ -310,12 +335,29 @@ describe('EksClusterConfigurationForm', () => {
expect(findSubnetDropdown().props('items')).toBe(subnetsState.items);
});
it('sets SubnetDropdown hasErrors to true when loading subnets fails', () => {
subnetsState.loadingItemsError = new Error();
it('displays a validation error in the subnet dropdown when loading subnets fails', () => {
createStore({
subnetsState: {
loadingItemsError: new Error(),
},
});
buildWrapper();
return Vue.nextTick().then(() => {
expect(findSubnetDropdown().props('hasErrors')).toEqual(true);
});
it('displays a validation error in the subnet dropdown when a single subnet is selected', () => {
createStore({
initialState: {
selectedSubnet: ['subnet 1'],
},
});
buildWrapper();
expect(findSubnetDropdown().props('hasErrors')).toEqual(true);
expect(findSubnetDropdown().props('errorMessage')).toEqual(
'You should select at least two subnets',
);
});
it('disables SecurityGroupDropdown when no vpc is selected', () => {
......@@ -386,11 +428,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(
expect.anything(),
{ subnet: null },
undefined,
);
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }, undefined);
});
it('cleans selected security group', () => {
......@@ -464,11 +502,7 @@ describe('EksClusterConfigurationForm', () => {
});
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(
expect.anything(),
{ subnet: null },
undefined,
);
expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet: [] }, undefined);
});
it('cleans selected security group', () => {
......@@ -573,22 +607,19 @@ describe('EksClusterConfigurationForm', () => {
});
describe('when all cluster configuration fields are set', () => {
beforeEach(() => {
setAllConfigurationFields();
});
it('enables create cluster button', () => {
createValidStateStore();
buildWrapper();
expect(findCreateClusterButton().props('disabled')).toBe(false);
});
});
describe('when at least one cluster configuration field is not set', () => {
beforeEach(() => {
setAllConfigurationFields();
store.replaceState({
...state,
clusterName: '',
createValidStateStore({
clusterName: null,
});
buildWrapper();
});
it('disables create cluster button', () => {
......@@ -596,13 +627,12 @@ describe('EksClusterConfigurationForm', () => {
});
});
describe('when isCreatingCluster', () => {
describe('when is creating cluster', () => {
beforeEach(() => {
setAllConfigurationFields();
store.replaceState({
...state,
createValidStateStore({
isCreatingCluster: true,
});
buildWrapper();
});
it('sets create cluster button as loading', () => {
......
import { subnetValid } from '~/create_cluster/eks_cluster/store/getters';
describe('EKS Cluster Store Getters', () => {
describe('subnetValid', () => {
it('returns true if there are 2 or more selected subnets', () => {
expect(subnetValid({ selectedSubnet: [1, 2] })).toBe(true);
});
it.each([[[], [1]]])('returns false if there are 1 or less selected subnets', subnets => {
expect(subnetValid({ selectedSubnet: subnets })).toBe(false);
});
});
});
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