Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2019-04-18 17:08:45 +0300
committerFilipa Lacerda <filipa@gitlab.com>2019-04-18 17:08:45 +0300
commitae534a17cc9b79524ba5bb727bbee574f0f1c510 (patch)
tree5bd969a2d81bdbccadc59e96b20a62cc82364c78
parent506afd5f69e49fff7df5d493e0e21aea3fb628f0 (diff)
parentd51a36ece68396ff79f11296774f54360aec9027 (diff)
Merge branch 'fe-uninstall-cluster-apps' into 'master'
Display "Uninstall App" button if app is uninstallable Closes #60641 See merge request gitlab-org/gitlab-ce!27423
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue56
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue7
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_button.vue14
-rw-r--r--app/assets/javascripts/clusters/constants.js15
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js55
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js28
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js23
7 files changed, 140 insertions, 58 deletions
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 19e5ac1567d..937e4c3bfc3 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -6,6 +6,8 @@ import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
+import UninstallApplicationButton from './uninstall_application_button.vue';
+
import {
APPLICATION_STATUS,
REQUEST_SUBMITTED,
@@ -19,6 +21,7 @@ export default {
identicon,
TimeagoTooltip,
GlLink,
+ UninstallApplicationButton,
},
props: {
id: {
@@ -47,6 +50,11 @@ export default {
required: false,
default: false,
},
+ uninstallable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
status: {
type: String,
required: false,
@@ -63,6 +71,11 @@ export default {
type: String,
required: false,
},
+ installed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
version: {
type: String,
required: false,
@@ -92,15 +105,7 @@ export default {
return (
this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING ||
- (this.requestStatus === REQUEST_SUBMITTED && !this.statusReason && !this.isInstalled)
- );
- },
- isInstalled() {
- return (
- this.status === APPLICATION_STATUS.INSTALLED ||
- this.status === APPLICATION_STATUS.UPDATED ||
- this.status === APPLICATION_STATUS.UPDATING ||
- this.status === APPLICATION_STATUS.UPDATE_ERRORED
+ (this.requestStatus === REQUEST_SUBMITTED && !this.statusReason && !this.installed)
);
},
canInstall() {
@@ -125,6 +130,12 @@ export default {
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
+ displayUninstallButton() {
+ return this.installed && this.uninstallable;
+ },
+ displayInstallButton() {
+ return !this.installed || !this.uninstallable;
+ },
installButtonLoading() {
return !this.status || this.status === APPLICATION_STATUS.SCHEDULED || this.isInstalling;
},
@@ -145,7 +156,7 @@ export default {
label = s__('ClusterIntegration|Install');
} else if (this.isInstalling) {
label = s__('ClusterIntegration|Installing');
- } else if (this.isInstalled) {
+ } else if (this.installed) {
label = s__('ClusterIntegration|Installed');
}
@@ -257,7 +268,7 @@ export default {
<div
:class="[
rowJsClass,
- isInstalled && 'cluster-application-installed',
+ installed && 'cluster-application-installed',
disabled && 'cluster-application-disabled',
]"
class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span"
@@ -280,10 +291,9 @@ export default {
target="blank"
rel="noopener noreferrer"
class="js-cluster-application-title"
+ >{{ title }}</a
>
- {{ title }}
- </a>
- <span v-else class="js-cluster-application-title"> {{ title }} </span>
+ <span v-else class="js-cluster-application-title">{{ title }}</span>
</strong>
<slot name="description"></slot>
<div
@@ -308,17 +318,15 @@ export default {
class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
>
{{ versionLabel }}
-
- <span v-if="upgradeSuccessful"> to</span>
+ <span v-if="upgradeSuccessful">to</span>
<gl-link
v-if="upgradeSuccessful"
:href="chartRepo"
target="_blank"
class="js-cluster-application-upgrade-version"
+ >chart v{{ version }}</gl-link
>
- chart v{{ version }}
- </gl-link>
</div>
<div
@@ -333,7 +341,6 @@ export default {
class="bs-callout bs-callout-success cluster-application-banner mt-2 mb-0 p-0 pl-3"
>
{{ upgradeSuccessDescription }}
-
<button class="close cluster-application-banner-close" @click="dismissUpgradeSuccess">
&times;
</button>
@@ -354,18 +361,23 @@ export default {
role="gridcell"
>
<div v-if="showManageButton" class="btn-group table-action-buttons">
- <a :href="manageLink" :class="{ disabled: disabled }" class="btn">
- {{ manageButtonLabel }}
- </a>
+ <a :href="manageLink" :class="{ disabled: disabled }" class="btn">{{
+ manageButtonLabel
+ }}</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
+ v-if="displayInstallButton"
:loading="installButtonLoading"
:disabled="disabled || installButtonDisabled"
:label="installButtonLabel"
class="js-cluster-application-install-button"
@click="installClicked"
/>
+ <uninstall-application-button
+ v-if="displayUninstallButton"
+ class="js-cluster-application-uninstall-button"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index d54f9ce552c..ae4fe11c6ae 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -238,6 +238,7 @@ export default {
:status-reason="applications.helm.statusReason"
:request-status="applications.helm.requestStatus"
:request-reason="applications.helm.requestReason"
+ :installed="applications.helm.installed"
class="rounded-top"
title-link="https://docs.helm.sh/"
>
@@ -265,6 +266,7 @@ export default {
:status-reason="applications.ingress.statusReason"
:request-status="applications.ingress.requestStatus"
:request-reason="applications.ingress.requestReason"
+ :installed="applications.ingress.installed"
:disabled="!helmInstalled"
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
>
@@ -341,6 +343,7 @@ export default {
:status-reason="applications.cert_manager.statusReason"
:request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason"
+ :installed="applications.cert_manager.installed"
:install-application-request-params="{ email: applications.cert_manager.email }"
:disabled="!helmInstalled"
title-link="https://cert-manager.readthedocs.io/en/latest/#"
@@ -387,6 +390,7 @@ export default {
:status-reason="applications.prometheus.statusReason"
:request-status="applications.prometheus.requestStatus"
:request-reason="applications.prometheus.requestReason"
+ :installed="applications.prometheus.installed"
:disabled="!helmInstalled"
title-link="https://prometheus.io/docs/introduction/overview/"
>
@@ -403,6 +407,7 @@ export default {
:version="applications.runner.version"
:chart-repo="applications.runner.chartRepo"
:upgrade-available="applications.runner.upgradeAvailable"
+ :installed="applications.runner.installed"
:disabled="!helmInstalled"
title-link="https://docs.gitlab.com/runner/"
>
@@ -424,6 +429,7 @@ export default {
:status-reason="applications.jupyter.statusReason"
:request-status="applications.jupyter.requestStatus"
:request-reason="applications.jupyter.requestReason"
+ :installed="applications.jupyter.installed"
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled"
title-link="https://jupyterhub.readthedocs.io/en/stable/"
@@ -483,6 +489,7 @@ export default {
:status-reason="applications.knative.statusReason"
:request-status="applications.knative.requestStatus"
:request-reason="applications.knative.requestReason"
+ :installed="applications.knative.installed"
:install-application-request-params="{ hostname: applications.knative.hostname }"
:disabled="!helmInstalled"
title-link="https://github.com/knative/docs"
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_button.vue b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
new file mode 100644
index 00000000000..30918d1d115
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
@@ -0,0 +1,14 @@
+<script>
+// TODO: Implement loading button component
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+
+export default {
+ components: {
+ LoadingButton,
+ },
+};
+</script>
+
+<template>
+ <loading-button @click="$emit('click')" />
+</template>
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 67f481f2afb..17849497c87 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -15,9 +15,24 @@ export const APPLICATION_STATUS = {
UPDATING: 'updating',
UPDATED: 'updated',
UPDATE_ERRORED: 'update_errored',
+ UNINSTALLING: 'uninstalling',
+ UNINSTALL_ERRORED: 'uninstall_errored',
ERROR: 'errored',
};
+/*
+ * The application cannot be in any of the following states without
+ * not being installed.
+ */
+export const APPLICATION_INSTALLED_STATUSES = [
+ APPLICATION_STATUS.INSTALLED,
+ APPLICATION_STATUS.UPDATING,
+ APPLICATION_STATUS.UPDATED,
+ APPLICATION_STATUS.UPDATE_ERRORED,
+ APPLICATION_STATUS.UNINSTALLING,
+ APPLICATION_STATUS.UNINSTALL_ERRORED,
+];
+
// These are only used client-side
export const REQUEST_SUBMITTED = 'request-submitted';
export const REQUEST_FAILURE = 'request-failure';
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 92993337f02..38512ac28c2 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,6 +1,23 @@
import { s__ } from '../../locale';
import { parseBoolean } from '../../lib/utils/common_utils';
-import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER, RUNNER } from '../constants';
+import {
+ INGRESS,
+ JUPYTER,
+ KNATIVE,
+ CERT_MANAGER,
+ RUNNER,
+ APPLICATION_INSTALLED_STATUSES,
+} from '../constants';
+
+const isApplicationInstalled = appStatus => APPLICATION_INSTALLED_STATUSES.includes(appStatus);
+
+const applicationInitialState = {
+ status: null,
+ statusReason: null,
+ requestReason: null,
+ requestStatus: null,
+ installed: false,
+};
export default class ClusterStore {
constructor() {
@@ -12,60 +29,39 @@ export default class ClusterStore {
statusReason: null,
applications: {
helm: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Helm Tiller'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
},
ingress: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Ingress'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
externalIp: null,
externalHostname: null,
},
cert_manager: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Cert-Manager'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
email: null,
},
runner: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
upgradeAvailable: null,
},
prometheus: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Prometheus'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
},
jupyter: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|JupyterHub'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
hostname: null,
},
knative: {
+ ...applicationInitialState,
title: s__('ClusterIntegration|Knative'),
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
hostname: null,
isEditingHostName: false,
externalIp: null,
@@ -118,6 +114,7 @@ export default class ClusterStore {
...(this.state.applications[appId] || {}),
status,
statusReason,
+ installed: isApplicationInstalled(status),
};
if (appId === INGRESS) {
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index b28d0075d06..038d2be9e98 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -114,10 +114,12 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
+ it('has disabled "Installed" when application is installed and not uninstallable', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.INSTALLED,
+ installed: true,
+ uninstallable: false,
});
expect(vm.installButtonLabel).toEqual('Installed');
@@ -125,15 +127,16 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => {
+ it('hides when application is installed and uninstallable', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
+ status: APPLICATION_STATUS.INSTALLED,
+ installed: true,
+ uninstallable: true,
});
+ const installBtn = vm.$el.querySelector('.js-cluster-application-install-button');
- expect(vm.installButtonLabel).toEqual('Installed');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
+ expect(installBtn).toBe(null);
});
it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
@@ -208,6 +211,19 @@ describe('Application Row', () => {
});
});
+ describe('Uninstall button', () => {
+ it('displays button when app is installed and uninstallable', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ installed: true,
+ uninstallable: true,
+ });
+ const uninstallButton = vm.$el.querySelector('.js-cluster-application-uninstall-button');
+
+ expect(uninstallButton).toBeTruthy();
+ });
+ });
+
describe('Upgrade button', () => {
it('has indeterminate state on page load', () => {
vm = mountComponent(ApplicationRow, {
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index 161722ec571..c0e8b737ea2 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -1,5 +1,5 @@
import ClustersStore from '~/clusters/stores/clusters_store';
-import { APPLICATION_STATUS } from '~/clusters/constants';
+import { APPLICATION_INSTALLED_STATUSES, APPLICATION_STATUS, RUNNER } from '~/clusters/constants';
import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
describe('Clusters Store', () => {
@@ -70,6 +70,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[0].status_reason,
requestStatus: null,
requestReason: null,
+ installed: false,
},
ingress: {
title: 'Ingress',
@@ -79,6 +80,7 @@ describe('Clusters Store', () => {
requestReason: null,
externalIp: null,
externalHostname: null,
+ installed: false,
},
runner: {
title: 'GitLab Runner',
@@ -89,6 +91,7 @@ describe('Clusters Store', () => {
version: mockResponseData.applications[2].version,
upgradeAvailable: mockResponseData.applications[2].update_available,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ installed: false,
},
prometheus: {
title: 'Prometheus',
@@ -96,6 +99,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[3].status_reason,
requestStatus: null,
requestReason: null,
+ installed: false,
},
jupyter: {
title: 'JupyterHub',
@@ -104,6 +108,7 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
hostname: '',
+ installed: false,
},
knative: {
title: 'Knative',
@@ -115,6 +120,7 @@ describe('Clusters Store', () => {
isEditingHostName: false,
externalIp: null,
externalHostname: null,
+ installed: false,
},
cert_manager: {
title: 'Cert-Manager',
@@ -123,11 +129,26 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
email: mockResponseData.applications[6].email,
+ installed: false,
},
},
});
});
+ describe.each(APPLICATION_INSTALLED_STATUSES)('given the current app status is %s', () => {
+ it('marks application as installed', () => {
+ const mockResponseData =
+ CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
+ const runnerAppIndex = 2;
+
+ mockResponseData.applications[runnerAppIndex].status = APPLICATION_STATUS.INSTALLED;
+
+ store.updateStateFromServer(mockResponseData);
+
+ expect(store.state.applications[RUNNER].installed).toBe(true);
+ });
+ });
+
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;