Commit 918e7d43 authored by Nick Kipling's avatar Nick Kipling Committed by Nathan Friend

Reworked how deletion works with multi vs single

Single deletion no longer requires a prop
Modal description is now generated on demand
Added dedicated functions for deleting
Updated tests to match new function naming
Updated css class name to be more specific
parent a37d672f
...@@ -41,6 +41,7 @@ export default { ...@@ -41,6 +41,7 @@ export default {
itemsToBeDeleted: [], itemsToBeDeleted: [],
modalId: `confirm-image-deletion-modal-${this.repo.id}`, modalId: `confirm-image-deletion-modal-${this.repo.id}`,
selectAllChecked: false, selectAllChecked: false,
modalDescription: '',
}; };
}, },
computed: { computed: {
...@@ -54,67 +55,68 @@ export default { ...@@ -54,67 +55,68 @@ export default {
return n__( return n__(
'ContainerRegistry|Remove image', 'ContainerRegistry|Remove image',
'ContainerRegistry|Remove images', 'ContainerRegistry|Remove images',
this.singleItemSelected ? 1 : this.itemsToBeDeleted.length, this.itemsToBeDeleted.length === 0 ? 1 : this.itemsToBeDeleted.length,
); );
}, },
modalDescription() { },
const selectedCount = this.itemsToBeDeleted.length; methods: {
...mapActions(['fetchList', 'deleteItem', 'multiDeleteItems']),
if (this.singleItemSelected) { setModalDescription(itemsToDeleteLength, itemIndex) {
// Attempt to pull 'single' property if it's an object in this.itemsToBeDeleted if (itemsToDeleteLength) {
// Otherwise, simply use the int value of the selected row this.modalDescription = sprintf(
const { single: itemIndex = this.itemsToBeDeleted[0] } = this.itemsToBeDeleted[0]; s__(`ContainerRegistry|You are about to delete <b>%{count}</b> images. This will
delete the images and all tags pointing to them.`),
{ count: itemsToDeleteLength },
);
} else {
const { tag } = this.repo.list[itemIndex]; const { tag } = this.repo.list[itemIndex];
return sprintf( this.modalDescription = sprintf(
s__(`ContainerRegistry|You are about to delete the image <b>%{title}</b>. This will s__(`ContainerRegistry|You are about to delete the image <b>%{title}</b>. This will
delete the image and all tags pointing to this image.`), delete the image and all tags pointing to this image.`),
{ title: `${this.repo.name}:${tag}` }, { title: `${this.repo.name}:${tag}` },
); );
} }
return sprintf(
s__(`ContainerRegistry|You are about to delete <b>%{count}</b> images. This will
delete the images and all tags pointing to them.`),
{ count: selectedCount },
);
},
singleItemSelected() {
return this.findSingleRowToDelete || this.itemsToBeDeleted.length === 1;
},
findSingleRowToDelete() {
return this.itemsToBeDeleted.find(x => x.single !== undefined);
}, },
},
methods: {
...mapActions(['fetchList', 'deleteItems']),
layers(item) { layers(item) {
return item.layers ? n__('%d layer', '%d layers', item.layers) : ''; return item.layers ? n__('%d layer', '%d layers', item.layers) : '';
}, },
formatSize(size) { formatSize(size) {
return numberToHumanSize(size); return numberToHumanSize(size);
}, },
addSingleItemToBeDeleted(index) { removeModalEvents() {
this.itemsToBeDeleted.push({ single: index }); this.$refs.deleteModal.$refs.modal.$off('ok');
this.$refs.deleteModal.$refs.modal.$off('hide');
}, },
removeSingleItemToBeDeleted() { deleteSingleItem(index) {
const singleIndex = this.itemsToBeDeleted.findIndex(x => x.single !== undefined); this.setModalDescription(0, index);
if (singleIndex > -1) { this.$refs.deleteModal.$refs.modal.$once('ok', () => {
this.itemsToBeDeleted.splice(singleIndex, 1); this.removeModalEvents();
} this.handleSingleDelete(this.repo.list[index]);
}, });
handleDeleteRegistry() {
let { itemsToBeDeleted } = this;
if (this.findSingleRowToDelete) { this.$refs.deleteModal.$refs.modal.$once('hide', this.removeModalEvents);
itemsToBeDeleted = [this.findSingleRowToDelete.single]; },
} deleteMultipleItems() {
this.$refs.deleteModal.$refs.modal.$once('ok', () => {
this.removeModalEvents();
this.handleMultipleDelete();
});
this.$refs.deleteModal.$refs.modal.$once('hide', this.removeModalEvents);
},
handleSingleDelete(itemToDelete) {
this.deleteItem(itemToDelete)
.then(() => this.fetchList({ repo: this.repo }))
.catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
},
handleMultipleDelete() {
const { itemsToBeDeleted } = this;
this.itemsToBeDeleted = []; this.itemsToBeDeleted = [];
if (this.bulkDeletePath) { if (this.bulkDeletePath) {
this.deleteItems({ this.multiDeleteItems({
path: this.bulkDeletePath, path: this.bulkDeletePath,
items: itemsToBeDeleted.map(x => this.repo.list[x].tag), items: itemsToBeDeleted.map(x => this.repo.list[x].tag),
}) })
...@@ -142,6 +144,7 @@ export default { ...@@ -142,6 +144,7 @@ export default {
selectAll() { selectAll() {
this.itemsToBeDeleted = this.repo.list.map((x, index) => index); this.itemsToBeDeleted = this.repo.list.map((x, index) => index);
this.selectAllChecked = true; this.selectAllChecked = true;
this.setModalDescription(this.itemsToBeDeleted.length);
}, },
deselectAll() { deselectAll() {
this.itemsToBeDeleted = []; this.itemsToBeDeleted = [];
...@@ -160,6 +163,12 @@ export default { ...@@ -160,6 +163,12 @@ export default {
this.selectAllChecked = true; this.selectAllChecked = true;
} }
} }
if (this.itemsToBeDeleted.length === 1) {
this.setModalDescription(0, this.itemsToBeDeleted[0]);
} else if (this.itemsToBeDeleted.length > 1) {
this.setModalDescription(this.itemsToBeDeleted.length);
}
}, },
}, },
}; };
...@@ -191,13 +200,14 @@ export default { ...@@ -191,13 +200,14 @@ export default {
variant="danger" variant="danger"
:title="s__('ContainerRegistry|Remove selected images')" :title="s__('ContainerRegistry|Remove selected images')"
:aria-label="s__('ContainerRegistry|Remove selected images')" :aria-label="s__('ContainerRegistry|Remove selected images')"
@click="deleteMultipleItems()"
><icon name="remove" ><icon name="remove"
/></gl-button> /></gl-button>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, index) in repo.list" :key="item.tag" class="image-row"> <tr v-for="(item, index) in repo.list" :key="item.tag" class="registry-image-row">
<td class="check"> <td class="check">
<gl-form-checkbox <gl-form-checkbox
v-if="item.canDelete" v-if="item.canDelete"
...@@ -242,7 +252,7 @@ export default { ...@@ -242,7 +252,7 @@ export default {
:aria-label="s__('ContainerRegistry|Remove image')" :aria-label="s__('ContainerRegistry|Remove image')"
variant="danger" variant="danger"
class="js-delete-registry-row float-right btn-inverted btn-border-color btn-icon" class="js-delete-registry-row float-right btn-inverted btn-border-color btn-icon"
@click="addSingleItemToBeDeleted(index)" @click="deleteSingleItem(index)"
> >
<icon name="remove" /> <icon name="remove" />
</gl-button> </gl-button>
...@@ -257,12 +267,7 @@ export default { ...@@ -257,12 +267,7 @@ export default {
:page-info="repo.pagination" :page-info="repo.pagination"
/> />
<gl-modal <gl-modal ref="deleteModal" :modal-id="modalId" ok-variant="danger">
:modal-id="modalId"
ok-variant="danger"
@ok="handleDeleteRegistry"
@cancel="removeSingleItemToBeDeleted"
>
<template v-slot:modal-title>{{ modalTitle }}</template> <template v-slot:modal-title>{{ modalTitle }}</template>
<template v-slot:modal-ok>{{ s__('ContainerRegistry|Remove image(s) and tags') }}</template> <template v-slot:modal-ok>{{ s__('ContainerRegistry|Remove image(s) and tags') }}</template>
<p v-html="modalDescription"></p> <p v-html="modalDescription"></p>
......
...@@ -36,7 +36,8 @@ export const fetchList = ({ commit }, { repo, page }) => { ...@@ -36,7 +36,8 @@ export const fetchList = ({ commit }, { repo, page }) => {
}; };
export const deleteItem = (_, item) => axios.delete(item.destroyPath); export const deleteItem = (_, item) => axios.delete(item.destroyPath);
export const deleteItems = (_, { path, items }) => axios.delete(path, { params: { ids: items } }); export const multiDeleteItems = (_, { path, items }) =>
axios.delete(path, { params: { ids: items } });
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
.table.tags { .table.tags {
margin-bottom: 0; margin-bottom: 0;
.image-row { .registry-image-row {
.check { .check {
padding-right: $gl-padding; padding-right: $gl-padding;
width: 5%; width: 5%;
......
...@@ -101,7 +101,7 @@ describe('table registry', () => { ...@@ -101,7 +101,7 @@ describe('table registry', () => {
expect(findDeleteBtn().disabled).toBe(false); expect(findDeleteBtn().disabled).toBe(false);
findDeleteBtn().click(); findDeleteBtn().click();
spyOn(vm, 'deleteItems').and.returnValue(Promise.resolve()); spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
Vue.nextTick(() => { Vue.nextTick(() => {
const modal = confirmationModal(); const modal = confirmationModal();
...@@ -111,7 +111,7 @@ describe('table registry', () => { ...@@ -111,7 +111,7 @@ describe('table registry', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.itemsToBeDeleted).toEqual([]); expect(vm.itemsToBeDeleted).toEqual([]);
expect(vm.deleteItems).toHaveBeenCalledWith({ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
path: bulkDeletePath, path: bulkDeletePath,
items: [firstImage.tag, secondImage.tag], items: [firstImage.tag, secondImage.tag],
}); });
...@@ -142,13 +142,13 @@ describe('table registry', () => { ...@@ -142,13 +142,13 @@ describe('table registry', () => {
expect(vm.itemsToBeDeleted).toEqual([0]); expect(vm.itemsToBeDeleted).toEqual([0]);
expect(findDeleteBtn().disabled).toBe(false); expect(findDeleteBtn().disabled).toBe(false);
findDeleteBtn().click(); findDeleteBtn().click();
spyOn(vm, 'deleteItems').and.returnValue(Promise.resolve()); spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
Vue.nextTick(() => { Vue.nextTick(() => {
confirmationModal('.btn-danger').click(); confirmationModal('.btn-danger').click();
expect(vm.itemsToBeDeleted).toEqual([]); expect(vm.itemsToBeDeleted).toEqual([]);
expect(vm.deleteItems).toHaveBeenCalledWith({ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
path: bulkDeletePath, path: bulkDeletePath,
items: [firstImage.tag], items: [firstImage.tag],
}); });
......
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