Merge branch '18215-collection-update-without-manifest' into main.
authorLucas Di Pentima <lucas.dipentima@curii.com>
Tue, 16 Nov 2021 17:47:22 +0000 (14:47 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Tue, 16 Nov 2021 17:47:22 +0000 (14:47 -0300)
Closes #18215

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

cypress/integration/favorites.spec.js
src/services/collection-service/collection-service.test.ts
src/services/collection-service/collection-service.ts
src/services/common-service/common-resource-service.ts
src/store/collection-panel/collection-panel-action.ts
src/store/collections/collection-update-actions.ts
src/views/collection-panel/collection-panel.tsx

index 9f4e2b84c1444812cf8715a2ecc83216b9b7ef86..13a2c4675f7492f2f872e63e38c2ba7df4c0da37 100644 (file)
@@ -150,7 +150,7 @@ describe('Favorites tests', function () {
         cy.getAll('@mySharedWritableProject', '@testTargetCollection')
             .then(function ([mySharedWritableProject, testTargetCollection]) {
                 cy.loginAs(adminUser);
-                
+
                 cy.get('[data-cy=side-panel-tree]').contains('My Favorites').click();
 
                 const newProjectName = `New project name ${mySharedWritableProject.name}`;
@@ -160,7 +160,7 @@ describe('Favorites tests', function () {
 
                 cy.testEditProjectOrCollection('main', mySharedWritableProject.name, newProjectName, newProjectDescription);
                 cy.testEditProjectOrCollection('main', testTargetCollection.name, newCollectionName, newCollectionDescription, false);
-                
+
                 cy.get('[data-cy=side-panel-tree]').contains('Projects').click();
 
                 cy.get('main').contains(newProjectName).rightclick();
@@ -171,7 +171,7 @@ describe('Favorites tests', function () {
                 cy.get('[data-cy=side-panel-tree]').contains('Public Favorites').click();
 
                 cy.testEditProjectOrCollection('main', newProjectName, mySharedWritableProject.name, 'newProjectDescription');
-                cy.testEditProjectOrCollection('main', newCollectionName, testTargetCollection.name, 'newCollectionDescription', false); 
+                cy.testEditProjectOrCollection('main', newCollectionName, testTargetCollection.name, 'newCollectionDescription', false);
             });
     });
 
index 061a45ec0111cfedb84405a699abe87017fbe404..c0aa85f1d3ab3e8df9912b993c978ef67d8c5834 100644 (file)
@@ -2,30 +2,53 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { AxiosInstance } from 'axios';
-import { WebDAV } from 'common/webdav';
-import { ApiActions } from '../api/api-actions';
+import axios, { AxiosInstance } from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { CollectionResource } from 'models/collection';
 import { AuthService } from '../auth-service/auth-service';
 import { CollectionService } from './collection-service';
 
 describe('collection-service', () => {
     let collectionService: CollectionService;
-    let serverApi;
+    let serverApi: AxiosInstance;
+    let axiosMock: MockAdapter;
     let webdavClient: any;
     let authService;
     let actions;
 
     beforeEach(() => {
-        serverApi = {} as AxiosInstance;
+        serverApi = axios.create();
+        axiosMock = new MockAdapter(serverApi);
         webdavClient = {
             delete: jest.fn(),
         } as any;
         authService = {} as AuthService;
-        actions = {} as ApiActions;
+        actions = {
+            progressFn: jest.fn(),
+        } as any;
         collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
         collectionService.update = jest.fn();
     });
 
+    describe('update', () => {
+        it('should call put selecting updated fields + others', async () => {
+            serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
+            const data: Partial<CollectionResource> = {
+                name: 'foo',
+            };
+            const expected = {
+                collection: {
+                    ...data,
+                    preserve_version: true,
+                },
+                select: ['uuid', 'name', 'version', 'modified_at'],
+            }
+            collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
+            await collectionService.update('uuid', data);
+            expect(serverApi.put).toHaveBeenCalledWith('/collections/uuid', expected);
+        });
+    });
+
     describe('deleteFiles', () => {
         it('should remove no files', async () => {
             // given
index 92437806818eb33a77c23c8bd2c1183a453cf3d9..0c3cda3bcb8abcc2ae779cf83156cdd54d7e3373 100644 (file)
@@ -33,7 +33,8 @@ export class CollectionService extends TrashableResourceService<CollectionResour
     }
 
     update(uuid: string, data: Partial<CollectionResource>) {
-        return super.update(uuid, { ...data, preserveVersion: true });
+        const select = [...Object.keys(data), 'version', 'modifiedAt'];
+        return super.update(uuid, { ...data, preserveVersion: true }, select);
     }
 
     async files(uuid: string) {
index 66e694a09b9f74d716416a223d7e8f5d76a98aa1..c6306779a9ee8cef4bb6eff287c485462f5e898a 100644 (file)
@@ -37,13 +37,16 @@ export class CommonResourceService<T extends Resource> extends CommonService<T>
         return super.create(payload);
     }
 
-    update(uuid: string, data: Partial<T>) {
+    update(uuid: string, data: Partial<T>, select?: string[]) {
         let payload: any;
         if (data !== undefined) {
             this.readOnlyFields.forEach( field => delete data[field] );
             payload = {
                 [this.resourceType.slice(0, -1)]: CommonService.mapKeys(snakeCase)(data),
             };
+            if (select !== undefined && select.length > 0) {
+                payload.select = ['uuid', ...select.map(field => snakeCase(field))];
+            };
         }
         return super.update(uuid, payload);
     }
index ca9542c5b18d447854f7110af6fa40104548da00..ee476524256512c9fd5f24a48e5238cb558759cf 100644 (file)
@@ -17,6 +17,7 @@ import { SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { navigateTo } from 'store/navigation/navigation-action';
 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
 import { addProperty, deleteProperty } from "lib/resource-properties";
+import { getResource } from "store/resources/resources";
 
 export const collectionPanelActions = unionize({
     SET_COLLECTION: ofType<CollectionResource>(),
@@ -39,7 +40,6 @@ export const loadCollectionPanel = (uuid: string, forceReload = false) =>
         dispatch(resourcesActions.SET_RESOURCES([collection]));
         if (collection.fileCount <= COLLECTION_PANEL_LOAD_FILES_THRESHOLD &&
             !getState().collectionPanel.loadBigCollections) {
-            // dispatch<any>(loadCollectionFiles(collection.uuid));
         }
         return collection;
     };
@@ -52,11 +52,13 @@ export const createCollectionTag = (data: TagProperty) =>
         const properties = Object.assign({}, item.properties);
         const key = data.keyID || data.key;
         const value = data.valueID || data.value;
+        const cachedCollection = getResource<CollectionResource>(item.uuid)(getState().resources);
         services.collectionService.update(
             item.uuid, {
                 properties: addProperty(properties, key, value)
             }
         ).then(updatedCollection => {
+            updatedCollection = {...cachedCollection, ...updatedCollection};
             dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
             dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
             dispatch(snackbarActions.OPEN_SNACKBAR({
@@ -89,11 +91,13 @@ export const deleteCollectionTag = (key: string, value: string) =>
         if (!item) { return; }
 
         const properties = Object.assign({}, item.properties);
+        const cachedCollection = getResource<CollectionResource>(item.uuid)(getState().resources);
         services.collectionService.update(
             item.uuid, {
                 properties: deleteProperty(properties, key, value)
             }
         ).then(updatedCollection => {
+            updatedCollection = {...cachedCollection, ...updatedCollection};
             dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
             dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
index a9077cfb7455db4fd5c4b6a133502e9eb6da72ed..04f42b8d82f033b8a4d87690bfa988976ad06c40 100644 (file)
@@ -14,6 +14,7 @@ import { progressIndicatorActions } from "store/progress-indicator/progress-indi
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
 import { updateResources } from "../resources/resources-actions";
 import { loadDetailsPanel } from "../details-panel/details-panel-action";
+import { getResource } from "store/resources/resources";
 
 export interface CollectionUpdateFormDialogData {
     uuid: string;
@@ -36,11 +37,13 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) =>
         dispatch(startSubmit(COLLECTION_UPDATE_FORM_NAME));
         dispatch(progressIndicatorActions.START_WORKING(COLLECTION_UPDATE_FORM_NAME));
 
+        const cachedCollection = getResource<CollectionResource>(collection.uuid)(getState().resources);
         services.collectionService.update(uuid, {
             name: collection.name,
             storageClassesDesired: collection.storageClassesDesired,
             description: collection.description }
         ).then(updatedCollection => {
+            updatedCollection = {...cachedCollection, ...updatedCollection};
             dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource }));
             dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_UPDATE_FORM_NAME }));
             dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_UPDATE_FORM_NAME));
index 4270cbbdd490199105390a4bed2300e2243d4ede..e78b1f3d04c2b3494195479a9a3d959b9f5032cb 100644 (file)
@@ -120,7 +120,7 @@ export const CollectionPanel = withStyles(styles)(
                 isWritable = true;
             } else {
                 const itemOwner = getResource<GroupResource | UserResource>(item.ownerUuid)(state.resources);
-                if (itemOwner) {
+                if (itemOwner && itemOwner.writableBy) {
                     isWritable = itemOwner.writableBy.indexOf(currentUserUUID || '') >= 0;
                 }
             }