Merge branch '17016-delete-single-file-deletes-whole-collection'
authorDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Mon, 11 Jan 2021 15:58:44 +0000 (16:58 +0100)
committerDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Mon, 11 Jan 2021 15:58:44 +0000 (16:58 +0100)
closes #17016

Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla@contractors.roche.com>

cypress/integration/delete-multiple-files.spec.js [new file with mode: 0644]
src/services/collection-service/collection-service.test.ts [new file with mode: 0644]
src/services/collection-service/collection-service.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts

diff --git a/cypress/integration/delete-multiple-files.spec.js b/cypress/integration/delete-multiple-files.spec.js
new file mode 100644 (file)
index 0000000..2c7f484
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Collection panel tests', function () {
+    let activeUser;
+    let adminUser;
+
+    before(function () {
+        cy.getUser('admin', 'Admin', 'User', true, true)
+            .as('adminUser').then(function () {
+                adminUser = this.adminUser;
+            }
+            );
+        cy.getUser('collectionuser1', 'Collection', 'User', false, true)
+            .as('activeUser').then(function () {
+                activeUser = this.activeUser;
+            }
+            );
+    });
+
+    beforeEach(function () {
+        cy.clearCookies();
+        cy.clearLocalStorage();
+    });
+
+    it('deletes all files from root dir', function () {
+        cy.createCollection(adminUser.token, {
+            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+            owner_uuid: activeUser.user.uuid,
+            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:baz\n"
+        })
+            .as('testCollection').then(function () {
+                cy.loginAs(activeUser);
+                cy.visit(`/collections/${this.testCollection.uuid}`);
+                cy.get('[data-cy=collection-files-panel]').within(() => {
+                    cy.get('[type="checkbox"]').first().check();
+                    cy.get('[type="checkbox"]').last().check();
+                });
+                cy.get('[data-cy=collection-files-panel-options-btn]').click();
+                cy.get('[data-cy=context-menu] div').contains('Remove selected').click();
+                cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+                cy.get('[data-cy=collection-files-panel]')
+                    .should('not.contain', 'baz')
+                    .and('not.contain', 'bar');
+            });
+    });
+
+    it('deletes all files from non root dir', function () {
+        cy.createCollection(adminUser.token, {
+            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+            owner_uuid: activeUser.user.uuid,
+            manifest_text: "./subdir 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:baz\n"
+        })
+            .as('testCollection').then(function () {
+                cy.loginAs(activeUser);
+                cy.visit(`/collections/${this.testCollection.uuid}`);
+
+                cy.get('[data-cy=virtual-file-tree] > div > i').first().click();
+                cy.get('[data-cy=collection-files-panel]')
+                    .should('contain', 'foo');
+
+                cy.get('[data-cy=collection-files-panel]')
+                    .contains('foo').closest('[data-cy=virtual-file-tree]').find('[type="checkbox"]').click();
+
+                cy.get('[data-cy=collection-files-panel-options-btn]').click();
+                cy.get('[data-cy=context-menu] div').contains('Remove selected').click();
+                cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+
+                cy.get('[data-cy=collection-files-panel]')
+                    .should('not.contain', 'subdir')
+                    .and('contain', 'baz');
+            });
+    });
+})
diff --git a/src/services/collection-service/collection-service.test.ts b/src/services/collection-service/collection-service.test.ts
new file mode 100644 (file)
index 0000000..19ac749
--- /dev/null
@@ -0,0 +1,69 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { AxiosInstance } from 'axios';
+import { WebDAV } from '~/common/webdav';
+import { ApiActions } from '../api/api-actions';
+import { AuthService } from '../auth-service/auth-service';
+import { CollectionService } from './collection-service';
+
+describe('collection-service', () => {
+    let collectionService: CollectionService;
+    let serverApi;
+    let webdavClient: any;
+    let authService;
+    let actions;
+
+    beforeEach(() => {
+        serverApi = {} as AxiosInstance;
+        webdavClient = {
+            delete: jest.fn(),
+        } as any;
+        authService = {} as AuthService;
+        actions = {} as ApiActions;
+        collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
+    });
+
+    describe('deleteFiles', () => {
+        it('should remove no files', async () => {
+            // given
+            const filePaths: string[] = [];
+            const collectionUUID = '';
+
+            // when
+            await collectionService.deleteFiles(collectionUUID, filePaths);
+
+            // then
+            expect(webdavClient.delete).not.toHaveBeenCalled();
+        });
+
+        it('should remove only root files', async () => {
+            // given
+            const filePaths: string[] = ['/root/1', '/root/1/100', '/root/1/100/test.txt', '/root/2', '/root/2/200', '/root/3/300/test.txt'];
+            const collectionUUID = '';
+
+            // when
+            await collectionService.deleteFiles(collectionUUID, filePaths);
+
+            // then
+            expect(webdavClient.delete).toHaveBeenCalledTimes(3);
+            expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/3/300/test.txt");
+            expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/2");
+            expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/1");
+        });
+
+        it('should remove files with uuid prefix', async () => {
+            // given
+            const filePaths: string[] = ['/root/1'];
+            const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
+
+            // when
+            await collectionService.deleteFiles(collectionUUID, filePaths);
+
+            // then
+            expect(webdavClient.delete).toHaveBeenCalledTimes(1);
+            expect(webdavClient.delete).toHaveBeenCalledWith("c=zzzzz-tpzed-5o5tg0l9a57gxxx/root/1");
+        });
+    });
+});
\ No newline at end of file
index 04c01a113e8852a02be4c034d4334bdc236bdc78..c46c3e27764470f1c214cc4ff82200d612b6a8c4 100644 (file)
@@ -44,13 +44,23 @@ export class CollectionService extends TrashableResourceService<CollectionResour
     }
 
     async deleteFiles(collectionUuid: string, filePaths: string[]) {
-        if (collectionUuid === "" || filePaths.length === 0) { return; }
-        for (const path of filePaths) {
-            const splittedPath = path.split('/');
-            if (collectionUuid) {
-                await this.webdavClient.delete(`c=${collectionUuid}/${splittedPath[1]}`);
-            } else {
+        const sortedUniquePaths = Array.from(new Set(filePaths))
+            .sort((a, b) => a.length - b.length)
+            .reduce((acc, currentPath) => {
+                const parentPathFound = acc.find((parentPath) => currentPath.indexOf(`${parentPath}/`) > -1);
+
+                if (!parentPathFound) {
+                    return [...acc, currentPath];
+                }
+
+                return acc;
+            }, []);
+
+        for (const path of sortedUniquePaths) {
+            if (path.indexOf(collectionUuid) === -1) {
                 await this.webdavClient.delete(`c=${collectionUuid}${path}`);
+            } else {
+                await this.webdavClient.delete(`c=${path}`);
             }
         }
         await this.update(collectionUuid, { preserveVersion: true });
index 80d61ea04cb3efbf42c1904a223d5165c45c7d44..c7a3bdc50372fdd457de1df63b7523174592f1ea 100644 (file)
@@ -47,7 +47,8 @@ export const loadCollectionFiles = (uuid: string) =>
             dispatch(snackbarActions.OPEN_SNACKBAR({
                 message: `Error getting file list`,
                 hideDuration: 2000,
-                kind: SnackbarKind.ERROR }));
+                kind: SnackbarKind.ERROR
+            }));
         });
     };
 
@@ -149,7 +150,7 @@ export const renameFile = (newFullPath: string) =>
                     dispatch<any>(loadCollectionPanel(currentCollection.uuid, true));
                     dispatch(dialogActions.CLOSE_DIALOG({ id: RENAME_FILE_DIALOG }));
                     dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File name changed.', hideDuration: 2000 }));
-                }).catch (e => {
+                }).catch(e => {
                     const errors: FormErrors<RenameFileDialogData, string> = {
                         path: `Could not rename the file: ${e.responseText}`
                     };