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:
-rw-r--r--app/assets/javascripts/environments/components/delete_environment_modal.vue69
-rw-r--r--app/assets/javascripts/environments/components/environment_delete.vue70
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue14
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue3
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue3
-rw-r--r--app/assets/javascripts/environments/index.js3
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js30
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js5
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/policies/environment_policy.rb6
-rw-r--r--app/serializers/environment_entity.rb8
-rw-r--r--app/views/projects/environments/show.html.haml22
-rw-r--r--changelogs/unreleased/41845-delete-environment.yml5
-rw-r--r--doc/ci/environments.md22
-rw-r--r--locale/gitlab.pot15
-rw-r--r--spec/javascripts/environments/environment_delete_spec.js23
16 files changed, 301 insertions, 1 deletions
diff --git a/app/assets/javascripts/environments/components/delete_environment_modal.vue b/app/assets/javascripts/environments/components/delete_environment_modal.vue
new file mode 100644
index 00000000000..7a0ee85b551
--- /dev/null
+++ b/app/assets/javascripts/environments/components/delete_environment_modal.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { s__, sprintf } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import eventHub from '../event_hub';
+
+export default {
+ id: 'delete-environment-modal',
+ name: 'DeleteEnvironmentModal',
+
+ components: {
+ GlModal,
+ LoadingButton,
+ },
+
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+
+ props: {
+ environment: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ confirmDeleteMessage() {
+ return sprintf(
+ s__(
+ `Environments|Deleting the '%{environmentName}' environment cannot be undone. Are you sure?`,
+ ),
+ {
+ environmentName: this.environment.name,
+ },
+ false,
+ );
+ },
+ },
+
+ methods: {
+ onSubmit() {
+ eventHub.$emit('deleteEnvironment', this.environment);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ :id="$options.id"
+ :footer-primary-button-text="s__('Environments|Delete environment')"
+ footer-primary-button-variant="danger"
+ @submit="onSubmit"
+ >
+ <template slot="header">
+ <h4 class="modal-title d-flex mw-100">
+ {{ __('Delete') }}
+ <span v-gl-tooltip :title="environment.name" class="text-truncate ml-1 mr-1 flex-fill">{{
+ environment.name
+ }}</span>
+ ?
+ </h4>
+ </template>
+
+ <p v-html="confirmDeleteMessage"></p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/environments/components/environment_delete.vue b/app/assets/javascripts/environments/components/environment_delete.vue
new file mode 100644
index 00000000000..b53c5fa6583
--- /dev/null
+++ b/app/assets/javascripts/environments/components/environment_delete.vue
@@ -0,0 +1,70 @@
+<script>
+/**
+ * Renders the delete button that allows deleting a stopped environment.
+ * Used in the environments table and the environment detail view.
+ */
+
+import $ from 'jquery';
+import { GlTooltipDirective } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { s__ } from '~/locale';
+import eventHub from '../event_hub';
+import LoadingButton from '../../vue_shared/components/loading_button.vue';
+
+export default {
+ components: {
+ Icon,
+ LoadingButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ environment: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+ computed: {
+ title() {
+ return s__('Environments|Delete environment');
+ },
+ },
+ mounted() {
+ eventHub.$on('deleteEnvironment', this.onDeleteEnvironment);
+ },
+ beforeDestroy() {
+ eventHub.$off('deleteEnvironment', this.onDeleteEnvironment);
+ },
+ methods: {
+ onClick() {
+ $(this.$el).tooltip('dispose');
+ eventHub.$emit('requestDeleteEnvironment', this.environment);
+ },
+ onDeleteEnvironment(environment) {
+ if (this.environment.id === environment.id) {
+ this.isLoading = true;
+ }
+ },
+ },
+};
+</script>
+<template>
+ <loading-button
+ v-gl-tooltip
+ :loading="isLoading"
+ :title="title"
+ :aria-label="title"
+ container-class="btn btn-danger d-none d-sm-none d-md-block"
+ data-toggle="modal"
+ data-target="#delete-environment-modal"
+ @click="onClick"
+ >
+ <icon name="remove" />
+ </loading-button>
+</template>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 95e1e8af9b3..c15bf939cf9 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -10,6 +10,7 @@ import environmentItemMixin from 'ee_else_ce/environments/mixins/environment_ite
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
+import DeleteComponent from './environment_delete.vue';
import RollbackComponent from './environment_rollback.vue';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
@@ -32,6 +33,7 @@ export default {
ActionsComponent,
ExternalUrlComponent,
StopComponent,
+ DeleteComponent,
RollbackComponent,
TerminalButtonComponent,
MonitoringButtonComponent,
@@ -90,6 +92,15 @@ export default {
},
/**
+ * Returns whether the environment can be deleted.
+ *
+ * @returns {Boolean}
+ */
+ canDeleteEnvironment() {
+ return this.model && !this.model.can_stop && this.model.can_update && this.model.delete_path;
+ },
+
+ /**
* Verifies if the `deployable` key is present in `last_deployment` key.
* Used to verify whether we should or not render the rollback partial.
*
@@ -431,6 +442,7 @@ export default {
this.externalURL ||
this.monitoringUrl ||
this.canStopEnvironment ||
+ this.canDeleteEnvironment ||
this.canRetry
);
},
@@ -582,6 +594,8 @@ export default {
/>
<stop-component v-if="canStopEnvironment" :environment="model" />
+
+ <delete-component v-if="canDeleteEnvironment" :environment="model" />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 81927d18f8b..99df5687057 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -7,12 +7,14 @@ import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from './stop_environment_modal.vue';
+import DeleteEnvironmentModal from './delete_environment_modal.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
export default {
components: {
emptyState,
StopEnvironmentModal,
+ DeleteEnvironmentModal,
ConfirmRollbackModal,
},
@@ -95,6 +97,7 @@ export default {
<template>
<div :class="cssContainerClass">
<stop-environment-modal :environment="environmentInStopModal" />
+ <delete-environment-modal :environment="environmentInDeleteModal" />
<confirm-rollback-modal :environment="environmentInRollbackModal" />
<div class="top-area">
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 6fd0561f682..604abb2ecb3 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -3,10 +3,12 @@ import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
+import DeleteEnvironmentModal from '../components/delete_environment_modal.vue';
export default {
components: {
StopEnvironmentModal,
+ DeleteEnvironmentModal,
},
mixins: [environmentsMixin, CIPaginationMixin, folderMixin],
@@ -39,6 +41,7 @@ export default {
<template>
<div :class="cssContainerClass">
<stop-environment-modal :environment="environmentInStopModal" />
+ <delete-environment-modal :environment="environmentInDeleteModal" />
<div v-if="!isLoading" class="top-area">
<h4 class="js-folder-name environments-folder-name">
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index dcdaf8731f8..b2aef256bd3 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -14,7 +14,8 @@ export default () =>
},
mixins: [canaryCalloutMixin],
data() {
- const environmentsData = document.querySelector(this.$options.el).dataset;
+ const domEl = document.querySelector(this.$options.el);
+ const environmentsData = domEl.dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 31347d95a25..d0189ef35f5 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -36,6 +36,7 @@ export default {
page: getParameterByName('page') || '1',
requestData: {},
environmentInStopModal: {},
+ environmentInDeleteModal: {},
environmentInRollbackModal: {},
};
},
@@ -117,6 +118,10 @@ export default {
this.environmentInStopModal = environment;
},
+ updateDeleteModal(environment) {
+ this.environmentInDeleteModal = environment;
+ },
+
updateRollbackModal(environment) {
this.environmentInRollbackModal = environment;
},
@@ -129,6 +134,23 @@ export default {
this.postAction({ endpoint, errorMessage });
},
+ deleteEnvironment(environment) {
+ const endpoint = environment.delete_path;
+ const errorMessage = s__(
+ 'Environments|An error occurred while deleting the environment, please try again',
+ );
+
+ this.service
+ .deleteAction(endpoint)
+ .then(() => {
+ // Reload to as a first solution to bust the ETag cache
+ window.location.reload();
+ })
+ .catch(() => {
+ Flash(errorMessage || s__('Environments|An error occurred while making the request.'));
+ });
+ },
+
rollbackEnvironment(environment) {
const { retryUrl, isLastDeployment } = environment;
const errorMessage = isLastDeployment
@@ -194,18 +216,26 @@ export default {
});
eventHub.$on('postAction', this.postAction);
+
eventHub.$on('requestStopEnvironment', this.updateStopModal);
eventHub.$on('stopEnvironment', this.stopEnvironment);
+ eventHub.$on('requestDeleteEnvironment', this.updateDeleteModal);
+ eventHub.$on('deleteEnvironment', this.deleteEnvironment);
+
eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal);
eventHub.$on('rollbackEnvironment', this.rollbackEnvironment);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
+
eventHub.$off('requestStopEnvironment', this.updateStopModal);
eventHub.$off('stopEnvironment', this.stopEnvironment);
+ eventHub.$off('requestDeleteEnvironment', this.updateDeleteModal);
+ eventHub.$off('deleteEnvironment', this.deleteEnvironment);
+
eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal);
eventHub.$off('rollbackEnvironment', this.rollbackEnvironment);
},
diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js
index cb4ff6856db..122c8f84a2c 100644
--- a/app/assets/javascripts/environments/services/environments_service.js
+++ b/app/assets/javascripts/environments/services/environments_service.js
@@ -16,6 +16,11 @@ export default class EnvironmentsService {
return axios.post(endpoint, {});
}
+ // eslint-disable-next-line class-methods-use-this
+ deleteAction(endpoint) {
+ return axios.delete(endpoint, {});
+ }
+
getFolderContent(folderUrl) {
return axios.get(`${folderUrl}.json?per_page=${this.folderResults}`);
}
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 04cf43be452..510176759c6 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -29,6 +29,10 @@ module GitlabRoutingHelper
metrics_project_environment_path(environment.project, environment, *args)
end
+ def environment_delete_path(project, environment, *args)
+ "#{Settings.gitlab.url}/api/v4/projects/#{project.id}/environments/#{environment.id}"
+ end
+
def issue_path(entity, *args)
project_issue_path(entity.project, entity, *args)
end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
index d1243491f5a..be349b81b85 100644
--- a/app/policies/environment_policy.rb
+++ b/app/policies/environment_policy.rb
@@ -12,5 +12,11 @@ class EnvironmentPolicy < BasePolicy
!@subject.stop_action_available? && can?(:update_environment, @subject)
end
+ condition(:update_allowed) do
+ can?(:update_environment, @subject)
+ end
+
rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment
+
+ rule { update_allowed }.enable :update_environment
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 8258135da4e..21f3e399c8a 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -24,6 +24,10 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
+ expose :delete_path do |environment|
+ environment_delete_path(environment.project, environment)
+ end
+
expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment|
cluster.cluster_type
end
@@ -42,6 +46,10 @@ class EnvironmentEntity < Grape::Entity
environment.available? && can?(current_user, :stop_environment, environment)
end
+ expose :can_update do |environment|
+ !environment.available? && can?(current_user, :update_environment, environment)
+ end
+
private
alias_method :environment, :object
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6100fd3ad37..462995b77e3 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -34,6 +34,24 @@
= button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do
= s_('Environments|Stop environment')
+%div{ class: container_class }
+ - if can?(current_user, :update_environment, @environment) && @environment.stopped?
+ #delete-environment-modal.modal.fade{ tabindex: -1 }
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %h4.modal-title.d-flex.mw-100
+ = s_("Environments|Delete environment")
+ %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#delete-environment-modal' } }
+ = @environment.name
+ ?
+ .modal-body
+ %p= s_("Environments|Deleting the '%{environment_name}' environment cannot be undone. Are you sure?") % { environment_name: @environment.name }
+ .modal-footer
+ = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
+ = button_to environment_delete_path(@project, @environment), class: 'btn btn-danger has-tooltip', data: { redirect_url: project_environments_path(@project) }, method: :delete do
+ = s_('Environments|Delete environment')
+
.top-area
%h3.page-title= @environment.name
.nav-controls.ml-auto.my-2
@@ -47,6 +65,10 @@
target: '#stop-environment-modal' } do
= sprite_icon('stop')
= s_('Environments|Stop')
+ - if can?(current_user, :update_environment, @environment) && @environment.stopped?
+ = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
+ target: '#delete-environment-modal' } do
+ = s_('Environments|Delete')
.environments-container
- if @deployments.blank?
diff --git a/changelogs/unreleased/41845-delete-environment.yml b/changelogs/unreleased/41845-delete-environment.yml
new file mode 100644
index 00000000000..197115ac238
--- /dev/null
+++ b/changelogs/unreleased/41845-delete-environment.yml
@@ -0,0 +1,5 @@
+---
+title: Adds features to delete stopped environments
+merge_request: 31032
+author:
+type: added
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index f6c47a99712..385ca37be47 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -561,6 +561,28 @@ to automatically stop.
You can read more in the [`.gitlab-ci.yml` reference](yaml/README.md#environmenton_stop).
+### Deleting an environment
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31032) in GitLab 12.3.
+
+You can delete stopped environments in one of two ways.
+
+The first way is to access the **Delete** button by viewing the list of
+**Stopped** environments.
+
+ 1. Navigate to **Operations > Environments**.
+ 1. Click the **Stopped** tab to access the list of stopped environments.
+ 1. Click the **Delete** button that appears next to the environment you want to delete.
+ 1. Finally, confirm your chosen environment in the modal that appears to delete it.
+
+The second way is to access the **Delete** button by viewing the details for a
+stopped environment.
+
+ 1. Navigate to **Operations > Environments**.
+ 1. Click on the name of an environment within the **Stopped** enfironments list.
+ 1. Click on the **Delete** button that appears at the top for all stopped environments.
+ 1. Finally, confirm your chosen environment in the modal that appears to delete it.
+
### Grouping similar environments
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015) in GitLab 8.14.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8c8574d0a48..9ddd1a4d9ed 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4405,6 +4405,9 @@ msgstr ""
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
msgstr ""
+msgid "Environments|An error occurred while deleting the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while fetching the environments."
msgstr ""
@@ -4426,6 +4429,18 @@ msgstr ""
msgid "Environments|Commit"
msgstr ""
+msgid "Environments|Delete"
+msgstr ""
+
+msgid "Environments|Delete environment"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environmentName}' environment cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Environments|Deleting the '%{environment_name}' environment cannot be undone. Are you sure?"
+msgstr ""
+
msgid "Environments|Deploy to..."
msgstr ""
diff --git a/spec/javascripts/environments/environment_delete_spec.js b/spec/javascripts/environments/environment_delete_spec.js
new file mode 100644
index 00000000000..8afaa03e2fb
--- /dev/null
+++ b/spec/javascripts/environments/environment_delete_spec.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import deleteComp from '~/environments/components/environment_delete.vue';
+
+describe('Delete Component', () => {
+ let DeleteComponent;
+ let component;
+
+ beforeEach(() => {
+ DeleteComponent = Vue.extend(deleteComp);
+ spyOn(window, 'confirm').and.returnValue(true);
+
+ component = new DeleteComponent({
+ propsData: {
+ environment: {},
+ },
+ }).$mount();
+ });
+
+ it('should render a button to delete the environment', () => {
+ expect(component.$el.tagName).toEqual('BUTTON');
+ expect(component.$el.getAttribute('data-original-title')).toEqual('Delete environment');
+ });
+});