Skip to content

Commit

Permalink
Merge pull request #2411 from vasilii-kovalev/improve-api-context
Browse files Browse the repository at this point in the history
Improve `ApiContext`
  • Loading branch information
AlekseyManetov authored Jul 12, 2024
2 parents 1c260ee + 8883b39 commit a6cd3c5
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 152 deletions.
63 changes: 38 additions & 25 deletions app/src/data/apiDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getDemoApi } from '@epam/uui-docs';
import type {
IProcessRequest, CommonContexts, UuiContexts, ITablePreset,
CommonContexts, UuiContexts, ITablePreset, IProcessRequest,
} from '@epam/uui-core';
import { TType, TTypeRef } from '@epam/uui-docs';
import { TDocsGenTypeSummary } from '../common/apiReference/types';
Expand All @@ -22,46 +22,59 @@ export interface GetCodeResponse {
highlighted: string;
}

export function getApi(params: { processRequest: IProcessRequest, origin?: string, fetchOptions?: RequestInit }) {
const { origin = '', fetchOptions } = params;
interface GetApiParams {
processRequest: IProcessRequest;
origin?: string;
fetchOptions?: RequestInit;
}

const processRequest: IProcessRequest = (url, method, data, options) => {
export function getApi({
processRequest,
origin = '',
fetchOptions,
} : GetApiParams) {
const processRequestLocal: IProcessRequest = (url, method, data, options) => {
const opts = fetchOptions ? { fetchOptions, ...options } : options;
return params.processRequest(url, method, data, opts);
return processRequest(url, method, data, opts);
};

return {
demo: getDemoApi(processRequest, origin),
demo: getDemoApi(processRequestLocal, origin),
form: {
validateForm: <T>(formState: T) => processRequest(origin.concat('api/form/validate-form'), 'POST', formState),
validateForm: <FormState>(formState: FormState) =>
processRequestLocal(
origin.concat('api/form/validate-form'),
'POST',
formState,
),
},
errors: {
status: (status: number) => processRequest(origin.concat(`api/error/status/${status}`), 'POST'),
setServerStatus: (status: number) => processRequest(origin.concat(`api//error/set-server-status/${status}`), "'POST'"),
mock: () => processRequest(origin.concat('api/error/mock'), 'GET'),
authLost: () => processRequest(origin.concat('api/error/auth-lost'), 'POST'),
status: (status: number) => processRequestLocal(origin.concat(`api/error/status/${status}`), 'POST'),
setServerStatus: (status: number) => processRequestLocal(origin.concat(`api//error/set-server-status/${status}`), "'POST'"),
mock: () => processRequestLocal(origin.concat('api/error/mock'), 'GET'),
authLost: () => processRequestLocal(origin.concat('api/error/auth-lost'), 'POST'),
},
getChangelog(): Promise<any> {
return processRequest(origin.concat('/api/get-changelog'), 'GET');
getChangelog() {
return processRequestLocal(origin.concat('/api/get-changelog'), 'GET');
},
getCode(rq: GetCodeParams): Promise<GetCodeResponse> {
return processRequest(origin.concat('/api/get-code'), 'POST', rq);
getCode(rq: GetCodeParams) {
return processRequestLocal<GetCodeResponse>(origin.concat('/api/get-code'), 'POST', rq);
},
getProps(): Promise<any> {
return processRequest(origin.concat('/api/get-props/'), 'GET');
getProps() {
return processRequestLocal(origin.concat('/api/get-props/'), 'GET');
},
getDocsGenType(shortRef: TTypeRef): Promise<{ content: TType }> {
getDocsGenType(shortRef: TTypeRef) {
const refEncoded = encodeURIComponent(shortRef);
return processRequest(origin.concat(`/api/docs-gen/details/${refEncoded}`), 'GET');
return processRequestLocal<{ content: TType }>(origin.concat(`/api/docs-gen/details/${refEncoded}`), 'GET');
},
getDocsGenSummaries(): Promise<{ content: TDocsGenTypeSummary }> {
return processRequest(origin.concat('/api/docs-gen/summaries'), 'GET');
getDocsGenSummaries() {
return processRequestLocal<{ content: TDocsGenTypeSummary }>(origin.concat('/api/docs-gen/summaries'), 'GET');
},
getDocsGenExports(): Promise<{ content: Record<string, string[]> }> {
return processRequest(origin.concat('/api/docs-gen/exports'), 'GET');
getDocsGenExports() {
return processRequestLocal<{ content: Record<string, string[]> }>(origin.concat('/api/docs-gen/exports'), 'GET');
},
getThemeTokens(): Promise<{ content: IUuiTokensCollection['exposedTokens'] }> {
return processRequest(origin.concat('/api/theme-tokens'), 'GET');
getThemeTokens() {
return processRequestLocal<{ content: IUuiTokensCollection['exposedTokens'] }>(origin.concat('/api/theme-tokens'), 'GET');
},
presets: {
async getPresets(): Promise<ITablePreset[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const router = new HistoryAdaptedRouter(history);
type TApi = ReturnType<typeof apiDefinition>;
function apiDefinition(processRequest: IProcessRequest) {
return {
loadDataExample(): Promise<any> {
loadDataExample() {
return processRequest('url goes here', 'GET');
},
loadAppContextData(): Promise<any> {
loadAppContextData() {
return processRequest('url goes here', 'GET');
},
// ... other api are defined here
Expand Down
2 changes: 1 addition & 1 deletion app/src/docs/_examples/form/ServerValidation.example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function ServerValidationExample() {
const svc = useUuiContext<TApi, UuiContexts>();

async function onSave(formState: Login): Promise<FormSaveResponse<Login>> {
const response: ServerResponseExample<Login> = await svc.api.form.validateForm(formState);
const response: ServerResponseExample<Login> = await svc.api.form.validateForm<Login>(formState);
if (!response.error) return response;

// Prefer to return the ICanBeInvalid structure from the server directly, and pass it to the Form as is. Here, we demonstrate how to handle the case when it's not possible. In such cases, you can convert your server-specific errors to the ICanBeInvalid interface on client.
Expand Down
9 changes: 9 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# 5.x.x - xx.xx.2024

**What's New**
* [ApiContext] Add `ResponseType` generic type for `processRequest` function, which defines the returned type of the function. It also takes into account the return type of the `parseResponse` function
* [ApiContext] Add suggestions for `method` parameter in `processRequest` function (to avoid typos), while allowing to pass any string value. For more details, check out `ProcessRequestMethod` type

**What's Fixed**
* [ApiContext] Combine user's headers with internal ones instead of replacing them when calling `processRequest` function

# 5.8.2 - 11.07.2024

**What's New**
Expand Down
19 changes: 7 additions & 12 deletions next-demo/next-app/src/helpers/apiDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getDemoApi } from '@epam/uui-docs';
import type { ApiCallOptions } from '@epam/uui-core';
import type { IProcessRequest } from '@epam/uui-core';

export interface GetCodeParams {
path: string;
Expand All @@ -13,18 +13,13 @@ export interface GetCodeResponse {
}

export function apiDefinition(
processRequest: (
request: string,
requestMethod: string,
data?: any,
options?: ApiCallOptions
) => any,
processRequest: IProcessRequest,
origin: string = ''
) {
return {
demo: getDemoApi(processRequest, origin),
success: {
validateForm: <T>(formState: T) =>
validateForm: <FormState>(formState: FormState) =>
processRequest(
origin.concat('api/success/validate-form'),
'POST',
Expand All @@ -46,13 +41,13 @@ export function apiDefinition(
authLost: () =>
processRequest(origin.concat(`api//error/auth-lost`), 'POST'),
},
getChangelog(): Promise<any> {
getChangelog() {
return processRequest(origin.concat('/api/get-changelog'), 'GET');
},
getCode(rq: GetCodeParams): Promise<GetCodeResponse> {
return processRequest(origin.concat(`/api/get-code`), 'POST', rq);
getCode(rq: GetCodeParams) {
return processRequest<GetCodeResponse>(origin.concat(`/api/get-code`), 'POST', rq);
},
getProps(): Promise<any> {
getProps() {
return processRequest(origin.concat(`/api/get-props/`), 'GET');
},
};
Expand Down
19 changes: 7 additions & 12 deletions next-demo/next-pages/src/helpers/apiDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getDemoApi } from '@epam/uui-docs';
import type { ApiCallOptions } from '@epam/uui-core';
import type { IProcessRequest } from '@epam/uui-core';

export interface GetCodeParams {
path: string;
Expand All @@ -13,18 +13,13 @@ export interface GetCodeResponse {
}

export function apiDefinition(
processRequest: (
request: string,
requestMethod: string,
data?: any,
options?: ApiCallOptions
) => any,
processRequest: IProcessRequest,
origin: string = ''
) {
return {
demo: getDemoApi(processRequest, origin),
success: {
validateForm: <T>(formState: T) =>
validateForm: <FormState>(formState: FormState) =>
processRequest(
origin.concat('api/success/validate-form'),
'POST',
Expand All @@ -46,13 +41,13 @@ export function apiDefinition(
authLost: () =>
processRequest(origin.concat('api//error/auth-lost'), 'POST'),
},
getChangelog(): Promise<any> {
getChangelog() {
return processRequest(origin.concat('/api/get-changelog'), 'GET');
},
getCode(rq: GetCodeParams): Promise<GetCodeResponse> {
return processRequest(origin.concat('/api/get-code'), 'POST', rq);
getCode(rq: GetCodeParams) {
return processRequest<GetCodeResponse>(origin.concat('/api/get-code'), 'POST', rq);
},
getProps(): Promise<any> {
getProps() {
return processRequest(origin.concat('/api/get-props/'), 'GET');
},
};
Expand Down
4 changes: 2 additions & 2 deletions uui-core/src/hooks/useUuiServices.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AnalyticsContext } from '../services/AnalyticsContext';
import { ApiContext, IProcessRequest, ApiContextProps } from '../services/ApiContext';
import { ApiContext, ApiContextProps } from '../services/ApiContext';
import { ErrorContext } from '../services/ErrorContext';
import { DndContext } from '../services/dnd/DndContext';
import { LayoutContext } from '../services/LayoutContext';
import { LockContext } from '../services/LockContext';
import { ModalContext } from '../services/ModalContext';
import { NotificationContext } from '../services/NotificationContext';
import { ApiCallOptions, CommonContexts, IRouterContext } from '../types';
import { ApiCallOptions, CommonContexts, IProcessRequest, IRouterContext } from '../types';
import { UserSettingsContext } from '../services/UserSettingsContext';
import { useEffect, useState } from 'react';

Expand Down
35 changes: 23 additions & 12 deletions uui-core/src/services/ApiContext.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseContext } from './BaseContext';
import { AnalyticsContext } from './AnalyticsContext';
import {
IApiContext, ApiStatus, ApiRecoveryReason, ApiCallOptions, ApiCallInfo,
IApiContext, ApiStatus, ApiRecoveryReason, ApiCallInfo, IProcessRequest,
} from '../types';
import { isClientSide } from '../helpers/ssr';
import { getCookie } from '../helpers/cookie';
Expand Down Expand Up @@ -48,8 +48,6 @@ export interface FileUploadResponse {
};
}

export type IProcessRequest = (url: string, method: string, data?: any, options?: ApiCallOptions) => Promise<any>;

export type BlockTypes = 'attachment' | 'iframe' | 'image';
export interface ApiContextProps {
/** Url to the relogin page. Used to open new browser window by this path, in case of auth lost error.
Expand Down Expand Up @@ -171,23 +169,36 @@ export class ApiContext extends BaseContext implements IApiContext {
}

private startCall(call: ApiCall) {
const headers = new Headers();
const fetchOptions = call.options?.fetchOptions;

const headers = new Headers(fetchOptions?.headers);

if (!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}

const csrfCookie = isClientSide && getCookie('CSRF-TOKEN');
headers.append('Content-Type', 'application/json');

if (csrfCookie) {
headers.append('X-CSRF-Token', csrfCookie);
}

call.attemptsCount += 1;
call.status = 'running';
call.startedAt = new Date();

const fetcher = this.props.fetch || fetch;
fetcher(this.props.apiServerUrl + call.url, {
headers,
method: call.method,
body: call.requestData && JSON.stringify(call.requestData),
credentials: 'include',
...call.options?.fetchOptions,
})

fetcher(
this.props.apiServerUrl + call.url,
{
method: call.method,
body: call.requestData && JSON.stringify(call.requestData),
credentials: 'include',
...fetchOptions,
headers,
},
)
.then((response) => {
this.handleResponse(call, response);
})
Expand Down
Loading

0 comments on commit a6cd3c5

Please sign in to comment.