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 {
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: {
createdByUserName(item) {
return item.latestVersion?.createdByUser?.name;
},
lockedByUserName(item) {
return item.lockedByUser?.name || s__('Terraform|Unknown User');
return item.lockedByUser?.name || this.$options.i18n.unknownUser;
},
updatedTime(item) {
return item.latestVersion?.updatedAt || item.updatedAt;
......@@ -74,18 +80,18 @@ export default {
{{ item.name }}
</p>
<div v-if="item.lockedAt" id="terraformLockedBadgeContainer" class="gl-mx-2">
<gl-badge id="terraformLockedBadge">
<div v-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" class="gl-mx-2">
<gl-badge :id="`terraformLockedBadge${item.name}`">
<gl-icon name="lock" />
{{ s__('Terraform|Locked') }}
{{ $options.i18n.locked }}
</gl-badge>
<gl-tooltip
container="terraformLockedBadgeContainer"
:container="`terraformLockedBadgeContainer${item.name}`"
:target="`terraformLockedBadge${item.name}`"
placement="right"
target="terraformLockedBadge"
>
<gl-sprintf :message="s__('Terraform|Locked by %{user} %{timeAgo}')">
<gl-sprintf :message="$options.i18n.lockedByUser">
<template #user>
{{ lockedByUserName(item) }}
</template>
......@@ -101,7 +107,7 @@ export default {
<template #cell(updated)="{ item }">
<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>
<span v-if="item.latestVersion">
{{ createdByUserName(item) }}
......
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
import unlockState from '../graphql/mutations/unlock_state.mutation.graphql';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlIcon,
},
props: {
state: {
......@@ -13,22 +16,73 @@ export default {
type: Object,
},
},
data() {
return {
loading: false,
};
},
i18n: {
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>
<template>
<div v-if="state.latestVersion">
<gl-dropdown icon="ellipsis_v" right :data-testid="`terraform-state-actions-${state.name}`">
<div>
<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
v-if="state.latestVersion"
data-testid="terraform-state-download"
:download="`${state.name}.json`"
:href="state.latestVersion.downloadPath"
>
{{ $options.i18n.downloadJSON }}
</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>
</div>
</template>
......@@ -15,13 +15,7 @@ export default {
...this.cursor,
};
},
update: data => {
return {
count: data?.project?.terraformStates?.count,
list: data?.project?.terraformStates?.nodes,
pageInfo: data?.project?.terraformStates?.pageInfo,
};
},
update: data => data,
error() {
this.states = null;
},
......@@ -67,35 +61,34 @@ export default {
return this.$apollo.queries.states.loading;
},
pageInfo() {
return this.states?.pageInfo || {};
return this.states?.project?.terraformStates?.pageInfo || {};
},
showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
},
statesCount() {
return this.states?.count;
return this.states?.project?.terraformStates?.count;
},
statesList() {
return this.states?.list;
return this.states?.project?.terraformStates?.nodes;
},
},
methods: {
updatePagination(item) {
if (item === this.pageInfo.endCursor) {
this.cursor = {
first: MAX_LIST_COUNT,
after: item,
last: null,
before: null,
};
} else {
this.cursor = {
first: null,
after: null,
last: MAX_LIST_COUNT,
before: item,
};
}
nextPage(item) {
this.cursor = {
first: MAX_LIST_COUNT,
after: item,
last: null,
before: null,
};
},
prevPage(item) {
this.cursor = {
first: null,
after: null,
last: MAX_LIST_COUNT,
before: item,
};
},
},
};
......@@ -119,11 +112,7 @@ export default {
<states-table :states="statesList" :terraform-admin="terraformAdmin" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination
v-bind="pageInfo"
@prev="updatePagination"
@next="updatePagination"
/>
<gl-keyset-pagination v-bind="pageInfo" @prev="prevPage" @next="nextPage" />
</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 ""
msgid "Terraform|Get started with Terraform"
msgstr ""
msgid "Terraform|Lock"
msgstr ""
msgid "Terraform|Locked"
msgstr ""
......@@ -27105,6 +27108,9 @@ msgstr ""
msgid "Terraform|Unknown User"
msgstr ""
msgid "Terraform|Unlock"
msgstr ""
msgid "Test"
msgstr ""
......
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 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', () => {
let lockResponse;
let unlockResponse;
let wrapper;
const defaultProps = {
......@@ -10,11 +19,29 @@ describe('StatesTableActions', () => {
id: 'gid/1',
name: 'state-1',
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 apolloProvider = createMockApolloProvider();
wrapper = shallowMount(StateActions, {
apolloProvider,
localVue,
propsData,
stubs: { GlDropdown },
});
......@@ -22,37 +49,92 @@ describe('StatesTableActions', () => {
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"]');
beforeEach(() => {
return createComponent();
});
afterEach(() => {
lockResponse = null;
unlockResponse = null;
wrapper.destroy();
});
describe('when state has a latestVersion', () => {
beforeEach(() => {
return createComponent();
describe('download button', () => {
it('displays a download button', () => {
expect(findDownloadBtn().text()).toBe('Download JSON');
});
it('displays a download button', () => {
const downloadBtn = findDownloadBtn();
describe('when state does not have a latestVersion', () => {
beforeEach(() => {
return createComponent({
state: {
id: 'gid/1',
name: 'state-1',
latestVersion: null,
},
});
});
expect(downloadBtn.text()).toBe('Download JSON');
it('does not display a download button', () => {
expect(findDownloadBtn().exists()).toBe(false);
});
});
});
describe('when state does not have a latestVersion', () => {
beforeEach(() => {
return createComponent({
state: {
id: 'gid/1',
name: 'state-1',
latestVersion: null,
},
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);
});
it('does not display a download button', () => {
expect(findDownloadBtn().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