diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2018-05-24 21:00:21 +0100 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2018-05-25 17:16:42 +0300 |
commit | e1d4deb2d2bc91c0730b57f81196b290fdfa86cf (patch) | |
tree | 179c389bcee4f893655f615b523321e746dfae89 | |
parent | b3cf1530829755411a7057031c2e8ea36ee31b62 (diff) | |
download | gitlab-ce-e1d4deb2d2bc91c0730b57f81196b290fdfa86cf.tar.gz |
Disables jupyter install button while ingress is not installed
Includes juptyer hostname in the post request
Adds tests
10 files changed, 212 insertions, 72 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 6bf9dca1112..e42a3632e79 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -211,11 +211,12 @@ export default class Clusters { } } - installApplication(appId) { + installApplication(data) { + const appId = data.id; this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING); this.store.updateAppProperty(appId, 'requestReason', null); - this.service.installApplication(appId) + this.service.installApplication(appId, data.params) .then(() => { this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS); }) diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index fae580c091b..ff4fd0b118c 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -52,6 +52,16 @@ type: String, required: false, }, + disableInstallButton: { + type: Boolean, + required: false, + default: false, + }, + installApplicationRequestParams: { + type: Object, + required: false, + default: () => ({}), + }, }, computed: { rowJsClass() { @@ -67,7 +77,7 @@ // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but // we already made a request to install and are just waiting for the real-time // to sync up. - return (this.status !== APPLICATION_INSTALLABLE + return this.disableInstallButton || (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) || this.requestStatus === REQUEST_LOADING || this.requestStatus === REQUEST_SUCCESS; @@ -109,7 +119,10 @@ }, methods: { installClicked() { - eventHub.$emit('installApplication', this.id); + eventHub.$emit('installApplication', { + id: this.id, + params: this.installApplicationRequestParams, + }); }, }, }; diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index e03db7b8974..5b127c7911c 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -37,11 +37,6 @@ export default { default: '', }, }, - data() { - return { - jupyterSuggestHostnameValue: '', - }; - }, computed: { generalApplicationDescription() { return sprintf( @@ -132,14 +127,6 @@ export default { jupyterHostname() { return this.applications.jupyter.hostname; }, - jupyterSuggestHostname() { - return `jupyter.${this.applications.ingress.externalIp}.xip.io`; - }, - }, - watch: { - jupyterSuggestHostname() { - this.jupyterSuggestHostnameValue = this.jupyterSuggestHostname; - }, }, }; </script> @@ -305,6 +292,8 @@ export default { :status-reason="applications.jupyter.statusReason" :request-status="applications.jupyter.requestStatus" :request-reason="applications.jupyter.requestReason" + :disable-install-button="!ingressInstalled" + :install-application-request-params="{ hostname: applications.jupyter.hostname }" > <div slot="description"> <p> @@ -314,45 +303,23 @@ export default { notebooks to a class of students, a corporate data science group, or a scientific research group.`) }} </p> - <template v-if="jupyterInstalled"> - <div class="form-group"> - <label for="jupyter-hostname"> - {{ s__('ClusterIntegration|Jupyter Hostname') }} - </label> - <div - v-if="jupyterHostname" - class="input-group" - > - <input - type="text" - id="jupyter-hostname" - class="form-control js-hostname" - :value="jupyterHostname" - readonly - /> - <span class="input-group-btn"> - <clipboard-button - :text="jupyterHostname" - :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" - class="js-clipboard-btn" - /> - </span> - </div> - </div> - </template> - <template v-else-if="ingressInstalled"> + + <template v-if="ingressInstalled"> <div class="form-group"> <label for="jupyter-hostname"> {{ s__('ClusterIntegration|Jupyter Hostname') }} </label> + <div class="input-group"> <input type="text" - id="jupyter-hostname" class="form-control js-hostname" - v-model="jupyterSuggestHostnameValue" + v-model="applications.jupyter.hostname" + :readonly="jupyterInstalled" /> - <span class="input-group-btn"> + <span + class="input-group-btn" + > <clipboard-button :text="jupyterHostname" :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" @@ -361,7 +328,7 @@ export default { </span> </div> </div> - <p> + <p v-if="ingressInstalled"> {{ s__(`ClusterIntegration|Replace this with your own hostname if you want. If you do so, point hostname to Ingress IP Address from above.`) }} <a diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index e49db9c2f4f..a7d82292ba9 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -1,5 +1,4 @@ import axios from '../../lib/utils/axios_utils'; -import { JUPYTER } from '../constants'; export default class ClusterService { constructor(options = {}) { @@ -17,14 +16,8 @@ export default class ClusterService { return axios.get(this.options.endpoint); } - installApplication(appId) { - const data = {}; - - if (appId === JUPYTER) { - data.hostname = document.getElementById('jupyter-hostname').value; - } - - return axios.post(this.appInstallEndpointMap[appId], data); + installApplication(appId, params) { + return axios.post(this.appInstallEndpointMap[appId], params); } static updateCluster(endpoint, data) { diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index f609b425190..c18d692ce81 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -92,7 +92,7 @@ export default class ClusterStore { if (appId === INGRESS) { this.state.applications.ingress.externalIp = serverAppEntry.external_ip; } else if (appId === JUPYTER) { - this.state.applications.jupyter.hostname = serverAppEntry.hostname; + this.state.applications.jupyter.hostname = serverAppEntry.hostname || this.state.applications.ingress.externalIp ? `jupyter.${this.state.applications.ingress.externalIp}.xip.io` : ''; } }); } diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index a5cd247b689..abe2954d506 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -207,11 +207,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - cluster.installApplication('helm'); + cluster.installApplication({ id: 'helm' }); expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('helm'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); getSetTimeoutPromise() .then(() => { @@ -226,11 +226,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); - cluster.installApplication('ingress'); + cluster.installApplication({ id: 'ingress' }); expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); getSetTimeoutPromise() .then(() => { @@ -245,11 +245,11 @@ describe('Clusters', () => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); - cluster.installApplication('runner'); + cluster.installApplication({ id: 'runner' }); expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.runner.requestReason).toEqual(null); - expect(cluster.service.installApplication).toHaveBeenCalledWith('runner'); + expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); getSetTimeoutPromise() .then(() => { @@ -260,11 +260,29 @@ describe('Clusters', () => { .catch(done.fail); }); + it('tries to install jupyter', (done) => { + spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); + cluster.installApplication({ id: 'jupyter', params: { hostname: cluster.store.state.applications.jupyter.hostname } }); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_LOADING); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { hostname: cluster.store.state.applications.jupyter.hostname }); + + getSetTimeoutPromise() + .then(() => { + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUCCESS); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + }) + .then(done) + .catch(done.fail); + }); + it('sets error request status when the request fails', (done) => { spyOn(cluster.service, 'installApplication').and.returnValue(Promise.reject(new Error('STUBBED ERROR'))); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - cluster.installApplication('helm'); + cluster.installApplication({ id: 'helm' }); expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js index 2c4707bb856..fed8f145ef1 100644 --- a/spec/javascripts/clusters/components/application_row_spec.js +++ b/spec/javascripts/clusters/components/application_row_spec.js @@ -174,7 +174,27 @@ describe('Application Row', () => { installButton.click(); - expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', DEFAULT_APPLICATION_STATE.id); + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking install button when installApplicationRequestParams are provided emits event', () => { + spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_INSTALLABLE, + installApplicationRequestParams: { hostname: 'jupyter' }, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: { hostname: 'jupyter' }, + }); }); it('clicking disabled install button emits nothing', () => { @@ -191,6 +211,16 @@ describe('Application Row', () => { expect(eventHub.$emit).not.toHaveBeenCalled(); }); + + it('is disabled when disableInstallButton prop is provided', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_INSTALLING, + disableInstallButton: true, + }); + + expect(vm.installButtonDisabled).toEqual(true); + }); }); describe('Error block', () => { diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index d546543d273..4619322bd4b 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -22,6 +22,7 @@ describe('Applications', () => { ingress: { title: 'Ingress' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub' }, }, }); }); @@ -41,6 +42,10 @@ describe('Applications', () => { it('renders a row for GitLab Runner', () => { expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined(); }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBe(null); + }); }); describe('Ingress application', () => { @@ -57,12 +62,11 @@ describe('Applications', () => { helm: { title: 'Helm Tiller' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); - expect( - vm.$el.querySelector('.js-ip-address').value, - ).toEqual('0.0.0.0'); + expect(vm.$el.querySelector('.js-ip-address').value).toEqual('0.0.0.0'); expect( vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), @@ -81,12 +85,11 @@ describe('Applications', () => { helm: { title: 'Helm Tiller' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); - expect( - vm.$el.querySelector('.js-ip-address').value, - ).toEqual('?'); + expect(vm.$el.querySelector('.js-ip-address').value).toEqual('?'); expect(vm.$el.querySelector('.js-no-ip-message')).not.toBe(null); }); @@ -101,6 +104,7 @@ describe('Applications', () => { ingress: { title: 'Ingress' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, }, }); @@ -108,5 +112,66 @@ describe('Applications', () => { expect(vm.$el.querySelector('.js-ip-address')).toBe(null); }); }); + + describe('Jupyter application', () => { + describe('with ingress installed & jupyter not installed', () => { + it('renders hostname active input', () => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller', status: 'installed' }, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null); + }); + describe('with ingress & jupyter installed', () => { + it('renders readonly input', () => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller', status: 'installed' }, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly'); + }); + }); + }); + + describe('without ingress installed', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: { + helm: { title: 'Helm Tiller' }, + ingress: { title: 'Ingress' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub' }, + }, + }); + }); + + it('does not render input', () => { + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + + it('renders disabled install button', () => { + expect( + vm.$el + .querySelector( + '.js-cluster-application-row-jupyter .js-cluster-application-install-button', + ) + .getAttribute('disabled'), + ).toEqual('disabled'); + }); + }); + }); }); }); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js index 6ae7a792329..b2b0ebf840b 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/javascripts/clusters/services/mock_data.js @@ -1,4 +1,5 @@ import { + APPLICATION_INSTALLED, APPLICATION_INSTALLABLE, APPLICATION_INSTALLING, APPLICATION_ERROR, @@ -28,6 +29,39 @@ const CLUSTERS_MOCK_DATA = { name: 'prometheus', status: APPLICATION_ERROR, status_reason: 'Cannot connect', + }, { + name: 'jupyter', + status: APPLICATION_INSTALLING, + status_reason: 'Cannot connect', + }], + }, + }, + '/gitlab-org/gitlab-shell/clusters/2/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [{ + name: 'helm', + status: APPLICATION_INSTALLED, + status_reason: null, + }, { + name: 'ingress', + status: APPLICATION_INSTALLED, + status_reason: 'Cannot connect', + external_ip: '1.1.1.1', + }, { + name: 'runner', + status: APPLICATION_INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_ERROR, + status_reason: 'Cannot connect', + }, { + name: 'jupyter', + status: APPLICATION_INSTALLABLE, + status_reason: 'Cannot connect', }], }, }, @@ -37,6 +71,7 @@ const CLUSTERS_MOCK_DATA = { '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { }, + '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': { }, }, }; diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index 8028faf2f02..6854b016852 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -91,8 +91,26 @@ describe('Clusters Store', () => { requestStatus: null, requestReason: null, }, + jupyter: { + title: 'JupyterHub', + status: mockResponseData.applications[4].status, + statusReason: mockResponseData.applications[4].status_reason, + requestStatus: null, + requestReason: null, + hostname: '', + }, }, }); }); + + it('sets default hostname for jupyter when ingress has a ip address', () => { + const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; + + store.updateStateFromServer(mockResponseData); + + expect( + store.state.applications.jupyter.hostname, + ).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.xip.io`); + }); }); }); |