Коммит 3525898b создал по автору Dietrich Stein's avatar Dietrich Stein
Просмотр файлов

Adds environments delete button and modal

Adds delete functionality
Adds a property the 'api' library to enable access for buildUrl
Passes the project ID as a prop to the environments component
Imports the Delete component for use within the environment items
Adds canDeleteEnvironment as a computed method for environment items
Defines project ID as a required prop for the environments component
Imports the delete modal for the environments folder view
Sets up the delete modal within the environments modal
Creates a deleteAction helper method within the environments mixin
Creates an update method for the delete modal
Adds a method to facilitate API access to delete an environment
Adds events for reactivity with the delete environment feature
Adds an axios helper for DELETE calls in the environments service
Exposes a data attribute with the project ID on the environments list
Adds a test for the delete environment component
Adds a project ID to the mock data for the environments app

Adds translations and formatting

Fixes commas, semicolons, and whitespace using prettier
Adds generated environments-related translations

Implements delete button for detail view

Removes front-end buildable delete endpoint
Updates deletion method to ensure endpoint access
Removes previously-added project id property
Accesses exposed deletion endpoint
Adds helper function for model-based access to API endpoint
Exposes update rule for front-end access
Exposes helper-based delete endpoint
Removes previously added project id property
Adds modal and button for detail view
Removes project ID from mock data

Using const instead of let

Removes now unused import

Adds entry to changelog

Adds reload on delete modal success

Adds reload on delete for etag expiration
Modifies method name
Modifies delete path helper on entity
Modifies delete action helper
Adds request_url param

Adding docs for delete feature

Adds translations and lint fixes
владелец 336ef2a9
<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>
<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>
......@@ -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,
......@@ -89,6 +91,15 @@ export default {
return this.model && this.model.can_stop;
},
/**
* 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>
......
......@@ -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">
......
......@@ -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">
......
......@@ -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,
......
......@@ -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);
},
......
......@@ -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}`);
}
......
......@@ -29,6 +29,10 @@ def environment_metrics_path(environment, *args)
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
......
......@@ -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
......@@ -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
......
......@@ -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?
......
---
title: Adds features to delete stopped environments
merge_request: 31032
author:
type: added
......@@ -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.
......
......@@ -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 ""
......
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');
});
});
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать