From 9f3a7b9a754621431048d1e877fc9497682d19f7 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Mon, 24 Jul 2023 16:16:02 -0300 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=A6=20chore(frontend):=20add=20@ty?= =?UTF-8?q?pes/axios=20package=20to=20improve=20type=20checking=20in=20the?= =?UTF-8?q?=20frontend=20=F0=9F=90=9B=20fix(typesContext.tsx):=20remove=20?= =?UTF-8?q?unnecessary=20retry=20logic=20and=20error=20logging=20in=20getT?= =?UTF-8?q?ypes=20function=20=F0=9F=9A=80=20feat(API/api.tsx):=20add=20Api?= =?UTF-8?q?Interceptor=20component=20to=20handle=20API=20response=20retrie?= =?UTF-8?q?s=20and=20error=20handling=20=F0=9F=9A=80=20feat(API/index.ts):?= =?UTF-8?q?=20replace=20axios=20calls=20with=20api=20instance=20from=20API?= =?UTF-8?q?/api.tsx=20for=20better=20code=20organization=20and=20reusabili?= =?UTF-8?q?ty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ feat(index.tsx): add ApiInterceptor component to intercept API requests and responses for additional processing --- src/frontend/package-lock.json | 25 +++++++--- src/frontend/package.json | 1 + src/frontend/src/contexts/typesContext.tsx | 13 +---- src/frontend/src/controllers/API/api.tsx | 58 ++++++++++++++++++++++ src/frontend/src/controllers/API/index.ts | 48 +++++++++--------- src/frontend/src/index.tsx | 2 + 6 files changed, 104 insertions(+), 43 deletions(-) create mode 100644 src/frontend/src/controllers/API/api.tsx diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 3665cbd02..2a3421492 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -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": [ diff --git a/src/frontend/package.json b/src/frontend/package.json index 323a9a7be..a5c4d61eb 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -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", diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index ff4cc1c29..d4523bf60 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -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."); } } diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx new file mode 100644 index 000000000..f850eb9ce --- /dev/null +++ b/src/frontend/src/controllers/API/api.tsx @@ -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 }; diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 7668fea0a..6d24e7d09 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -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>} A promise that resolves to an AxiosResponse containing all the objects. */ export async function getAll(): Promise> { - 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} 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> { - 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> { - 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 { 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 { 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 { 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>} 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>} 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 { - 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> { - 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> { 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); } diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index 794fcbf32..f78c9da94 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -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( + ); From 812f5d2961e00ea2d4d1820dedace3881107ad99 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Tue, 25 Jul 2023 10:08:11 -0300 Subject: [PATCH 2/2] Styling fixes --- src/frontend/src/components/ui/accordion.tsx | 2 +- src/frontend/src/components/ui/card.tsx | 8 ++++---- .../src/components/ui/dropdown-menu.tsx | 14 ++++++------- src/frontend/src/components/ui/menubar.tsx | 20 +++++++++---------- src/frontend/src/components/ui/table.tsx | 6 +++--- src/frontend/src/components/ui/tooltip.tsx | 2 +- src/frontend/src/controllers/API/api.tsx | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/frontend/src/components/ui/accordion.tsx b/src/frontend/src/components/ui/accordion.tsx index 4add6c8ac..27dbba93b 100644 --- a/src/frontend/src/components/ui/accordion.tsx +++ b/src/frontend/src/components/ui/accordion.tsx @@ -56,4 +56,4 @@ const AccordionContent = React.forwardRef< )); AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/src/frontend/src/components/ui/card.tsx b/src/frontend/src/components/ui/card.tsx index 585a81a76..c904310cd 100644 --- a/src/frontend/src/components/ui/card.tsx +++ b/src/frontend/src/components/ui/card.tsx @@ -77,9 +77,9 @@ CardFooter.displayName = "CardFooter"; export { Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, }; diff --git a/src/frontend/src/components/ui/dropdown-menu.tsx b/src/frontend/src/components/ui/dropdown-menu.tsx index c61e09fc1..f9a0d87d3 100644 --- a/src/frontend/src/components/ui/dropdown-menu.tsx +++ b/src/frontend/src/components/ui/dropdown-menu.tsx @@ -182,18 +182,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, DropdownMenuCheckboxItem, - DropdownMenuRadioItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenuTrigger, }; diff --git a/src/frontend/src/components/ui/menubar.tsx b/src/frontend/src/components/ui/menubar.tsx index 276f5fc5b..512bb6e63 100644 --- a/src/frontend/src/components/ui/menubar.tsx +++ b/src/frontend/src/components/ui/menubar.tsx @@ -218,19 +218,19 @@ MenubarShortcut.displayname = "MenubarShortcut"; export { Menubar, - MenubarMenu, - MenubarTrigger, - MenubarContent, - MenubarItem, - MenubarSeparator, - MenubarLabel, MenubarCheckboxItem, + MenubarContent, + MenubarGroup, + MenubarItem, + MenubarLabel, + MenubarMenu, + MenubarPortal, MenubarRadioGroup, MenubarRadioItem, - MenubarPortal, + MenubarSeparator, + MenubarShortcut, + MenubarSub, MenubarSubContent, MenubarSubTrigger, - MenubarGroup, - MenubarSub, - MenubarShortcut, + MenubarTrigger, }; diff --git a/src/frontend/src/components/ui/table.tsx b/src/frontend/src/components/ui/table.tsx index 80a74f378..eb1e3bda6 100644 --- a/src/frontend/src/components/ui/table.tsx +++ b/src/frontend/src/components/ui/table.tsx @@ -103,11 +103,11 @@ TableCaption.displayName = "TableCaption"; export { Table, - TableHeader, TableBody, + TableCaption, + TableCell, TableFooter, TableHead, + TableHeader, TableRow, - TableCell, - TableCaption, }; diff --git a/src/frontend/src/components/ui/tooltip.tsx b/src/frontend/src/components/ui/tooltip.tsx index c54245a53..3d31ab66d 100644 --- a/src/frontend/src/components/ui/tooltip.tsx +++ b/src/frontend/src/components/ui/tooltip.tsx @@ -28,4 +28,4 @@ const TooltipContent = React.forwardRef< )); TooltipContent.displayName = TooltipPrimitive.Content.displayName; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index f850eb9ce..8dd9eac9f 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -55,4 +55,4 @@ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -export { api, ApiInterceptor }; +export { ApiInterceptor, api };