From 9fc9a22d0e7c66ab729c7ac03b7ed522575265d7 Mon Sep 17 00:00:00 2001 From: "Mariana R. Santos" Date: Thu, 19 Dec 2024 14:42:41 +0100 Subject: [PATCH] Introduce useQuery for server calls --- frontend/package-lock.json | 25 +++++ frontend/package.json | 1 + frontend/src/App.tsx | 65 ++++++----- .../components/Contexts/InpectionsContext.tsx | 69 +----------- .../InspectionStyles.tsx | 4 +- .../InspectionView.tsx | 105 +++++------------- 6 files changed, 92 insertions(+), 177 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4c4cd8a10..692f6c277 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@livekit/components-styles": "^1.0.12", "@microsoft/applicationinsights-web": "^3.1.2", "@microsoft/signalr": "^8.0.0", + "@tanstack/react-query": "^5.62.8", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.3.0", "@testing-library/user-event": "^14.5.2", @@ -3273,6 +3274,30 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", + "dependencies": { + "@tanstack/query-core": "5.62.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8dea0d2e7..84c562a2c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@livekit/components-styles": "^1.0.12", "@microsoft/applicationinsights-web": "^3.1.2", "@microsoft/signalr": "^8.0.0", + "@tanstack/react-query": "^5.62.8", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.3.0", "@testing-library/user-event": "^14.5.2", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 32baea97f..58acf4a3f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ import { MissionDefinitionsProvider } from 'components/Contexts/MissionDefinitio import { MediaStreamProvider } from 'components/Contexts/MediaStreamContext' import { DockProvider } from 'components/Contexts/DockContext' import { InspectionsProvider } from 'components/Contexts/InpectionsContext' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const appInsights = new ApplicationInsights({ config: { @@ -28,40 +29,44 @@ if (config.AI_CONNECTION_STRING.length > 0) { appInsights.trackPageView() } +const queryClient = new QueryClient() + const App = () => ( - - - - - - - - - - -
- -
-
- - - - - - - -
-
-
-
-
-
-
-
-
+ + + + + + + + + + + +
+ +
+
+ + + + + + + +
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Contexts/InpectionsContext.tsx b/frontend/src/components/Contexts/InpectionsContext.tsx index e8019fb5c..553541a36 100644 --- a/frontend/src/components/Contexts/InpectionsContext.tsx +++ b/frontend/src/components/Contexts/InpectionsContext.tsx @@ -1,14 +1,9 @@ -import { createContext, FC, useContext, useState, useEffect, useRef } from 'react' -import { BackendAPICaller } from 'api/ApiCaller' +import { createContext, FC, useContext, useState } from 'react' import { Task } from 'models/Task' -import { useInstallationContext } from './InstallationContext' interface IInspectionsContext { selectedInspectionTask: Task | undefined - selectedInspectionTasks: Task[] switchSelectedInspectionTask: (selectedInspectionTask: Task | undefined) => void - switchSelectedInspectionTasks: (selectedInspectionTask: Task[]) => void - mappingInspectionTasksObjectURL: { [taskIsarId: string]: string } } interface Props { @@ -17,81 +12,23 @@ interface Props { const defaultInspectionsContext = { selectedInspectionTask: undefined, - selectedInspectionTasks: [], switchSelectedInspectionTask: (selectedInspectionTask: Task | undefined) => undefined, - switchSelectedInspectionTasks: (selectedInspectionTasks: Task[]) => [], - mappingInspectionTasksObjectURL: {}, } const InspectionsContext = createContext(defaultInspectionsContext) export const InspectionsProvider: FC = ({ children }) => { - const { installationCode } = useInstallationContext() - const [selectedInspectionTask, setSelectedInspectionTask] = useState() - const [selectedInspectionTasks, setSelectedInspectionTasks] = useState([]) - const [selectedInspectionTasksToFetch, setSelectedInspectionTasksToFetch] = useState([]) - - const [mappingInspectionTasksObjectURL, setMappingInspectionTasksObjectURL] = useState<{ - [taskId: string]: string - }>({}) - - const [triggerFetch, setTriggerFetch] = useState(false) - const [startTimer, setStartTimer] = useState(false) - const imageObjectURL = useRef('') - - useEffect(() => { - const timeoutId = setTimeout(() => { - if (selectedInspectionTasksToFetch.length > 0) - setTriggerFetch((oldSetTriggerToFetch) => !oldSetTriggerToFetch) - }, 10000) - return () => clearTimeout(timeoutId) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [startTimer]) - - useEffect(() => { - Object.values(selectedInspectionTasksToFetch).forEach((task, index) => { - if (task.isarTaskId) { - BackendAPICaller.getInspection(installationCode, task.isarTaskId!) - .then((imageBlob) => { - imageObjectURL.current = URL.createObjectURL(imageBlob) - }) - .then(() => { - setMappingInspectionTasksObjectURL((oldMappingInspectionTasksObjectURL) => { - return { ...oldMappingInspectionTasksObjectURL, [task.isarTaskId!]: imageObjectURL.current } - }) - setSelectedInspectionTasksToFetch((oldSelectedInspectionTasksToFetch) => { - let newInspectionTaksToFetch = { ...oldSelectedInspectionTasksToFetch } - delete newInspectionTaksToFetch[index] - return newInspectionTaksToFetch - }) - }) - .catch(() => { - setStartTimer((oldValue) => !oldValue) - }) - } - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [installationCode, selectedInspectionTasksToFetch, triggerFetch]) - - const switchSelectedInspectionTask = (selectedName: Task | undefined) => { - setSelectedInspectionTask(selectedName) - } - const switchSelectedInspectionTasks = (selectedName: Task[]) => { - setMappingInspectionTasksObjectURL({}) - setSelectedInspectionTasks(selectedName) - setSelectedInspectionTasksToFetch(selectedName) + const switchSelectedInspectionTask = (selectedTask: Task | undefined) => { + setSelectedInspectionTask(selectedTask) } return ( {children} diff --git a/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionStyles.tsx b/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionStyles.tsx index 8423bd358..2d15a5f54 100644 --- a/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionStyles.tsx +++ b/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionStyles.tsx @@ -2,7 +2,7 @@ import { Button, Card, Dialog } from '@equinor/eds-core-react' import { tokens } from '@equinor/eds-tokens' import { styled } from 'styled-components' -export const StyledInspection = styled.canvas` +export const StyledInspection = styled.img` flex: 1 0 0; align-self: stretch; width: 80vh; @@ -12,7 +12,7 @@ export const StyledInspection = styled.canvas` } ` -export const StyledInspectionImage = styled.canvas` +export const StyledInspectionImage = styled.img` flex: 1 0 0; align-self: center; max-width: 100%; diff --git a/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionView.tsx b/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionView.tsx index 2c5ed477d..68e7cfe71 100644 --- a/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionView.tsx +++ b/frontend/src/components/Pages/InspectionReportPage.tsx/InspectionView.tsx @@ -1,5 +1,4 @@ import { Icon, Typography } from '@equinor/eds-core-react' -import { useCallback, useEffect, useRef, useState } from 'react' import { Task, TaskStatus } from 'models/Task' import { useInstallationContext } from 'components/Contexts/InstallationContext' import { Icons } from 'utils/icons' @@ -23,55 +22,23 @@ import { StyledInspectionImage, StyledSection, } from './InspectionStyles' +import { BackendAPICaller } from 'api/ApiCaller' +import { useQuery } from '@tanstack/react-query' interface InspectionDialogViewProps { task: Task tasks: Task[] } -const getMeta = async (url: string) => { - const image = new Image() - image.src = url - await image.decode() - return image -} - export const InspectionDialogView = ({ task, tasks }: InspectionDialogViewProps) => { const { TranslateText } = useLanguageContext() const { installationName } = useInstallationContext() - const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) - const imageObjectURL = useRef('') - - const { switchSelectedInspectionTask, mappingInspectionTasksObjectURL } = useInspectionsContext() - - const updateImage = useCallback(() => { - if (task.isarTaskId && mappingInspectionTasksObjectURL[task.isarTaskId]) { - imageObjectURL.current = mappingInspectionTasksObjectURL[task.isarTaskId] - - getMeta(imageObjectURL.current).then((img) => { - const inspectionCanvas = document.getElementById('inspectionCanvas') as HTMLCanvasElement - if (inspectionCanvas) { - inspectionCanvas.width = img.width - inspectionCanvas.height = img.height - let context = inspectionCanvas.getContext('2d') - if (context) { - context.drawImage(img, 0, 0) - } - } - setInspectionImage(img) - }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mappingInspectionTasksObjectURL]) - - useEffect(() => { - updateImage() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mappingInspectionTasksObjectURL, inspectionImage]) + const { switchSelectedInspectionTask } = useInspectionsContext() + const { data } = FetchImageData(task) return ( <> - {imageObjectURL.current !== '' && ( + {data !== undefined && ( @@ -84,7 +51,7 @@ export const InspectionDialogView = ({ task, tasks }: InspectionDialogViewProps)
- + {data !== undefined && } {TranslateText('Installation') + ':'} @@ -153,7 +120,7 @@ export const InspectionsViewSection = ({ tasks, dialogView }: InspectionsViewSec key={task.isarTaskId} onClick={() => switchSelectedInspectionTask(task)} > - + {task.tagId && ( @@ -184,46 +151,26 @@ export const InspectionsViewSection = ({ tasks, dialogView }: InspectionsViewSec ) } -interface GetInspectionImageProps { - task: Task - tasks: Task[] +const FetchImageData = (task: Task) => { + const { installationCode } = useInstallationContext() + const data = useQuery({ + queryKey: [task.isarTaskId], + queryFn: async () => { + const imageBlob = await BackendAPICaller.getInspection(installationCode, task.isarTaskId!) + return URL.createObjectURL(imageBlob) + }, + retryDelay: 60 * 1000, // Will always wait 1 min to retry, regardless of how many retries + staleTime: 10 * 60 * 1000, // I don't want an API call for 10 min after the first time I get data + enabled: task.status === TaskStatus.Successful && task.isarTaskId !== undefined, + }) + return data } -const GetInspectionImage = ({ task, tasks }: GetInspectionImageProps) => { - const imageObjectURL = useRef('') - const [inspectionImage, setInspectionImage] = useState(document.createElement('img')) - - const { switchSelectedInspectionTasks, mappingInspectionTasksObjectURL } = useInspectionsContext() - - useEffect(() => { - switchSelectedInspectionTasks(tasks) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tasks]) - - const updateImage = useCallback(() => { - if (task.isarTaskId && mappingInspectionTasksObjectURL[task.isarTaskId]) { - imageObjectURL.current = mappingInspectionTasksObjectURL[task.isarTaskId] - - getMeta(imageObjectURL.current).then((img) => { - const inspectionCanvas = document.getElementById(task.isarTaskId!) as HTMLCanvasElement - if (inspectionCanvas) { - inspectionCanvas.width = img.width - inspectionCanvas.height = img.height - let context = inspectionCanvas.getContext('2d') - if (context) { - context.drawImage(img, 0, 0) - } - } - setInspectionImage(img) - }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mappingInspectionTasksObjectURL]) - - useEffect(() => { - updateImage() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mappingInspectionTasksObjectURL, inspectionImage]) +interface IGetInspectionImageProps { + task: Task +} - return +const GetInspectionImage = ({ task }: IGetInspectionImageProps) => { + const { data } = FetchImageData(task) + return <>{data !== undefined && } }