mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* CB-3305 api for driver editing * CB-3305 gql api for driver provider list * CB-3303 feat: file uploading API * CB-3303 feat: support gql request in file upload API * CB-3303 code style fix * CB-3376 file uploading mechanism * CB-3303 feat: gql sdk extension API * CB-3303 feat: gql sdk extensions API rename * CB-3303 add driver mutations, update drivers resource * CB-3303 move driver gql functions to another service * CB-3305 web driver config fix * CB-3305 driver config use same fields as in driver info * CB-3437 add driver service to the codegen.yml * CB-3437 add driver providers * CB-3376 delete used driver library after restart server * CB-3376 driver library add fix * CB-3306 include driver libraries * CB-3303 return empty driver properties array if there is the error * CB-3303 fix conflicts * CB-3437 reuse defaults * CB-3303 feat: add sub-secondary color * CB-3303 fix: use sub-secondary color in tables * CB-3305 delete driver libraries fix * CB-3376 driver libraries save delete fix * CB-3437 naming * CB-3376 do not delete jar library file from the server * CB-3376 create file controller fix * CB-3305 move drivers api to ee * CB-3305 move drivers api to ee * CB-3376 file controller use same path * CB-3305 move driver provider list to ee * CB-3303 move mutations to the EE --------- Co-authored-by: Aleksey Potsetsuev <wrouds@gmail.com> Co-authored-by: Tatiana Fokina <tatiana.fokina@dbeaver.com> Co-authored-by: naumov <iamemptyhuh@gmail.com> Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Alexander Skoblikov <aleksandr.skoblikov@dbeaver.com>
202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2023 DBeaver Corp and others
|
|
*
|
|
* Licensed under the Apache License, Version 2.0.
|
|
* you may not use this file except in compliance with the License.
|
|
*/
|
|
|
|
import axios, { AxiosProgressEvent, AxiosResponse } from 'axios';
|
|
import { GraphQLClient, ClientError, resolveRequestDocument } from 'graphql-request';
|
|
import { parseRequestArgs } from 'graphql-request/dist/parseArgs';
|
|
import type { RequestDocument, RequestOptions, Variables } from 'graphql-request/dist/types';
|
|
import type * as Dom from 'graphql-request/dist/types.dom';
|
|
|
|
import { GQLError } from './GQLError';
|
|
import type { IResponseInterceptor } from './IResponseInterceptor';
|
|
import { PlainGQLError } from './PlainGQLError';
|
|
|
|
export type UploadProgressEvent = AxiosProgressEvent;
|
|
|
|
type GqlResponse = (
|
|
{ data: object; errors: undefined }[]
|
|
| { data: object; errors: undefined }
|
|
| { data: undefined; errors: object }
|
|
| { data: undefined; errors: object[] }
|
|
);
|
|
|
|
export class CustomGraphQLClient extends GraphQLClient {
|
|
get blockReason(): Error | string | null {
|
|
return this.requestsBlockedReason;
|
|
}
|
|
|
|
private readonly interceptors: IResponseInterceptor[] = [];
|
|
private isRequestsBlocked = false;
|
|
private requestsBlockedReason: Error | string | null = null;
|
|
|
|
async uploadFile<T = any, V extends Variables = Variables>(
|
|
url: string,
|
|
files: FileList,
|
|
query?: string,
|
|
variables?: V,
|
|
onUploadProgress?: (event: UploadProgressEvent) => void
|
|
): Promise<T> {
|
|
return this.interceptors.reduce(
|
|
(accumulator, interceptor) => interceptor(accumulator),
|
|
this.overrideFileUpload<T, V>(url, files, query, variables, onUploadProgress)
|
|
);
|
|
}
|
|
|
|
registerInterceptor(interceptor: IResponseInterceptor): void {
|
|
this.interceptors.push(interceptor);
|
|
}
|
|
|
|
request<T = any, V = Variables>(document: RequestDocument, variables?: V, requestHeaders?: Dom.RequestInit['headers']): Promise<T>;
|
|
request<T = any, V extends Variables = Variables>(options: RequestOptions<V>): Promise<T>;
|
|
request<T = any, V extends Variables = Variables>(
|
|
document: RequestDocument | RequestOptions<V>,
|
|
variables?: V,
|
|
requestHeaders?: Dom.RequestInit['headers']
|
|
): Promise<T> {
|
|
return this.interceptors.reduce(
|
|
(accumulator, interceptor) => interceptor(accumulator),
|
|
this.overrideRequest<T, V>(document, variables, requestHeaders)
|
|
);
|
|
}
|
|
|
|
enableRequests(): void {
|
|
this.isRequestsBlocked = false;
|
|
this.requestsBlockedReason = null;
|
|
}
|
|
|
|
blockRequests(reason: Error | string): void {
|
|
this.isRequestsBlocked = true;
|
|
this.requestsBlockedReason = reason;
|
|
}
|
|
|
|
private blockRequestsReasonHandler(): void {
|
|
if (this.isRequestsBlocked) {
|
|
if (this.requestsBlockedReason instanceof Error) {
|
|
throw this.requestsBlockedReason;
|
|
} else {
|
|
throw new Error(this.requestsBlockedReason ?? undefined);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async overrideRequest<T, V extends Variables = Variables>(
|
|
documentOrOptions: RequestDocument | RequestOptions<V>,
|
|
variables?: V,
|
|
requestHeaders?: Dom.RequestInit['headers']
|
|
): Promise<T> {
|
|
this.blockRequestsReasonHandler();
|
|
try {
|
|
const requestOptions = parseRequestArgs(documentOrOptions, variables, requestHeaders);
|
|
const { query, operationName } = resolveRequestDocument(requestOptions.document);
|
|
|
|
const response = await this.rawRequest<T, V>(query, variables, requestHeaders);
|
|
|
|
// TODO: seems here can be undefined
|
|
return response.data;
|
|
} catch (error: any) {
|
|
if (isClientError(error)) {
|
|
if (isObjectError(error)) {
|
|
throw new GQLError(error);
|
|
} else {
|
|
throw new PlainGQLError(error);
|
|
}
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async overrideFileUpload<T, V extends Variables = Variables>(
|
|
url: string,
|
|
files: FileList,
|
|
query?: string,
|
|
variables?: V,
|
|
onUploadProgress?: (event: UploadProgressEvent) => void
|
|
): Promise<T> {
|
|
this.blockRequestsReasonHandler();
|
|
try {
|
|
const { operationName } = resolveRequestDocument(query ?? '');
|
|
// TODO: we don't support GQL response right now
|
|
const response = await axios.postForm/*<GqlResponse>*/<T>(url, {
|
|
operationName,
|
|
query,
|
|
variables: JSON.stringify(variables),
|
|
'files[]': files,
|
|
}, {
|
|
onUploadProgress,
|
|
responseType: 'json',
|
|
});
|
|
|
|
// TODO: we don't support GQL response right now
|
|
// TODO: seems here can be undefined
|
|
// return this.parseGQLResponse(response, query ?? '', variables);
|
|
|
|
return response.data;
|
|
} catch (error: any) {
|
|
if (isClientError(error)) {
|
|
if (isObjectError(error)) {
|
|
throw new GQLError(error);
|
|
} else {
|
|
throw new PlainGQLError(error);
|
|
}
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private parseGQLResponse<T>(
|
|
response: AxiosResponse<GqlResponse>,
|
|
query: string,
|
|
variables?: Variables,
|
|
): T {
|
|
const result = response.data;
|
|
|
|
const successfullyReceivedData = Array.isArray(result)
|
|
? !result.some(({ data }) => !data)
|
|
: Boolean(result.data);
|
|
|
|
const successfullyPassedErrorPolicy = Array.isArray(result)
|
|
|| !result.errors
|
|
|| (Array.isArray(result.errors) && !result.errors.length);
|
|
|
|
if (response.status === 200 && successfullyPassedErrorPolicy && successfullyReceivedData) {
|
|
const data = result;
|
|
const dataEnvelope = data;
|
|
|
|
// @ts-expect-error TODO
|
|
return {
|
|
...dataEnvelope,
|
|
headers: response.headers,
|
|
status: response.status,
|
|
};
|
|
} else {
|
|
const errorResult
|
|
= typeof result === 'string'
|
|
? {
|
|
error: result,
|
|
}
|
|
: result;
|
|
throw new ClientError(
|
|
// @ts-expect-error TODO
|
|
{ ...errorResult, status: response.status, headers: response.headers },
|
|
{ query, variables }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isClientError(obj: any): obj is ClientError {
|
|
// in es5 build `instanceof ClientError` always false, so we try to determine by checking response property
|
|
return obj instanceof ClientError || obj.response;
|
|
}
|
|
|
|
function isObjectError(obj: ClientError) {
|
|
return !!obj.response.errors;
|
|
}
|