Skip to content

Commit

Permalink
Introduce useQuery for server calls
Browse files Browse the repository at this point in the history
  • Loading branch information
mrica-equinor committed Dec 19, 2024
1 parent d65b2ec commit 9fc9a22
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 177 deletions.
25 changes: 25 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
65 changes: 35 additions & 30 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -28,40 +29,44 @@ if (config.AI_CONNECTION_STRING.length > 0) {
appInsights.trackPageView()
}

const queryClient = new QueryClient()

const App = () => (
<AuthProvider>
<LanguageProvider>
<SignalRProvider>
<InstallationProvider>
<InspectionsProvider>
<MissionDefinitionsProvider>
<RobotProvider>
<MissionRunsProvider>
<AlertProvider>
<DockProvider>
<MissionRunsProvider>
<MissionControlProvider>
<UnauthenticatedTemplate>
<div className="sign-in-page">
<AssetSelectionPage></AssetSelectionPage>
</div>
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<MissionFilterProvider>
<MediaStreamProvider>
<FlotillaSite />
</MediaStreamProvider>
</MissionFilterProvider>
</AuthenticatedTemplate>
</MissionControlProvider>
</MissionRunsProvider>
</DockProvider>
</AlertProvider>
</MissionRunsProvider>
</RobotProvider>
</MissionDefinitionsProvider>
</InspectionsProvider>
</InstallationProvider>
<QueryClientProvider client={queryClient}>
<InstallationProvider>
<InspectionsProvider>
<MissionDefinitionsProvider>
<RobotProvider>
<MissionRunsProvider>
<AlertProvider>
<DockProvider>
<MissionRunsProvider>
<MissionControlProvider>
<UnauthenticatedTemplate>
<div className="sign-in-page">
<AssetSelectionPage></AssetSelectionPage>
</div>
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
<MissionFilterProvider>
<MediaStreamProvider>
<FlotillaSite />
</MediaStreamProvider>
</MissionFilterProvider>
</AuthenticatedTemplate>
</MissionControlProvider>
</MissionRunsProvider>
</DockProvider>
</AlertProvider>
</MissionRunsProvider>
</RobotProvider>
</MissionDefinitionsProvider>
</InspectionsProvider>
</InstallationProvider>
</QueryClientProvider>
</SignalRProvider>
</LanguageProvider>
</AuthProvider>
Expand Down
69 changes: 3 additions & 66 deletions frontend/src/components/Contexts/InpectionsContext.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -17,81 +12,23 @@ interface Props {

const defaultInspectionsContext = {
selectedInspectionTask: undefined,
selectedInspectionTasks: [],
switchSelectedInspectionTask: (selectedInspectionTask: Task | undefined) => undefined,
switchSelectedInspectionTasks: (selectedInspectionTasks: Task[]) => [],
mappingInspectionTasksObjectURL: {},
}

const InspectionsContext = createContext<IInspectionsContext>(defaultInspectionsContext)

export const InspectionsProvider: FC<Props> = ({ children }) => {
const { installationCode } = useInstallationContext()

const [selectedInspectionTask, setSelectedInspectionTask] = useState<Task>()
const [selectedInspectionTasks, setSelectedInspectionTasks] = useState<Task[]>([])
const [selectedInspectionTasksToFetch, setSelectedInspectionTasksToFetch] = useState<Task[]>([])

const [mappingInspectionTasksObjectURL, setMappingInspectionTasksObjectURL] = useState<{
[taskId: string]: string
}>({})

const [triggerFetch, setTriggerFetch] = useState<boolean>(false)
const [startTimer, setStartTimer] = useState<boolean>(false)
const imageObjectURL = useRef<string>('')

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 (
<InspectionsContext.Provider
value={{
selectedInspectionTask,
selectedInspectionTasks,
switchSelectedInspectionTask,
switchSelectedInspectionTasks,
mappingInspectionTasksObjectURL,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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%;
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<HTMLImageElement>(document.createElement('img'))
const imageObjectURL = useRef<string>('')

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 && (
<StyledDialog open={true}>
<StyledDialogContent>
<StyledDialogHeader>
Expand All @@ -84,7 +51,7 @@ export const InspectionDialogView = ({ task, tasks }: InspectionDialogViewProps)
</StyledDialogHeader>
<StyledDialogInspectionView>
<div>
<StyledInspection id="inspectionCanvas" />
{data !== undefined && <StyledInspection src={data} />}
<StyledBottomContent>
<StyledInfoContent>
<Typography variant="caption">{TranslateText('Installation') + ':'}</Typography>
Expand Down Expand Up @@ -153,7 +120,7 @@ export const InspectionsViewSection = ({ tasks, dialogView }: InspectionsViewSec
key={task.isarTaskId}
onClick={() => switchSelectedInspectionTask(task)}
>
<GetInspectionImage task={task} tasks={tasks} />
<GetInspectionImage task={task} />
<StyledInspectionData>
{task.tagId && (
<StyledInspectionContent>
Expand Down Expand Up @@ -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<string>('')
const [inspectionImage, setInspectionImage] = useState<HTMLImageElement>(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 <StyledInspectionImage id={task.isarTaskId} />
const GetInspectionImage = ({ task }: IGetInspectionImageProps) => {
const { data } = FetchImageData(task)
return <>{data !== undefined && <StyledInspectionImage src={data} />}</>
}

0 comments on commit 9fc9a22

Please sign in to comment.