Commit ba98e91c authored by Andrew Fontaine's avatar Andrew Fontaine Committed by Filipa Lacerda

Add Component to Confirm Environment Rollback

Ensure a vue.js component to confirm the re-deployment/rollback of an
environment pops up when rollback/re-deploy has been clicked so the user
must confirm their actions before accidentally rolling back or
re-deploying an environment.

Only on the `environment-table` view.
parent 5fd1dc15
<script>
/**
* Render modal to confirm rollback/redeploy.
*/
import _ from 'underscore';
import { GlModal } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
export default {
name: 'ConfirmRollbackModal',
components: {
GlModal,
},
props: {
environment: {
type: Object,
required: true,
},
},
computed: {
modalTitle() {
const title = this.environment.isLastDeployment
? s__('Environments|Re-deploy environment %{name}?')
: s__('Environments|Rollback environment %{name}?');
return sprintf(title, {
name: _.escape(this.environment.name),
});
},
commitShortSha() {
const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'short_id');
},
commitUrl() {
const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'commit_path');
},
commitTitle() {
const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'title');
},
modalText() {
const linkStart = `<a class="commit-sha" href="${_.escape(this.commitUrl)}">`;
const commitId = _.escape(this.commitShortSha);
const linkEnd = '</a>';
const name = _.escape(this.name);
const body = this.environment.isLastDeployment
? s__(
'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?',
)
: s__(
'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?',
);
return sprintf(
body,
{
commitId,
linkStart,
linkEnd,
name,
},
false,
);
},
modalActionText() {
return this.environment.isLastDeployment
? s__('Environments|Re-deploy')
: s__('Environments|Rollback');
},
},
methods: {
onOk() {
eventHub.$emit('rollbackEnvironment', this.environment);
},
commitData(lastDeployment, key) {
if (lastDeployment && lastDeployment.commit) {
return lastDeployment.commit[key];
}
return '';
},
},
};
</script>
<template>
<gl-modal
:title="modalTitle"
modal-id="confirm-rollback-modal"
:ok-title="modalActionText"
ok-variant="danger"
@ok="onOk"
>
<p v-html="modalText"></p>
</gl-modal>
</template>
...@@ -556,6 +556,7 @@ export default { ...@@ -556,6 +556,7 @@ export default {
<rollback-component <rollback-component
v-if="canRetry" v-if="canRetry"
:environment="model"
:is-last-deployment="isLastDeployment" :is-last-deployment="isLastDeployment"
:retry-url="retryUrl" :retry-url="retryUrl"
/> />
......
...@@ -5,29 +5,38 @@ ...@@ -5,29 +5,38 @@
* *
* Makes a post request when the button is clicked. * Makes a post request when the button is clicked.
*/ */
import { GlTooltipDirective, GlLoadingIcon, GlModalDirective, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
components: { components: {
Icon, Icon,
GlLoadingIcon, GlLoadingIcon,
GlButton,
ConfirmRollbackModal,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
}, },
props: { props: {
retryUrl: {
type: String,
default: '',
},
isLastDeployment: { isLastDeployment: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
environment: {
type: Object,
required: true,
},
retryUrl: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -45,23 +54,31 @@ export default { ...@@ -45,23 +54,31 @@ export default {
methods: { methods: {
onClick() { onClick() {
this.isLoading = true; eventHub.$emit('requestRollbackEnvironment', {
...this.environment,
eventHub.$emit('postAction', { endpoint: this.retryUrl }); retryUrl: this.retryUrl,
isLastDeployment: this.isLastDeployment,
});
eventHub.$on('rollbackEnvironment', environment => {
if (environment.id === this.environment.id) {
this.isLoading = true;
}
});
}, },
}, },
}; };
</script> </script>
<template> <template>
<button <gl-button
v-gl-tooltip v-gl-tooltip
v-gl-modal.confirm-rollback-modal
variant="secondary"
:disabled="isLoading" :disabled="isLoading"
:title="title" :title="title"
type="button" class="d-none d-md-block"
class="btn d-none d-sm-none d-md-block"
@click="onClick" @click="onClick"
> >
<icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" /> <icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" />
<gl-loading-icon v-if="isLoading" /> <gl-loading-icon v-if="isLoading" />
</button> </gl-button>
</template> </template>
...@@ -6,11 +6,13 @@ import eventHub from '../event_hub'; ...@@ -6,11 +6,13 @@ import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin'; import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from './stop_environment_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
export default { export default {
components: { components: {
emptyState, emptyState,
StopEnvironmentModal, StopEnvironmentModal,
ConfirmRollbackModal,
}, },
mixins: [CIPaginationMixin, environmentsMixin], mixins: [CIPaginationMixin, environmentsMixin],
...@@ -87,6 +89,7 @@ export default { ...@@ -87,6 +89,7 @@ export default {
<template> <template>
<div :class="cssContainerClass"> <div :class="cssContainerClass">
<stop-environment-modal :environment="environmentInStopModal" /> <stop-environment-modal :environment="environmentInStopModal" />
<confirm-rollback-modal :environment="environmentInRollbackModal" />
<div class="top-area"> <div class="top-area">
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" /> <tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
......
...@@ -36,6 +36,7 @@ export default { ...@@ -36,6 +36,7 @@ export default {
page: getParameterByName('page') || '1', page: getParameterByName('page') || '1',
requestData: {}, requestData: {},
environmentInStopModal: {}, environmentInStopModal: {},
environmentInRollbackModal: {},
}; };
}, },
...@@ -116,6 +117,10 @@ export default { ...@@ -116,6 +117,10 @@ export default {
this.environmentInStopModal = environment; this.environmentInStopModal = environment;
}, },
updateRollbackModal(environment) {
this.environmentInRollbackModal = environment;
},
stopEnvironment(environment) { stopEnvironment(environment) {
const endpoint = environment.stop_path; const endpoint = environment.stop_path;
const errorMessage = s__( const errorMessage = s__(
...@@ -123,6 +128,16 @@ export default { ...@@ -123,6 +128,16 @@ export default {
); );
this.postAction({ endpoint, errorMessage }); this.postAction({ endpoint, errorMessage });
}, },
rollbackEnvironment(environment) {
const { retryUrl, isLastDeployment } = environment;
const errorMessage = isLastDeployment
? s__('Environments|An error occurred while re-deploying the environment, please try again')
: s__(
'Environments|An error occurred while rolling back the environment, please try again',
);
this.postAction({ endpoint: retryUrl, errorMessage });
},
}, },
computed: { computed: {
...@@ -181,11 +196,17 @@ export default { ...@@ -181,11 +196,17 @@ export default {
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
eventHub.$on('requestStopEnvironment', this.updateStopModal); eventHub.$on('requestStopEnvironment', this.updateStopModal);
eventHub.$on('stopEnvironment', this.stopEnvironment); eventHub.$on('stopEnvironment', this.stopEnvironment);
eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal);
eventHub.$on('rollbackEnvironment', this.rollbackEnvironment);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('postAction', this.postAction); eventHub.$off('postAction', this.postAction);
eventHub.$off('requestStopEnvironment', this.updateStopModal); eventHub.$off('requestStopEnvironment', this.updateStopModal);
eventHub.$off('stopEnvironment', this.stopEnvironment); eventHub.$off('stopEnvironment', this.stopEnvironment);
eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal);
eventHub.$off('rollbackEnvironment', this.rollbackEnvironment);
}, },
}; };
...@@ -404,6 +404,7 @@ img.emoji { ...@@ -404,6 +404,7 @@ img.emoji {
.flex-grow { flex-grow: 1; } .flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; } .flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; } .ws-initial { white-space: initial; }
.ws-normal { white-space: normal; }
.overflow-auto { overflow: auto; } .overflow-auto { overflow: auto; }
.d-flex-center { .d-flex-center {
......
- commit_sha = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha has-tooltip", title: h(deployment.commit_title)
.modal.ws-normal.fade{ tabindex: -1, id: "confirm-rollback-modal-#{deployment.id}" }
.modal-dialog
.modal-content
.modal-header
%h4.modal-title.d-flex.mw-100
- if deployment.last?
= s_("Environments|Re-deploy environment %{environment_name}?") % {environment_name: @environment.name}
- else
= s_("Environments|Rollback environment %{environment_name}?") % {environment_name: @environment.name}
.modal-body
- if deployment.last?
%p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
- else
%p
= s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do
- if deployment.last?
= s_('Environments|Re-deploy')
- else
= s_('Environments|Rollback')
- if can?(current_user, :create_deployment, deployment) - if can?(current_user, :create_deployment, deployment)
- tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment') - tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment')
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do = button_tag class: 'btn btn-default btn-build has-tooltip', type: 'button', data: { toggle: 'modal', target: "#confirm-rollback-modal-#{deployment.id}" }, title: tooltip do
- if deployment.last? - if deployment.last?
= sprite_icon('repeat') = sprite_icon('repeat')
- else - else
= sprite_icon('redo') = sprite_icon('redo')
= render 'projects/deployments/confirm_rollback_modal', deployment: deployment
---
title: Add Confirmation Modal to Rollback on Environment
merge_request: 25110
author:
type: added
...@@ -3055,6 +3055,12 @@ msgstr "" ...@@ -3055,6 +3055,12 @@ msgstr ""
msgid "Environments|An error occurred while making the request." msgid "Environments|An error occurred while making the request."
msgstr "" msgstr ""
msgid "Environments|An error occurred while re-deploying the environment, please try again"
msgstr ""
msgid "Environments|An error occurred while rolling back the environment, please try again"
msgstr ""
msgid "Environments|An error occurred while stopping the environment, please try again" msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "" msgstr ""
...@@ -3100,15 +3106,33 @@ msgstr "" ...@@ -3100,15 +3106,33 @@ msgstr ""
msgid "Environments|Open live environment" msgid "Environments|Open live environment"
msgstr "" msgstr ""
msgid "Environments|Re-deploy"
msgstr ""
msgid "Environments|Re-deploy environment %{environment_name}?"
msgstr ""
msgid "Environments|Re-deploy environment %{name}?"
msgstr ""
msgid "Environments|Re-deploy to environment" msgid "Environments|Re-deploy to environment"
msgstr "" msgstr ""
msgid "Environments|Read more about environments" msgid "Environments|Read more about environments"
msgstr "" msgstr ""
msgid "Environments|Rollback"
msgstr ""
msgid "Environments|Rollback environment" msgid "Environments|Rollback environment"
msgstr "" msgstr ""
msgid "Environments|Rollback environment %{environment_name}?"
msgstr ""
msgid "Environments|Rollback environment %{name}?"
msgstr ""
msgid "Environments|Show all" msgid "Environments|Show all"
msgstr "" msgstr ""
...@@ -3121,6 +3145,18 @@ msgstr "" ...@@ -3121,6 +3145,18 @@ msgstr ""
msgid "Environments|Stopping" msgid "Environments|Stopping"
msgstr "" msgstr ""
msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
msgstr ""
msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
msgstr ""
msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|Updated" msgid "Environments|Updated"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
import eventHub from '~/environments/event_hub';
describe('Confirm Rollback Modal Component', () => {
let environment;
beforeEach(() => {
environment = {
name: 'test',
last_deployment: {
commit: {
short_id: 'abc0123',
},
},
modalId: 'test',
};
});
it('should show "Rollback" when isLastDeployment is false', () => {
const component = shallowMount(ConfirmRollbackModal, {
propsData: {
environment: {
...environment,
isLastDeployment: false,
},
},
});
const modal = component.find(GlModal);
expect(modal.attributes('title')).toContain('Rollback');
expect(modal.attributes('title')).toContain('test');
expect(modal.attributes('ok-title')).toBe('Rollback');
expect(modal.text()).toContain('commit abc0123');
expect(modal.text()).toContain('Are you sure you want to continue?');
});
it('should show "Re-deploy" when isLastDeployment is true', () => {
const component = shallowMount(ConfirmRollbackModal, {
propsData: {
environment: {
...environment,
isLastDeployment: true,
},
},
});
const modal = component.find(GlModal);
expect(modal.attributes('title')).toContain('Re-deploy');
expect(modal.attributes('title')).toContain('test');
expect(modal.attributes('ok-title')).toBe('Re-deploy');
expect(modal.text()).toContain('commit abc0123');
expect(modal.text()).toContain('Are you sure you want to continue?');
});
it('should emit the "rollback" event when "ok" is clicked', () => {
environment = { ...environment, isLastDeployment: true };
const component = shallowMount(ConfirmRollbackModal, {
propsData: {
environment,
},
});
const eventHubSpy = spyOn(eventHub, '$emit');
const modal = component.find(GlModal);
modal.vm.$emit('ok');
expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment);
});
});
import Vue from 'vue'; import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import eventHub from '~/environments/event_hub';
import rollbackComp from '~/environments/components/environment_rollback.vue'; import rollbackComp from '~/environments/components/environment_rollback.vue';
describe('Rollback Component', () => { describe('Rollback Component', () => {
const retryURL = 'https://gitlab.com/retry'; const retryUrl = 'https://gitlab.com/retry';
let RollbackComponent; let RollbackComponent;
beforeEach(() => { beforeEach(() => {
...@@ -13,8 +16,9 @@ describe('Rollback Component', () => { ...@@ -13,8 +16,9 @@ describe('Rollback Component', () => {
const component = new RollbackComponent({ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
retryUrl: retryURL, retryUrl,
isLastDeployment: true, isLastDeployment: true,
environment: {},
}, },
}).$mount(); }).$mount();
...@@ -25,11 +29,33 @@ describe('Rollback Component', () => { ...@@ -25,11 +29,33 @@ describe('Rollback Component', () => {
const component = new RollbackComponent({ const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
retryUrl: retryURL, retryUrl,
isLastDeployment: false, isLastDeployment: false,
environment: {},
}, },
}).$mount(); }).$mount();
expect(component.$el).toHaveSpriteIcon('redo'); expect(component.$el).toHaveSpriteIcon('redo');
}); });
it('should emit a "rollback" event on button click', () => {
const eventHubSpy = spyOn(eventHub, '$emit');
const component = shallowMount(RollbackComponent, {
propsData: {
retryUrl,
environment: {
name: 'test',
},
},
});
const button = component.find(GlButton);
button.vm.$emit('click');
expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
retryUrl,
isLastDeployment: true,
name: 'test',
});
});
}); });
# frozen_string_literal: true
require 'spec_helper'
describe 'projects/deployments/_confirm_rollback_modal' do
let(:environment) { create(:environment, :with_review_app) }
let(:deployments) { environment.deployments }
let(:project) { environment.project }
before do
assign(:environment, environment)
assign(:deployments, deployments)
assign(:project, project)
end
context 'when re-deploying last deployment' do
let(:deployment) { deployments.first }
before do
allow(view).to receive(:deployment).and_return(deployment)
end
it 'shows "re-deploy"' do
render
expect(rendered).to have_selector('h4', text: "Re-deploy environment #{environment.name}?")
expect(rendered).to have_selector('p', text: "This action will relaunch the job for commit #{deployment.short_sha}, putting the environment in a previous version. Are you sure you want to continue?")
expect(rendered).to have_selector('a.btn-danger', text: 'Re-deploy')
end
it 'links to re-deploying the environment' do
expected_link = retry_project_job_path(environment.project, deployment.deployable)
render
expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Re-deploy')
end
end
context 'when rolling back to previous deployment' do
let(:deployment) { create(:deployment, environment: environment) }
before do
allow(view).to receive(:deployment).and_return(deployment)
end
it 'shows "rollback"' do
render
expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?")
expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
expect(rendered).to have_selector('a.btn-danger', text: 'Rollback')
end
it 'links to re-deploying the environment' do
expected_link = retry_project_job_path(environment.project, deployment.deployable)
render
expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Rollback')
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment