Commit 4348c959 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'psi-cadence-edit-delete-' into 'master'

Add button to delete cadence

See merge request gitlab-org/gitlab!62547
parents 8392eb0f be3adf55
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
GlInfiniteScroll, GlInfiniteScroll,
GlModal,
GlSkeletonLoader, GlSkeletonLoader,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
...@@ -18,6 +19,14 @@ const pageSize = 20; ...@@ -18,6 +19,14 @@ const pageSize = 20;
const i18n = Object.freeze({ const i18n = Object.freeze({
noResults: s__('Iterations|No iterations in cadence.'), noResults: s__('Iterations|No iterations in cadence.'),
error: __('Error loading iterations'), error: __('Error loading iterations'),
deleteCadence: s__('Iterations|Delete cadence'),
modalTitle: s__('Iterations|Delete iteration cadence?'),
modalText: s__(
'Iterations|This will delete the cadence as well as all of the iterations within it.',
),
modalConfirm: s__('Iterations|Delete cadence'),
modalCancel: __('Cancel'),
}); });
export default { export default {
...@@ -30,6 +39,7 @@ export default { ...@@ -30,6 +39,7 @@ export default {
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
GlInfiniteScroll, GlInfiniteScroll,
GlModal,
GlSkeletonLoader, GlSkeletonLoader,
}, },
apollo: { apollo: {
...@@ -154,6 +164,12 @@ export default { ...@@ -154,6 +164,12 @@ export default {
}, },
}; };
}, },
showModal() {
this.$refs.modal.show();
},
focusMenu() {
this.$refs.menu.$el.focus();
},
}, },
}; };
</script> </script>
...@@ -181,17 +197,31 @@ export default { ...@@ -181,17 +197,31 @@ export default {
> >
<gl-dropdown <gl-dropdown
v-if="canEditCadence" v-if="canEditCadence"
ref="menu"
icon="ellipsis_v" icon="ellipsis_v"
category="tertiary" category="tertiary"
right right
lazy
text-sr-only text-sr-only
no-caret no-caret
> >
<gl-dropdown-item :to="editCadence"> <gl-dropdown-item :to="editCadence">
{{ s__('Iterations|Edit cadence') }} {{ s__('Iterations|Edit cadence') }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-item data-testid="delete-cadence" @click="showModal">
{{ i18n.deleteCadence }}
</gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<gl-modal
ref="modal"
:modal-id="`${cadenceId}-delete-modal`"
:title="i18n.modalTitle"
:ok-title="i18n.modalConfirm"
ok-variant="danger"
@hidden="focusMenu"
@ok="$emit('delete-cadence', cadenceId)"
>
{{ i18n.modalText }}
</gl-modal>
</div> </div>
<gl-alert v-if="error" variant="danger" :dismissible="true" @dismiss="error = ''"> <gl-alert v-if="error" variant="danger" :dismissible="true" @dismiss="error = ''">
......
<script> <script>
import { GlAlert, GlButton, GlLoadingIcon, GlKeysetPagination, GlTab, GlTabs } from '@gitlab/ui'; import { GlAlert, GlButton, GlLoadingIcon, GlKeysetPagination, GlTab, GlTabs } from '@gitlab/ui';
import produce from 'immer';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import destroyIterationCadence from '../queries/destroy_cadence.mutation.graphql';
import query from '../queries/iteration_cadences_list.query.graphql'; import query from '../queries/iteration_cadences_list.query.graphql';
import IterationCadenceListItem from './iteration_cadence_list_item.vue'; import IterationCadenceListItem from './iteration_cadence_list_item.vue';
...@@ -96,6 +98,40 @@ export default { ...@@ -96,6 +98,40 @@ export default {
handleTabChange() { handleTabChange() {
this.pagination = {}; this.pagination = {};
}, },
deleteCadence(cadenceId) {
this.$apollo
.mutate({
mutation: destroyIterationCadence,
variables: {
id: cadenceId,
},
update: (store, { data: { iterationCadenceDestroy } }) => {
if (iterationCadenceDestroy.errors?.length) {
throw iterationCadenceDestroy.errors[0];
}
const sourceData = store.readQuery({
query,
variables: this.queryVariables,
});
const data = produce(sourceData, (draftData) => {
draftData.group.iterationCadences.nodes = draftData.group.iterationCadences.nodes.filter(
({ id }) => id !== cadenceId,
);
});
store.writeQuery({
query,
variables: this.queryVariables,
data,
});
},
})
.catch((err) => {
this.error = err;
});
},
}, },
}; };
</script> </script>
...@@ -122,6 +158,7 @@ export default { ...@@ -122,6 +158,7 @@ export default {
:duration-in-weeks="cadence.durationInWeeks" :duration-in-weeks="cadence.durationInWeeks"
:title="cadence.title" :title="cadence.title"
:iteration-state="state" :iteration-state="state"
@delete-cadence="deleteCadence"
/> />
</ul> </ul>
<p v-else class="nothing-here-block"> <p v-else class="nothing-here-block">
......
...@@ -113,7 +113,7 @@ export function initCadenceApp({ namespaceType }) { ...@@ -113,7 +113,7 @@ export function initCadenceApp({ namespaceType }) {
previewMarkdownPath, previewMarkdownPath,
noIssuesSvgPath, noIssuesSvgPath,
} = el.dataset; } = el.dataset;
const router = createRouter(cadencesListPath); const router = createRouter({ base: cadencesListPath });
return new Vue({ return new Vue({
el, el,
......
mutation destroyIterationCadence($id: IterationsCadenceID!) {
iterationCadenceDestroy(input: { id: $id }) {
errors
}
}
...@@ -29,7 +29,7 @@ const routes = [ ...@@ -29,7 +29,7 @@ const routes = [
}, },
]; ];
export default function createRouter(base) { export default function createRouter({ base }) {
const router = new VueRouter({ const router = new VueRouter({
base, base,
mode: 'history', mode: 'history',
......
import { GlDropdown, GlInfiniteScroll, GlSkeletonLoader } from '@gitlab/ui'; import { GlDropdown, GlInfiniteScroll, GlModal, GlSkeletonLoader } from '@gitlab/ui';
import { createLocalVue, RouterLinkStub } from '@vue/test-utils'; import { createLocalVue, RouterLinkStub } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
...@@ -188,6 +188,46 @@ describe('Iteration cadence list item', () => { ...@@ -188,6 +188,46 @@ describe('Iteration cadence list item', () => {
); );
}); });
describe('deleting cadence', () => {
describe('canEditCadence = false', () => {
beforeEach(async () => {
await createComponent({
canEditCadence: false,
});
});
it('hides dropdown and delete button', () => {
expect(wrapper.find(GlDropdown).exists()).toBe(false);
});
});
describe('canEditCadence = true', () => {
beforeEach(async () => {
createComponent({
canEditCadence: true,
});
wrapper.vm.$refs.modal.show = jest.fn();
});
it('shows delete button', () => {
expect(wrapper.find(GlDropdown).exists()).toBe(true);
});
it('opens confirmation modal to delete cadence', () => {
wrapper.findByTestId('delete-cadence').trigger('click');
expect(wrapper.vm.$refs.modal.show).toHaveBeenCalled();
});
it('emits delete-cadence event with cadence ID', () => {
wrapper.find(GlModal).vm.$emit('ok');
expect(wrapper.emitted('delete-cadence')).toEqual([[cadence.id]]);
});
});
});
it('hides dropdown when canEditCadence is false', async () => { it('hides dropdown when canEditCadence is false', async () => {
await createComponent({ canEditCadence: false }); await createComponent({ canEditCadence: false });
......
...@@ -2,20 +2,21 @@ import { GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui'; ...@@ -2,20 +2,21 @@ import { GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import IterationCadenceListItem from 'ee/iterations/components/iteration_cadence_list_item.vue';
import IterationCadencesList from 'ee/iterations/components/iteration_cadences_list.vue'; import IterationCadencesList from 'ee/iterations/components/iteration_cadences_list.vue';
import destroyIterationCadence from 'ee/iterations/queries/destroy_cadence.mutation.graphql';
import cadencesListQuery from 'ee/iterations/queries/iteration_cadences_list.query.graphql'; import cadencesListQuery from 'ee/iterations/queries/iteration_cadences_list.query.graphql';
import createRouter from 'ee/iterations/router';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { mountExtended as mount } from 'helpers/vue_test_utils_helper'; import { mountExtended as mount } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
const push = jest.fn();
const $router = {
push,
};
const localVue = createLocalVue(); const localVue = createLocalVue();
const baseUrl = '/cadences/';
const router = createRouter(baseUrl);
function createMockApolloProvider(requestHandlers) { function createMockApolloProvider(requestHandlers) {
localVue.use(VueApollo); localVue.use(VueApollo);
...@@ -84,15 +85,19 @@ describe('Iteration cadences list', () => { ...@@ -84,15 +85,19 @@ describe('Iteration cadences list', () => {
canCreateCadence, canCreateCadence,
canEditCadence, canEditCadence,
resolverMock = jest.fn().mockResolvedValue(querySuccessResponse), resolverMock = jest.fn().mockResolvedValue(querySuccessResponse),
destroyMutationMock = jest
.fn()
.mockResolvedValue({ data: { iterationCadenceDestroy: { errors: [] } } }),
} = {}) { } = {}) {
apolloProvider = createMockApolloProvider([[cadencesListQuery, resolverMock]]); apolloProvider = createMockApolloProvider([
[cadencesListQuery, resolverMock],
[destroyIterationCadence, destroyMutationMock],
]);
wrapper = mount(IterationCadencesList, { wrapper = mount(IterationCadencesList, {
localVue, localVue,
apolloProvider, apolloProvider,
mocks: { router,
$router,
},
provide: { provide: {
groupPath, groupPath,
cadencesListPath, cadencesListPath,
...@@ -184,9 +189,9 @@ describe('Iteration cadences list', () => { ...@@ -184,9 +189,9 @@ describe('Iteration cadences list', () => {
resolverMock.mockReset(); resolverMock.mockReset();
}); });
it('correctly disables pagination buttons', async () => { it('correctly disables pagination buttons', () => {
expect(findNextPageButton().element.disabled).toBe(false); expect(findNextPageButton().element).not.toBeDisabled();
expect(findPrevPageButton().element.disabled).toBe(true); expect(findPrevPageButton().element).toBeDisabled();
}); });
it('updates query when next page clicked', async () => { it('updates query when next page clicked', async () => {
...@@ -215,5 +220,26 @@ describe('Iteration cadences list', () => { ...@@ -215,5 +220,26 @@ describe('Iteration cadences list', () => {
); );
}); });
}); });
describe('deleting cadence', () => {
it('removes item from list', async () => {
await createComponent({
canEditCadence: true,
});
await waitForPromises();
// 3 cadences * 3 tabs, so 9 in total
expect(wrapper.findAllComponents(IterationCadenceListItem).length).toBe(9);
expect(wrapper.text()).toContain(cadences[0].title);
wrapper.findComponent(IterationCadenceListItem).vm.$emit('delete-cadence', cadences[0].id);
await waitForPromises();
expect(wrapper.findAllComponents(IterationCadenceListItem).length).toBe(6);
expect(wrapper.text()).not.toContain(cadences[0].title);
});
});
}); });
}); });
...@@ -18238,6 +18238,12 @@ msgstr "" ...@@ -18238,6 +18238,12 @@ msgstr ""
msgid "Iterations|Create cadence" msgid "Iterations|Create cadence"
msgstr "" msgstr ""
msgid "Iterations|Delete cadence"
msgstr ""
msgid "Iterations|Delete iteration cadence?"
msgstr ""
msgid "Iterations|Duration" msgid "Iterations|Duration"
msgstr "" msgstr ""
...@@ -18295,6 +18301,9 @@ msgstr "" ...@@ -18295,6 +18301,9 @@ msgstr ""
msgid "Iterations|The start date of your first iteration" msgid "Iterations|The start date of your first iteration"
msgstr "" msgstr ""
msgid "Iterations|This will delete the cadence as well as all of the iterations within it."
msgstr ""
msgid "Iterations|Title" msgid "Iterations|Title"
msgstr "" msgstr ""
......
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