Skip to content

Commit

Permalink
change: [M3-7396] - Update axios to 1.6.1 (#9911)
Browse files Browse the repository at this point in the history
* poc

* update cypress interceptors, thanks @jdamore-linode

* improve `request.test.tsx`

* remove unneeded export

* update `tsup` to pick up the latest esbuild so we can revert the tsup config change

* add changesets

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored Nov 17, 2023
1 parent f465b7d commit d0792d6
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 248 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Tech Stories
---

Update `axios` to `1.6.1` ([#9911](https://github.com/linode/manager/pull/9911))
6 changes: 3 additions & 3 deletions packages/api-v4/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"unpkg": "./lib/index.global.js",
"dependencies": {
"@linode/validation": "*",
"axios": "~0.21.4",
"axios": "~1.6.1",
"ipaddr.js": "^2.0.0",
"yup": "^0.32.9"
},
Expand All @@ -59,14 +59,14 @@
"devDependencies": {
"@types/node": "^12.7.1",
"@types/yup": "^0.29.13",
"axios-mock-adapter": "^1.18.1",
"axios-mock-adapter": "^1.22.0",
"concurrently": "^4.1.1",
"eslint": "^6.8.0",
"eslint-plugin-ramda": "^2.5.1",
"eslint-plugin-sonarjs": "^0.5.0",
"lint-staged": "^13.2.2",
"prettier": "~2.2.1",
"tsup": "^6.7.0",
"tsup": "^7.2.0",
"vitest": "^0.34.6"
},
"lint-staged": {
Expand Down
53 changes: 29 additions & 24 deletions packages/api-v4/src/request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import Axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import Axios, {
AxiosError,
AxiosHeaders,
AxiosRequestConfig,
AxiosResponse,
} from 'axios';
import { ValidationError, AnySchema } from 'yup';
import { APIError, Filter, Params } from './types';

Expand All @@ -14,14 +19,16 @@ export const baseRequest = Axios.create({

baseRequest.interceptors.request.use((config) => {
const isRunningInNode = typeof process === 'object';
const newConfig = {
...config,
headers: {
...config.headers,
'User-Agent': 'linodejs',
},
};
return isRunningInNode ? newConfig : config;

if (!isRunningInNode) {
return config;
}

const headers = new AxiosHeaders(config.headers);

headers.set('User-Agent', 'linodejs');

return { ...config, headers };
});

/**
Expand All @@ -34,13 +41,11 @@ baseRequest.interceptors.request.use((config) => {
*/
export const setToken = (token: string) => {
return baseRequest.interceptors.request.use((config) => {
return {
...config,
headers: {
...config.headers,
Authorization: `Bearer ${token}`,
},
};
const headers = new AxiosHeaders(config.headers);

headers.set('Authorization', `Bearer ${token}`);

return { ...config, headers };
});
};

Expand Down Expand Up @@ -205,7 +210,9 @@ export const mockAPIError = (
status,
statusText,
headers: {},
config: {},
config: {
headers: new AxiosHeaders(),
},
})
),
process.env.NODE_ENV === 'test' ? 0 : 250
Expand Down Expand Up @@ -258,13 +265,11 @@ export const CancellableRequest = <T>(
*/
export const setUserAgentPrefix = (prefix: string) => {
return baseRequest.interceptors.request.use((config) => {
return {
...config,
headers: {
...config.headers,
'User-Agent': `${prefix}/${config.headers['User-Agent']}`,
},
};
const headers = new AxiosHeaders(config.headers);

headers.set('User-Agent', `${prefix}/${config.headers['User-Agent']}`);

return { ...config, headers };
});
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Update `axios` to `1.6.1` ([#9911](https://github.com/linode/manager/pull/9911))
25 changes: 9 additions & 16 deletions packages/manager/cypress/support/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { baseRequest } from '@linode/api-v4';
import { AxiosHeaders } from 'axios';

// Note: This file is imported by Cypress plugins, and indirectly by Cypress
// tests. Because Cypress has not been initiated when plugins are executed, we
Expand All @@ -26,23 +27,15 @@ export const defaultApiRoot = 'https://api.linode.com/v4';
*/
export const configureLinodeApi = (accessToken: string, baseUrl?: string) => {
baseRequest.interceptors.request.use((config) => {
const headers = new AxiosHeaders(config.headers);
headers.set('Authorization', `Bearer ${accessToken}`);

// If a base URL is provided, override the request URL
// using the given base URL. Otherwise, evaluate to an empty object so
// we can still use the spread operator later on.
const url = config.url;
const urlOverride =
!baseUrl || !url ? {} : { url: url.replace(defaultApiRoot, baseUrl) };
// using the given base URL.
if (baseUrl && config.url) {
config.url = config.url.replace(defaultApiRoot, baseUrl);
}

return {
...config,
headers: {
...config.headers,
common: {
...config.headers.common,
authorization: `Bearer ${accessToken}`,
},
},
...urlOverride,
};
return { ...config, headers };
});
};
3 changes: 1 addition & 2 deletions packages/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
"@reach/tabs": "^0.10.5",
"@sentry/react": "^7.57.0",
"algoliasearch": "^4.14.3",
"axios": "~0.21.4",
"axios-mock-adapter": "^1.15.0",
"axios": "~1.6.1",
"braintree-web": "^3.92.2",
"chart.js": "~2.9.4",
"chartjs-adapter-luxon": "^0.2.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/Images/requests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Axios, { AxiosRequestConfig } from 'axios';
import Axios, { AxiosProgressEvent, AxiosRequestConfig } from 'axios';

const axiosInstance = Axios.create({});

Expand All @@ -8,7 +8,7 @@ const headerContentType = 'application/octet-stream';
export const uploadImageFile = (
signedUrl: string,
file: File,
onUploadProgress: (e: ProgressEvent) => void
onUploadProgress: (e: AxiosProgressEvent) => void
) => {
const CancelToken = Axios.CancelToken;
const source = CancelToken.source();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
defaultState,
pathOrFileName,
} from './reducer';
import { AxiosProgressEvent } from 'axios';

interface Props {
bucketName: string;
Expand Down Expand Up @@ -285,10 +286,10 @@ export const ObjectUploader = React.memo((props: Props) => {
export const onUploadProgressFactory = (
dispatch: (value: ObjectUploaderAction) => void,
fileName: string
) => (progressEvent: ProgressEvent) => {
) => (progressEvent: AxiosProgressEvent) => {
dispatch({
data: {
percentComplete: (progressEvent.loaded / progressEvent.total) * 100,
percentComplete: (progressEvent.loaded / (progressEvent.total ?? 1)) * 100,
},
filesToUpdate: [fileName],
type: 'UPDATE_FILES',
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/ObjectStorage/requests.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Axios, { AxiosRequestConfig } from 'axios';
import Axios, { AxiosProgressEvent, AxiosRequestConfig } from 'axios';

const axiosInstance = Axios.create({});

export const uploadObject = (
signedUrl: string,
file: File,
onUploadProgress: (e: ProgressEvent) => void
onUploadProgress: (e: AxiosProgressEvent) => void
) => {
const config: AxiosRequestConfig = {
data: file,
Expand Down
46 changes: 25 additions & 21 deletions packages/manager/src/request.test.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { AxiosError, AxiosHeaders, AxiosResponse } from 'axios';

import { handleStartSession } from 'src/store/authentication/authentication.actions';

import { profileFactory } from './factories';
import { queryClientFactory } from './queries/base';
import { getURL, handleError, injectEuuidToProfile } from './request';
import {
LinodeError,
getURL,
handleError,
injectEuuidToProfile,
} from './request';
import { storeFactory } from './store';

import type { APIError } from '@linode/api-v4';

const store = storeFactory(queryClientFactory());

const baseErrorConfig: AxiosRequestConfig = {
headers: {},
method: 'POST',
};
const baseError = {
config: baseErrorConfig,
const mockAxiosError = {
isAxiosError: true,
message: 'helloworld',
name: 'requestName',
response: {
config: {},
data: [],
config: {
headers: new AxiosHeaders({}),
},
data: { errors: [{ reason: 'This is a Linode error.' }] },
headers: {},
statusText: '',
},
};
const baseErrorWithJson = {
...baseError,
toJSON: () => baseError,
...mockAxiosError,
toJSON: () => mockAxiosError,
};

const error400: AxiosError = {
const error400: AxiosError<LinodeError> = {
...baseErrorWithJson,
response: {
...baseError.response,
...mockAxiosError.response,
status: 400,
},
};

const error401: AxiosError = {
const error401: AxiosError<LinodeError> = {
...baseErrorWithJson,
response: {
...baseError.response,
...mockAxiosError.response,
status: 401,
},
};
Expand All @@ -67,8 +71,8 @@ describe('Expiring Tokens', () => {
scopes: null,
token: null,
});
expireToken.catch((e: AxiosError) =>
expect(e[0].reason).toMatch(/unexpected error/)
expireToken.catch((e: APIError[]) =>
expect(e[0].reason).toMatch(mockAxiosError.response.data.errors[0].reason)
);
});

Expand All @@ -92,8 +96,8 @@ describe('Expiring Tokens', () => {
scopes: '*',
token: 'helloworld',
});
expireToken.catch((e: AxiosError) =>
expect(e[0].reason).toMatch(/unexpected error/)
expireToken.catch((e: APIError[]) =>
expect(e[0].reason).toMatch(mockAxiosError.response.data.errors[0].reason)
);
});
});
Expand All @@ -116,7 +120,7 @@ describe('getURL', () => {
describe('injectEuuidToProfile', () => {
const profile = profileFactory.build();
const response: Partial<AxiosResponse> = {
config: { method: 'get', url: '/profile' },
config: { headers: new AxiosHeaders(), method: 'get', url: '/profile' },
data: profile,
headers: { 'x-customer-uuid': '1234' },
status: 200,
Expand Down
33 changes: 23 additions & 10 deletions packages/manager/src/request.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { baseRequest } from '@linode/api-v4/lib/request';
import { APIError } from '@linode/api-v4/lib/types';
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import {
AxiosError,
AxiosHeaders,
AxiosRequestConfig,
AxiosResponse,
} from 'axios';
import * as React from 'react';

import { AccountActivationError } from 'src/components/AccountActivation';
Expand All @@ -25,7 +30,13 @@ const handleSuccess: <T extends AxiosResponse<any>>(response: T) => T | T = (
return response;
};

export const handleError = (error: AxiosError, store: ApplicationStore) => {
// All errors returned by the actual Linode API are in this shape.
export type LinodeError = { errors: APIError[] };

export const handleError = (
error: AxiosError<LinodeError>,
store: ApplicationStore
) => {
if (error.response && error.response.status === 401) {
/**
* this will blow out redux state and the componentDidUpdate in the
Expand All @@ -34,8 +45,8 @@ export const handleError = (error: AxiosError, store: ApplicationStore) => {
store.dispatch(handleLogout());
}

const config = error.response?.config ?? {};
const url = config.url ?? '';
const config = error.response?.config;
const url = config?.url ?? '';
const status: number = error.response?.status ?? 0;
const errors: APIError[] = error.response?.data?.errors ?? [
{ reason: DEFAULT_ERROR_MESSAGE },
Expand Down Expand Up @@ -186,12 +197,13 @@ export const setupInterceptors = (store: ApplicationStore) => {

const url = getURL(config);

const headers = new AxiosHeaders(config.headers);

headers.setAuthorization(token);

return {
...config,
headers: {
...config.headers,
...(token && { Authorization: `${token}` }),
},
headers,
url,
};
});
Expand All @@ -202,8 +214,9 @@ export const setupInterceptors = (store: ApplicationStore) => {
* displays a Maintenance view if the API is in Maintenance mode
Also rejects non-error responses if the API is in Maintenance mode
*/
baseRequest.interceptors.response.use(handleSuccess, (error: AxiosError) =>
handleError(error, store)
baseRequest.interceptors.response.use(
handleSuccess,
(error: AxiosError<LinodeError>) => handleError(error, store)
);

baseRequest.interceptors.response.use(injectEuuidToProfile);
Expand Down
2 changes: 1 addition & 1 deletion packages/validation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"eslint-plugin-sonarjs": "^0.5.0",
"lint-staged": "^13.2.2",
"prettier": "~2.2.1",
"tsup": "^6.7.0"
"tsup": "^7.2.0"
},
"keywords": [
"linode",
Expand Down
Loading

0 comments on commit d0792d6

Please sign in to comment.