Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove polling and refactor mission page #1274

Merged
merged 4 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 20 additions & 27 deletions frontend/src/api/ApiCaller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ export class BackendAPICaller {
}
}

private static initializeRequest<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: T): RequestInit {
private static initializeRequest<T>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
body?: T,
contentType?: string
): RequestInit {
const headers = {
'content-type': 'application/json',
'content-type': contentType ?? 'application/json',
Authorization: `Bearer ${BackendAPICaller.accessToken}`,
}

Expand All @@ -51,11 +55,12 @@ export class BackendAPICaller {
private static async query<TBody, TContent>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: string,
body?: TBody
body?: TBody,
contentType?: string
): Promise<{ content: TContent; headers: Headers }> {
await BackendAPICaller.ApiReady()

const initializedRequest: RequestInit = BackendAPICaller.initializeRequest(method, body)
const initializedRequest: RequestInit = BackendAPICaller.initializeRequest(method, body, contentType)

const url = `${config.BACKEND_URL}/${path}`

Expand All @@ -79,8 +84,11 @@ export class BackendAPICaller {
return { content: responseContent, headers: response.headers }
}

private static async GET<TContent>(path: string): Promise<{ content: TContent; headers: Headers }> {
return BackendAPICaller.query('GET', path)
private static async GET<TContent>(
path: string,
contentType?: string
): Promise<{ content: TContent; headers: Headers }> {
return BackendAPICaller.query('GET', path, undefined, contentType)
}

private static async POST<TBody, TContent>(
Expand Down Expand Up @@ -398,28 +406,13 @@ export class BackendAPICaller {

static async getMap(installationCode: string, mapName: string): Promise<Blob> {
const path: string = 'missions/' + installationCode + '/' + mapName + '/map'
const url = `${config.BACKEND_URL}/${path}`

const headers = {
'content-type': 'image/png',
Authorization: `Bearer ${BackendAPICaller.accessToken}`,
}

const options: RequestInit = {
method: 'GET',
headers,
mode: 'cors',
}

let response = await fetch(url, options)

if (response.status === 200) {
const imageBlob = await response.blob()
return imageBlob
} else {
console.error('HTTP-Error: ' + response.status)
throw Error
}
return BackendAPICaller.GET<Response>(path, 'image/png')
.then((response) => response.content.blob())
.catch((e) => {
console.error(`Failed to GET /${path}: ` + e)
throw e
})
}

static async getAreas(): Promise<Area[]> {
Expand Down
15 changes: 7 additions & 8 deletions frontend/src/components/Contexts/MissionControlContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createContext, useContext, useState, FC } from 'react'
import { BackendAPICaller } from 'api/ApiCaller'
import { Mission } from 'models/Mission'
import { MissionStatusRequest } from 'components/Pages/FrontPage/MissionOverview/StopDialogs'

interface IMissionControlState {
Expand All @@ -13,12 +12,12 @@ interface Props {

export interface IMissionControlContext {
missionControlState: IMissionControlState
updateMissionState: (newState: MissionStatusRequest, mission: Mission) => void
updateRobotMissionState: (newState: MissionStatusRequest, robotId: string) => void
}

const defaultMissionControlInterface = {
missionControlState: { isWaitingForResponse: false },
updateMissionState: (newState: MissionStatusRequest, mission: Mission) => {},
updateRobotMissionState: (newState: MissionStatusRequest, robotId: string) => {},
}

export const MissionControlContext = createContext<IMissionControlContext>(defaultMissionControlInterface)
Expand All @@ -33,21 +32,21 @@ export const MissionControlProvider: FC<Props> = ({ children }) => {
setMissionControlState({ isWaitingForResponse: isWaiting })
}

const updateMissionState = (newState: MissionStatusRequest, mission: Mission) => {
const updateRobotMissionState = (newState: MissionStatusRequest, robotId: string) => {
switch (newState) {
case MissionStatusRequest.Pause: {
setIsWaitingForResponse(true)
BackendAPICaller.pauseMission(mission.robot.id).then((_) => setIsWaitingForResponse(false))
BackendAPICaller.pauseMission(robotId).then((_) => setIsWaitingForResponse(false))
break
}
case MissionStatusRequest.Resume: {
setIsWaitingForResponse(true)
BackendAPICaller.resumeMission(mission.robot.id).then((_) => setIsWaitingForResponse(false))
BackendAPICaller.resumeMission(robotId).then((_) => setIsWaitingForResponse(false))
break
}
case MissionStatusRequest.Stop: {
setIsWaitingForResponse(true)
BackendAPICaller.stopMission(mission.robot.id).then((_) => setIsWaitingForResponse(false))
BackendAPICaller.stopMission(robotId).then((_) => setIsWaitingForResponse(false))
break
}
}
Expand All @@ -57,7 +56,7 @@ export const MissionControlProvider: FC<Props> = ({ children }) => {
<MissionControlContext.Provider
value={{
missionControlState,
updateMissionState,
updateRobotMissionState,
}}
>
{children}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Contexts/MissionListsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createContext, FC, useContext, useEffect, useState } from 'react'
import { Mission, MissionStatus } from 'models/Mission'
import { BackendAPICaller } from 'api/ApiCaller'
import { SignalREventLabels, useSignalRContext } from './SignalRContext'
import { translateSignalRMission } from 'utils/EnumTranslations'

const upsertList = (list: Mission[], mission: Mission) => {
let newList = [...list]
Expand Down Expand Up @@ -91,8 +92,7 @@ export const useMissions = (): MissionsResult => {
})
registerEvent(SignalREventLabels.missionRunUpdated, (username: string, message: string) => {
let updatedMission: Mission = JSON.parse(message)
// This conversion translates from the enum as a number to an enum as a string
updatedMission.status = Object.values(MissionStatus)[updatedMission.status as unknown as number]
updatedMission = translateSignalRMission(updatedMission)

setMissionQueue((oldQueue) => {
const oldQueueCopy = [...oldQueue]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Mission, MissionStatus } from 'models/Mission'
import { MissionStatus } from 'models/Mission'
import { Button, CircularProgress, Icon } from '@equinor/eds-core-react'
import { Icons } from 'utils/icons'
import { tokens } from '@equinor/eds-tokens'
Expand All @@ -8,8 +8,15 @@ import { useLanguageContext } from 'components/Contexts/LanguageContext'
import { useMissionControlContext } from 'components/Contexts/MissionControlContext'
import { StopMissionDialog, MissionStatusRequest } from 'components/Pages/FrontPage/MissionOverview/StopDialogs'

interface MissionControlButtonsProps {
missionName: string
robotId: string
missionStatus: MissionStatus
}

interface MissionProps {
mission: Mission
missionName: string
robotId: string
}

const ButtonStyle = styled.div`
Expand All @@ -24,7 +31,7 @@ const ButtonText = styled.div`
align-items: center;
`

export const MissionControlButtons = ({ mission }: MissionProps) => {
export const MissionControlButtons = ({ missionName, robotId, missionStatus }: MissionControlButtonsProps) => {
const { missionControlState } = useMissionControlContext()

return (
Expand All @@ -33,29 +40,33 @@ export const MissionControlButtons = ({ mission }: MissionProps) => {
<CircularProgress size={32} />
) : (
<>
{mission.status === MissionStatus.Ongoing && <OngoingMissionButton mission={mission} />}
{mission.status === MissionStatus.Paused && <PausedMissionButton mission={mission} />}
{missionStatus === MissionStatus.Ongoing && (
<OngoingMissionButton missionName={missionName} robotId={robotId} />
)}
{missionStatus === MissionStatus.Paused && (
<PausedMissionButton missionName={missionName} robotId={robotId} />
)}
</>
)}
</>
)
}

const OngoingMissionButton = ({ mission }: MissionProps) => {
const OngoingMissionButton = ({ missionName, robotId }: MissionProps) => {
const { TranslateText } = useLanguageContext()
const { updateMissionState } = useMissionControlContext()
const { updateRobotMissionState } = useMissionControlContext()

return (
<>
<ButtonStyle>
<ButtonText>
<StopMissionDialog mission={mission} />
<StopMissionDialog missionName={missionName} robotId={robotId} />
<Typography variant="caption">{TranslateText('Stop')}</Typography>
</ButtonText>
<ButtonText>
<Button
variant="ghost_icon"
onClick={() => updateMissionState(MissionStatusRequest.Pause, mission)}
onClick={() => updateRobotMissionState(MissionStatusRequest.Pause, robotId)}
>
<Icon
name={Icons.PauseButton}
Expand All @@ -70,21 +81,21 @@ const OngoingMissionButton = ({ mission }: MissionProps) => {
)
}

const PausedMissionButton = ({ mission }: MissionProps) => {
const PausedMissionButton = ({ missionName, robotId }: MissionProps) => {
const { TranslateText } = useLanguageContext()
const { updateMissionState } = useMissionControlContext()
const { updateRobotMissionState } = useMissionControlContext()

return (
<>
<ButtonStyle>
<ButtonText>
<StopMissionDialog mission={mission} />
<StopMissionDialog missionName={missionName} robotId={robotId} />
<Typography variant="caption">{TranslateText('Stop')}</Typography>
</ButtonText>
<ButtonText>
<Button
variant="ghost_icon"
onClick={() => updateMissionState(MissionStatusRequest.Resume, mission)}
onClick={() => updateRobotMissionState(MissionStatusRequest.Resume, robotId)}
>
<Icon
name={Icons.PlayButton}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Mission } from 'models/Mission'
import { Button, EdsProvider, Icon, Menu, Tooltip } from '@equinor/eds-core-react'
import { Icons } from 'utils/icons'
import { tokens } from '@equinor/eds-tokens'
Expand All @@ -8,7 +7,6 @@ import { useNavigate } from 'react-router-dom'
import { useLanguageContext } from 'components/Contexts/LanguageContext'
import styled from 'styled-components'
import { useRef, useState } from 'react'
import { TaskStatus } from 'models/Task'

const Centered = styled.div`
display: flex;
Expand All @@ -17,15 +15,16 @@ const Centered = styled.div`
`

interface MissionProps {
mission: Mission
missionId: string
hasFailedTasks: boolean
}

export enum ReRunOptions {
ReRun,
ReRunFailed,
}

export const MissionRestartButton = ({ mission }: MissionProps) => {
export const MissionRestartButton = ({ missionId, hasFailedTasks }: MissionProps) => {
const { TranslateText } = useLanguageContext()
const [isOpen, setIsOpen] = useState<boolean>(false)
const anchorRef = useRef<HTMLButtonElement>(null)
Expand All @@ -44,7 +43,7 @@ export const MissionRestartButton = ({ mission }: MissionProps) => {
}

const startReRun = (option: ReRunOptions) => {
BackendAPICaller.reRunMission(mission.id, option === ReRunOptions.ReRunFailed).then((mission) =>
BackendAPICaller.reRunMission(missionId, option === ReRunOptions.ReRunFailed).then((mission) =>
navigateToHome()
)
}
Expand All @@ -59,7 +58,7 @@ export const MissionRestartButton = ({ mission }: MissionProps) => {
aria-haspopup="true"
aria-expanded={isOpen}
aria-controls="menu-default"
onClick={() => (isOpen ? closeMenu() : openMenu())}
onClick={isOpen ? closeMenu : openMenu}
>
<Icon
name={Icons.Replay}
Expand All @@ -79,9 +78,7 @@ export const MissionRestartButton = ({ mission }: MissionProps) => {
<Menu.Item onClick={() => startReRun(ReRunOptions.ReRun)}>
{TranslateText('Re-run full mission')}
</Menu.Item>
{mission.tasks.some(
(t) => t.status !== TaskStatus.PartiallySuccessful && t.status !== TaskStatus.Successful
) && (
{hasFailedTasks && (
<Menu.Item onClick={() => startReRun(ReRunOptions.ReRunFailed)}>
{TranslateText('Re-run failed and cancelled tasks in the mission')}
</Menu.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export const OngoingMissionCard = ({ mission }: MissionProps): JSX.Element => {
{mission.name}
</Typography>
</StyledTitle>
<MissionControlButtons mission={mission} />
<MissionControlButtons
missionName={mission.name}
robotId={mission.robot.id}
missionStatus={mission.status}
/>
</TopContent>
<BottomContent>
<MissionStatusDisplayWithHeader status={mission.status} />
Expand Down
Loading
Loading