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

feat(app): activate estop in unboxing flow #13149

Closed
wants to merge 15 commits into from
Closed
1 change: 1 addition & 0 deletions api-client/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const GET = 'GET'
export const POST = 'POST'
export const PATCH = 'PATCH'
export const DELETE = 'DELETE'
export const PUT = 'PUT'

export function request<ResData, ReqData = null>(
method: Method,
Expand Down
11 changes: 11 additions & 0 deletions api-client/src/robot/getEstopStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GET, request } from '../request'

import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'
import type { EstopStatus } from './types'

export function getEstopStatus(
config: HostConfig
): ResponsePromise<EstopStatus> {
return request<EstopStatus>(GET, '/robot/control/estopStatus', null, config)
}
10 changes: 9 additions & 1 deletion api-client/src/robot/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export { getEstopStatus } from './getEstopStatus'
export { setEstopPhysicalStatus } from './setEstopPhysicalStatus'
export { getLights } from './getLights'
export { setLights } from './setLights'
export type { Lights, SetLightsData } from './types'
export type {
EstopPhysicalStatus,
EstopState,
EstopStatus,
Lights,
SetLightsData,
} from './types'
17 changes: 17 additions & 0 deletions api-client/src/robot/setEstopPhysicalStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PUT, request } from '../request'

import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'
import type { EstopStatus } from './types'

export function setEstopPhysicalStatus(
config: HostConfig,
data: null
): ResponsePromise<EstopStatus> {
return request<EstopStatus, null>(
PUT,
'/robot/control/acknowledgeEstopDisengage',
data,
config
)
}
14 changes: 14 additions & 0 deletions api-client/src/robot/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
export type EstopState =
| 'physicallyEngaged'
| 'logicallyEngaged'
| 'notPresent'
| 'disengaged'

export type EstopPhysicalStatus = 'engaged' | 'disengaged' | 'notPresent'

export interface EstopStatus {
status: EstopState
leftEstopPhysicalStatus: EstopPhysicalStatus
rightEstopPhysicalStatus: EstopPhysicalStatus
}

export interface Lights {
on: boolean
}
Expand Down
11 changes: 8 additions & 3 deletions app/src/pages/EmergencyStop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@ import {
ALIGN_CENTER,
TYPOGRAPHY,
} from '@opentrons/components'
import { useEstopQuery } from '@opentrons/react-api-client'

import { StyledText } from '../../atoms/text'
import { MediumButton } from '../../atoms/buttons'
import { StepMeter } from '../../atoms/StepMeter'

import estopImg from '../../assets/images/on-device-display/install_e_stop.png'

const ESTOP_STATUS_REFETCH_INTERVAL_MS = 10000

export function EmergencyStop(): JSX.Element {
const { i18n, t } = useTranslation(['device_settings', 'shared'])
const history = useHistory()
// Note (kk:06/28/2023) this IF is for test and it will be removed when the e-stop status check function
// I will add the function soon
const isEstopConnected = true
const estopStatus = useEstopQuery({
refetchInterval: ESTOP_STATUS_REFETCH_INTERVAL_MS,
})
console.log(estopStatus?.data?.status)
const isEstopConnected = estopStatus?.data?.status !== 'notPresent'

return (
<>
Expand Down
74 changes: 74 additions & 0 deletions react-api-client/src/robot/__tests__/useEstopQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react'
import { when } from 'jest-when'
import { QueryClient, QueryClientProvider } from 'react-query'
import { renderHook } from '@testing-library/react-hooks'

import { getEstopStatus } from '@opentrons/api-client'
import { useHost } from '../../api'
import { useEstopQuery } from '..'

import type { HostConfig, Response, EstopStatus } from '@opentrons/api-client'

jest.mock('@opentrons/api-client')
jest.mock('../../api/useHost')

const mockGetEstopStatus = getEstopStatus as jest.MockedFunction<
typeof getEstopStatus
>
const mockUseHost = useHost as jest.MockedFunction<typeof useHost>

const HOST_CONFIG: HostConfig = { hostname: 'localhost' }
const ESTOP_STATE_RESPONSE: EstopStatus = {
status: 'disengaged',
leftEstopPhysicalStatus: 'disengaged',
rightEstopPhysicalStatus: 'disengaged',
} as EstopStatus

describe('useEstopQuery hook', () => {
let wrapper: React.FunctionComponent<{}>

beforeEach(() => {
const queryClient = new QueryClient()
const clientProvider: React.FunctionComponent<{}> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)

wrapper = clientProvider
})

afterEach(() => {
jest.resetAllMocks()
})

it('should return no data if no host', () => {
when(mockUseHost).calledWith().mockReturnValue(null)

const { result } = renderHook(useEstopQuery, { wrapper })

expect(result.current?.data).toBeUndefined()
})

it('should return no data if estop request fails', () => {
when(useHost).calledWith().mockReturnValue(HOST_CONFIG)
when(mockGetEstopStatus).calledWith(HOST_CONFIG).mockRejectedValue('oh no')

const { result } = renderHook(useEstopQuery, { wrapper })

expect(result.current?.data).toBeUndefined()
})

it('should return estop state response data', async () => {
when(useHost).calledWith().mockReturnValue(HOST_CONFIG)
when(mockGetEstopStatus)
.calledWith(HOST_CONFIG)
.mockResolvedValue({
data: ESTOP_STATE_RESPONSE,
} as Response<EstopStatus>)

const { result, waitFor } = renderHook(useEstopQuery, { wrapper })

await waitFor(() => result.current?.data != null)

expect(result.current?.data).toEqual(ESTOP_STATE_RESPONSE)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from 'react'
import { when, resetAllWhenMocks } from 'jest-when'
import { QueryClient, QueryClientProvider } from 'react-query'
import { act, renderHook } from '@testing-library/react-hooks'
import { setEstopPhysicalStatus } from '@opentrons/api-client'
import { useSetEstopPhysicalStatusMutation } from '..'

import type { HostConfig, Response, EstopStatus } from '@opentrons/api-client'
import { useHost } from '../../api'

jest.mock('@opentrons/api-client')
jest.mock('../../api/useHost.ts')

const mockSetEstopPhysicalStatus = setEstopPhysicalStatus as jest.MockedFunction<
typeof setEstopPhysicalStatus
>
const mockUseHost = useHost as jest.MockedFunction<typeof useHost>
const HOST_CONFIG: HostConfig = { hostname: 'localhost' }

describe('useSetEstopPhysicalStatusMutation hook', () => {
let wrapper: React.FunctionComponent<{}>
const updatedEstopPhysicalStatus: EstopStatus = {
status: 'disengaged',
leftEstopPhysicalStatus: 'disengaged',
rightEstopPhysicalStatus: 'disengaged',
}

beforeEach(() => {
const queryClient = new QueryClient()
const clientProvider: React.FunctionComponent<{}> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
wrapper = clientProvider
})

afterEach(() => {
resetAllWhenMocks()
})

it('should return no data when calling setEstopPhysicalStatus if the request fails', async () => {
when(mockUseHost).calledWith().mockReturnValue(HOST_CONFIG)
when(mockSetEstopPhysicalStatus)
.calledWith(HOST_CONFIG, null)
.mockRejectedValue('oh no')
const { result, waitFor } = renderHook(
() => useSetEstopPhysicalStatusMutation(),
{ wrapper }
)
expect(result.current.data).toBeUndefined()
result.current.setEstopPhysicalStatus(null)
await waitFor(() => {
return result.current.status !== 'loading'
})
expect(result.current.data).toBeUndefined()
})

it('should update a estop status when calling the setEstopPhysicalStatus with empty payload', async () => {
when(mockUseHost).calledWith().mockReturnValue(HOST_CONFIG)
when(mockSetEstopPhysicalStatus)
.calledWith(HOST_CONFIG, null)
.mockResolvedValue({
data: updatedEstopPhysicalStatus,
} as Response<EstopStatus>)

const { result, waitFor } = renderHook(
() => useSetEstopPhysicalStatusMutation(),
{ wrapper }
)
act(() => result.current.setEstopPhysicalStatus(null))
await waitFor(() => result.current.data != null)
expect(result.current.data).toEqual(updatedEstopPhysicalStatus)
})
})
2 changes: 2 additions & 0 deletions react-api-client/src/robot/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { useEstopQuery } from './useEstopQuery'
export { useLightsQuery } from './useLightsQuery'
export { useSetEstopPhysicalStatusMutation } from './useSetEstopPhysicalStatusMutation'
export { useSetLightsMutation } from './useSetLightsMutation'
18 changes: 18 additions & 0 deletions react-api-client/src/robot/useEstopQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from 'react-query'
import { getEstopStatus } from '@opentrons/api-client'
import { useHost } from '../api'
import type { UseQueryResult, UseQueryOptions } from 'react-query'
import type { HostConfig, EstopStatus } from '@opentrons/api-client'

export function useEstopQuery<TError = Error>(
options: UseQueryOptions<EstopStatus, TError> = {}
): UseQueryResult<EstopStatus, TError> {
const host = useHost()
const query = useQuery<EstopStatus, TError>(
[host as HostConfig, 'robot/control/estopStatus'],
() => getEstopStatus(host as HostConfig).then(response => response.data),
{ enabled: host !== null, ...options }
)

return query
}
53 changes: 53 additions & 0 deletions react-api-client/src/robot/useSetEstopPhysicalStatusMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
HostConfig,
EstopStatus,
setEstopPhysicalStatus,
} from '@opentrons/api-client'

import {
UseMutationResult,
useMutation,
UseMutateFunction,
UseMutationOptions,
} from 'react-query'

import { useHost } from '../api'
import type { AxiosError } from 'axios'

export type UseSetEstopPhysicalStatusMutationResult = UseMutationResult<
EstopStatus,
AxiosError,
null
> & {
setEstopPhysicalStatus: UseMutateFunction<EstopStatus, AxiosError, null>
}

export type UseSetEstopPhysicalStatusMutationOptions = UseMutationOptions<
EstopStatus,
AxiosError,
null
>

export function useSetEstopPhysicalStatusMutation(
options: UseSetEstopPhysicalStatusMutationOptions = {},
hostOverride?: HostConfig | null
): UseSetEstopPhysicalStatusMutationResult {
const contextHost = useHost()
const host =
hostOverride != null ? { ...contextHost, ...hostOverride } : contextHost

const mutation = useMutation<EstopStatus, AxiosError, null>(
[host, 'robot/control/acknowledgeEstopDisengage'],
(newStatus: null) =>
setEstopPhysicalStatus(host as HostConfig, newStatus)
.then(response => response.data)
.catch(e => {
throw e
}),
options
)
return {
...mutation,
setEstopPhysicalStatus: mutation.mutate,
}
}
Loading