Skip to content

Commit

Permalink
Add an ability to set the custom default timeout for Axios (#1324)
Browse files Browse the repository at this point in the history
* fix: add an ability to set the custom default timeout for Axios

Signed-off-by: Oleksii Orel <[email protected]>
  • Loading branch information
olexii4 authored Mar 6, 2025
1 parent 9177f78 commit 13063fb
Show file tree
Hide file tree
Showing 17 changed files with 68 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/common/src/dto/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export interface IServerConfig {
inactivityTimeout: number;
runTimeout: number;
startTimeout: number;
axiosRequestTimeout: number;
};
networking?: {
auth?: {
Expand Down
1 change: 1 addition & 0 deletions packages/dashboard-backend/src/constants/server-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
*/

export const startTimeoutSeconds = 300; // 5 minutes
export const requestTimeoutSeconds = 30; // 30 seconds
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ describe('Server Config API Service', () => {
const res = serverConfigService.getAllowedSourceUrls(buildCustomResource());
expect(res).toEqual(['https://github.com']);
});

describe('Axios Request Timeout', () => {
test('getting default value', () => {
const res = serverConfigService.getAxiosRequestTimeout();
expect(res).toEqual(30000);
});
test('getting custom value', () => {
process.env['CHE_DASHBOARD_AXIOS_REQUEST_TIMEOUT'] = '55000';
const res = serverConfigService.getAxiosRequestTimeout();
expect(res).toEqual(55000);
});
});
});

function buildCustomResourceList(): { body: CustomResourceDefinitionList } {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as k8s from '@kubernetes/client-node';
import { readFileSync } from 'fs';
import path from 'path';

import { startTimeoutSeconds } from '@/constants/server-config';
import { requestTimeoutSeconds, startTimeoutSeconds } from '@/constants/server-config';
import { createError } from '@/devworkspaceClient/services/helpers/createError';
import {
CustomObjectAPI,
Expand Down Expand Up @@ -213,6 +213,17 @@ export class ServerConfigApiService implements IServerConfigApi {
return cheCustomResource.spec.devEnvironments?.startTimeoutSeconds || startTimeoutSeconds;
}

getAxiosRequestTimeout(): number {
const requestTimeoutStr = process.env['CHE_DASHBOARD_AXIOS_REQUEST_TIMEOUT'];
if (requestTimeoutStr === undefined) {
return requestTimeoutSeconds * 1000;
}

const requestTimeout = parseInt(requestTimeoutStr, 10);

return isNaN(requestTimeout) ? requestTimeoutSeconds * 1000 : requestTimeout;
}

getDashboardLogo(
cheCustomResource: CheClusterCustomResource,
): { base64data: string; mediatype: string } | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export function isCheClusterCustomResourceSpecDevEnvironments(
export type CheClusterCustomResourceSpecComponents = {
cheServer?: Record<string, unknown>;
dashboard?: {
requestTimeout?: number;
branding?: {
logo?: {
base64data: string;
Expand Down Expand Up @@ -347,6 +348,11 @@ export interface IServerConfigApi {
*/
getWorkspaceStartTimeout(cheCustomResource: CheClusterCustomResource): number;

/**
* Returns the axios request timeout
*/
getAxiosRequestTimeout(): number;

/**
* Returns the dashboard branding logo
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ describe('Server Config Route', () => {
},
defaults: { components: [], plugins: [], pvcStrategy: '' },
pluginRegistry: { openVSXURL: 'openvsx-url' },
timeouts: { inactivityTimeout: 0, runTimeout: 0, startTimeout: 0 },
timeouts: {
inactivityTimeout: 0,
runTimeout: 0,
startTimeout: 0,
axiosRequestTimeout: 0,
},
networking: {
auth: {
advancedAuthorization: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const stubAllWorkspacesLimit = 1;
export const stubWorkspaceInactivityTimeout = 0;
export const stubWorkspaceRunTimeout = 0;
export const stubWorkspaceStartupTimeout = 0;
export const stubAxiosRequestTimeout = 0;
export const defaultPluginRegistryUrl = 'http://plugin-registry.eclipse-che.svc/v3';
export const internalRegistryDisableStatus = true;
export const externalDevfileRegistries = [{ url: 'https://devfile.registry.test.org/' }];
Expand Down Expand Up @@ -181,6 +182,7 @@ export const getDevWorkspaceClient = jest.fn(
getWorkspaceInactivityTimeout: _cheCustomResource => stubWorkspaceInactivityTimeout,
getWorkspaceRunTimeout: _cheCustomResource => stubWorkspaceRunTimeout,
getWorkspaceStartTimeout: _cheCustomResource => stubWorkspaceStartupTimeout,
getAxiosRequestTimeout: () => stubAxiosRequestTimeout,
getDefaultPluginRegistryUrl: _cheCustomResource => defaultPluginRegistryUrl,
getExternalDevfileRegistries: _cheCustomResource => externalDevfileRegistries,
getInternalRegistryDisableStatus: _cheCustomResource => internalRegistryDisableStatus,
Expand Down
2 changes: 2 additions & 0 deletions packages/dashboard-backend/src/routes/api/serverConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function registerServerConfigRoute(instance: FastifyInstance) {
const advancedAuthorization = serverConfigApi.getAdvancedAuthorization(cheCustomResource);
const autoProvision = serverConfigApi.getAutoProvision(cheCustomResource);
const allowedSourceUrls = serverConfigApi.getAllowedSourceUrls(cheCustomResource);
const axiosRequestTimeout = serverConfigApi.getAxiosRequestTimeout();

const serverConfig: api.IServerConfig = {
containerBuild,
Expand All @@ -61,6 +62,7 @@ export function registerServerConfigRoute(instance: FastifyInstance) {
inactivityTimeout,
runTimeout,
startTimeout,
axiosRequestTimeout,
},
devfileRegistry: {
disableInternalRegistry,
Expand Down
1 change: 1 addition & 0 deletions packages/dashboard-frontend/src/__tests__/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const serverConfigData = {
inactivityTimeout: 10800,
runTimeout: 86400,
startTimeout: 300,
axiosRequestTimeout: 30000,
},
devfileRegistry: {
disableInternalRegistry: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const serverConfig: api.IServerConfig = {
inactivityTimeout: -1,
runTimeout: -1,
startTimeout,
axiosRequestTimeout: 30000,
},
defaultNamespace: {
autoProvision: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class CheAxiosInstance {
private readonly axiosInstance: AxiosInstance;

private constructor() {
this.axiosInstance = axios.create({ timeout: 30000 });
this.axiosInstance = axios.create();
this.axiosInstance.defaults.timeout = 15000;
}

public static getInstance(): CheAxiosInstance {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import common, { api } from '@eclipse-che/common';

import { getAxiosInstance } from '@/services/axios-wrapper/getAxiosInstance';
import * as ServerConfigApi from '@/services/backend-client/serverConfigApi';
import { createMockStore } from '@/store/__mocks__/mockActionsTestStore';
import { verifyAuthorized } from '@/store/SanityCheck';
Expand All @@ -38,14 +39,21 @@ describe('ServerConfig, actions', () => {
it('should dispatch receive action on successful fetch', async () => {
const mockConfig = {
allowedSourceUrls: ['https://github.com'],
timeouts: {
axiosRequestTimeout: 30000,
},
// ...
} as api.IServerConfig;

(verifyAuthorized as jest.Mock).mockResolvedValue(true);
expect(getAxiosInstance().defaults.timeout).toEqual(15000);

(ServerConfigApi.fetchServerConfig as jest.Mock).mockResolvedValue(mockConfig);

await store.dispatch(actionCreators.requestServerConfig());

expect(getAxiosInstance().defaults.timeout).toEqual(30000);
expect(verifyAuthorized).not.toHaveBeenCalled();

const actions = store.getActions();
expect(actions).toHaveLength(2);
expect(actions[0]).toEqual(serverConfigRequestAction());
Expand All @@ -55,14 +63,16 @@ describe('ServerConfig, actions', () => {
it('should dispatch error action on failed fetch', async () => {
const errorMessage = 'Network error';

(verifyAuthorized as jest.Mock).mockResolvedValue(true);
(ServerConfigApi.fetchServerConfig as jest.Mock).mockRejectedValue(new Error(errorMessage));
(common.helpers.errors.getMessage as jest.Mock).mockReturnValue(errorMessage);
(verifyAuthorized as jest.Mock).mockResolvedValue(true);

await expect(store.dispatch(actionCreators.requestServerConfig())).rejects.toThrow(
`Failed to fetch workspace defaults. ${errorMessage}`,
);

expect(verifyAuthorized).toHaveBeenCalled();

const actions = store.getActions();
expect(actions).toHaveLength(2);
expect(actions[0]).toEqual(serverConfigRequestAction());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe('ServerConfig, reducer', () => {
inactivityTimeout: 100,
runTimeout: 200,
startTimeout: 300,
axiosRequestTimeout: 30000,
},
defaultNamespace: {
autoProvision: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe('ServerConfig Selectors', () => {
pluginRegistryInternalURL: 'https://internal.plugin.registry',
timeouts: {
startTimeout: 300,
axiosRequestTimeout: 30000,
},
dashboardLogo: {
base64data: 'base64data',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const serverConfig: api.IServerConfig = {
inactivityTimeout: -1,
runTimeout: -1,
startTimeout: 300,
axiosRequestTimeout: 30000,
},
defaultNamespace: {
autoProvision: true,
Expand Down
8 changes: 6 additions & 2 deletions packages/dashboard-frontend/src/store/ServerConfig/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import common, { api } from '@eclipse-che/common';
import { createAction } from '@reduxjs/toolkit';

import { getAxiosInstance } from '@/services/axios-wrapper/getAxiosInstance';
import * as ServerConfigApi from '@/services/backend-client/serverConfigApi';
import { AppThunk } from '@/store';
import { verifyAuthorized } from '@/store/SanityCheck';
Expand All @@ -24,14 +25,17 @@ export const serverConfigErrorAction = createAction<string>('serverConfig/error'
export const actionCreators = {
requestServerConfig: (): AppThunk => async (dispatch, getState) => {
try {
await verifyAuthorized(dispatch, getState);

dispatch(serverConfigRequestAction());

const config = await ServerConfigApi.fetchServerConfig();

if (config?.timeouts?.axiosRequestTimeout) {
getAxiosInstance().defaults.timeout = config.timeouts.axiosRequestTimeout;
}

dispatch(serverConfigReceiveAction(config));
} catch (e) {
await verifyAuthorized(dispatch, getState);
const error = common.helpers.errors.getMessage(e);
dispatch(serverConfigErrorAction(error));
throw new Error(`Failed to fetch workspace defaults. ${error}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const unloadedState: State = {
inactivityTimeout: -1,
runTimeout: -1,
startTimeout: 300,
axiosRequestTimeout: 30000,
},
defaultNamespace: {
autoProvision: true,
Expand Down

0 comments on commit 13063fb

Please sign in to comment.