Commit 5c115050 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '292713-tf-lock-loading' into 'master'

Add loading display to Terraform lock actions

See merge request gitlab-org/gitlab!53475
parents 91dc753a ba67fcca
<script> <script>
import { GlAlert, GlBadge, GlIcon, GlLink, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; import {
GlAlert,
GlBadge,
GlIcon,
GlLink,
GlLoadingIcon,
GlSprintf,
GlTable,
GlTooltip,
} from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
...@@ -14,6 +23,7 @@ export default { ...@@ -14,6 +23,7 @@ export default {
GlBadge, GlBadge,
GlIcon, GlIcon,
GlLink, GlLink,
GlLoadingIcon,
GlSprintf, GlSprintf,
GlTable, GlTable,
GlTooltip, GlTooltip,
...@@ -67,15 +77,20 @@ export default { ...@@ -67,15 +77,20 @@ export default {
jobStatus: s__('Terraform|Job status'), jobStatus: s__('Terraform|Job status'),
locked: s__('Terraform|Locked'), locked: s__('Terraform|Locked'),
lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'), lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'),
lockingState: s__('Terraform|Locking state'),
name: s__('Terraform|Name'), name: s__('Terraform|Name'),
pipeline: s__('Terraform|Pipeline'), pipeline: s__('Terraform|Pipeline'),
unknownUser: s__('Terraform|Unknown User'), unknownUser: s__('Terraform|Unknown User'),
unlockingState: s__('Terraform|Unlocking state'),
updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), updatedUser: s__('Terraform|%{user} updated %{timeAgo}'),
}, },
methods: { methods: {
createdByUserName(item) { createdByUserName(item) {
return item.latestVersion?.createdByUser?.name; return item.latestVersion?.createdByUser?.name;
}, },
loadingLockText(item) {
return item.lockedAt ? this.$options.i18n.unlockingState : this.$options.i18n.lockingState;
},
lockedByUserName(item) { lockedByUserName(item) {
return item.lockedByUser?.name || this.$options.i18n.unknownUser; return item.lockedByUser?.name || this.$options.i18n.unknownUser;
}, },
...@@ -119,7 +134,18 @@ export default { ...@@ -119,7 +134,18 @@ export default {
{{ item.name }} {{ item.name }}
</p> </p>
<div v-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" class="gl-mx-2"> <div v-if="item.loadingLock" class="gl-mx-3">
<p class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0">
<gl-loading-icon class="gl-pr-1" />
{{ loadingLockText(item) }}
</p>
</div>
<div
v-else-if="item.lockedAt"
:id="`terraformLockedBadgeContainer${item.name}`"
class="gl-mx-3"
>
<gl-badge :id="`terraformLockedBadge${item.name}`"> <gl-badge :id="`terraformLockedBadge${item.name}`">
<gl-icon name="lock" /> <gl-icon name="lock" />
{{ $options.i18n.locked }} {{ $options.i18n.locked }}
......
...@@ -64,6 +64,9 @@ export default { ...@@ -64,6 +64,9 @@ export default {
disableModalSubmit() { disableModalSubmit() {
return this.removeConfirmText !== this.state.name; return this.removeConfirmText !== this.state.name;
}, },
loading() {
return this.state.loadingLock || this.state.loadingRemove;
},
primaryModalProps() { primaryModalProps() {
return { return {
text: this.$options.i18n.modalRemove, text: this.$options.i18n.modalRemove,
...@@ -77,9 +80,23 @@ export default { ...@@ -77,9 +80,23 @@ export default {
this.removeConfirmText = ''; this.removeConfirmText = '';
}, },
lock() { lock() {
this.updateStateCache({
_showDetails: false,
errorMessages: [],
loadingLock: true,
loadingRemove: false,
});
this.stateActionMutation(lockState); this.stateActionMutation(lockState);
}, },
unlock() { unlock() {
this.updateStateCache({
_showDetails: false,
errorMessages: [],
loadingLock: true,
loadingRemove: false,
});
this.stateActionMutation(unlockState); this.stateActionMutation(unlockState);
}, },
updateStateCache(newData) { updateStateCache(newData) {
...@@ -96,18 +113,20 @@ export default { ...@@ -96,18 +113,20 @@ export default {
remove() { remove() {
if (!this.disableModalSubmit) { if (!this.disableModalSubmit) {
this.hideModal(); this.hideModal();
this.updateStateCache({
_showDetails: false,
errorMessages: [],
loadingLock: false,
loadingRemove: true,
});
this.stateActionMutation(removeState); this.stateActionMutation(removeState);
} }
}, },
stateActionMutation(mutation) { stateActionMutation(mutation) {
let errorMessages = []; let errorMessages = [];
this.updateStateCache({
_showDetails: false,
errorMessages,
loadingActions: true,
});
this.$apollo this.$apollo
.mutate({ .mutate({
mutation, mutation,
...@@ -132,7 +151,8 @@ export default { ...@@ -132,7 +151,8 @@ export default {
this.updateStateCache({ this.updateStateCache({
_showDetails: Boolean(errorMessages.length), _showDetails: Boolean(errorMessages.length),
errorMessages, errorMessages,
loadingActions: false, loadingLock: false,
loadingRemove: false,
}); });
}); });
}, },
...@@ -146,7 +166,7 @@ export default { ...@@ -146,7 +166,7 @@ export default {
icon="ellipsis_v" icon="ellipsis_v"
right right
:data-testid="`terraform-state-actions-${state.name}`" :data-testid="`terraform-state-actions-${state.name}`"
:disabled="state.loadingActions" :disabled="loading"
toggle-class="gl-px-3! gl-shadow-none!" toggle-class="gl-px-3! gl-shadow-none!"
> >
<template #button-content> <template #button-content>
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
fragment State on TerraformState { fragment State on TerraformState {
_showDetails @client _showDetails @client
errorMessages @client errorMessages @client
loadingActions @client loadingLock @client
loadingRemove @client
id id
name name
......
...@@ -9,8 +9,11 @@ export default { ...@@ -9,8 +9,11 @@ export default {
errorMessages: (state) => { errorMessages: (state) => {
return state.errorMessages || []; return state.errorMessages || [];
}, },
loadingActions: (state) => { loadingLock: (state) => {
return state.loadingActions || false; return state.loadingLock || false;
},
loadingRemove: (state) => {
return state.loadingRemove || false;
}, },
}, },
Mutation: { Mutation: {
...@@ -32,7 +35,8 @@ export default { ...@@ -32,7 +35,8 @@ export default {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
_showDetails: terraformState._showDetails, _showDetails: terraformState._showDetails,
errorMessages: terraformState.errorMessages, errorMessages: terraformState.errorMessages,
loadingActions: terraformState.loadingActions, loadingLock: terraformState.loadingLock,
loadingRemove: terraformState.loadingRemove,
}, },
}); });
} }
......
---
title: Add loading display to Terraform lock action
merge_request: 53475
author:
type: changed
...@@ -28681,6 +28681,9 @@ msgstr "" ...@@ -28681,6 +28681,9 @@ msgstr ""
msgid "Terraform|Locked by %{user} %{timeAgo}" msgid "Terraform|Locked by %{user} %{timeAgo}"
msgstr "" msgstr ""
msgid "Terraform|Locking state"
msgstr ""
msgid "Terraform|Name" msgid "Terraform|Name"
msgstr "" msgstr ""
...@@ -28714,6 +28717,9 @@ msgstr "" ...@@ -28714,6 +28717,9 @@ msgstr ""
msgid "Terraform|Unlock" msgid "Terraform|Unlock"
msgstr "" msgstr ""
msgid "Terraform|Unlocking state"
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." 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 "" msgstr ""
......
...@@ -89,17 +89,34 @@ describe('StatesTableActions', () => { ...@@ -89,17 +89,34 @@ describe('StatesTableActions', () => {
}); });
describe('when the state is loading', () => { describe('when the state is loading', () => {
beforeEach(() => { describe('when lock/unlock is processing', () => {
return createComponent({ beforeEach(() => {
state: { return createComponent({
...defaultProps.state, state: {
loadingActions: true, ...defaultProps.state,
}, loadingLock: true,
},
});
});
it('disables the actions dropdown', () => {
expect(findActionsDropdown().props('disabled')).toBe(true);
}); });
}); });
it('disables the actions dropdown', () => { describe('when remove is processing', () => {
expect(findActionsDropdown().props('disabled')).toBe(true); beforeEach(() => {
return createComponent({
state: {
...defaultProps.state,
loadingRemove: true,
},
});
});
it('disables the actions dropdown', () => {
expect(findActionsDropdown().props('disabled')).toBe(true);
});
}); });
}); });
...@@ -188,7 +205,8 @@ describe('StatesTableActions', () => { ...@@ -188,7 +205,8 @@ describe('StatesTableActions', () => {
...unlockedProps.state, ...unlockedProps.state,
_showDetails: false, _showDetails: false,
errorMessages: [], errorMessages: [],
loadingActions: true, loadingLock: true,
loadingRemove: false,
}, },
}, },
// Apollo fields // Apollo fields
...@@ -205,7 +223,8 @@ describe('StatesTableActions', () => { ...@@ -205,7 +223,8 @@ describe('StatesTableActions', () => {
...unlockedProps.state, ...unlockedProps.state,
_showDetails: true, _showDetails: true,
errorMessages: ['There was an error'], errorMessages: ['There was an error'],
loadingActions: false, loadingLock: false,
loadingRemove: false,
}, },
}, },
// Apollo fields // Apollo fields
......
...@@ -14,6 +14,8 @@ describe('StatesTable', () => { ...@@ -14,6 +14,8 @@ describe('StatesTable', () => {
_showDetails: true, _showDetails: true,
errorMessages: ['State 1 has errored'], errorMessages: ['State 1 has errored'],
name: 'state-1', name: 'state-1',
loadingLock: false,
loadingRemove: false,
lockedAt: '2020-10-13T00:00:00Z', lockedAt: '2020-10-13T00:00:00Z',
lockedByUser: { lockedByUser: {
name: 'user-1', name: 'user-1',
...@@ -25,6 +27,8 @@ describe('StatesTable', () => { ...@@ -25,6 +27,8 @@ describe('StatesTable', () => {
_showDetails: false, _showDetails: false,
errorMessages: [], errorMessages: [],
name: 'state-2', name: 'state-2',
loadingLock: true,
loadingRemove: false,
lockedAt: null, lockedAt: null,
lockedByUser: null, lockedByUser: null,
updatedAt: '2020-10-10T00:00:00Z', updatedAt: '2020-10-10T00:00:00Z',
...@@ -34,6 +38,8 @@ describe('StatesTable', () => { ...@@ -34,6 +38,8 @@ describe('StatesTable', () => {
_showDetails: false, _showDetails: false,
errorMessages: [], errorMessages: [],
name: 'state-3', name: 'state-3',
loadingLock: true,
loadingRemove: false,
lockedAt: '2020-10-10T00:00:00Z', lockedAt: '2020-10-10T00:00:00Z',
lockedByUser: { lockedByUser: {
name: 'user-2', name: 'user-2',
...@@ -63,6 +69,8 @@ describe('StatesTable', () => { ...@@ -63,6 +69,8 @@ describe('StatesTable', () => {
_showDetails: true, _showDetails: true,
errorMessages: ['State 4 has errored'], errorMessages: ['State 4 has errored'],
name: 'state-4', name: 'state-4',
loadingLock: false,
loadingRemove: false,
lockedAt: '2020-10-10T00:00:00Z', lockedAt: '2020-10-10T00:00:00Z',
lockedByUser: null, lockedByUser: null,
updatedAt: '2020-10-10T00:00:00Z', updatedAt: '2020-10-10T00:00:00Z',
...@@ -106,8 +114,8 @@ describe('StatesTable', () => { ...@@ -106,8 +114,8 @@ describe('StatesTable', () => {
it.each` it.each`
name | toolTipText | locked | lineNumber name | toolTipText | locked | lineNumber
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0} ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0}
${'state-2'} | ${null} | ${false} | ${1} ${'state-2'} | ${'Locking state'} | ${false} | ${1}
${'state-3'} | ${'Locked by user-2 5 days ago'} | ${true} | ${2} ${'state-3'} | ${'Unlocking state'} | ${false} | ${2}
${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3} ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3}
`( `(
'displays the name and locked information "$name" for line "$lineNumber"', 'displays the name and locked information "$name" for line "$lineNumber"',
......
...@@ -2,6 +2,7 @@ import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab } from '@git ...@@ -2,6 +2,7 @@ import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab } from '@git
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import EmptyState from '~/terraform/components/empty_state.vue'; import EmptyState from '~/terraform/components/empty_state.vue';
import StatesTable from '~/terraform/components/states_table.vue'; import StatesTable from '~/terraform/components/states_table.vue';
import TerraformList from '~/terraform/components/terraform_list.vue'; import TerraformList from '~/terraform/components/terraform_list.vue';
...@@ -27,17 +28,20 @@ describe('TerraformList', () => { ...@@ -27,17 +28,20 @@ describe('TerraformList', () => {
}, },
}; };
// Override @client _showDetails const mockResolvers = {
getStatesQuery.getStates.definitions[1].selectionSet.selections[0].directives = []; TerraformState: {
_showDetails: jest.fn().mockResolvedValue(false),
// Override @client errorMessages errorMessages: jest.fn().mockResolvedValue([]),
getStatesQuery.getStates.definitions[1].selectionSet.selections[1].directives = []; loadingLock: jest.fn().mockResolvedValue(false),
loadingRemove: jest.fn().mockResolvedValue(false),
// Override @client loadingActions },
getStatesQuery.getStates.definitions[1].selectionSet.selections[2].directives = []; Mutation: {
addDataToTerraformState: jest.fn().mockResolvedValue({}),
},
};
const statsQueryResponse = queryResponse || jest.fn().mockResolvedValue(apolloQueryResponse); const statsQueryResponse = queryResponse || jest.fn().mockResolvedValue(apolloQueryResponse);
const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]]); const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]], mockResolvers);
wrapper = shallowMount(TerraformList, { wrapper = shallowMount(TerraformList, {
localVue, localVue,
...@@ -66,7 +70,8 @@ describe('TerraformList', () => { ...@@ -66,7 +70,8 @@ describe('TerraformList', () => {
id: 'gid://gitlab/Terraform::State/1', id: 'gid://gitlab/Terraform::State/1',
name: 'state-1', name: 'state-1',
latestVersion: null, latestVersion: null,
loadingActions: false, loadingLock: false,
loadingRemove: false,
lockedAt: null, lockedAt: null,
lockedByUser: null, lockedByUser: null,
updatedAt: null, updatedAt: null,
...@@ -77,7 +82,8 @@ describe('TerraformList', () => { ...@@ -77,7 +82,8 @@ describe('TerraformList', () => {
id: 'gid://gitlab/Terraform::State/2', id: 'gid://gitlab/Terraform::State/2',
name: 'state-2', name: 'state-2',
latestVersion: null, latestVersion: null,
loadingActions: false, loadingLock: false,
loadingRemove: false,
lockedAt: null, lockedAt: null,
lockedByUser: null, lockedByUser: null,
updatedAt: null, updatedAt: null,
...@@ -98,7 +104,7 @@ describe('TerraformList', () => { ...@@ -98,7 +104,7 @@ describe('TerraformList', () => {
}, },
}); });
return wrapper.vm.$nextTick(); return waitForPromises();
}); });
it('displays a states tab and count', () => { it('displays a states tab and count', () => {
...@@ -126,7 +132,7 @@ describe('TerraformList', () => { ...@@ -126,7 +132,7 @@ describe('TerraformList', () => {
}, },
}); });
return wrapper.vm.$nextTick(); return waitForPromises();
}); });
it('renders the states table without pagination buttons', () => { it('renders the states table without pagination buttons', () => {
...@@ -146,7 +152,7 @@ describe('TerraformList', () => { ...@@ -146,7 +152,7 @@ describe('TerraformList', () => {
}, },
}); });
return wrapper.vm.$nextTick(); return waitForPromises();
}); });
it('displays a states tab with no count', () => { it('displays a states tab with no count', () => {
...@@ -164,7 +170,7 @@ describe('TerraformList', () => { ...@@ -164,7 +170,7 @@ describe('TerraformList', () => {
beforeEach(() => { beforeEach(() => {
createWrapper({ terraformStates: null, queryResponse: jest.fn().mockRejectedValue() }); createWrapper({ terraformStates: null, queryResponse: jest.fn().mockRejectedValue() });
return wrapper.vm.$nextTick(); return waitForPromises();
}); });
it('displays an alert message', () => { it('displays an alert message', () => {
......
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