Uninstall application confirm modal component
- Vue confirmation modal implementation - CSS tweaks for modal default height
This commit is contained in:
parent
d6aa8a0553
commit
bf229a6c63
19 changed files with 595 additions and 147 deletions
|
@ -132,6 +132,7 @@ export default class Clusters {
|
|||
eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
|
||||
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
|
||||
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
|
||||
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
|
@ -141,6 +142,7 @@ export default class Clusters {
|
|||
eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
|
||||
eventHub.$off('saveKnativeDomain');
|
||||
eventHub.$off('setKnativeHostname');
|
||||
eventHub.$off('uninstallApplication');
|
||||
}
|
||||
|
||||
initPolling() {
|
||||
|
@ -249,14 +251,13 @@ export default class Clusters {
|
|||
}
|
||||
}
|
||||
|
||||
installApplication(data) {
|
||||
const appId = data.id;
|
||||
installApplication({ id: appId, params }) {
|
||||
this.store.updateAppProperty(appId, 'requestReason', null);
|
||||
this.store.updateAppProperty(appId, 'statusReason', null);
|
||||
|
||||
this.store.installApplication(appId);
|
||||
|
||||
return this.service.installApplication(appId, data.params).catch(() => {
|
||||
return this.service.installApplication(appId, params).catch(() => {
|
||||
this.store.notifyInstallFailure(appId);
|
||||
this.store.updateAppProperty(
|
||||
appId,
|
||||
|
@ -266,6 +267,22 @@ export default class Clusters {
|
|||
});
|
||||
}
|
||||
|
||||
uninstallApplication({ id: appId }) {
|
||||
this.store.updateAppProperty(appId, 'requestReason', null);
|
||||
this.store.updateAppProperty(appId, 'statusReason', null);
|
||||
|
||||
this.store.uninstallApplication(appId);
|
||||
|
||||
return this.service.uninstallApplication(appId).catch(() => {
|
||||
this.store.notifyUninstallFailure(appId);
|
||||
this.store.updateAppProperty(
|
||||
appId,
|
||||
'requestReason',
|
||||
s__('ClusterIntegration|Request to begin uninstalling failed'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
upgradeApplication(data) {
|
||||
const appId = data.id;
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script>
|
||||
/* eslint-disable vue/require-default-prop */
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { GlLink, GlModalDirective } from '@gitlab/ui';
|
||||
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
||||
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 UninstallApplicationConfirmationModal from './uninstall_application_confirmation_modal.vue';
|
||||
|
||||
import { APPLICATION_STATUS } from '../constants';
|
||||
|
||||
|
@ -17,6 +18,10 @@ export default {
|
|||
TimeagoTooltip,
|
||||
GlLink,
|
||||
UninstallApplicationButton,
|
||||
UninstallApplicationConfirmationModal,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
|
@ -94,6 +99,16 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
uninstallFailed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
uninstallSuccessful: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
updateAcknowledged: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -170,10 +185,21 @@ export default {
|
|||
manageButtonLabel() {
|
||||
return s__('ClusterIntegration|Manage');
|
||||
},
|
||||
hasError() {
|
||||
return this.installFailed || this.uninstallFailed;
|
||||
},
|
||||
generalErrorDescription() {
|
||||
return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), {
|
||||
title: this.title,
|
||||
});
|
||||
let errorDescription;
|
||||
|
||||
if (this.installFailed) {
|
||||
errorDescription = s__('ClusterIntegration|Something went wrong while installing %{title}');
|
||||
} else if (this.uninstallFailed) {
|
||||
errorDescription = s__(
|
||||
'ClusterIntegration|Something went wrong while uninstalling %{title}',
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(errorDescription, { title: this.title });
|
||||
},
|
||||
versionLabel() {
|
||||
if (this.updateFailed) {
|
||||
|
@ -214,13 +240,23 @@ export default {
|
|||
// AND new upgrade is unavailable AND version information is present.
|
||||
return (this.updateSuccessful || this.updateFailed) && !this.upgradeAvailable && this.version;
|
||||
},
|
||||
uninstallSuccessDescription() {
|
||||
return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
|
||||
title: this.title,
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
updateSuccessful() {
|
||||
if (this.updateSuccessful) {
|
||||
updateSuccessful(updateSuccessful) {
|
||||
if (updateSuccessful) {
|
||||
this.$toast.show(this.upgradeSuccessDescription);
|
||||
}
|
||||
},
|
||||
uninstallSuccessful(uninstallSuccessful) {
|
||||
if (uninstallSuccessful) {
|
||||
this.$toast.show(this.uninstallSuccessDescription);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
installClicked() {
|
||||
|
@ -235,6 +271,11 @@ export default {
|
|||
params: this.installApplicationRequestParams,
|
||||
});
|
||||
},
|
||||
uninstallConfirmed() {
|
||||
eventHub.$emit('uninstallApplication', {
|
||||
id: this.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -271,10 +312,7 @@ export default {
|
|||
<span v-else class="js-cluster-application-title">{{ title }}</span>
|
||||
</strong>
|
||||
<slot name="description"></slot>
|
||||
<div
|
||||
v-if="installFailed || isUnknownStatus"
|
||||
class="cluster-application-error text-danger prepend-top-10"
|
||||
>
|
||||
<div v-if="hasError" class="cluster-application-error text-danger prepend-top-10">
|
||||
<p class="js-cluster-application-general-error-message append-bottom-0">
|
||||
{{ generalErrorDescription }}
|
||||
</p>
|
||||
|
@ -325,9 +363,9 @@ 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
|
||||
|
@ -340,8 +378,15 @@ export default {
|
|||
/>
|
||||
<uninstall-application-button
|
||||
v-if="displayUninstallButton"
|
||||
v-gl-modal-directive="'uninstall-' + id"
|
||||
:status="status"
|
||||
class="js-cluster-application-uninstall-button"
|
||||
/>
|
||||
<uninstall-application-confirmation-modal
|
||||
:application="id"
|
||||
:application-title="title"
|
||||
@confirm="uninstallConfirmed()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -240,6 +240,9 @@ export default {
|
|||
:request-reason="applications.helm.requestReason"
|
||||
:installed="applications.helm.installed"
|
||||
:install-failed="applications.helm.installFailed"
|
||||
:uninstallable="applications.helm.uninstallable"
|
||||
:uninstall-successful="applications.helm.uninstallSuccessful"
|
||||
:uninstall-failed="applications.helm.uninstallFailed"
|
||||
class="rounded-top"
|
||||
title-link="https://docs.helm.sh/"
|
||||
>
|
||||
|
@ -269,6 +272,9 @@ export default {
|
|||
:request-reason="applications.ingress.requestReason"
|
||||
:installed="applications.ingress.installed"
|
||||
:install-failed="applications.ingress.installFailed"
|
||||
:uninstallable="applications.ingress.uninstallable"
|
||||
:uninstall-successful="applications.ingress.uninstallSuccessful"
|
||||
:uninstall-failed="applications.ingress.uninstallFailed"
|
||||
:disabled="!helmInstalled"
|
||||
title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
|
||||
>
|
||||
|
@ -312,9 +318,9 @@ export default {
|
|||
generated endpoint in order to access
|
||||
your application after it has been deployed.`)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -324,9 +330,9 @@ export default {
|
|||
the process of being assigned. Please check your Kubernetes
|
||||
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="!ingressInstalled">
|
||||
|
@ -345,6 +351,9 @@ export default {
|
|||
:installed="applications.cert_manager.installed"
|
||||
:install-failed="applications.cert_manager.installFailed"
|
||||
:install-application-request-params="{ email: applications.cert_manager.email }"
|
||||
:uninstallable="applications.cert_manager.uninstallable"
|
||||
:uninstall-successful="applications.cert_manager.uninstallSuccessful"
|
||||
:uninstall-failed="applications.cert_manager.uninstallFailed"
|
||||
:disabled="!helmInstalled"
|
||||
title-link="https://cert-manager.readthedocs.io/en/latest/#"
|
||||
>
|
||||
|
@ -352,9 +361,9 @@ export default {
|
|||
<div slot="description">
|
||||
<p v-html="certManagerDescription"></p>
|
||||
<div class="form-group">
|
||||
<label for="cert-manager-issuer-email">
|
||||
{{ s__('ClusterIntegration|Issuer Email') }}
|
||||
</label>
|
||||
<label for="cert-manager-issuer-email">{{
|
||||
s__('ClusterIntegration|Issuer Email')
|
||||
}}</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="applications.cert_manager.email"
|
||||
|
@ -391,6 +400,9 @@ export default {
|
|||
:request-reason="applications.prometheus.requestReason"
|
||||
:installed="applications.prometheus.installed"
|
||||
:install-failed="applications.prometheus.installFailed"
|
||||
:uninstallable="applications.prometheus.uninstallable"
|
||||
:uninstall-successful="applications.prometheus.uninstallSuccessful"
|
||||
:uninstall-failed="applications.prometheus.uninstallFailed"
|
||||
:disabled="!helmInstalled"
|
||||
title-link="https://prometheus.io/docs/introduction/overview/"
|
||||
>
|
||||
|
@ -411,6 +423,9 @@ export default {
|
|||
:install-failed="applications.runner.installFailed"
|
||||
:update-successful="applications.runner.updateSuccessful"
|
||||
:update-failed="applications.runner.updateFailed"
|
||||
:uninstallable="applications.runner.uninstallable"
|
||||
:uninstall-successful="applications.runner.uninstallSuccessful"
|
||||
:uninstall-failed="applications.runner.uninstallFailed"
|
||||
:disabled="!helmInstalled"
|
||||
title-link="https://docs.gitlab.com/runner/"
|
||||
>
|
||||
|
@ -434,6 +449,9 @@ export default {
|
|||
:request-reason="applications.jupyter.requestReason"
|
||||
:installed="applications.jupyter.installed"
|
||||
:install-failed="applications.jupyter.installFailed"
|
||||
:uninstallable="applications.jupyter.uninstallable"
|
||||
:uninstall-successful="applications.jupyter.uninstallSuccessful"
|
||||
:uninstall-failed="applications.jupyter.uninstallFailed"
|
||||
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
|
||||
:disabled="!helmInstalled"
|
||||
title-link="https://jupyterhub.readthedocs.io/en/stable/"
|
||||
|
@ -474,9 +492,9 @@ export default {
|
|||
s__(`ClusterIntegration|Replace this with your own hostname if you want.
|
||||
If you do so, point hostname to Ingress IP Address from above.`)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -494,6 +512,9 @@ export default {
|
|||
:installed="applications.knative.installed"
|
||||
:install-failed="applications.knative.installFailed"
|
||||
:install-application-request-params="{ hostname: applications.knative.hostname }"
|
||||
:uninstallable="applications.knative.uninstallable"
|
||||
:uninstall-successful="applications.knative.uninstallSuccessful"
|
||||
:uninstall-failed="applications.knative.uninstallFailed"
|
||||
:disabled="!helmInstalled"
|
||||
v-bind="applications.knative"
|
||||
title-link="https://github.com/knative/docs"
|
||||
|
@ -505,9 +526,9 @@ export default {
|
|||
s__(`ClusterIntegration|You must have an RBAC-enabled cluster
|
||||
to install Knative.`)
|
||||
}}
|
||||
<a :href="helpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="helpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
<br />
|
||||
</span>
|
||||
|
@ -572,9 +593,9 @@ export default {
|
|||
`ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
|
||||
)
|
||||
}}
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
|
||||
{{ __('More information') }}
|
||||
</a>
|
||||
<a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
|
||||
__('More information')
|
||||
}}</a>
|
||||
</p>
|
||||
|
||||
<p
|
||||
|
|
|
@ -1,14 +1,33 @@
|
|||
<script>
|
||||
// TODO: Implement loading button component
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import { APPLICATION_STATUS } from '~/clusters/constants';
|
||||
|
||||
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingButton,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
return [UNINSTALLING, UPDATING].includes(this.status);
|
||||
},
|
||||
loading() {
|
||||
return this.status === UNINSTALLING;
|
||||
},
|
||||
label() {
|
||||
return this.loading ? this.__('Uninstalling') : this.__('Uninstall');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<loading-button @click="$emit('click')" />
|
||||
<loading-button :label="label" :disabled="disabled" :loading="loading" />
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import { INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
|
||||
|
||||
const CUSTOM_APP_WARNING_TEXT = {
|
||||
[INGRESS]: s__(
|
||||
'ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored.',
|
||||
),
|
||||
[CERT_MANAGER]: s__(
|
||||
'ClusterIntegration|The associated certifcate will be deleted and cannot be restored.',
|
||||
),
|
||||
[PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
|
||||
[RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
|
||||
[KNATIVE]: s__('ClusterIntegration|The associated IP will be deleted and cannot be restored.'),
|
||||
[JUPYTER]: '',
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
application: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
applicationTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return sprintf(s__('ClusterIntegration|Uninstall %{appTitle}'), {
|
||||
appTitle: this.applicationTitle,
|
||||
});
|
||||
},
|
||||
warningText() {
|
||||
return sprintf(
|
||||
s__('ClusterIntegration|You are about to uninstall %{appTitle} from your cluster.'),
|
||||
{
|
||||
appTitle: this.applicationTitle,
|
||||
},
|
||||
);
|
||||
},
|
||||
customAppWarningText() {
|
||||
return CUSTOM_APP_WARNING_TEXT[this.application];
|
||||
},
|
||||
modalId() {
|
||||
return `uninstall-${this.application}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-modal
|
||||
ok-variant="danger"
|
||||
cancel-variant="light"
|
||||
:ok-title="title"
|
||||
:modal-id="modalId"
|
||||
:title="title"
|
||||
@ok="$emit('confirm')"
|
||||
>{{ warningText }} {{ customAppWarningText }}</gl-modal
|
||||
>
|
||||
</template>
|
|
@ -28,16 +28,23 @@ export const APPLICATION_STATUS = {
|
|||
export const APPLICATION_INSTALLED_STATUSES = [
|
||||
APPLICATION_STATUS.INSTALLED,
|
||||
APPLICATION_STATUS.UPDATING,
|
||||
APPLICATION_STATUS.UNINSTALLING,
|
||||
];
|
||||
|
||||
// These are only used client-side
|
||||
|
||||
export const UPDATE_EVENT = 'update';
|
||||
export const INSTALL_EVENT = 'install';
|
||||
export const UNINSTALL_EVENT = 'uninstall';
|
||||
|
||||
export const HELM = 'helm';
|
||||
export const INGRESS = 'ingress';
|
||||
export const JUPYTER = 'jupyter';
|
||||
export const KNATIVE = 'knative';
|
||||
export const RUNNER = 'runner';
|
||||
export const CERT_MANAGER = 'cert_manager';
|
||||
export const PROMETHEUS = 'prometheus';
|
||||
|
||||
export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS];
|
||||
|
||||
export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT } from '../constants';
|
||||
import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT, UNINSTALL_EVENT } from '../constants';
|
||||
|
||||
const {
|
||||
NO_STATUS,
|
||||
|
@ -11,6 +11,8 @@ const {
|
|||
UPDATING,
|
||||
UPDATED,
|
||||
UPDATE_ERRORED,
|
||||
UNINSTALLING,
|
||||
UNINSTALL_ERRORED,
|
||||
} = APPLICATION_STATUS;
|
||||
|
||||
const applicationStateMachine = {
|
||||
|
@ -52,6 +54,15 @@ const applicationStateMachine = {
|
|||
updateFailed: true,
|
||||
},
|
||||
},
|
||||
[UNINSTALLING]: {
|
||||
target: UNINSTALLING,
|
||||
},
|
||||
[UNINSTALL_ERRORED]: {
|
||||
target: INSTALLED,
|
||||
effects: {
|
||||
uninstallFailed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[NOT_INSTALLABLE]: {
|
||||
|
@ -97,6 +108,13 @@ const applicationStateMachine = {
|
|||
updateSuccessful: false,
|
||||
},
|
||||
},
|
||||
[UNINSTALL_EVENT]: {
|
||||
target: UNINSTALLING,
|
||||
effects: {
|
||||
uninstallFailed: false,
|
||||
uninstallSuccessful: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[UPDATING]: {
|
||||
|
@ -116,6 +134,22 @@ const applicationStateMachine = {
|
|||
},
|
||||
},
|
||||
},
|
||||
[UNINSTALLING]: {
|
||||
on: {
|
||||
[INSTALLABLE]: {
|
||||
target: INSTALLABLE,
|
||||
effects: {
|
||||
uninstallSuccessful: true,
|
||||
},
|
||||
},
|
||||
[UNINSTALL_ERRORED]: {
|
||||
target: INSTALLED,
|
||||
effects: {
|
||||
uninstallFailed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,6 +29,10 @@ export default class ClusterService {
|
|||
return axios.patch(this.appUpdateEndpointMap[appId], params);
|
||||
}
|
||||
|
||||
uninstallApplication(appId, params) {
|
||||
return axios.delete(this.appInstallEndpointMap[appId], params);
|
||||
}
|
||||
|
||||
static updateCluster(endpoint, data) {
|
||||
return axios.put(endpoint, data);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
APPLICATION_STATUS,
|
||||
INSTALL_EVENT,
|
||||
UPDATE_EVENT,
|
||||
UNINSTALL_EVENT,
|
||||
} from '../constants';
|
||||
import transitionApplicationState from '../services/application_state_machine';
|
||||
|
||||
|
@ -21,6 +22,9 @@ const applicationInitialState = {
|
|||
requestReason: null,
|
||||
installed: false,
|
||||
installFailed: false,
|
||||
uninstallable: false,
|
||||
uninstallFailed: false,
|
||||
uninstallSuccessful: false,
|
||||
};
|
||||
|
||||
export default class ClusterStore {
|
||||
|
@ -116,6 +120,14 @@ export default class ClusterStore {
|
|||
this.handleApplicationEvent(appId, APPLICATION_STATUS.UPDATE_ERRORED);
|
||||
}
|
||||
|
||||
uninstallApplication(appId) {
|
||||
this.handleApplicationEvent(appId, UNINSTALL_EVENT);
|
||||
}
|
||||
|
||||
notifyUninstallFailure(appId) {
|
||||
this.handleApplicationEvent(appId, APPLICATION_STATUS.UNINSTALL_ERRORED);
|
||||
}
|
||||
|
||||
handleApplicationEvent(appId, event) {
|
||||
const currentAppState = this.state.applications[appId];
|
||||
|
||||
|
@ -141,6 +153,7 @@ export default class ClusterStore {
|
|||
status_reason: statusReason,
|
||||
version,
|
||||
update_available: upgradeAvailable,
|
||||
can_uninstall: uninstallable,
|
||||
} = serverAppEntry;
|
||||
const currentApplicationState = this.state.applications[appId] || {};
|
||||
const nextApplicationState = transitionApplicationState(currentApplicationState, status);
|
||||
|
@ -150,8 +163,7 @@ export default class ClusterStore {
|
|||
...nextApplicationState,
|
||||
statusReason,
|
||||
installed: isApplicationInstalled(nextApplicationState.status),
|
||||
// Make sure uninstallable is always false until this feature is unflagged
|
||||
uninstallable: false,
|
||||
uninstallable,
|
||||
};
|
||||
|
||||
if (appId === INGRESS) {
|
||||
|
|
|
@ -34,10 +34,10 @@
|
|||
.modal-body {
|
||||
background-color: $modal-body-bg;
|
||||
line-height: $line-height-base;
|
||||
min-height: $modal-body-height;
|
||||
position: relative;
|
||||
padding: #{3 * $grid-size} #{2 * $grid-size};
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
|
||||
.form-actions {
|
||||
margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
|
||||
|
|
5
changelogs/unreleased/60777-uninstall-button.yml
Normal file
5
changelogs/unreleased/60777-uninstall-button.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Implement UI for uninstalling Cluster’s managed apps
|
||||
merge_request: 27559
|
||||
author:
|
||||
type: added
|
|
@ -1984,6 +1984,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|%{title} uninstalled successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|%{title} upgraded successfully."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2011,6 +2014,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|All data will be deleted and cannot be restored."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Alternatively"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2026,6 +2032,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Any running pipelines will be canceled."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Applications"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2317,6 +2326,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Request to begin installing failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Request to begin uninstalling failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Retry update"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2371,6 +2383,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Something went wrong while installing %{title}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2380,6 +2395,15 @@ msgstr ""
|
|||
msgid "ClusterIntegration|The URL used to access the Kubernetes API."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The associated IP will be deleted and cannot be restored."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The associated certifcate will be deleted and cannot be restored."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2395,6 +2419,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Toggle Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Uninstall %{appTitle}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2422,6 +2449,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|You are about to uninstall %{appTitle} from your cluster."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Clusters from '~/clusters/clusters_bundle';
|
||||
import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX } from '~/clusters/constants';
|
||||
import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { loadHTMLFixture } from 'helpers/fixtures';
|
||||
import { setTestTimeout } from 'helpers/timeout';
|
||||
import $ from 'jquery';
|
||||
|
||||
const { INSTALLING, INSTALLABLE, INSTALLED } = APPLICATION_STATUS;
|
||||
const { INSTALLING, INSTALLABLE, INSTALLED, UNINSTALLING } = APPLICATION_STATUS;
|
||||
|
||||
describe('Clusters', () => {
|
||||
setTestTimeout(1000);
|
||||
|
@ -212,55 +212,16 @@ describe('Clusters', () => {
|
|||
});
|
||||
|
||||
describe('installApplication', () => {
|
||||
it('tries to install helm', () => {
|
||||
it.each(APPLICATIONS)('tries to install %s', applicationId => {
|
||||
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
|
||||
|
||||
cluster.store.state.applications.helm.status = INSTALLABLE;
|
||||
cluster.store.state.applications[applicationId].status = INSTALLABLE;
|
||||
|
||||
cluster.installApplication({ id: 'helm' });
|
||||
cluster.installApplication({ id: applicationId });
|
||||
|
||||
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING);
|
||||
expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined);
|
||||
});
|
||||
|
||||
it('tries to install ingress', () => {
|
||||
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
|
||||
|
||||
cluster.store.state.applications.ingress.status = INSTALLABLE;
|
||||
|
||||
cluster.installApplication({ id: 'ingress' });
|
||||
|
||||
expect(cluster.store.state.applications.ingress.status).toEqual(INSTALLING);
|
||||
expect(cluster.store.state.applications.ingress.requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined);
|
||||
});
|
||||
|
||||
it('tries to install runner', () => {
|
||||
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
|
||||
|
||||
cluster.store.state.applications.runner.status = INSTALLABLE;
|
||||
|
||||
cluster.installApplication({ id: 'runner' });
|
||||
|
||||
expect(cluster.store.state.applications.runner.status).toEqual(INSTALLING);
|
||||
expect(cluster.store.state.applications.runner.requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined);
|
||||
});
|
||||
|
||||
it('tries to install jupyter', () => {
|
||||
jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce();
|
||||
|
||||
cluster.installApplication({
|
||||
id: 'jupyter',
|
||||
params: { hostname: cluster.store.state.applications.jupyter.hostname },
|
||||
});
|
||||
|
||||
cluster.store.state.applications.jupyter.status = INSTALLABLE;
|
||||
expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', {
|
||||
hostname: cluster.store.state.applications.jupyter.hostname,
|
||||
});
|
||||
expect(cluster.store.state.applications[applicationId].status).toEqual(INSTALLING);
|
||||
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalledWith(applicationId, undefined);
|
||||
});
|
||||
|
||||
it('sets error request status when the request fails', () => {
|
||||
|
@ -272,10 +233,6 @@ describe('Clusters', () => {
|
|||
|
||||
const promise = cluster.installApplication({ id: 'helm' });
|
||||
|
||||
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLING);
|
||||
expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
|
||||
expect(cluster.service.installApplication).toHaveBeenCalled();
|
||||
|
||||
return promise.then(() => {
|
||||
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLABLE);
|
||||
expect(cluster.store.state.applications.helm.installFailed).toBe(true);
|
||||
|
@ -285,6 +242,37 @@ describe('Clusters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('uninstallApplication', () => {
|
||||
it.each(APPLICATIONS)('tries to uninstall %s', applicationId => {
|
||||
jest.spyOn(cluster.service, 'uninstallApplication').mockResolvedValueOnce();
|
||||
|
||||
cluster.store.state.applications[applicationId].status = INSTALLED;
|
||||
|
||||
cluster.uninstallApplication({ id: applicationId });
|
||||
|
||||
expect(cluster.store.state.applications[applicationId].status).toEqual(UNINSTALLING);
|
||||
expect(cluster.store.state.applications[applicationId].requestReason).toEqual(null);
|
||||
expect(cluster.service.uninstallApplication).toHaveBeenCalledWith(applicationId);
|
||||
});
|
||||
|
||||
it('sets error request status when the uninstall request fails', () => {
|
||||
jest
|
||||
.spyOn(cluster.service, 'uninstallApplication')
|
||||
.mockRejectedValueOnce(new Error('STUBBED ERROR'));
|
||||
|
||||
cluster.store.state.applications.helm.status = INSTALLED;
|
||||
|
||||
const promise = cluster.uninstallApplication({ id: 'helm' });
|
||||
|
||||
return promise.then(() => {
|
||||
expect(cluster.store.state.applications.helm.status).toEqual(INSTALLED);
|
||||
expect(cluster.store.state.applications.helm.uninstallFailed).toBe(true);
|
||||
|
||||
expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSuccess', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis();
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import eventHub from '~/clusters/event_hub';
|
||||
import { APPLICATION_STATUS } from '~/clusters/constants';
|
||||
import applicationRow from '~/clusters/components/application_row.vue';
|
||||
import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
|
||||
|
||||
|
@ -194,11 +197,52 @@ describe('Application Row', () => {
|
|||
...DEFAULT_APPLICATION_STATE,
|
||||
installed: true,
|
||||
uninstallable: true,
|
||||
status: APPLICATION_STATUS.NOT_INSTALLABLE,
|
||||
});
|
||||
const uninstallButton = vm.$el.querySelector('.js-cluster-application-uninstall-button');
|
||||
|
||||
expect(uninstallButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays a success toast message if application uninstall was successful', () => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
title: 'GitLab Runner',
|
||||
uninstallSuccessful: false,
|
||||
});
|
||||
|
||||
vm.$toast = { show: jest.fn() };
|
||||
vm.uninstallSuccessful = true;
|
||||
|
||||
return vm.$nextTick(() => {
|
||||
expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner uninstalled successfully.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when confirmation modal triggers confirm event', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(ApplicationRow, {
|
||||
propsData: {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('triggers uninstallApplication event', () => {
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
wrapper.find(UninstallApplicationConfirmationModal).vm.$emit('confirm');
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('uninstallApplication', {
|
||||
id: DEFAULT_APPLICATION_STATE.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Upgrade button', () => {
|
||||
|
@ -304,7 +348,7 @@ describe('Application Row', () => {
|
|||
vm.$toast = { show: jest.fn() };
|
||||
vm.updateSuccessful = true;
|
||||
|
||||
vm.$nextTick(() => {
|
||||
return vm.$nextTick(() => {
|
||||
expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.');
|
||||
});
|
||||
});
|
||||
|
@ -360,60 +404,88 @@ describe('Application Row', () => {
|
|||
});
|
||||
|
||||
describe('Error block', () => {
|
||||
it('does not show error block when there is no error', () => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: null,
|
||||
});
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
describe('when nothing fails', () => {
|
||||
it('does not show error block', () => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
});
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
|
||||
expect(generalErrorMessage).toBeNull();
|
||||
expect(generalErrorMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows status reason when install fails', () => {
|
||||
describe('when install or uninstall fails', () => {
|
||||
const statusReason = 'We broke it 0.0';
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
statusReason,
|
||||
installFailed: true,
|
||||
const requestReason = 'We broke the request 0.0';
|
||||
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
statusReason,
|
||||
requestReason,
|
||||
installFailed: true,
|
||||
});
|
||||
});
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
const statusErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-status-error-message',
|
||||
);
|
||||
|
||||
expect(generalErrorMessage.textContent.trim()).toEqual(
|
||||
`Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
|
||||
);
|
||||
it('shows status reason if it is available', () => {
|
||||
const statusErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-status-error-message',
|
||||
);
|
||||
|
||||
expect(statusErrorMessage.textContent.trim()).toEqual(statusReason);
|
||||
expect(statusErrorMessage.textContent.trim()).toEqual(statusReason);
|
||||
});
|
||||
|
||||
it('shows request reason if it is available', () => {
|
||||
const requestErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-request-error-message',
|
||||
);
|
||||
|
||||
expect(requestErrorMessage.textContent.trim()).toEqual(requestReason);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows request reason when REQUEST_FAILURE', () => {
|
||||
const requestReason = 'We broke thre request 0.0';
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.INSTALLABLE,
|
||||
installFailed: true,
|
||||
requestReason,
|
||||
describe('when install fails', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
installFailed: true,
|
||||
});
|
||||
});
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
const requestErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-request-error-message',
|
||||
);
|
||||
|
||||
expect(generalErrorMessage.textContent.trim()).toEqual(
|
||||
`Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
|
||||
);
|
||||
it('shows a general message indicating the installation failed', () => {
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
|
||||
expect(requestErrorMessage.textContent.trim()).toEqual(requestReason);
|
||||
expect(generalErrorMessage.textContent.trim()).toEqual(
|
||||
`Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when uninstall fails', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(ApplicationRow, {
|
||||
...DEFAULT_APPLICATION_STATE,
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
uninstallFailed: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a general message indicating the uninstalling failed', () => {
|
||||
const generalErrorMessage = vm.$el.querySelector(
|
||||
'.js-cluster-application-general-error-message',
|
||||
);
|
||||
|
||||
expect(generalErrorMessage.textContent.trim()).toEqual(
|
||||
`Something went wrong while uninstalling ${DEFAULT_APPLICATION_STATE.title}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import UninstallApplicationButton from '~/clusters/components/uninstall_application_button.vue';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import { APPLICATION_STATUS } from '~/clusters/constants';
|
||||
|
||||
const { INSTALLED, UPDATING, UNINSTALLING } = APPLICATION_STATUS;
|
||||
|
||||
describe('UninstallApplicationButton', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(UninstallApplicationButton, {
|
||||
propsData: { ...props },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe.each`
|
||||
status | loading | disabled | label
|
||||
${INSTALLED} | ${false} | ${false} | ${'Uninstall'}
|
||||
${UPDATING} | ${false} | ${true} | ${'Uninstall'}
|
||||
${UNINSTALLING} | ${true} | ${true} | ${'Uninstalling'}
|
||||
`('when app status is $status', ({ loading, disabled, status, label }) => {
|
||||
it(`renders a loading=${loading}, disabled=${disabled} button with label="${label}"`, () => {
|
||||
createComponent({ status });
|
||||
expect(wrapper.find(LoadingButton).props()).toMatchObject({ loading, disabled, label });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import UninstallApplicationConfirmationModal from '~/clusters/components/uninstall_application_confirmation_modal.vue';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { INGRESS } from '~/clusters/constants';
|
||||
|
||||
describe('UninstallApplicationConfirmationModal', () => {
|
||||
let wrapper;
|
||||
const appTitle = 'Ingress';
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(UninstallApplicationConfirmationModal, {
|
||||
propsData: { ...props },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ application: INGRESS, applicationTitle: appTitle });
|
||||
});
|
||||
|
||||
it(`renders a modal with a title "Uninstall ${appTitle}"`, () => {
|
||||
expect(wrapper.find(GlModal).attributes('title')).toEqual(`Uninstall ${appTitle}`);
|
||||
});
|
||||
|
||||
it(`renders a modal with an ok button labeled "Uninstall ${appTitle}"`, () => {
|
||||
expect(wrapper.find(GlModal).attributes('ok-title')).toEqual(`Uninstall ${appTitle}`);
|
||||
});
|
||||
|
||||
it('triggers confirm event when ok button is clicked', () => {
|
||||
wrapper.find(GlModal).vm.$emit('ok');
|
||||
|
||||
expect(wrapper.emitted('confirm')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays a warning text indicating the app will be uninstalled', () => {
|
||||
expect(wrapper.text()).toContain(`You are about to uninstall ${appTitle} from your cluster.`);
|
||||
});
|
||||
|
||||
it('displays a custom warning text depending on the application', () => {
|
||||
expect(wrapper.text()).toContain(
|
||||
`The associated load balancer and IP will be deleted and cannot be restored.`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,10 @@
|
|||
import transitionApplicationState from '~/clusters/services/application_state_machine';
|
||||
import { APPLICATION_STATUS, UPDATE_EVENT, INSTALL_EVENT } from '~/clusters/constants';
|
||||
import {
|
||||
APPLICATION_STATUS,
|
||||
UNINSTALL_EVENT,
|
||||
UPDATE_EVENT,
|
||||
INSTALL_EVENT,
|
||||
} from '~/clusters/constants';
|
||||
|
||||
const {
|
||||
NO_STATUS,
|
||||
|
@ -12,6 +17,8 @@ const {
|
|||
UPDATING,
|
||||
UPDATED,
|
||||
UPDATE_ERRORED,
|
||||
UNINSTALLING,
|
||||
UNINSTALL_ERRORED,
|
||||
} = APPLICATION_STATUS;
|
||||
|
||||
const NO_EFFECTS = 'no effects';
|
||||
|
@ -21,16 +28,18 @@ describe('applicationStateMachine', () => {
|
|||
|
||||
describe(`current state is ${NO_STATUS}`, () => {
|
||||
it.each`
|
||||
expectedState | event | effects
|
||||
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
|
||||
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
|
||||
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
|
||||
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
|
||||
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
|
||||
${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
|
||||
expectedState | event | effects
|
||||
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
|
||||
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
|
||||
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
|
||||
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
|
||||
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
|
||||
${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
|
||||
${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
|
||||
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
|
||||
`(`transitions to $expectedState on $event event and applies $effects`, data => {
|
||||
const { expectedState, event, effects } = data;
|
||||
const currentAppState = {
|
||||
|
@ -99,8 +108,9 @@ describe('applicationStateMachine', () => {
|
|||
|
||||
describe(`current state is ${INSTALLED}`, () => {
|
||||
it.each`
|
||||
expectedState | event | effects
|
||||
${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }}
|
||||
expectedState | event | effects
|
||||
${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }}
|
||||
${UNINSTALLING} | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }}
|
||||
`(`transitions to $expectedState on $event event and applies $effects`, data => {
|
||||
const { expectedState, event, effects } = data;
|
||||
const currentAppState = {
|
||||
|
@ -131,4 +141,22 @@ describe('applicationStateMachine', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`current state is ${UNINSTALLING}`, () => {
|
||||
it.each`
|
||||
expectedState | event | effects
|
||||
${INSTALLABLE} | ${INSTALLABLE} | ${{ uninstallSuccessful: true }}
|
||||
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
|
||||
`(`transitions to $expectedState on $event event and applies $effects`, data => {
|
||||
const { expectedState, event, effects } = data;
|
||||
const currentAppState = {
|
||||
status: UNINSTALLING,
|
||||
};
|
||||
|
||||
expect(transitionApplicationState(currentAppState, event)).toEqual({
|
||||
status: expectedState,
|
||||
...effects,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ const CLUSTERS_MOCK_DATA = {
|
|||
name: 'helm',
|
||||
status: APPLICATION_STATUS.INSTALLABLE,
|
||||
status_reason: null,
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'ingress',
|
||||
|
@ -18,32 +19,38 @@ const CLUSTERS_MOCK_DATA = {
|
|||
status_reason: 'Cannot connect',
|
||||
external_ip: null,
|
||||
external_hostname: null,
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'runner',
|
||||
status: APPLICATION_STATUS.INSTALLING,
|
||||
status_reason: null,
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'prometheus',
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
status_reason: 'Cannot connect',
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'jupyter',
|
||||
status: APPLICATION_STATUS.INSTALLING,
|
||||
status_reason: 'Cannot connect',
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'knative',
|
||||
status: APPLICATION_STATUS.INSTALLING,
|
||||
status_reason: 'Cannot connect',
|
||||
can_uninstall: false,
|
||||
},
|
||||
{
|
||||
name: 'cert_manager',
|
||||
status: APPLICATION_STATUS.ERROR,
|
||||
status_reason: 'Cannot connect',
|
||||
email: 'test@example.com',
|
||||
can_uninstall: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -63,6 +63,8 @@ describe('Clusters Store', () => {
|
|||
installed: false,
|
||||
installFailed: false,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
ingress: {
|
||||
title: 'Ingress',
|
||||
|
@ -74,6 +76,8 @@ describe('Clusters Store', () => {
|
|||
installed: false,
|
||||
installFailed: true,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
runner: {
|
||||
title: 'GitLab Runner',
|
||||
|
@ -89,6 +93,8 @@ describe('Clusters Store', () => {
|
|||
updateFailed: false,
|
||||
updateSuccessful: false,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
prometheus: {
|
||||
title: 'Prometheus',
|
||||
|
@ -98,6 +104,8 @@ describe('Clusters Store', () => {
|
|||
installed: false,
|
||||
installFailed: true,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
jupyter: {
|
||||
title: 'JupyterHub',
|
||||
|
@ -108,6 +116,8 @@ describe('Clusters Store', () => {
|
|||
installed: false,
|
||||
installFailed: false,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
knative: {
|
||||
title: 'Knative',
|
||||
|
@ -121,6 +131,8 @@ describe('Clusters Store', () => {
|
|||
installed: false,
|
||||
installFailed: false,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
cert_manager: {
|
||||
title: 'Cert-Manager',
|
||||
|
@ -131,6 +143,8 @@ describe('Clusters Store', () => {
|
|||
email: mockResponseData.applications[6].email,
|
||||
installed: false,
|
||||
uninstallable: false,
|
||||
uninstallSuccessful: false,
|
||||
uninstallFailed: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue