Disables jupyter install button while ingress is not installed
Includes juptyer hostname in the post request Adds tests
This commit is contained in:
parent
b3cf153082
commit
e1d4deb2d2
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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` : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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': { },
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue