Explorar el Código

Refactor the API caller class

Moved the logic from classic promise type functions to async / await.
Split the error handling code into a separate file.
Sergiu Miclea hace 4 años
padre
commit
54606ebc90

+ 2 - 0
.githooks/pre-commit

@@ -5,6 +5,8 @@
 
 printf "\nRunning TSC:\n"
 
+PATH=$PATH:/usr/local/bin:/usr/local/sbin
+
 yarn tsc
 
 if [[ "$?" == 0 ]]; then

+ 3 - 5
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.tsx

@@ -178,16 +178,14 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   getLastExecution() {
-    const replica = this.replica
-    if (replica && replica.executions && replica.executions.length) {
-      return replica.executions[replica.executions.length - 1]
+    if (this.replica?.executions?.length) {
+      return this.replica.executions[this.replica.executions.length - 1]
     }
     return null
   }
 
   getStatus() {
-    const lastExecution = this.getLastExecution()
-    return lastExecution && lastExecution.status
+    return this.getLastExecution()?.status
   }
 
   async loadIsEditable(replicaDetails: ReplicaItemDetails) {

+ 2 - 4
src/stores/ReplicaStore.ts

@@ -78,8 +78,7 @@ class ReplicaStore {
     }
 
     try {
-      const replicas = await ReplicaSource
-        .getReplicas(options && options.skipLog, options && options.quietError)
+      const replicas = await ReplicaSource.getReplicas(options && options.skipLog, options && options.quietError)
       this.getReplicasSuccess(replicas)
     } finally {
       this.getReplicasDone()
@@ -184,8 +183,7 @@ class ReplicaStore {
 
   @action executeSuccess(replicaId: string, execution: Execution) {
     if (this.replicaDetails?.id === replicaId) {
-      const updatedReplica = ReplicaStoreUtils
-        .getNewReplica(this.replicaDetails, execution)
+      const updatedReplica = ReplicaStoreUtils.getNewReplica(this.replicaDetails, execution)
       this.replicaDetails = updatedReplica
     }
     this.getExecutionTasks({ replicaId, executionId: execution.id })

+ 47 - 158
src/utils/ApiCaller.ts

@@ -17,14 +17,14 @@ import cookie from 'js-cookie'
 
 import cacher from './Cacher'
 import logger from './ApiLogger'
-import notificationStore from '../stores/NotificationStore'
+import ApiCallerHandlers from './ApiCallerHandlers'
 
 type Cancelable = {
   requestId: string,
   cancel: () => void,
 }
 
-type RequestOptions = {
+export type RequestOptions = {
   url: string,
   method?: AxiosRequestConfig['method'],
   cancelId?: string,
@@ -47,29 +47,6 @@ const addCancelable = (cancelable: Cancelable) => {
   }
 }
 
-const isOnLoginPage = (): boolean => window.location.pathname.indexOf('login') > -1
-
-const redirect = (statusCode: number) => {
-  if (statusCode !== 401 || isOnLoginPage()) {
-    return
-  }
-  let currentPath = '?prev=/'
-  if (window.location.pathname !== '/') {
-    currentPath = `?prev=${window.location.pathname}${window.location.search}`
-  }
-  window.location.href = `/login${currentPath}`
-}
-
-const truncateUrl = (url: string): string => {
-  const MAX_LENGTH = 100
-  let relativePath = url.replace(/http(s)?:\/\/.*?\//, '/')
-  relativePath += relativePath
-  if (relativePath.length > MAX_LENGTH) {
-    relativePath = `${relativePath.substr(0, MAX_LENGTH)}...`
-  }
-  return relativePath
-}
-
 class ApiCaller {
   constructor() {
     axios.defaults.headers.common['Content-Type'] = 'application/json'
@@ -91,151 +68,63 @@ class ApiCaller {
     return this.send({ url })
   }
 
-  send(options: RequestOptions): Promise<AxiosResponse<any>> {
-    const cachedData = options.cache ? cacher
-      .load({ key: options.url, maxAge: options.cacheFor }) : null
+  async send(options: RequestOptions): Promise<AxiosResponse<any>> {
+    const cachedData = options.cache ? cacher.load({ key: options.url, maxAge: options.cacheFor }) : null
     if (cachedData) {
       const response: any = { data: cachedData }
-      return Promise.resolve(response)
+      return response
     }
 
-    return new Promise((resolve, reject) => {
-      const axiosOptions: AxiosRequestConfig = {
-        url: options.url,
-        method: options.method || 'GET',
-        headers: options.headers || {},
-        data: options.data || null,
-        responseType: options.responseType || 'json',
-      }
+    const axiosOptions: AxiosRequestConfig = {
+      url: options.url,
+      method: options.method || 'GET',
+      headers: options.headers || {},
+      data: options.data || null,
+      responseType: options.responseType || 'json',
+    }
 
-      if (options.cancelId) {
-        let cancel = () => { }
-        axiosOptions.cancelToken = new CancelToken(c => {
-          cancel = c
-        })
-        addCancelable({ requestId: options.cancelId, cancel })
-      }
+    if (options.cancelId) {
+      let cancel = () => { }
+      axiosOptions.cancelToken = new CancelToken(c => {
+        cancel = c
+      })
+      addCancelable({ requestId: options.cancelId, cancel })
+    }
+
+    if (!options.skipLog) {
+      logger.log({
+        url: axiosOptions.url,
+        method: axiosOptions.method || 'GET',
+        type: 'REQUEST',
+      })
+    }
+
+    const apiCallerHandlers = new ApiCallerHandlers(options, axiosOptions)
 
+    try {
+      const response = await axios(axiosOptions)
       if (!options.skipLog) {
+        console.log(`%cResponse ${axiosOptions.url}`, 'color: #0044CA', response.data)
         logger.log({
           url: axiosOptions.url,
           method: axiosOptions.method || 'GET',
-          type: 'REQUEST',
+          type: 'RESPONSE',
+          requestStatus: 200,
         })
       }
-
-      axios(axiosOptions).then(response => {
-        if (!options.skipLog) {
-          console.log(`%cResponse ${axiosOptions.url}`, 'color: #0044CA', response.data)
-          logger.log({
-            url: axiosOptions.url,
-            method: axiosOptions.method || 'GET',
-            type: 'RESPONSE',
-            requestStatus: 200,
-          })
-        }
-        if (options.cache) {
-          cacher.save({ key: options.url, data: response.data })
-        }
-        resolve(response)
-      }).catch(error => {
-        if (error.response) {
-          // The request was made and the server responded with a status code
-          // that falls out of the range of 2xx
-          if (
-            (error.response.status !== 401 || !isOnLoginPage())
-            && !options.quietError) {
-            const data = error.response.data
-            const message = (data && data.error && data.error.message) || (data && data.description)
-            const alertMessage = message || `${error.response.statusText || error.response.status} ${truncateUrl(options.url)}`
-            const status = error.response.status && error.response.statusText
-              ? `${error.response.status} - ${error.response.statusText}`
-              : error.response.statusText || error.response.status
-            notificationStore.alert(alertMessage, 'error', {
-              action: {
-                label: 'View details',
-                callback: () => ({ request: axiosOptions, error: { status, message } }),
-              },
-            })
-          }
-
-          if (error.request.responseURL.indexOf('/proxy/') === -1
-            && error.request.responseURL.indexOf('/azure-login') === -1) {
-            redirect(error.response.status)
-          }
-
-          logger.log({
-            url: axiosOptions.url,
-            method: axiosOptions.method || 'GET',
-            type: 'RESPONSE',
-            requestStatus: error.response.status,
-            requestError: error,
-          })
-          reject(error.response)
-        } else if (error.request) {
-          // The request was made but no response was received
-          // `error.request` is an instance of XMLHttpRequest
-          if (!isOnLoginPage() && !options.quietError) {
-            notificationStore.alert(
-              `Request failed, there might be a problem with the connection to the server. ${truncateUrl(options.url)}`,
-              'error',
-              {
-                action: {
-                  label: 'View details',
-                  callback: () => ({
-                    request: axiosOptions,
-                    error: { message: 'Request was made but no response was received' },
-                  }),
-                },
-              },
-            )
-          }
-          logger.log({
-            url: axiosOptions.url,
-            method: axiosOptions.method || 'GET',
-            type: 'RESPONSE',
-            description: 'No response',
-            requestStatus: 500,
-            requestError: error,
-          })
-          reject({})
-        } else {
-          const canceled = error.__CANCEL__
-          reject({ canceled })
-          if (canceled) {
-            logger.log({
-              url: axiosOptions.url,
-              method: axiosOptions.method || 'GET',
-              type: 'RESPONSE',
-              requestStatus: 'canceled',
-            })
-            return
-          }
-
-          // Something happened in setting up the request that triggered an Error
-          logger.log({
-            url: axiosOptions.url,
-            method: axiosOptions.method || 'GET',
-            type: 'RESPONSE',
-            description: 'Something happened in setting up the request',
-            requestStatus: 500,
-          })
-          notificationStore.alert(
-            `Request failed, there might be a problem with the connection to the server. ${truncateUrl(options.url)}`,
-            'error',
-            {
-              action: {
-                label: 'View details',
-                callback: () => ({
-                  request: axiosOptions,
-                  error: { message: 'Something happened in setting up the request' },
-                }),
-              },
-            },
-          )
-        }
-      })
-    })
+      if (options.cache) {
+        cacher.save({ key: options.url, data: response.data })
+      }
+      return response
+    } catch (error) {
+      if (error.response) {
+        throw apiCallerHandlers.handleErrorResponse(error)
+      } else if (error.request) {
+        throw apiCallerHandlers.handleErrorRequest(error)
+      } else {
+        throw apiCallerHandlers.handleRequestCancel(error)
+      }
+    }
   }
 
   setDefaultHeader(name: string, value: string | null) {

+ 135 - 0
src/utils/ApiCallerHandlers.ts

@@ -0,0 +1,135 @@
+import { AxiosRequestConfig } from 'axios'
+import notificationStore from '../stores/NotificationStore'
+import { RequestOptions } from './ApiCaller'
+import logger from './ApiLogger'
+
+const isOnLoginPage = (): boolean => window.location.pathname.indexOf('login') > -1
+
+const truncateUrl = (url: string): string => {
+  const MAX_LENGTH = 100
+  let relativePath = url.replace(/http(s)?:\/\/.*?\//, '/')
+  relativePath += relativePath
+  if (relativePath.length > MAX_LENGTH) {
+    relativePath = `${relativePath.substr(0, MAX_LENGTH)}...`
+  }
+  return relativePath
+}
+
+const redirect = (statusCode: number) => {
+  if (statusCode !== 401 || isOnLoginPage()) {
+    return
+  }
+  let currentPath = '?prev=/'
+  if (window.location.pathname !== '/') {
+    currentPath = `?prev=${window.location.pathname}${window.location.search}`
+  }
+  window.location.href = `/login${currentPath}`
+}
+
+class ApiCallerHandlers {
+  setupOptions: RequestOptions
+
+  axiosOptions: AxiosRequestConfig
+
+  constructor(setupOptions: RequestOptions, axiosOptions: AxiosRequestConfig) {
+    this.setupOptions = setupOptions
+    this.axiosOptions = axiosOptions
+  }
+
+  handleErrorResponse(error: any) {
+    if ((error.response.status !== 401 || !isOnLoginPage()) && !this.setupOptions.quietError) {
+      const data = error.response.data
+      const message = (data && data.error && data.error.message) || (data && data.description)
+      const alertMessage = message || `${error.response.statusText || error.response.status} ${truncateUrl(this.setupOptions.url)}`
+      const status = error.response.status && error.response.statusText
+        ? `${error.response.status} - ${error.response.statusText}`
+        : error.response.statusText || error.response.status
+      notificationStore.alert(alertMessage, 'error', {
+        action: {
+          label: 'View details',
+          callback: () => ({ request: this.axiosOptions, error: { status, message } }),
+        },
+      })
+    }
+
+    if (error.request.responseURL.indexOf('/proxy/') === -1 && error.request.responseURL.indexOf('/azure-login') === -1) {
+      redirect(error.response.status)
+    }
+
+    logger.log({
+      url: this.axiosOptions.url,
+      method: this.axiosOptions.method || 'GET',
+      type: 'RESPONSE',
+      requestStatus: error.response.status,
+      requestError: error,
+    })
+    return error.response
+  }
+
+  handleErrorRequest(error: any) {
+    // The request was made but no response was received
+    // `error.request` is an instance of XMLHttpRequest
+    if (!isOnLoginPage() && !this.setupOptions.quietError) {
+      notificationStore.alert(
+        `Request failed, there might be a problem with the connection to the server. ${truncateUrl(this.setupOptions.url)}`,
+        'error',
+        {
+          action: {
+            label: 'View details',
+            callback: () => ({
+              request: this.axiosOptions,
+              error: { message: 'Request was made but no response was received' },
+            }),
+          },
+        },
+      )
+    }
+    logger.log({
+      url: this.axiosOptions.url,
+      method: this.axiosOptions.method || 'GET',
+      type: 'RESPONSE',
+      description: 'No response',
+      requestStatus: 500,
+      requestError: error,
+    })
+    return {}
+  }
+
+  handleRequestCancel(error: any) {
+    const canceled = error.__CANCEL__
+    if (canceled) {
+      logger.log({
+        url: this.axiosOptions.url,
+        method: this.axiosOptions.method || 'GET',
+        type: 'RESPONSE',
+        requestStatus: 'canceled',
+      })
+      return { canceled }
+    }
+
+    // Something happened in setting up the request that triggered an Error
+    logger.log({
+      url: this.axiosOptions.url,
+      method: this.axiosOptions.method || 'GET',
+      type: 'RESPONSE',
+      description: 'Something happened in setting up the request',
+      requestStatus: 500,
+    })
+    notificationStore.alert(
+      `Request failed, there might be a problem with the connection to the server. ${truncateUrl(this.setupOptions.url)}`,
+      'error',
+      {
+        action: {
+          label: 'View details',
+          callback: () => ({
+            request: this.axiosOptions,
+            error: { message: 'Something happened in setting up the request' },
+          }),
+        },
+      },
+    )
+    return error
+  }
+}
+
+export default ApiCallerHandlers