chore: making upload work with fetchClient

This commit is contained in:
Bassel Kanso 2024-04-23 17:12:52 +03:00
parent 7899cd5db8
commit 8f75d1e78a
6 changed files with 55 additions and 48 deletions

View File

@ -1,3 +1,4 @@
import axios from 'axios';
import pipe from 'lodash/fp/pipe';
import qs from 'qs';
@ -21,6 +22,7 @@ export type FetchOptions = {
params?: any;
signal?: AbortSignal;
headers?: Record<string, string>;
onUploadProgress?: (progressEvent: any) => void;
};
export type FetchConfig = {
@ -106,6 +108,9 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
Authorization: `Bearer ${getToken()}`,
};
const isFormDataRequest = (headers: Headers) =>
headers.has('Content-Type') && headers.get('Content-Type') === 'multipart/form-data';
const addPrependingSlash = (url: string) => (url.charAt(0) !== '/' ? `/${url}` : url);
// This regular expression matches a string that starts with either "http://" or "https://" or any other protocol name in lower case letters, followed by "://" and ends with anything else
@ -186,14 +191,26 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
...defaultHeader,
...options?.headers,
});
const createRequestUrl = makeCreateRequestUrl(options);
// Todo: remove this and replace it with a native implementation
if (isFormDataRequest(headers)) {
return axios.post(createRequestUrl(url), data, {
headers: {
...defaultHeader,
...options?.headers,
},
signal: options?.signal ?? defaultOptions.signal,
onUploadProgress: options?.onUploadProgress,
});
}
const response = await fetch(createRequestUrl(url), {
signal: options?.signal ?? defaultOptions.signal,
method: 'POST',
headers,
body: options?.headers?.['Content-Type'].includes('multipart/form-data')
? data
: JSON.stringify(data),
body: JSON.stringify(data),
});
return responseInterceptor<TData>(response);
},
@ -208,13 +225,24 @@ const getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {
});
const createRequestUrl = makeCreateRequestUrl(options);
// Todo: remove this and replace it with a native implementation
if (isFormDataRequest(headers)) {
return axios.post(createRequestUrl(url), data, {
headers: {
...defaultHeader,
...options?.headers,
},
signal: options?.signal ?? defaultOptions.signal,
onUploadProgress: options?.onUploadProgress,
});
}
const response = await fetch(createRequestUrl(url), {
signal: options?.signal ?? defaultOptions.signal,
method: 'PUT',
headers,
body: options?.headers?.['Content-Type'].includes('multipart/form-data')
? data
: JSON.stringify(data),
body: JSON.stringify(data),
});
return responseInterceptor<TData>(response);

View File

@ -131,7 +131,7 @@ describe('useRemoveAsset', () => {
await waitFor(() =>
expect(toggleNotification).toHaveBeenCalledWith(
expect.objectContaining({ type: 'danger', message: 'Request failed with status code 500' })
expect.objectContaining({ type: 'danger', message: 'Unexpected end of JSON input' })
)
);

View File

@ -1,14 +1,13 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import { useNotification, useFetchClient } from '@strapi/admin/strapi-admin';
import axios from 'axios';
import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query';
import pluginId from '../pluginId';
import { getTrad } from '../utils';
const editAssetRequest = (asset, file, cancelToken, onProgress, post) => {
const editAssetRequest = (asset, file, signal, onProgress, post) => {
const endpoint = `/${pluginId}?id=${asset.id}`;
const formData = new FormData();
@ -28,7 +27,7 @@ const editAssetRequest = (asset, file, cancelToken, onProgress, post) => {
);
return post(endpoint, formData, {
cancelToken: cancelToken.token,
signal,
onUploadProgress({ total, loaded }) {
onProgress((loaded / total) * 100);
},
@ -43,11 +42,12 @@ export const useEditAsset = () => {
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const queryClient = useQueryClient();
const tokenRef = useRef(axios.CancelToken.source());
const abortController = new AbortController();
const signal = abortController.signal;
const { post } = useFetchClient();
const mutation = useMutation(
({ asset, file }) => editAssetRequest(asset, file, tokenRef.current, setProgress, post),
({ asset, file }) => editAssetRequest(asset, file, signal, setProgress, post),
{
onSuccess() {
queryClient.refetchQueries([pluginId, 'assets'], { active: true });
@ -69,10 +69,7 @@ export const useEditAsset = () => {
const editAsset = (asset, file) => mutation.mutateAsync({ asset, file });
const cancel = () =>
tokenRef.current.cancel(
formatMessage({ id: getTrad('modal.upload.cancelled'), defaultMessage: '' })
);
const cancel = () => abortController.abort();
return { ...mutation, cancel, editAsset, progress, status: mutation.status };
};

View File

@ -1,16 +1,13 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import { useFetchClient } from '@strapi/admin/strapi-admin';
import axios from 'axios';
import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query';
import pluginId from '../pluginId';
import { getTrad } from '../utils';
const endpoint = `/${pluginId}`;
const uploadAsset = (asset, folderId, cancelToken, onProgress, post) => {
const uploadAsset = (asset, folderId, signal, onProgress, post) => {
const { rawFile, caption, name, alternativeText } = asset;
const formData = new FormData();
@ -30,7 +27,7 @@ const uploadAsset = (asset, folderId, cancelToken, onProgress, post) => {
headers: {
'Content-Type': 'multipart/form-data',
},
cancelToken: cancelToken.token,
signal,
onUploadProgress({ total, loaded }) {
onProgress((loaded / total) * 100);
},
@ -39,14 +36,14 @@ const uploadAsset = (asset, folderId, cancelToken, onProgress, post) => {
export const useUpload = () => {
const [progress, setProgress] = useState(0);
const { formatMessage } = useIntl();
const queryClient = useQueryClient();
const tokenRef = useRef(axios.CancelToken.source());
const abortController = new AbortController();
const signal = abortController.signal;
const { post } = useFetchClient();
const mutation = useMutation(
({ asset, folderId }) => {
return uploadAsset(asset, folderId, tokenRef.current, setProgress, post);
return uploadAsset(asset, folderId, signal, setProgress, post);
},
{
onSuccess() {
@ -58,10 +55,7 @@ export const useUpload = () => {
const upload = (asset, folderId) => mutation.mutateAsync({ asset, folderId });
const cancel = () =>
tokenRef.current.cancel(
formatMessage({ id: getTrad('modal.upload.cancelled'), defaultMessage: '' })
);
const cancel = () => abortController.abort();
return {
upload,

View File

@ -1,8 +1,8 @@
import { AxiosError, AxiosHeaders } from 'axios';
import { FetchError } from '@strapi/admin/strapi-admin';
import getAPIInnerErrors from '../getAPIInnerErrors';
const API_VALIDATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefined, undefined, {
const API_VALIDATION_ERROR_FIXTURE = new FetchError('ValidationError', {
data: {
error: {
name: 'ValidationError',
@ -25,12 +25,9 @@ const API_VALIDATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefi
},
},
status: 422,
statusText: 'Validation',
headers: {},
config: { headers: new AxiosHeaders() },
});
const API_APPLICATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefined, undefined, {
const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', {
data: {
error: {
name: 'ApplicationError',
@ -39,9 +36,6 @@ const API_APPLICATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undef
},
},
status: 400,
statusText: 'Bad Request',
headers: {},
config: { headers: new AxiosHeaders() },
});
describe('getAPIInnerError', () => {

View File

@ -1,8 +1,8 @@
import { AxiosError, AxiosHeaders } from 'axios';
import { FetchError } from '@strapi/admin/strapi-admin';
import { normalizeAPIError } from '../normalizeAPIError';
const API_VALIDATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefined, undefined, {
const API_VALIDATION_ERROR_FIXTURE = new FetchError('ValidationError', {
data: {
error: {
name: 'ValidationError',
@ -25,12 +25,9 @@ const API_VALIDATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefi
},
},
status: 422,
statusText: 'Validation',
headers: {},
config: { headers: new AxiosHeaders() },
});
const API_APPLICATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undefined, undefined, {
const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', {
data: {
error: {
name: 'ApplicationError',
@ -39,9 +36,6 @@ const API_APPLICATION_ERROR_FIXTURE = new AxiosError(undefined, undefined, undef
},
},
status: 400,
statusText: 'Bad Request',
headers: {},
config: { headers: new AxiosHeaders() },
});
describe('normalizeAPIError', () => {