Commit 59175ca5 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '273592-terraform-delete' into 'master'

Add delete action to terraform list vue

See merge request gitlab-org/gitlab!48485
parents da43ad14 03c0a6cd
<script> <script>
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui'; import {
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlFormGroup,
GlFormInput,
GlIcon,
GlModal,
GlSprintf,
} from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import lockState from '../graphql/mutations/lock_state.mutation.graphql'; import lockState from '../graphql/mutations/lock_state.mutation.graphql';
import unlockState from '../graphql/mutations/unlock_state.mutation.graphql'; import unlockState from '../graphql/mutations/unlock_state.mutation.graphql';
import removeState from '../graphql/mutations/remove_state.mutation.graphql';
export default { export default {
components: { components: {
GlDropdown, GlDropdown,
GlDropdownDivider,
GlDropdownItem, GlDropdownItem,
GlFormGroup,
GlFormInput,
GlIcon, GlIcon,
GlModal,
GlSprintf,
}, },
props: { props: {
state: { state: {
...@@ -19,20 +34,59 @@ export default { ...@@ -19,20 +34,59 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
showRemoveModal: false,
removeConfirmText: '',
}; };
}, },
i18n: { i18n: {
downloadJSON: s__('Terraform|Download JSON'), downloadJSON: s__('Terraform|Download JSON'),
lock: s__('Terraform|Lock'), lock: s__('Terraform|Lock'),
modalBody: s__(
'Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously will remain intact, only the state file with all its versions are to be removed. This action is non-revertible.',
),
modalCancel: s__('Terraform|Cancel'),
modalHeader: s__('Terraform|Are you sure you want to remove the Terraform State %{name}?'),
modalInputLabel: s__(
'Terraform|To remove the State file and its versions, type %{name} to confirm:',
),
modalRemove: s__('Terraform|Remove'),
remove: s__('Terraform|Remove state file and versions'),
unlock: s__('Terraform|Unlock'), unlock: s__('Terraform|Unlock'),
}, },
computed: {
cancelModalProps() {
return {
text: this.$options.i18n.modalCancel,
attributes: [],
};
},
disableModalSubmit() {
return this.removeConfirmText !== this.state.name;
},
primaryModalProps() {
return {
text: this.$options.i18n.modalRemove,
attributes: [{ disabled: this.disableModalSubmit }, { variant: 'danger' }],
};
},
},
methods: { methods: {
hideModal() {
this.showRemoveModal = false;
this.removeConfirmText = '';
},
lock() { lock() {
this.stateMutation(lockState); this.stateMutation(lockState);
}, },
unlock() { unlock() {
this.stateMutation(unlockState); this.stateMutation(unlockState);
}, },
remove() {
if (!this.disableModalSubmit) {
this.hideModal();
this.stateMutation(removeState);
}
},
stateMutation(mutation) { stateMutation(mutation) {
this.loading = true; this.loading = true;
this.$apollo this.$apollo
...@@ -83,6 +137,56 @@ export default { ...@@ -83,6 +137,56 @@ export default {
<gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock"> <gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock">
{{ $options.i18n.lock }} {{ $options.i18n.lock }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item data-testid="terraform-state-remove" @click="showRemoveModal = true">
{{ $options.i18n.remove }}
</gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<gl-modal
:modal-id="`terraform-state-actions-remove-modal-${state.name}`"
:visible="showRemoveModal"
:action-primary="primaryModalProps"
:action-cancel="cancelModalProps"
@ok="remove"
@cancel="hideModal"
@close="hideModal"
@hide="hideModal"
>
<template #modal-title>
<gl-sprintf :message="$options.i18n.modalHeader">
<template #name>
<span>{{ state.name }}</span>
</template>
</gl-sprintf>
</template>
<p>
<gl-sprintf :message="$options.i18n.modalBody">
<template #name>
<span>{{ state.name }}</span>
</template>
</gl-sprintf>
</p>
<gl-form-group>
<template #label>
<gl-sprintf :message="$options.i18n.modalInputLabel">
<template #name>
<code>{{ state.name }}</code>
</template>
</gl-sprintf>
</template>
<gl-form-input
:id="`terraform-state-remove-input-${state.name}`"
ref="input"
v-model="removeConfirmText"
type="text"
@keyup.enter="remove"
/>
</gl-form-group>
</gl-modal>
</div> </div>
</template> </template>
mutation removeState($stateID: TerraformStateID!) {
terraformStateDelete(input: { id: $stateID }) {
errors
}
}
---
title: Add delete button to terraform list vue
merge_request: 48485
author:
type: added
...@@ -27210,6 +27210,12 @@ msgstr "" ...@@ -27210,6 +27210,12 @@ msgstr ""
msgid "Terraform|An error occurred while loading your Terraform States" msgid "Terraform|An error occurred while loading your Terraform States"
msgstr "" msgstr ""
msgid "Terraform|Are you sure you want to remove the Terraform State %{name}?"
msgstr ""
msgid "Terraform|Cancel"
msgstr ""
msgid "Terraform|Details" msgid "Terraform|Details"
msgstr "" msgstr ""
...@@ -27243,6 +27249,12 @@ msgstr "" ...@@ -27243,6 +27249,12 @@ msgstr ""
msgid "Terraform|Pipeline" msgid "Terraform|Pipeline"
msgstr "" msgstr ""
msgid "Terraform|Remove"
msgstr ""
msgid "Terraform|Remove state file and versions"
msgstr ""
msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete" msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
msgstr "" msgstr ""
...@@ -27255,12 +27267,18 @@ msgstr "" ...@@ -27255,12 +27267,18 @@ msgstr ""
msgid "Terraform|The Terraform report %{name} was generated in your pipelines." msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
msgstr "" msgstr ""
msgid "Terraform|To remove the State file and its versions, type %{name} to confirm:"
msgstr ""
msgid "Terraform|Unknown User" msgid "Terraform|Unknown User"
msgstr "" msgstr ""
msgid "Terraform|Unlock" msgid "Terraform|Unlock"
msgstr "" msgstr ""
msgid "Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously\twill remain intact, only the state file with all its versions are to be removed. This action is non-revertible."
msgstr ""
msgid "Test" msgid "Test"
msgstr "" msgstr ""
......
...@@ -54,6 +54,24 @@ RSpec.describe 'Terraform', :js do ...@@ -54,6 +54,24 @@ RSpec.describe 'Terraform', :js do
expect(page).to have_content(terraform_state.name) expect(page).to have_content(terraform_state.name)
end end
end end
context 'when clicking on the delete button' do
let(:additional_state) { create(:terraform_state, project: project) }
it 'removes the state', :aggregate_failures do
visit project_terraform_index_path(project)
expect(page).to have_content(additional_state.name)
find("[data-testid='terraform-state-actions-#{additional_state.name}']").click
find('[data-testid="terraform-state-remove"]').click
fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
click_button 'Remove'
expect(page).not_to have_content(additional_state.name)
expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end end
end end
......
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import StateActions from '~/terraform/components/states_table_actions.vue'; import StateActions from '~/terraform/components/states_table_actions.vue';
import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql'; import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql';
import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql'; import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -11,6 +12,7 @@ localVue.use(VueApollo); ...@@ -11,6 +12,7 @@ localVue.use(VueApollo);
describe('StatesTableActions', () => { describe('StatesTableActions', () => {
let lockResponse; let lockResponse;
let removeResponse;
let unlockResponse; let unlockResponse;
let wrapper; let wrapper;
...@@ -26,12 +28,17 @@ describe('StatesTableActions', () => { ...@@ -26,12 +28,17 @@ describe('StatesTableActions', () => {
const createMockApolloProvider = () => { const createMockApolloProvider = () => {
lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } }); lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
removeResponse = jest
.fn()
.mockResolvedValue({ data: { terraformStateDelete: { errors: [] } } });
unlockResponse = jest unlockResponse = jest
.fn() .fn()
.mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } }); .mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
return createMockApollo([ return createMockApollo([
[lockStateMutation, lockResponse], [lockStateMutation, lockResponse],
[removeStateMutation, removeResponse],
[unlockStateMutation, unlockResponse], [unlockStateMutation, unlockResponse],
]); ]);
}; };
...@@ -43,7 +50,7 @@ describe('StatesTableActions', () => { ...@@ -43,7 +50,7 @@ describe('StatesTableActions', () => {
apolloProvider, apolloProvider,
localVue, localVue,
propsData, propsData,
stubs: { GlDropdown }, stubs: { GlDropdown, GlModal, GlSprintf },
}); });
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
...@@ -52,6 +59,8 @@ describe('StatesTableActions', () => { ...@@ -52,6 +59,8 @@ describe('StatesTableActions', () => {
const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]'); const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]'); const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]');
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]'); const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
const findRemoveBtn = () => wrapper.find('[data-testid="terraform-state-remove"]');
const findRemoveModal = () => wrapper.find(GlModal);
beforeEach(() => { beforeEach(() => {
return createComponent(); return createComponent();
...@@ -59,6 +68,7 @@ describe('StatesTableActions', () => { ...@@ -59,6 +68,7 @@ describe('StatesTableActions', () => {
afterEach(() => { afterEach(() => {
lockResponse = null; lockResponse = null;
removeResponse = null;
unlockResponse = null; unlockResponse = null;
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -137,4 +147,43 @@ describe('StatesTableActions', () => { ...@@ -137,4 +147,43 @@ describe('StatesTableActions', () => {
}); });
}); });
}); });
describe('remove button', () => {
it('displays a remove button', () => {
expect(findRemoveBtn().text()).toBe(StateActions.i18n.remove);
});
describe('when clicking the remove button', () => {
beforeEach(() => {
findRemoveBtn().vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('displays a remove modal', () => {
expect(findRemoveModal().text()).toContain(
`You are about to remove the State file ${defaultProps.state.name}`,
);
});
describe('when submitting the remove modal', () => {
it('does not call the remove mutation when state name is missing', async () => {
findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(removeResponse).not.toHaveBeenCalledWith();
});
it('calls the remove mutation when state name is present', async () => {
await wrapper.setData({ removeConfirmText: defaultProps.state.name });
findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(removeResponse).toHaveBeenCalledWith({
stateID: defaultProps.state.id,
});
});
});
});
});
}); });
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