Rename progressFn to api actions, add colors to snackbar
[arvados-workbench2.git] / src / services / common-service / common-resource-service.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as _ from "lodash";
6 import { AxiosInstance, AxiosPromise } from "axios";
7 import { Resource } from "src/models/resource";
8 import * as uuid from "uuid/v4";
9 import { ApiActions } from "~/services/api/api-actions";
10
11 export interface ListArguments {
12     limit?: number;
13     offset?: number;
14     filters?: string;
15     order?: string;
16     select?: string[];
17     distinct?: boolean;
18     count?: string;
19 }
20
21 export interface ListResults<T> {
22     kind: string;
23     offset: number;
24     limit: number;
25     items: T[];
26     itemsAvailable: number;
27 }
28
29 export interface Errors {
30     errors: string[];
31     errorToken: string;
32 }
33
34 export enum CommonResourceServiceError {
35     UNIQUE_VIOLATION = 'UniqueViolation',
36     OWNERSHIP_CYCLE = 'OwnershipCycle',
37     MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingContainerRequestFinalState',
38     UNKNOWN = 'Unknown',
39     NONE = 'None'
40 }
41
42 export class CommonResourceService<T extends Resource> {
43
44     static mapResponseKeys = (response: { data: any }): Promise<any> =>
45         CommonResourceService.mapKeys(_.camelCase)(response.data)
46
47     static mapKeys = (mapFn: (key: string) => string) =>
48         (value: any): any => {
49             switch (true) {
50                 case _.isPlainObject(value):
51                     return Object
52                         .keys(value)
53                         .map(key => [key, mapFn(key)])
54                         .reduce((newValue, [key, newKey]) => ({
55                             ...newValue,
56                             [newKey]: CommonResourceService.mapKeys(mapFn)(value[key])
57                         }), {});
58                 case _.isArray(value):
59                     return value.map(CommonResourceService.mapKeys(mapFn));
60                 default:
61                     return value;
62             }
63         }
64
65     static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions): Promise<R> {
66         const reqId = uuid();
67         actions.progressFn(reqId, true);
68         return promise
69             .then(data => {
70                 actions.progressFn(reqId, false);
71                 return data;
72             })
73             .then(CommonResourceService.mapResponseKeys)
74             .catch(({ response }) => {
75                 actions.progressFn(reqId, false);
76                 actions.errorFn(reqId, response.message);
77                 Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response));
78             });
79     }
80
81     protected serverApi: AxiosInstance;
82     protected resourceType: string;
83     protected actions: ApiActions;
84
85     constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
86         this.serverApi = serverApi;
87         this.resourceType = '/' + resourceType + '/';
88         this.actions = actions;
89     }
90
91     create(data?: Partial<T> | any) {
92         return CommonResourceService.defaultResponse(
93             this.serverApi
94                 .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
95             this.actions
96         );
97     }
98
99     delete(uuid: string): Promise<T> {
100         return CommonResourceService.defaultResponse(
101             this.serverApi
102                 .delete(this.resourceType + uuid),
103             this.actions
104         );
105     }
106
107     get(uuid: string) {
108         return CommonResourceService.defaultResponse(
109             this.serverApi
110                 .get<T>(this.resourceType + uuid),
111             this.actions
112         );
113     }
114
115     list(args: ListArguments = {}): Promise<ListResults<T>> {
116         const { filters, order, ...other } = args;
117         const params = {
118             ...other,
119             filters: filters ? `[${filters}]` : undefined,
120             order: order ? order : undefined
121         };
122         return CommonResourceService.defaultResponse(
123             this.serverApi
124                 .get(this.resourceType, {
125                     params: CommonResourceService.mapKeys(_.snakeCase)(params)
126                 }),
127             this.actions
128         );
129     }
130
131     update(uuid: string, data: Partial<T>) {
132         return CommonResourceService.defaultResponse(
133             this.serverApi
134                 .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
135             this.actions
136         );
137     }
138 }
139
140 export const getCommonResourceServiceError = (errorResponse: any) => {
141     if ('errors' in errorResponse && 'errorToken' in errorResponse) {
142         const error = errorResponse.errors.join('');
143         switch (true) {
144             case /UniqueViolation/.test(error):
145                 return CommonResourceServiceError.UNIQUE_VIOLATION;
146             case /ownership cycle/.test(error):
147                 return CommonResourceServiceError.OWNERSHIP_CYCLE;
148             case /Mounts cannot be modified in state 'Final'/.test(error):
149                 return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE;
150             default:
151                 return CommonResourceServiceError.UNKNOWN;
152         }
153     }
154     return CommonResourceServiceError.NONE;
155 };
156
157