📦 chore(frontend): add @types/axios package to improve type checking in the frontend

🐛 fix(typesContext.tsx): remove unnecessary retry logic and error logging in getTypes function
🚀 feat(API/api.tsx): add ApiInterceptor component to handle API response retries and error handling
🚀 feat(API/index.ts): replace axios calls with api instance from API/api.tsx for better code organization and reusability

 feat(index.tsx): add ApiInterceptor component to intercept API requests and responses for additional processing
This commit is contained in:
Cristhian Zanforlin Lousa 2023-07-24 16:16:02 -03:00
commit 9f3a7b9a75
6 changed files with 104 additions and 43 deletions

View file

@ -29,6 +29,7 @@
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0",
"add": "^2.0.6",
@ -147,9 +148,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.22.9",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz",
"integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==",
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
"integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
"engines": {
"node": ">=6.9.0"
}
@ -1191,9 +1192,9 @@
}
},
"node_modules/@mui/private-theming/node_modules/@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
@ -3218,6 +3219,15 @@
"integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==",
"dev": true
},
"node_modules/@types/axios": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
"integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==",
"deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!",
"dependencies": {
"axios": "*"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@ -3550,7 +3560,7 @@
"version": "16.18.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz",
"integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==",
"devOptional": true
"dev": true
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -5596,6 +5606,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [

View file

@ -24,6 +24,7 @@
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0",
"add": "^2.0.6",

View file

@ -71,18 +71,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
// Clear the interval if successful.
clearInterval(intervalId);
} catch (error) {
retryCount++;
// On error, double the delay for the next attempt up to a maximum.
delay = Math.min(30000, delay * 2);
// Log errors but don't do anything else - the function will try again on the next interval.
console.error(error);
// Clear the old interval and start a new one with the new delay.
if (retryCount <= maxRetryCount) {
clearInterval(intervalId);
intervalId = setInterval(getTypes, delay);
} else {
console.error("Max retry attempts reached. Stopping retries.");
}
console.error("An error has occurred while fetching types.");
}
}

View file

@ -0,0 +1,58 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef } from "react";
import { alertContext } from "../../contexts/alertContext";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
baseURL: "",
});
function ApiInterceptor() {
const retryCounts = useRef([]);
const { setErrorData } = useContext(alertContext);
useEffect(() => {
const interceptor = api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
let retryCount = 0;
while (retryCount < 4) {
await sleep(5000); // Sleep for 5 seconds
retryCount++;
try {
const response = await axios.request(error.config);
return response;
} catch (error) {
if (retryCount === 3) {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
"Endpoint: " + error.config.url,
],
});
return Promise.reject(error);
}
}
}
}
);
return () => {
// Clean up the interceptor when the component unmounts
api.interceptors.response.eject(interceptor);
};
}, [retryCounts]);
return null;
}
// Function to sleep for a given duration in milliseconds
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export { api, ApiInterceptor };

View file

@ -1,5 +1,6 @@
import axios, { AxiosResponse } from "axios";
import { AxiosResponse } from "axios";
import { ReactFlowJsonObject } from "reactflow";
import { api } from "../../controllers/API/api";
import { APIObjectType, sendAllProps } from "../../types/api/index";
import { FlowStyleType, FlowType } from "../../types/flow";
import {
@ -17,16 +18,14 @@ import {
* @returns {Promise<AxiosResponse<APIObjectType>>} A promise that resolves to an AxiosResponse containing all the objects.
*/
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
return await axios.get(`/api/v1/all`);
return await api.get(`/api/v1/all`);
}
const GITHUB_API_URL = "https://api.github.com";
export async function getRepoStars(owner, repo) {
try {
const response = await axios.get(
`${GITHUB_API_URL}/repos/${owner}/${repo}`
);
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
return response.data.stargazers_count;
} catch (error) {
console.error("Error fetching repository data:", error);
@ -41,13 +40,13 @@ export async function getRepoStars(owner, repo) {
* @returns {AxiosResponse<any>} The API response.
*/
export async function sendAll(data: sendAllProps) {
return await axios.post(`/api/v1/predict`, data);
return await api.post(`/api/v1/predict`, data);
}
export async function postValidateCode(
code: string
): Promise<AxiosResponse<errorsTypeAPI>> {
return await axios.post("/api/v1/validate/code", { code });
return await api.post("/api/v1/validate/code", { code });
}
/**
@ -62,7 +61,7 @@ export async function postValidatePrompt(
template: string,
frontend_node: APIClassType
): Promise<AxiosResponse<PromptTypeAPI>> {
return await axios.post("/api/v1/validate/prompt", {
return await api.post("/api/v1/validate/prompt", {
name: name,
template: template,
frontend_node: frontend_node,
@ -77,14 +76,14 @@ export async function postValidatePrompt(
export async function getExamples(): Promise<FlowType[]> {
const url =
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=main";
const response = await axios.get(url);
const response = await api.get(url);
const jsonFiles = response.data.filter((file: any) => {
return file.name.endsWith(".json");
});
const contentsPromises = jsonFiles.map(async (file: any) => {
const contentResponse = await axios.get(file.download_url);
const contentResponse = await api.get(file.download_url);
return contentResponse.data;
});
@ -106,11 +105,12 @@ export async function saveFlowToDatabase(newFlow: {
style?: FlowStyleType;
}): Promise<FlowType> {
try {
const response = await axios.post("/api/v1/flows/", {
const response = await api.post("/api/v1/flows/", {
name: newFlow.name,
data: newFlow.data,
description: newFlow.description,
});
if (response.status !== 201) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -131,7 +131,7 @@ export async function updateFlowInDatabase(
updatedFlow: FlowType
): Promise<FlowType> {
try {
const response = await axios.patch(`/api/v1/flows/${updatedFlow.id}`, {
const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
name: updatedFlow.name,
data: updatedFlow.data,
description: updatedFlow.description,
@ -155,7 +155,7 @@ export async function updateFlowInDatabase(
*/
export async function readFlowsFromDatabase() {
try {
const response = await axios.get("/api/v1/flows/");
const response = await api.get("/api/v1/flows/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -168,7 +168,7 @@ export async function readFlowsFromDatabase() {
export async function downloadFlowsFromDatabase() {
try {
const response = await axios.get("/api/v1/flows/download/");
const response = await api.get("/api/v1/flows/download/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -181,7 +181,7 @@ export async function downloadFlowsFromDatabase() {
export async function uploadFlowsToDatabase(flows) {
try {
const response = await axios.post(`/api/v1/flows/upload/`, flows);
const response = await api.post(`/api/v1/flows/upload/`, flows);
if (response.status !== 201) {
throw new Error(`HTTP error! status: ${response.status}`);
@ -202,7 +202,7 @@ export async function uploadFlowsToDatabase(flows) {
*/
export async function deleteFlowFromDatabase(flowId: string) {
try {
const response = await axios.delete(`/api/v1/flows/${flowId}`);
const response = await api.delete(`/api/v1/flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -222,7 +222,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
*/
export async function getFlowFromDatabase(flowId: number) {
try {
const response = await axios.get(`/api/v1/flows/${flowId}`);
const response = await api.get(`/api/v1/flows/${flowId}`);
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -241,7 +241,7 @@ export async function getFlowFromDatabase(flowId: number) {
*/
export async function getFlowStylesFromDatabase() {
try {
const response = await axios.get("/api/v1/flow_styles/");
const response = await api.get("/api/v1/flow_styles/");
if (response.status !== 200) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@ -261,7 +261,7 @@ export async function getFlowStylesFromDatabase() {
*/
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
try {
const response = await axios.post("/api/v1/flow_styles/", flowStyle, {
const response = await api.post("/api/v1/flow_styles/", flowStyle, {
headers: {
accept: "application/json",
"Content-Type": "application/json",
@ -284,7 +284,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
*/
export async function getVersion() {
const respnose = await axios.get("/api/v1/version");
const respnose = await api.get("/api/v1/version");
return respnose.data;
}
@ -294,7 +294,7 @@ export async function getVersion() {
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the health status.
*/
export async function getHealth() {
return await axios.get("/health"); // Health is the only endpoint that doesn't require /api/v1
return await api.get("/health"); // Health is the only endpoint that doesn't require /api/v1
}
/**
@ -306,7 +306,7 @@ export async function getHealth() {
export async function getBuildStatus(
flowId: string
): Promise<BuildStatusTypeAPI> {
return await axios.get(`/api/v1/build/${flowId}/status`);
return await api.get(`/api/v1/build/${flowId}/status`);
}
//docs for postbuildinit
@ -319,7 +319,7 @@ export async function getBuildStatus(
export async function postBuildInit(
flow: FlowType
): Promise<AxiosResponse<InitTypeAPI>> {
return await axios.post(`/api/v1/build/init/${flow.id}`, flow);
return await api.post(`/api/v1/build/init/${flow.id}`, flow);
}
// fetch(`/upload/${id}`, {
@ -337,5 +337,5 @@ export async function uploadFile(
): Promise<AxiosResponse<UploadFileTypeAPI>> {
const formData = new FormData();
formData.append("file", file);
return await axios.post(`/api/v1/upload/${id}`, formData);
return await api.post(`/api/v1/upload/${id}`, formData);
}

View file

@ -4,6 +4,7 @@ import App from "./App";
import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";
import { ApiInterceptor } from "./controllers/API/api";
import "./index.css";
const root = ReactDOM.createRoot(
@ -13,6 +14,7 @@ root.render(
<ContextWrapper>
<BrowserRouter>
<App />
<ApiInterceptor />
</BrowserRouter>
</ContextWrapper>
);