📦 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", "@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2", "accordion": "^3.0.2",
"ace-builds": "^1.16.0", "ace-builds": "^1.16.0",
"add": "^2.0.6", "add": "^2.0.6",
@ -147,9 +148,9 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.22.9", "version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
"integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -1191,9 +1192,9 @@
} }
}, },
"node_modules/@mui/private-theming/node_modules/@babel/runtime": { "node_modules/@mui/private-theming/node_modules/@babel/runtime": {
"version": "7.22.6", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
}, },
@ -3218,6 +3219,15 @@
"integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==",
"dev": true "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": { "node_modules/@types/cacheable-request": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@ -3550,7 +3560,7 @@
"version": "16.18.12", "version": "16.18.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz",
"integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==", "integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==",
"devOptional": true "dev": true
}, },
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
"version": "4.0.0", "version": "4.0.0",
@ -5596,6 +5606,7 @@
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [

View file

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

View file

@ -71,18 +71,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
// Clear the interval if successful. // Clear the interval if successful.
clearInterval(intervalId); clearInterval(intervalId);
} catch (error) { } catch (error) {
retryCount++; console.error("An error has occurred while fetching types.");
// 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.");
}
} }
} }

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