Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / services / common-service / common-resource-service.ts
index 077e23dbaa228f6504122db9a7779307a36d570b..907f0081fdf6053fd983f073cefeb7eadbc77134 100644 (file)
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as _ from "lodash";
-import { AxiosInstance, AxiosPromise } from "axios";
-import * as uuid from "uuid/v4";
-import { ApiActions } from "~/services/api/api-actions";
-
-export interface ListArguments {
-    limit?: number;
-    offset?: number;
-    filters?: string;
-    order?: string;
-    select?: string[];
-    distinct?: boolean;
-    count?: string;
-}
-
-export interface ListResults<T> {
-    kind: string;
-    offset: number;
-    limit: number;
-    items: T[];
-    itemsAvailable: number;
-}
-
-export interface Errors {
-    errors: string[];
-    errorToken: string;
-}
+import { AxiosInstance } from "axios";
+import { snakeCase } from "lodash";
+import { Resource } from "models/resource";
+import { ApiActions } from "services/api/api-actions";
+import { CommonService } from "services/common-service/common-service";
 
 export enum CommonResourceServiceError {
-    UNIQUE_VIOLATION = 'UniqueViolation',
+    UNIQUE_NAME_VIOLATION = 'UniqueNameViolation',
     OWNERSHIP_CYCLE = 'OwnershipCycle',
     MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingContainerRequestFinalState',
     NAME_HAS_ALREADY_BEEN_TAKEN = 'NameHasAlreadyBeenTaken',
+    PERMISSION_ERROR_FORBIDDEN = 'PermissionErrorForbidden',
+    SOURCE_DESTINATION_CANNOT_BE_SAME = 'SourceDestinationCannotBeSame',
     UNKNOWN = 'Unknown',
     NONE = 'None'
 }
 
-export class CommonResourceService<T> {
-
-    static mapResponseKeys = (response: { data: any }) =>
-        CommonResourceService.mapKeys(_.camelCase)(response.data)
-
-    static mapKeys = (mapFn: (key: string) => string) =>
-        (value: any): any => {
-            switch (true) {
-                case _.isPlainObject(value):
-                    return Object
-                        .keys(value)
-                        .map(key => [key, mapFn(key)])
-                        .reduce((newValue, [key, newKey]) => ({
-                            ...newValue,
-                            [newKey]: CommonResourceService.mapKeys(mapFn)(value[key])
-                        }), {});
-                case _.isArray(value):
-                    return value.map(CommonResourceService.mapKeys(mapFn));
-                default:
-                    return value;
-            }
-        }
-
-    static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions, mapKeys = true): Promise<R> {
-        const reqId = uuid();
-        actions.progressFn(reqId, true);
-        return promise
-            .then(data => {
-                actions.progressFn(reqId, false);
-                return data;
-            })
-            .then((response: { data: any }) => {
-                return mapKeys ? CommonResourceService.mapResponseKeys(response) : response.data;
-            })
-            .catch(({ response }) => {
-                actions.progressFn(reqId, false);
-                const errors = CommonResourceService.mapResponseKeys(response) as Errors;
-                actions.errorFn(reqId, errors);
-                throw errors;
-            });
+export class CommonResourceService<T extends Resource> extends CommonService<T> {
+    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
+        super(serverApi, resourceType, actions, readOnlyFields.concat([
+            'uuid',
+            'etag',
+            'kind',
+            'canWrite',
+            'canManage',
+            'createdAt',
+            'modifiedAt',
+            'modifiedByClientUuid',
+            'modifiedByUserUuid'
+        ]));
     }
 
-    protected serverApi: AxiosInstance;
-    protected resourceType: string;
-    protected actions: ApiActions;
-
-    constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
-        this.serverApi = serverApi;
-        this.resourceType = '/' + resourceType + '/';
-        this.actions = actions;
-    }
-
-    create(data?: Partial<T>) {
-        return CommonResourceService.defaultResponse(
-            this.serverApi
-                .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
-            this.actions
-        );
-    }
-
-    delete(uuid: string): Promise<T> {
-        return CommonResourceService.defaultResponse(
-            this.serverApi
-                .delete(this.resourceType + uuid),
-            this.actions
-        );
-    }
-
-    get(uuid: string) {
-        return CommonResourceService.defaultResponse(
-            this.serverApi
-                .get<T>(this.resourceType + uuid),
-            this.actions
-        );
-    }
-
-    list(args: ListArguments = {}): Promise<ListResults<T>> {
-        const { filters, order, ...other } = args;
-        const params = {
-            ...other,
-            filters: filters ? `[${filters}]` : undefined,
-            order: order ? order : undefined
-        };
-        return CommonResourceService.defaultResponse(
-            this.serverApi
-                .get(this.resourceType, {
-                    params: CommonResourceService.mapKeys(_.snakeCase)(params)
-                }),
-            this.actions
-        );
+    create(data?: Partial<T>, showErrors?: boolean) {
+        let payload: any;
+        if (data !== undefined) {
+            this.readOnlyFields.forEach(field => delete data[field]);
+            payload = {
+                [this.resourceType.slice(0, -1)]: CommonService.mapKeys(snakeCase)(data),
+            };
+        }
+        return super.create(payload, showErrors);
     }
 
-    update(uuid: string, data: Partial<T>) {
-        return CommonResourceService.defaultResponse(
-            this.serverApi
-                .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
-            this.actions
-        );
+    update(uuid: string, data: Partial<T>, showErrors?: boolean, 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, showErrors);
     }
 }
 
 export const getCommonResourceServiceError = (errorResponse: any) => {
-    if ('errors' in errorResponse && 'errorToken' in errorResponse) {
+    if (errorResponse && 'errors' in errorResponse) {
         const error = errorResponse.errors.join('');
         switch (true) {
             case /UniqueViolation/.test(error):
-                return CommonResourceServiceError.UNIQUE_VIOLATION;
+                return CommonResourceServiceError.UNIQUE_NAME_VIOLATION;
             case /ownership cycle/.test(error):
                 return CommonResourceServiceError.OWNERSHIP_CYCLE;
             case /Mounts cannot be modified in state 'Final'/.test(error):
                 return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE;
             case /Name has already been taken/.test(error):
                 return CommonResourceServiceError.NAME_HAS_ALREADY_BEEN_TAKEN;
+            case /403 Forbidden/.test(error):
+                return CommonResourceServiceError.PERMISSION_ERROR_FORBIDDEN;
+            case new RegExp(CommonResourceServiceError.SOURCE_DESTINATION_CANNOT_BE_SAME).test(error):
+                return CommonResourceServiceError.SOURCE_DESTINATION_CANNOT_BE_SAME;
             default:
                 return CommonResourceServiceError.UNKNOWN;
         }
     }
     return CommonResourceServiceError.NONE;
 };
-
-