1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as _ from "lodash";
6 import { AxiosInstance, AxiosPromise } from "axios";
7 import * as uuid from "uuid/v4";
8 import { ApiActions } from "~/services/api/api-actions";
9 import * as QueryString from "query-string";
17 export interface ListArguments {
27 export interface ListResults<T> {
33 itemsAvailable: number;
36 export class CommonService<T> {
37 protected serverApi: AxiosInstance;
38 protected resourceType: string;
39 protected actions: ApiActions;
40 protected readOnlyFields: string[];
42 constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
43 this.serverApi = serverApi;
44 this.resourceType = '/' + resourceType;
45 this.actions = actions;
46 this.readOnlyFields = readOnlyFields;
49 static mapResponseKeys = (response: { data: any }) =>
50 CommonService.mapKeys(_.camelCase)(response.data)
52 static mapKeys = (mapFn: (key: string) => string) =>
53 (value: any): any => {
55 case _.isPlainObject(value):
58 .map(key => [key, mapFn(key)])
59 .reduce((newValue, [key, newKey]) => ({
61 [newKey]: (key === 'items') ? CommonService.mapKeys(mapFn)(value[key]) : value[key]
63 case _.isArray(value):
64 return value.map(CommonService.mapKeys(mapFn));
70 static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions, mapKeys = true, showErrors = true): Promise<R> {
72 actions.progressFn(reqId, true);
75 actions.progressFn(reqId, false);
78 .then((response: { data: any }) => {
79 return mapKeys ? CommonService.mapResponseKeys(response) : response.data;
81 .catch(({ response }) => {
82 actions.progressFn(reqId, false);
83 const errors = CommonService.mapResponseKeys(response) as Errors;
84 errors.status = response.status;
85 actions.errorFn(reqId, errors, showErrors);
90 create(data?: Partial<T>) {
91 return CommonService.defaultResponse(
93 .post<T>(this.resourceType, data && CommonService.mapKeys(_.snakeCase)(data)),
98 delete(uuid: string): Promise<T> {
99 return CommonService.defaultResponse(
101 .delete(this.resourceType + '/' + uuid),
106 get(uuid: string, showErrors?: boolean) {
107 return CommonService.defaultResponse(
109 .get<T>(this.resourceType + '/' + uuid),
116 list(args: ListArguments = {}): Promise<ListResults<T>> {
117 const { filters, order, ...other } = args;
120 filters: filters ? `[${filters}]` : undefined,
121 order: order ? order : undefined
124 if (QueryString.stringify(params).length <= 1500) {
125 return CommonService.defaultResponse(
126 this.serverApi.get(this.resourceType, { params }),
130 // Using the POST special case to avoid URI length 414 errors.
131 const formData = new FormData();
132 formData.append("_method", "GET");
133 Object.keys(params).map(key => {
134 if (params[key] !== undefined) {
135 formData.append(key, params[key]);
138 return CommonService.defaultResponse(
139 this.serverApi.post(this.resourceType, formData, {
149 update(uuid: string, data: Partial<T>) {
150 return CommonService.defaultResponse(
152 .put<T>(this.resourceType + '/' + uuid, data && CommonService.mapKeys(_.snakeCase)(data)),