Commit c960fea1 authored by Dennis Tang's avatar Dennis Tang

validate project billing status after selection

parent 2b903ece
...@@ -8,14 +8,14 @@ export default { ...@@ -8,14 +8,14 @@ export default {
name: 'GkeMachineTypeDropdown', name: 'GkeMachineTypeDropdown',
mixins: [gkeDropdownMixin], mixins: [gkeDropdownMixin],
computed: { computed: {
...mapState(['selectedProject', 'selectedZone', 'selectedMachineType']), ...mapState(['projectHasBillingEnabled', 'selectedZone', 'selectedMachineType']),
...mapState({ items: 'machineTypes' }), ...mapState({ items: 'machineTypes' }),
...mapGetters(['hasProject', 'hasZone', 'hasMachineType']), ...mapGetters(['hasZone', 'hasMachineType']),
allDropdownsSelected() { allDropdownsSelected() {
return this.hasProject && this.hasZone && this.hasMachineType; return this.projectHasBillingEnabled && this.hasZone && this.hasMachineType;
}, },
isDisabled() { isDisabled() {
return !this.selectedProject || !this.selectedZone; return !this.projectHasBillingEnabled || !this.selectedZone;
}, },
toggleText() { toggleText() {
if (this.isLoading) { if (this.isLoading) {
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
return this.selectedMachineType; return this.selectedMachineType;
} }
if (!this.hasProject && !this.hasZone) { if (!this.projectHasBillingEnabled && !this.hasZone) {
return s__('ClusterIntegration|Select project and zone to choose machine type'); return s__('ClusterIntegration|Select project and zone to choose machine type');
} }
......
...@@ -14,8 +14,13 @@ export default { ...@@ -14,8 +14,13 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
isValidatingProjectBilling: false,
};
},
computed: { computed: {
...mapState(['selectedProject']), ...mapState(['selectedProject', 'projectHasBillingEnabled']),
...mapState({ items: 'projects' }), ...mapState({ items: 'projects' }),
...mapGetters(['hasProject']), ...mapGetters(['hasProject']),
hasOneProject() { hasOneProject() {
...@@ -25,6 +30,10 @@ export default { ...@@ -25,6 +30,10 @@ export default {
return this.items && this.items.length < 2; return this.items && this.items.length < 2;
}, },
toggleText() { toggleText() {
if (this.isValidatingProjectBilling) {
return s__('ClusterIntegration|Validating project billing status');
}
if (this.isLoading) { if (this.isLoading) {
return s__('ClusterIntegration|Fetching projects'); return s__('ClusterIntegration|Fetching projects');
} }
...@@ -42,7 +51,7 @@ export default { ...@@ -42,7 +51,7 @@ export default {
helpText() { helpText() {
let message; let message;
if (this.hasErrors) { if (this.hasErrors) {
message = this.gapiError; return this.errorMessage;
} }
if (!this.items) { if (!this.items) {
...@@ -67,12 +76,45 @@ export default { ...@@ -67,12 +76,45 @@ export default {
); );
}, },
errorMessage() { errorMessage() {
if (!this.projectHasBillingEnabled) {
if (this.gapiError) {
return s__(
'ClusterIntegration|We could not verify that one of your projects on GCP has billing enabled. Please try again.',
);
}
return sprintf(
s__(
'Please <a href=%{linkToBilling} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.',
),
{
linkToBilling:
'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral',
},
false,
);
}
return sprintf( return sprintf(
s__('ClusterIntegration|An error occured while trying to fetch your projects: %{error}'), s__('ClusterIntegration|An error occured while trying to fetch your projects: %{error}'),
{ error: this.gapiError }, { error: this.gapiError },
); );
}, },
}, },
watch: {
selectedProject() {
this.isLoading = true;
this.isValidatingProjectBilling = true;
this.validateProjectBilling()
.then(this.validateProjectBillingSuccessHandler)
.catch(this.validateProjectBillingFailureHandler);
},
projectHasBillingEnabled(billingEnabled) {
this.hasErrors = !billingEnabled;
this.isValidatingProjectBilling = false;
},
},
created() { created() {
this.isLoading = true; this.isLoading = true;
...@@ -81,7 +123,7 @@ export default { ...@@ -81,7 +123,7 @@ export default {
.catch(this.fetchFailureHandler); .catch(this.fetchFailureHandler);
}, },
methods: { methods: {
...mapActions(['fetchProjects']), ...mapActions(['fetchProjects', 'validateProjectBilling']),
...mapActions({ setItem: 'setProject' }), ...mapActions({ setItem: 'setProject' }),
fetchSuccessHandler() { fetchSuccessHandler() {
if (this.defaultValue) { if (this.defaultValue) {
...@@ -97,6 +139,15 @@ export default { ...@@ -97,6 +139,15 @@ export default {
this.isLoading = false; this.isLoading = false;
this.hasErrors = false; this.hasErrors = false;
}, },
validateProjectBillingSuccessHandler() {
this.isLoading = false;
},
validateProjectBillingFailureHandler(resp) {
this.isLoading = false;
this.hasErrors = true;
this.gapiError = resp.result ? resp.result.error.message : resp;
},
}, },
}; };
</script> </script>
......
<script> <script>
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import gkeDropdownMixin from './gke_dropdown_mixin'; import gkeDropdownMixin from './gke_dropdown_mixin';
...@@ -8,11 +8,10 @@ export default { ...@@ -8,11 +8,10 @@ export default {
name: 'GkeZoneDropdown', name: 'GkeZoneDropdown',
mixins: [gkeDropdownMixin], mixins: [gkeDropdownMixin],
computed: { computed: {
...mapState(['selectedProject', 'selectedZone', 'projects']), ...mapState(['selectedProject', 'selectedZone', 'projects', 'projectHasBillingEnabled']),
...mapState({ items: 'zones' }), ...mapState({ items: 'zones' }),
...mapGetters(['hasProject']),
isDisabled() { isDisabled() {
return !this.hasProject; return !this.projectHasBillingEnabled;
}, },
toggleText() { toggleText() {
if (this.isLoading) { if (this.isLoading) {
...@@ -23,7 +22,7 @@ export default { ...@@ -23,7 +22,7 @@ export default {
return this.selectedZone; return this.selectedZone;
} }
return !this.hasProject return !this.projectHasBillingEnabled
? s__('ClusterIntegration|Select project to choose zone') ? s__('ClusterIntegration|Select project to choose zone')
: s__('ClusterIntegration|Select zone'); : s__('ClusterIntegration|Select zone');
}, },
...@@ -35,10 +34,11 @@ export default { ...@@ -35,10 +34,11 @@ export default {
}, },
}, },
watch: { watch: {
selectedProject() { projectHasBillingEnabled(billingEnabled) {
if (!billingEnabled) return false;
this.isLoading = true; this.isLoading = true;
this.fetchZones() return this.fetchZones()
.then(this.fetchSuccessHandler) .then(this.fetchSuccessHandler)
.catch(this.fetchFailureHandler); .catch(this.fetchFailureHandler);
}, },
......
...@@ -3,6 +3,8 @@ import { s__ } from '~/locale'; ...@@ -3,6 +3,8 @@ import { s__ } from '~/locale';
export const GCP_API_ERROR = s__( export const GCP_API_ERROR = s__(
'ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later.', 'ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later.',
); );
export const GCP_API_CLOUD_BILLING_ENDPOINT =
'https://www.googleapis.com/discovery/v1/apis/cloudbilling/v1/rest';
export const GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT = export const GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT =
'https://www.googleapis.com/discovery/v1/apis/cloudresourcemanager/v1/rest'; 'https://www.googleapis.com/discovery/v1/apis/cloudresourcemanager/v1/rest';
export const GCP_API_COMPUTE_ENDPOINT = export const GCP_API_COMPUTE_ENDPOINT =
......
...@@ -56,19 +56,20 @@ const gkeDropdownErrorHandler = () => { ...@@ -56,19 +56,20 @@ const gkeDropdownErrorHandler = () => {
const initializeGapiClient = () => { const initializeGapiClient = () => {
const el = document.querySelector('.js-gke-cluster-creation'); const el = document.querySelector('.js-gke-cluster-creation');
if (!el) return false;
return gapi.client
.init({
discoveryDocs: [
CONSTANTS.GCP_API_CLOUD_BILLING_ENDPOINT,
CONSTANTS.GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT,
CONSTANTS.GCP_API_COMPUTE_ENDPOINT,
],
})
.then(() => {
gapi.client.setToken({ access_token: el.dataset.token }); gapi.client.setToken({ access_token: el.dataset.token });
gapi.client
.load(CONSTANTS.GCP_API_CLOUD_RESOURCE_MANAGER_ENDPOINT)
.then(() => {
mountGkeProjectIdDropdown(); mountGkeProjectIdDropdown();
})
.catch(gkeDropdownErrorHandler);
gapi.client
.load(CONSTANTS.GCP_API_COMPUTE_ENDPOINT)
.then(() => {
mountGkeZoneDropdown(); mountGkeZoneDropdown();
mountGkeMachineTypeDropdown(); mountGkeMachineTypeDropdown();
}) })
......
/* global gapi */ /* global gapi */
import * as types from './mutation_types'; import * as types from './mutation_types';
const gapiRequest = ({ service, params, commit, mutation, payloadKey }) => const gapiResourceListRequest = ({ resource, params, commit, mutation, payloadKey }) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const request = service.list(params); const request = resource.list(params);
return request.then( return request.then(
resp => { resp => {
...@@ -32,17 +32,36 @@ export const setMachineType = ({ commit }, selectedMachineType) => { ...@@ -32,17 +32,36 @@ export const setMachineType = ({ commit }, selectedMachineType) => {
}; };
export const fetchProjects = ({ commit }) => export const fetchProjects = ({ commit }) =>
gapiRequest({ gapiResourceListRequest({
service: gapi.client.cloudresourcemanager.projects, resource: gapi.client.cloudresourcemanager.projects,
params: {}, params: {},
commit, commit,
mutation: types.SET_PROJECTS, mutation: types.SET_PROJECTS,
payloadKey: 'projects', payloadKey: 'projects',
}); });
export const validateProjectBilling = ({ commit, state }) =>
new Promise((resolve, reject) => {
const request = gapi.client.cloudbilling.projects.getBillingInfo({
name: `projects/${state.selectedProject.projectId}`,
});
return request.then(
resp => {
const { billingEnabled } = resp.result;
commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled);
resolve();
},
resp => {
reject(resp);
},
);
});
export const fetchZones = ({ commit, state }) => export const fetchZones = ({ commit, state }) =>
gapiRequest({ gapiResourceListRequest({
service: gapi.client.compute.zones, resource: gapi.client.compute.zones,
params: { params: {
project: state.selectedProject.projectId, project: state.selectedProject.projectId,
}, },
...@@ -52,8 +71,8 @@ export const fetchZones = ({ commit, state }) => ...@@ -52,8 +71,8 @@ export const fetchZones = ({ commit, state }) =>
}); });
export const fetchMachineTypes = ({ commit, state }) => export const fetchMachineTypes = ({ commit, state }) =>
gapiRequest({ gapiResourceListRequest({
service: gapi.client.compute.machineTypes, resource: gapi.client.compute.machineTypes,
params: { params: {
project: state.selectedProject.projectId, project: state.selectedProject.projectId,
zone: state.selectedZone, zone: state.selectedZone,
......
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
export const SET_PROJECT_BILLING_STATUS = 'SET_PROJECT_BILLING_STATUS';
export const SET_ZONE = 'SET_ZONE'; export const SET_ZONE = 'SET_ZONE';
export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE'; export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE';
export const SET_PROJECTS = 'SET_PROJECTS'; export const SET_PROJECTS = 'SET_PROJECTS';
......
...@@ -4,6 +4,9 @@ export default { ...@@ -4,6 +4,9 @@ export default {
[types.SET_PROJECT](state, selectedProject) { [types.SET_PROJECT](state, selectedProject) {
Object.assign(state, { selectedProject }); Object.assign(state, { selectedProject });
}, },
[types.SET_PROJECT_BILLING_STATUS](state, projectHasBillingEnabled) {
Object.assign(state, { projectHasBillingEnabled });
},
[types.SET_ZONE](state, selectedZone) { [types.SET_ZONE](state, selectedZone) {
Object.assign(state, { selectedZone }); Object.assign(state, { selectedZone });
}, },
......
...@@ -5,6 +5,7 @@ export default { ...@@ -5,6 +5,7 @@ export default {
}, },
selectedZone: '', selectedZone: '',
selectedMachineType: '', selectedMachineType: '',
projectHasBillingEnabled: null,
projects: [], projects: [],
zones: [], zones: [],
machineTypes: [], machineTypes: [],
......
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