Commit d4074416 authored by Emily Ring's avatar Emily Ring Committed by Phil Hughes

Added lock buttons to terraform list vue

Added lock and unlock button to terraform states list vue
Updated associated translations and tests
parent fa85a714
...@@ -52,12 +52,18 @@ export default { ...@@ -52,12 +52,18 @@ export default {
return columns; return columns;
}, },
}, },
i18n: {
locked: s__('Terraform|Locked'),
lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'),
unknownUser: s__('Terraform|Unknown User'),
updatedUser: s__('Terraform|%{user} updated %{timeAgo}'),
},
methods: { methods: {
createdByUserName(item) { createdByUserName(item) {
return item.latestVersion?.createdByUser?.name; return item.latestVersion?.createdByUser?.name;
}, },
lockedByUserName(item) { lockedByUserName(item) {
return item.lockedByUser?.name || s__('Terraform|Unknown User'); return item.lockedByUser?.name || this.$options.i18n.unknownUser;
}, },
updatedTime(item) { updatedTime(item) {
return item.latestVersion?.updatedAt || item.updatedAt; return item.latestVersion?.updatedAt || item.updatedAt;
...@@ -74,18 +80,18 @@ export default { ...@@ -74,18 +80,18 @@ export default {
{{ item.name }} {{ item.name }}
</p> </p>
<div v-if="item.lockedAt" id="terraformLockedBadgeContainer" class="gl-mx-2"> <div v-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" class="gl-mx-2">
<gl-badge id="terraformLockedBadge"> <gl-badge :id="`terraformLockedBadge${item.name}`">
<gl-icon name="lock" /> <gl-icon name="lock" />
{{ s__('Terraform|Locked') }} {{ $options.i18n.locked }}
</gl-badge> </gl-badge>
<gl-tooltip <gl-tooltip
container="terraformLockedBadgeContainer" :container="`terraformLockedBadgeContainer${item.name}`"
:target="`terraformLockedBadge${item.name}`"
placement="right" placement="right"
target="terraformLockedBadge"
> >
<gl-sprintf :message="s__('Terraform|Locked by %{user} %{timeAgo}')"> <gl-sprintf :message="$options.i18n.lockedByUser">
<template #user> <template #user>
{{ lockedByUserName(item) }} {{ lockedByUserName(item) }}
</template> </template>
...@@ -101,7 +107,7 @@ export default { ...@@ -101,7 +107,7 @@ export default {
<template #cell(updated)="{ item }"> <template #cell(updated)="{ item }">
<p class="gl-m-0" data-testid="terraform-states-table-updated"> <p class="gl-m-0" data-testid="terraform-states-table-updated">
<gl-sprintf :message="s__('Terraform|%{user} updated %{timeAgo}')"> <gl-sprintf :message="$options.i18n.updatedUser">
<template #user> <template #user>
<span v-if="item.latestVersion"> <span v-if="item.latestVersion">
{{ createdByUserName(item) }} {{ createdByUserName(item) }}
......
<script> <script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
import unlockState from '../graphql/mutations/unlock_state.mutation.graphql';
export default { export default {
components: { components: {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlIcon,
}, },
props: { props: {
state: { state: {
...@@ -13,22 +16,73 @@ export default { ...@@ -13,22 +16,73 @@ export default {
type: Object, type: Object,
}, },
}, },
data() {
return {
loading: false,
};
},
i18n: { i18n: {
downloadJSON: s__('Terraform|Download JSON'), downloadJSON: s__('Terraform|Download JSON'),
lock: s__('Terraform|Lock'),
unlock: s__('Terraform|Unlock'),
},
methods: {
lock() {
this.stateMutation(lockState);
},
unlock() {
this.stateMutation(unlockState);
},
stateMutation(mutation) {
this.loading = true;
this.$apollo
.mutate({
mutation,
variables: {
stateID: this.state.id,
},
refetchQueries: () => ['getStates'],
awaitRefetchQueries: true,
notifyOnNetworkStatusChange: true,
})
.catch(() => {})
.finally(() => {
this.loading = false;
});
},
}, },
}; };
</script> </script>
<template> <template>
<div v-if="state.latestVersion"> <div>
<gl-dropdown icon="ellipsis_v" right :data-testid="`terraform-state-actions-${state.name}`"> <gl-dropdown
icon="ellipsis_v"
right
:data-testid="`terraform-state-actions-${state.name}`"
:disabled="loading"
toggle-class="gl-px-3! gl-shadow-none!"
>
<template #button-content>
<gl-icon class="gl-mr-0" name="ellipsis_v" />
</template>
<gl-dropdown-item <gl-dropdown-item
v-if="state.latestVersion"
data-testid="terraform-state-download" data-testid="terraform-state-download"
:download="`${state.name}.json`" :download="`${state.name}.json`"
:href="state.latestVersion.downloadPath" :href="state.latestVersion.downloadPath"
> >
{{ $options.i18n.downloadJSON }} {{ $options.i18n.downloadJSON }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-item v-if="state.lockedAt" data-testid="terraform-state-unlock" @click="unlock">
{{ $options.i18n.unlock }}
</gl-dropdown-item>
<gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock">
{{ $options.i18n.lock }}
</gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</div> </div>
</template> </template>
...@@ -15,13 +15,7 @@ export default { ...@@ -15,13 +15,7 @@ export default {
...this.cursor, ...this.cursor,
}; };
}, },
update: data => { update: data => data,
return {
count: data?.project?.terraformStates?.count,
list: data?.project?.terraformStates?.nodes,
pageInfo: data?.project?.terraformStates?.pageInfo,
};
},
error() { error() {
this.states = null; this.states = null;
}, },
...@@ -67,35 +61,34 @@ export default { ...@@ -67,35 +61,34 @@ export default {
return this.$apollo.queries.states.loading; return this.$apollo.queries.states.loading;
}, },
pageInfo() { pageInfo() {
return this.states?.pageInfo || {}; return this.states?.project?.terraformStates?.pageInfo || {};
}, },
showPagination() { showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
}, },
statesCount() { statesCount() {
return this.states?.count; return this.states?.project?.terraformStates?.count;
}, },
statesList() { statesList() {
return this.states?.list; return this.states?.project?.terraformStates?.nodes;
}, },
}, },
methods: { methods: {
updatePagination(item) { nextPage(item) {
if (item === this.pageInfo.endCursor) {
this.cursor = { this.cursor = {
first: MAX_LIST_COUNT, first: MAX_LIST_COUNT,
after: item, after: item,
last: null, last: null,
before: null, before: null,
}; };
} else { },
prevPage(item) {
this.cursor = { this.cursor = {
first: null, first: null,
after: null, after: null,
last: MAX_LIST_COUNT, last: MAX_LIST_COUNT,
before: item, before: item,
}; };
}
}, },
}, },
}; };
...@@ -119,11 +112,7 @@ export default { ...@@ -119,11 +112,7 @@ export default {
<states-table :states="statesList" :terraform-admin="terraformAdmin" /> <states-table :states="statesList" :terraform-admin="terraformAdmin" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5"> <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination <gl-keyset-pagination v-bind="pageInfo" @prev="prevPage" @next="nextPage" />
v-bind="pageInfo"
@prev="updatePagination"
@next="updatePagination"
/>
</div> </div>
</div> </div>
......
mutation lockState($stateID: TerraformStateID!) {
terraformStateLock(input: { id: $stateID }) {
errors
}
}
mutation unlockState($stateID: TerraformStateID!) {
terraformStateUnlock(input: { id: $stateID }) {
errors
}
}
---
title: Add lock button to the Terraform State list view
merge_request: 47842
author:
type: added
...@@ -27084,6 +27084,9 @@ msgstr "" ...@@ -27084,6 +27084,9 @@ msgstr ""
msgid "Terraform|Get started with Terraform" msgid "Terraform|Get started with Terraform"
msgstr "" msgstr ""
msgid "Terraform|Lock"
msgstr ""
msgid "Terraform|Locked" msgid "Terraform|Locked"
msgstr "" msgstr ""
...@@ -27105,6 +27108,9 @@ msgstr "" ...@@ -27105,6 +27108,9 @@ msgstr ""
msgid "Terraform|Unknown User" msgid "Terraform|Unknown User"
msgstr "" msgstr ""
msgid "Terraform|Unlock"
msgstr ""
msgid "Test" msgid "Test"
msgstr "" msgstr ""
......
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
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 unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('StatesTableActions', () => { describe('StatesTableActions', () => {
let lockResponse;
let unlockResponse;
let wrapper; let wrapper;
const defaultProps = { const defaultProps = {
...@@ -10,11 +19,29 @@ describe('StatesTableActions', () => { ...@@ -10,11 +19,29 @@ describe('StatesTableActions', () => {
id: 'gid/1', id: 'gid/1',
name: 'state-1', name: 'state-1',
latestVersion: { downloadPath: '/path' }, latestVersion: { downloadPath: '/path' },
lockedAt: '2020-10-13T00:00:00Z',
}, },
}; };
const createMockApolloProvider = () => {
lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
unlockResponse = jest
.fn()
.mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
return createMockApollo([
[lockStateMutation, lockResponse],
[unlockStateMutation, unlockResponse],
]);
};
const createComponent = (propsData = defaultProps) => { const createComponent = (propsData = defaultProps) => {
const apolloProvider = createMockApolloProvider();
wrapper = shallowMount(StateActions, { wrapper = shallowMount(StateActions, {
apolloProvider,
localVue,
propsData, propsData,
stubs: { GlDropdown }, stubs: { GlDropdown },
}); });
...@@ -22,22 +49,23 @@ describe('StatesTableActions', () => { ...@@ -22,22 +49,23 @@ describe('StatesTableActions', () => {
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}; };
const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
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"]');
afterEach(() => {
wrapper.destroy();
});
describe('when state has a latestVersion', () => {
beforeEach(() => { beforeEach(() => {
return createComponent(); return createComponent();
}); });
it('displays a download button', () => { afterEach(() => {
const downloadBtn = findDownloadBtn(); lockResponse = null;
unlockResponse = null;
expect(downloadBtn.text()).toBe('Download JSON'); wrapper.destroy();
}); });
describe('download button', () => {
it('displays a download button', () => {
expect(findDownloadBtn().text()).toBe('Download JSON');
}); });
describe('when state does not have a latestVersion', () => { describe('when state does not have a latestVersion', () => {
...@@ -55,4 +83,58 @@ describe('StatesTableActions', () => { ...@@ -55,4 +83,58 @@ describe('StatesTableActions', () => {
expect(findDownloadBtn().exists()).toBe(false); expect(findDownloadBtn().exists()).toBe(false);
}); });
}); });
});
describe('unlock button', () => {
it('displays an unlock button', () => {
expect(findUnlockBtn().text()).toBe('Unlock');
expect(findLockBtn().exists()).toBe(false);
});
describe('when clicking the unlock button', () => {
beforeEach(() => {
findUnlockBtn().vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('calls the unlock mutation', () => {
expect(unlockResponse).toHaveBeenCalledWith({
stateID: defaultProps.state.id,
});
});
});
});
describe('lock button', () => {
const unlockedProps = {
state: {
id: 'gid/2',
name: 'state-2',
latestVersion: null,
lockedAt: null,
},
};
beforeEach(() => {
return createComponent(unlockedProps);
});
it('displays a lock button', () => {
expect(findLockBtn().text()).toBe('Lock');
expect(findUnlockBtn().exists()).toBe(false);
});
describe('when clicking the lock button', () => {
beforeEach(() => {
findLockBtn().vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('calls the lock mutation', () => {
expect(lockResponse).toHaveBeenCalledWith({
stateID: unlockedProps.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