Commit 9ec44ece authored by Emily Ring's avatar Emily Ring Committed by Natalia Tepluhina

Add loading display when removing Terraform state

Updated terraform vue components to include loading display
Updated associated tests and translations
parent b028eb64
...@@ -80,6 +80,7 @@ export default { ...@@ -80,6 +80,7 @@ export default {
lockingState: s__('Terraform|Locking state'), lockingState: s__('Terraform|Locking state'),
name: s__('Terraform|Name'), name: s__('Terraform|Name'),
pipeline: s__('Terraform|Pipeline'), pipeline: s__('Terraform|Pipeline'),
removing: s__('Terraform|Removing'),
unknownUser: s__('Terraform|Unknown User'), unknownUser: s__('Terraform|Unknown User'),
unlockingState: s__('Terraform|Unlocking state'), unlockingState: s__('Terraform|Unlocking state'),
updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), updatedUser: s__('Terraform|%{user} updated %{timeAgo}'),
...@@ -141,6 +142,15 @@ export default { ...@@ -141,6 +142,15 @@ export default {
</p> </p>
</div> </div>
<div v-else-if="item.loadingRemove" class="gl-mx-3">
<p
class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0 gl-text-red-500"
>
<gl-loading-icon class="gl-pr-1" />
{{ $options.i18n.removing }}
</p>
</div>
<div <div
v-else-if="item.lockedAt" v-else-if="item.lockedAt"
:id="`terraformLockedBadgeContainer${item.name}`" :id="`terraformLockedBadgeContainer${item.name}`"
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
GlModal, GlModal,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__, sprintf } from '~/locale';
import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql'; import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql';
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';
...@@ -52,6 +52,7 @@ export default { ...@@ -52,6 +52,7 @@ export default {
), ),
modalRemove: s__('Terraform|Remove'), modalRemove: s__('Terraform|Remove'),
remove: s__('Terraform|Remove state file and versions'), remove: s__('Terraform|Remove state file and versions'),
removeSuccessful: s__('Terraform|%{name} successfully removed'),
unlock: s__('Terraform|Unlock'), unlock: s__('Terraform|Unlock'),
}, },
computed: { computed: {
...@@ -121,10 +122,13 @@ export default { ...@@ -121,10 +122,13 @@ export default {
loadingRemove: true, loadingRemove: true,
}); });
this.stateActionMutation(removeState); this.stateActionMutation(
removeState,
sprintf(this.$options.i18n.removeSuccessful, { name: this.state.name }),
);
} }
}, },
stateActionMutation(mutation) { stateActionMutation(mutation, successMessage = null) {
let errorMessages = []; let errorMessages = [];
this.$apollo this.$apollo
...@@ -143,6 +147,10 @@ export default { ...@@ -143,6 +147,10 @@ export default {
data?.terraformStateLock?.errors || data?.terraformStateLock?.errors ||
data?.terraformStateUnlock?.errors || data?.terraformStateUnlock?.errors ||
[]; [];
if (errorMessages.length === 0 && successMessage) {
this.$toast.show(successMessage);
}
}) })
.catch(() => { .catch(() => {
errorMessages = [this.$options.i18n.errorUpdate]; errorMessages = [this.$options.i18n.errorUpdate];
......
import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { GlToast } from '@gitlab/ui';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import TerraformList from './components/terraform_list.vue'; import TerraformList from './components/terraform_list.vue';
import resolvers from './graphql/resolvers'; import resolvers from './graphql/resolvers';
Vue.use(GlToast);
Vue.use(VueApollo); Vue.use(VueApollo);
export default () => { export default () => {
......
---
title: Display loading when removing Terraform state
merge_request: 53897
author:
type: changed
...@@ -28689,6 +28689,9 @@ msgstr "" ...@@ -28689,6 +28689,9 @@ msgstr ""
msgid "Terraform" msgid "Terraform"
msgstr "" msgstr ""
msgid "Terraform|%{name} successfully removed"
msgstr ""
msgid "Terraform|%{number} Terraform report failed to generate" msgid "Terraform|%{number} Terraform report failed to generate"
msgid_plural "Terraform|%{number} Terraform reports failed to generate" msgid_plural "Terraform|%{number} Terraform reports failed to generate"
msgstr[0] "" msgstr[0] ""
...@@ -28765,6 +28768,9 @@ msgstr "" ...@@ -28765,6 +28768,9 @@ msgstr ""
msgid "Terraform|Remove state file and versions" msgid "Terraform|Remove state file and versions"
msgstr "" msgstr ""
msgid "Terraform|Removing"
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 ""
......
...@@ -68,7 +68,7 @@ RSpec.describe 'Terraform', :js do ...@@ -68,7 +68,7 @@ RSpec.describe 'Terraform', :js do
fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
click_button 'Remove' click_button 'Remove'
expect(page).not_to have_content(additional_state.name) expect(page).to have_content("#{additional_state.name} successfully removed")
expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
end end
end end
......
...@@ -14,6 +14,7 @@ localVue.use(VueApollo); ...@@ -14,6 +14,7 @@ localVue.use(VueApollo);
describe('StatesTableActions', () => { describe('StatesTableActions', () => {
let lockResponse; let lockResponse;
let removeResponse; let removeResponse;
let toast;
let unlockResponse; let unlockResponse;
let updateStateResponse; let updateStateResponse;
let wrapper; let wrapper;
...@@ -59,10 +60,13 @@ describe('StatesTableActions', () => { ...@@ -59,10 +60,13 @@ describe('StatesTableActions', () => {
const createComponent = (propsData = defaultProps) => { const createComponent = (propsData = defaultProps) => {
const apolloProvider = createMockApolloProvider(); const apolloProvider = createMockApolloProvider();
toast = jest.fn();
wrapper = shallowMount(StateActions, { wrapper = shallowMount(StateActions, {
apolloProvider, apolloProvider,
localVue, localVue,
propsData, propsData,
mocks: { $toast: { show: toast } },
stubs: { GlDropdown, GlModal, GlSprintf }, stubs: { GlDropdown, GlModal, GlSprintf },
}); });
...@@ -83,6 +87,7 @@ describe('StatesTableActions', () => { ...@@ -83,6 +87,7 @@ describe('StatesTableActions', () => {
afterEach(() => { afterEach(() => {
lockResponse = null; lockResponse = null;
removeResponse = null; removeResponse = null;
toast = null;
unlockResponse = null; unlockResponse = null;
updateStateResponse = null; updateStateResponse = null;
wrapper.destroy(); wrapper.destroy();
...@@ -243,7 +248,6 @@ describe('StatesTableActions', () => { ...@@ -243,7 +248,6 @@ describe('StatesTableActions', () => {
describe('when clicking the remove button', () => { describe('when clicking the remove button', () => {
beforeEach(() => { beforeEach(() => {
findRemoveBtn().vm.$emit('click'); findRemoveBtn().vm.$emit('click');
return waitForPromises(); return waitForPromises();
}); });
...@@ -254,21 +258,70 @@ describe('StatesTableActions', () => { ...@@ -254,21 +258,70 @@ describe('StatesTableActions', () => {
}); });
describe('when submitting the remove modal', () => { describe('when submitting the remove modal', () => {
it('does not call the remove mutation when state name is missing', async () => { describe('when state name is missing', () => {
beforeEach(() => {
findRemoveModal().vm.$emit('ok'); findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick(); return waitForPromises();
});
it('does not call the remove mutation', () => {
expect(removeResponse).not.toHaveBeenCalledWith(); expect(removeResponse).not.toHaveBeenCalledWith();
}); });
});
it('calls the remove mutation when state name is present', async () => { describe('when state name is present', () => {
beforeEach(async () => {
await wrapper.setData({ removeConfirmText: defaultProps.state.name }); await wrapper.setData({ removeConfirmText: defaultProps.state.name });
findRemoveModal().vm.$emit('ok'); findRemoveModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(removeResponse).toHaveBeenCalledWith({ await waitForPromises();
stateID: defaultProps.state.id, });
it('calls the remove mutation', () => {
expect(removeResponse).toHaveBeenCalledWith({ stateID: defaultProps.state.id });
});
it('calls the toast action', () => {
expect(toast).toHaveBeenCalledWith(`${defaultProps.state.name} successfully removed`);
});
it('calls mutations to set loading and errors', () => {
// loading update
expect(updateStateResponse).toHaveBeenNthCalledWith(
1,
{},
{
terraformState: {
...defaultProps.state,
_showDetails: false,
errorMessages: [],
loadingLock: false,
loadingRemove: true,
},
},
// Apollo fields
expect.any(Object),
expect.any(Object),
);
// final update
expect(updateStateResponse).toHaveBeenNthCalledWith(
2,
{},
{
terraformState: {
...defaultProps.state,
_showDetails: false,
errorMessages: [],
loadingLock: false,
loadingRemove: false,
},
},
// Apollo fields
expect.any(Object),
expect.any(Object),
);
}); });
}); });
}); });
......
import { GlIcon, GlTooltip } from '@gitlab/ui'; import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import StateActions from '~/terraform/components/states_table_actions.vue'; import StateActions from '~/terraform/components/states_table_actions.vue';
...@@ -92,6 +92,17 @@ describe('StatesTable', () => { ...@@ -92,6 +92,17 @@ describe('StatesTable', () => {
}, },
}, },
}, },
{
_showDetails: false,
errorMessages: [],
name: 'state-5',
loadingLock: false,
loadingRemove: true,
lockedAt: null,
lockedByUser: null,
updatedAt: '2020-10-10T00:00:00Z',
latestVersion: null,
},
], ],
}; };
...@@ -112,14 +123,15 @@ describe('StatesTable', () => { ...@@ -112,14 +123,15 @@ describe('StatesTable', () => {
}); });
it.each` it.each`
name | toolTipText | locked | lineNumber name | toolTipText | locked | loading | lineNumber
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0} ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0}
${'state-2'} | ${'Locking state'} | ${false} | ${1} ${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1}
${'state-3'} | ${'Unlocking state'} | ${false} | ${2} ${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2}
${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3} ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3}
${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4}
`( `(
'displays the name and locked information "$name" for line "$lineNumber"', 'displays the name and locked information "$name" for line "$lineNumber"',
({ name, toolTipText, locked, lineNumber }) => { ({ name, toolTipText, locked, loading, lineNumber }) => {
const states = wrapper.findAll('[data-testid="terraform-states-table-name"]'); const states = wrapper.findAll('[data-testid="terraform-states-table-name"]');
const state = states.at(lineNumber); const state = states.at(lineNumber);
...@@ -127,6 +139,7 @@ describe('StatesTable', () => { ...@@ -127,6 +139,7 @@ describe('StatesTable', () => {
expect(state.text()).toContain(name); expect(state.text()).toContain(name);
expect(state.find(GlIcon).exists()).toBe(locked); expect(state.find(GlIcon).exists()).toBe(locked);
expect(state.find(GlLoadingIcon).exists()).toBe(loading);
expect(toolTip.exists()).toBe(locked); expect(toolTip.exists()).toBe(locked);
if (locked) { if (locked) {
......
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