Skip to content

Commit ec1a4bb

Browse files
authored
fix(app): fix Browse file system button issues (#11113)
This PR will fix Browse file system button bugs (make disabled work as expected and use useIsRobotBusy correctly) fix #11105
1 parent b7da457 commit ec1a4bb

File tree

10 files changed

+110
-62
lines changed

10 files changed

+110
-62
lines changed

app/src/assets/localization/en/device_settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"jupyter_notebook_link": "Learn more about using Jupyter notebook with the OT-2",
9595
"update_robot_software": "Update robot software manually with a local file (.zip)",
9696
"update_robot_software_description": "Bypass the Opentrons App auto-update process and update the robot software manually.",
97-
"update_robot_software_browse_button": "Browse file system",
97+
"browse_file_system": "Browse file system",
9898
"update_robot_software_link": "Launch Opentrons software update page",
9999
"update_robot_software_troubleshooting": "Troubleshooting",
100100
"update_robot_software_download_logs": "Download logs",

app/src/organisms/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
import { StyledText } from '../../../../atoms/text'
1515
import { TertiaryButton } from '../../../../atoms/buttons'
1616
import { useIsRobotBusy } from '../../hooks'
17-
1817
interface DisplayRobotNameProps {
1918
robotName: string
2019
updateIsExpanded: (

app/src/organisms/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ import {
1010
SPACING,
1111
SPACING_AUTO,
1212
Box,
13-
Tooltip,
1413
useHoverTooltip,
1514
TYPOGRAPHY,
1615
} from '@opentrons/components'
1716

1817
import { StyledText } from '../../../../atoms/text'
1918
import { ExternalLink } from '../../../../atoms/Link/ExternalLink'
2019
import { TertiaryButton } from '../../../../atoms/buttons'
21-
import { useIsRobotBusy } from '../../hooks'
20+
import { Tooltip } from '../../../../atoms/Tooltip'
2221
import {
2322
getBuildrootUpdateDisplayInfo,
2423
startBuildrootUpdate,
@@ -34,14 +33,14 @@ const HIDDEN_CSS = css`
3433

3534
interface UpdateRobotSoftwareProps {
3635
robotName: string
37-
updateIsRobotBusy: (isRobotBusy: boolean) => void
3836
onUpdateStart: () => void
37+
isRobotBusy: boolean
3938
}
4039

4140
export function UpdateRobotSoftware({
4241
robotName,
43-
updateIsRobotBusy,
4442
onUpdateStart,
43+
isRobotBusy,
4544
}: UpdateRobotSoftwareProps): JSX.Element {
4645
const { t } = useTranslation('device_settings')
4746
const dispatch = useDispatch<Dispatch>()
@@ -50,20 +49,20 @@ export function UpdateRobotSoftware({
5049
})
5150
const updateDisabled = updateFromFileDisabledReason !== null
5251
const [updateButtonProps, updateButtonTooltipProps] = useHoverTooltip()
53-
const isBusy = useIsRobotBusy()
52+
const inputRef = React.useRef<HTMLInputElement>(null)
5453

5554
const handleChange: React.ChangeEventHandler<HTMLInputElement> = event => {
56-
if (isBusy) {
57-
updateIsRobotBusy(true)
58-
} else {
59-
const { files } = event.target
60-
if (files?.length === 1 && !updateDisabled) {
61-
dispatch(startBuildrootUpdate(robotName, files[0].path))
62-
onUpdateStart()
63-
}
55+
const { files } = event.target
56+
if (files?.length === 1 && !updateDisabled) {
57+
dispatch(startBuildrootUpdate(robotName, files[0].path))
58+
onUpdateStart()
6459
}
6560
}
6661

62+
const handleClick: React.MouseEventHandler<HTMLButtonElement> = () => {
63+
inputRef.current?.click()
64+
}
65+
6766
return (
6867
<Flex
6968
alignItems={ALIGN_CENTER}
@@ -84,21 +83,23 @@ export function UpdateRobotSoftware({
8483
</ExternalLink>
8584
</Box>
8685
<TertiaryButton
87-
as="label"
8886
marginLeft={SPACING_AUTO}
8987
id="AdvancedSettings_softwareUpdateButton"
9088
{...updateButtonProps}
89+
disabled={updateDisabled || isRobotBusy}
90+
onClick={handleClick}
9191
>
92-
{t('update_robot_software_browse_button')}
92+
{t('browse_file_system')}
9393
<input
94+
ref={inputRef}
9495
type="file"
9596
onChange={handleChange}
9697
disabled={updateDisabled}
9798
css={HIDDEN_CSS}
9899
/>
99100
</TertiaryButton>
100101
{updateFromFileDisabledReason != null && (
101-
<Tooltip {...updateButtonTooltipProps}>
102+
<Tooltip tooltipProps={updateButtonTooltipProps}>
102103
{updateFromFileDisabledReason}
103104
</Tooltip>
104105
)}
Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as React from 'react'
22
import { MemoryRouter } from 'react-router-dom'
3-
import { fireEvent } from '@testing-library/react'
43

54
import { renderWithProviders } from '@opentrons/components'
65

76
import { i18n } from '../../../../../i18n'
87
import { getBuildrootUpdateDisplayInfo } from '../../../../../redux/buildroot'
9-
import { useIsRobotBusy } from '../../../hooks'
108

119
import { UpdateRobotSoftware } from '../UpdateRobotSoftware'
1210

@@ -15,13 +13,9 @@ jest.mock('../../../../../redux/discovery')
1513
jest.mock('../../../../../redux/buildroot/selectors')
1614
jest.mock('../../../hooks')
1715

18-
const mockUpdateRobotStatus = jest.fn()
1916
const mockGetBuildrootUpdateDisplayInfo = getBuildrootUpdateDisplayInfo as jest.MockedFunction<
2017
typeof getBuildrootUpdateDisplayInfo
2118
>
22-
const mockUseIsRobotBusy = useIsRobotBusy as jest.MockedFunction<
23-
typeof useIsRobotBusy
24-
>
2519

2620
const mockOnUpdateStart = jest.fn()
2721

@@ -30,7 +24,7 @@ const render = () => {
3024
<MemoryRouter>
3125
<UpdateRobotSoftware
3226
robotName="otie"
33-
updateIsRobotBusy={mockUpdateRobotStatus}
27+
isRobotBusy={false}
3428
onUpdateStart={mockOnUpdateStart}
3529
/>
3630
</MemoryRouter>,
@@ -41,24 +35,23 @@ const render = () => {
4135
describe('RobotSettings UpdateRobotSoftware', () => {
4236
beforeEach(() => {
4337
mockGetBuildrootUpdateDisplayInfo.mockReturnValue({
44-
autoUpdateAction: 'reinstall',
38+
autoUpdateAction: 'update',
4539
autoUpdateDisabledReason: null,
4640
updateFromFileDisabledReason: null,
4741
})
48-
mockUseIsRobotBusy.mockReturnValue(false)
4942
})
5043

5144
afterEach(() => {
5245
jest.resetAllMocks()
5346
})
5447

5548
it('should render title, description and toggle button', () => {
56-
const [{ getByText, getByLabelText }] = render()
49+
const [{ getByText }] = render()
5750
getByText('Update robot software manually with a local file (.zip)')
5851
getByText(
5952
'Bypass the Opentrons App auto-update process and update the robot software manually.'
6053
)
61-
getByLabelText('Browse file system')
54+
getByText('Browse file system')
6255
getByText('Launch Opentrons software update page')
6356
})
6457

@@ -69,11 +62,14 @@ describe('RobotSettings UpdateRobotSoftware', () => {
6962
expect(link.closest('a')).toHaveAttribute('href', targetLink)
7063
})
7164

72-
it('should call update robot status if a robot is busy', () => {
73-
mockUseIsRobotBusy.mockReturnValue(true)
74-
const [{ getByLabelText }] = render()
75-
const button = getByLabelText('Browse file system')
76-
fireEvent.change(button)
77-
expect(mockUpdateRobotStatus).toHaveBeenCalled()
65+
it('should be disabled if updateFromFileDisabledReason is not null', () => {
66+
mockGetBuildrootUpdateDisplayInfo.mockReturnValue({
67+
autoUpdateAction: 'update',
68+
autoUpdateDisabledReason: null,
69+
updateFromFileDisabledReason: 'mock reason',
70+
})
71+
const [{ getByText }] = render()
72+
const button = getByText('Browse file system')
73+
expect(button).toBeDisabled()
7874
})
7975
})

app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Box, SPACING, IconProps } from '@opentrons/components'
66

77
import { Divider } from '../../../atoms/structure'
88
import { Toast } from '../../../atoms/Toast'
9-
import { useRobot } from '../hooks'
9+
import { useIsRobotBusy, useRobot } from '../hooks'
1010
import { DisplayRobotName } from './AdvancedTab/DisplayRobotName'
1111
import { RobotInformation } from './AdvancedTab/RobotInformation'
1212
import { RobotServerVersion } from './AdvancedTab/RobotServerVersion'
@@ -25,15 +25,15 @@ import { RenameRobotSlideout } from './AdvancedTab/AdvancedTabSlideouts/RenameRo
2525
import { FactoryResetSlideout } from './AdvancedTab/AdvancedTabSlideouts/FactoryResetSlideout'
2626
import { FactoryResetModal } from './AdvancedTab/AdvancedTabSlideouts/FactoryResetModal'
2727
import { UpdateBuildroot } from './UpdateBuildroot'
28+
import { UNREACHABLE } from '../../../redux/discovery'
29+
import { Portal } from '../../../App/portal'
2830

2931
import type { State, Dispatch } from '../../../redux/types'
3032
import type {
3133
RobotSettings,
3234
RobotSettingsField,
3335
} from '../../../redux/robot-settings/types'
3436
import type { ResetConfigRequest } from '../../../redux/robot-admin/types'
35-
import { UNREACHABLE } from '../../../redux/discovery'
36-
import { Portal } from '../../../App/portal'
3737

3838
interface RobotSettingsAdvancedProps {
3939
robotName: string
@@ -65,6 +65,8 @@ export function RobotSettingsAdvanced({
6565
false
6666
)
6767

68+
const isRobotBusy = useIsRobotBusy({ poll: true })
69+
6870
const toastIcon: IconProps = { name: 'ot-spinner', spin: true }
6971
const robot = useRobot(robotName)
7072
const ipAddress = robot?.ip != null ? robot.ip : ''
@@ -103,6 +105,7 @@ export function RobotSettingsAdvanced({
103105
const updateDownloadLogsStatus = (isDownloading: boolean): void =>
104106
setShowDownloadToast(isDownloading)
105107

108+
// TODO: kj 07/1 this function and all props will be removed by another PR
106109
const updateIsRobotBusy = (isRobotBusy: boolean): void => {
107110
updateRobotStatus(isRobotBusy)
108111
}
@@ -113,6 +116,10 @@ export function RobotSettingsAdvanced({
113116
dispatch(fetchSettings(robotName))
114117
}, [dispatch, robotName])
115118

119+
React.useEffect(() => {
120+
updateRobotStatus(isRobotBusy)
121+
}, [isRobotBusy, updateRobotStatus])
122+
116123
return (
117124
<>
118125
{showSoftwareUpdateModal &&
@@ -185,7 +192,7 @@ export function RobotSettingsAdvanced({
185192
<Divider marginY={SPACING.spacing5} />
186193
<UpdateRobotSoftware
187194
robotName={robotName}
188-
updateIsRobotBusy={updateIsRobotBusy}
195+
isRobotBusy={isRobotBusy}
189196
onUpdateStart={() => setShowSoftwareUpdateModal(true)}
190197
/>
191198
<Divider marginY={SPACING.spacing4} />

app/src/organisms/Devices/__tests__/RecentProtocolRuns.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('RecentProtocolRuns', () => {
5757
mockUseIsRobotViewable.mockReturnValue(true)
5858
mockUseAllRunsQuery.mockReturnValue({
5959
data: {},
60-
} as UseQueryResult<Runs>)
60+
} as UseQueryResult<Runs, Error>)
6161
const [{ getByText }] = render()
6262

6363
getByText('No protocol runs yet!')
@@ -76,7 +76,7 @@ describe('RecentProtocolRuns', () => {
7676
},
7777
],
7878
},
79-
} as UseQueryResult<Runs>)
79+
} as UseQueryResult<Runs, Error>)
8080
const [{ getByText }] = render()
8181
getByText(`otie's Protocol Runs`)
8282
getByText('Run')
Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,59 @@
11
import { UseQueryResult } from 'react-query'
2-
import { useAllSessionsQuery } from '@opentrons/react-api-client'
2+
import {
3+
useAllSessionsQuery,
4+
useAllRunsQuery,
5+
} from '@opentrons/react-api-client'
36

4-
import { useCurrentRunId } from '../../../ProtocolUpload/hooks'
57
import { useIsRobotBusy } from '../useIsRobotBusy'
68

7-
import type { Sessions } from '@opentrons/api-client'
9+
import type { Sessions, Runs } from '@opentrons/api-client'
810

911
jest.mock('@opentrons/react-api-client')
1012
jest.mock('../../../ProtocolUpload/hooks')
1113

12-
const mockUseCurrentRunId = useCurrentRunId as jest.MockedFunction<
13-
typeof useCurrentRunId
14-
>
1514
const mockUseAllSessionsQuery = useAllSessionsQuery as jest.MockedFunction<
1615
typeof useAllSessionsQuery
1716
>
17+
const mockUseAllRunsQuery = useAllRunsQuery as jest.MockedFunction<
18+
typeof useAllRunsQuery
19+
>
1820

1921
describe('useIsRobotBusy', () => {
2022
beforeEach(() => {
21-
mockUseCurrentRunId.mockReturnValue('123')
2223
mockUseAllSessionsQuery.mockReturnValue({
2324
data: {},
2425
} as UseQueryResult<Sessions, Error>)
26+
mockUseAllRunsQuery.mockReturnValue({
27+
data: {
28+
links: {
29+
current: {},
30+
},
31+
},
32+
} as UseQueryResult<Runs, Error>)
2533
})
2634

2735
afterEach(() => {
2836
jest.resetAllMocks()
2937
})
3038

3139
it('returns true when current runId is not null', () => {
32-
const result = useIsRobotBusy()
40+
const result = useIsRobotBusy({ poll: false })
3341
expect(result).toBe(true)
3442
})
3543

3644
it('returns true when sessions are not empty', () => {
37-
const result = useIsRobotBusy()
45+
const result = useIsRobotBusy({ poll: false })
3846
expect(result).toBe(true)
3947
})
4048

4149
it('returns false when current runId is null and sessions are empty', () => {
42-
mockUseCurrentRunId.mockReturnValue(null)
50+
mockUseAllRunsQuery.mockReturnValue({
51+
data: {
52+
links: {
53+
current: null,
54+
},
55+
},
56+
} as any)
4357
mockUseAllSessionsQuery.mockReturnValue(({
4458
data: [
4559
{
@@ -55,4 +69,16 @@ describe('useIsRobotBusy', () => {
5569
const result = useIsRobotBusy()
5670
expect(result).toBe(false)
5771
})
72+
73+
// TODO: kj 07/13/2022 This test is temporary pending but should be solved by another PR.
74+
// it('should poll the run and sessions if poll option is true', async () => {
75+
// const result = useIsRobotBusy({ poll: true })
76+
// expect(result).toBe(true)
77+
78+
// act(() => {
79+
// jest.advanceTimersByTime(30000)
80+
// })
81+
// expect(mockUseAllRunsQuery).toHaveBeenCalled()
82+
// expect(mockUseAllSessionsQuery).toHaveBeenCalled()
83+
// })
5884
})

app/src/organisms/Devices/hooks/useIsRobotBusy.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { useAllSessionsQuery } from '@opentrons/react-api-client'
1+
import {
2+
useAllSessionsQuery,
3+
useAllRunsQuery,
4+
} from '@opentrons/react-api-client'
25

3-
import { useCurrentRunId } from '../../ProtocolUpload/hooks'
6+
const ROBOT_STATUS_POLL_MS = 30000
47

5-
export function useIsRobotBusy(): boolean {
6-
const robotHasCurrentRun = useCurrentRunId() !== null
7-
const allSessionsQueryResponse = useAllSessionsQuery()
8+
interface UseIsRobotBusyOptions {
9+
poll: boolean
10+
}
11+
export function useIsRobotBusy(
12+
options: UseIsRobotBusyOptions = { poll: false }
13+
): boolean {
14+
const { poll } = options
15+
const queryOptions = poll ? { refetchInterval: ROBOT_STATUS_POLL_MS } : {}
16+
const robotHasCurrentRun =
17+
useAllRunsQuery(queryOptions)?.data?.links?.current != null
18+
const allSessionsQueryResponse = useAllSessionsQuery(queryOptions)
819
return (
920
robotHasCurrentRun ||
1021
(allSessionsQueryResponse?.data?.data != null &&

0 commit comments

Comments
 (0)