16067: Ignores certain fields on create & update api calls.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 5 Feb 2020 21:05:08 +0000 (18:05 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 5 Feb 2020 21:05:08 +0000 (18:05 -0300)
Some fields like 'etag', 'kind', 'uuid' and others are read-only, so
they shouldn't be sent to the API server.

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

src/services/collection-service/collection-service.ts
src/services/common-service/common-resource-service.test.ts
src/services/common-service/common-resource-service.ts
src/services/common-service/common-service.ts
src/services/common-service/trashable-resource-service.ts
src/store/collections/collection-copy-actions.ts

index 6eb9b5ba664f15892d570206051a93803050987b..f7955fbae1dbc21feb55be34e64cc8cbd7673c91 100644 (file)
@@ -15,7 +15,11 @@ export type UploadProgress = (fileId: number, loaded: number, total: number, cur
 
 export class CollectionService extends TrashableResourceService<CollectionResource> {
     constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
-        super(serverApi, "collections", actions);
+        super(serverApi, "collections", actions, [
+            'unsignedManifestText',
+            'storageClassesConfirmed',
+            'storageClassesConfirmedAt'
+        ]);
     }
 
     async files(uuid: string) {
index 2a18ce2369b7f7bd849646f7458b1d716f69bd93..038c6943fbf9df52b6fb491205c85de777f3dd8d 100644 (file)
@@ -48,6 +48,22 @@ describe("CommonResourceService", () => {
         expect(axiosInstance.post).toHaveBeenCalledWith("/resource", {owner_uuid: "ownerUuidValue"});
     });
 
+    it("#create ignores fields listed as readonly", async () => {
+        axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+        // UUID fields are read-only on all resources.
+        await commonResourceService.create({ uuid: "this should be ignored", ownerUuid: "ownerUuidValue" });
+        expect(axiosInstance.post).toHaveBeenCalledWith("/resource", {owner_uuid: "ownerUuidValue"});
+    });
+
+    it("#update ignores fields listed as readonly", async () => {
+        axiosInstance.put = jest.fn(() => Promise.resolve({data: {}}));
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+        // UUID fields are read-only on all resources.
+        await commonResourceService.update('resource-uuid', { uuid: "this should be ignored", ownerUuid: "ownerUuidValue" });
+        expect(axiosInstance.put).toHaveBeenCalledWith("/resource/resource-uuid", {owner_uuid: "ownerUuidValue"});
+    });
+
     it("#delete", async () => {
         axiosMock
             .onDelete("/resource/uuid")
index d29ea15642f47dd51153c4d9ceb6e1986617c4db..bc24f22796b21001bce09b358e1efe6c657d6248 100644 (file)
@@ -17,8 +17,26 @@ export enum CommonResourceServiceError {
 }
 
 export class CommonResourceService<T extends Resource> extends CommonService<T> {
-   constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
-        super(serverApi, resourceType, actions);
+    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
+        super(serverApi, resourceType, actions, readOnlyFields.concat([
+            'uuid',
+            'etag',
+            'kind'
+        ]));
+    }
+
+    create(data?: Partial<T>) {
+        if (data !== undefined) {
+            this.readOnlyFields.forEach( field => delete data[field] );
+        }
+        return super.create(data);
+    }
+
+    update(uuid: string, data: Partial<T>) {
+        if (data !== undefined) {
+            this.readOnlyFields.forEach( field => delete data[field] );
+        }
+        return super.update(uuid, data);
     }
 }
 
index d81178212711a7e7634e5493b7e516793d158aa2..44233eb17bd2f683ecf4fcb48459160dd583119e 100644 (file)
@@ -36,11 +36,13 @@ export class CommonService<T> {
     protected serverApi: AxiosInstance;
     protected resourceType: string;
     protected actions: ApiActions;
+    protected readOnlyFields: string[];
 
-    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
+    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
         this.serverApi = serverApi;
         this.resourceType = '/' + resourceType;
         this.actions = actions;
+        this.readOnlyFields = readOnlyFields;
     }
 
     static mapResponseKeys = (response: { data: any }) =>
index 5746bffb83136a6a80723d7da58b28ae0fe69301..63be3ab866673ee98084579d48bd160734ffa5f4 100644 (file)
@@ -10,8 +10,8 @@ import { ApiActions } from "~/services/api/api-actions";
 
 export class TrashableResourceService<T extends TrashableResource> extends CommonResourceService<T> {
 
-    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
-        super(serverApi, resourceType, actions);
+    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
+        super(serverApi, resourceType, actions, readOnlyFields);
     }
 
     trash(uuid: string): Promise<T> {
index b13d08aaad236eb8eae6962a969fc1aaae79e326..cf87f3f104801ffe18fd916b18e8072b4958ff28 100644 (file)
@@ -28,13 +28,9 @@ export const copyCollection = (resource: CopyFormDialogData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(startSubmit(COLLECTION_COPY_FORM_NAME));
         try {
-            dispatch(progressIndicatorActions.START_WORKING(COLLECTION_COPY_FORM_NAME));
             const collection = await services.collectionService.get(resource.uuid);
-            const uuidKey = 'uuid';
-            delete collection[uuidKey];
             const newCollection = await services.collectionService.create({ ...collection, ownerUuid: resource.ownerUuid, name: resource.name });
             dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_COPY_FORM_NAME }));
-            dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_COPY_FORM_NAME));
             return newCollection;
         } catch (e) {
             const error = getCommonResourceServiceError(e);
@@ -47,7 +43,8 @@ export const copyCollection = (resource: CopyFormDialogData) =>
                 dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_COPY_FORM_NAME }));
                 throw new Error('Could not copy the collection.');
             }
-            dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_COPY_FORM_NAME));
             return;
+        } finally {
+            dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_COPY_FORM_NAME));
         }
     };