Skip to content

Commit

Permalink
fix(app): properly enable/disable robot update from file button (#6483)
Browse files Browse the repository at this point in the history
Fixes #5429
  • Loading branch information
mcous authored Sep 14, 2020
1 parent 5308562 commit a996cdc
Show file tree
Hide file tree
Showing 22 changed files with 639 additions and 385 deletions.
470 changes: 289 additions & 181 deletions app/src/buildroot/__tests__/selectors.test.js

Large diffs are not rendered by default.

80 changes: 70 additions & 10 deletions app/src/buildroot/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import semver from 'semver'
import { createSelector } from 'reselect'

import { getViewableRobots, getRobotApiVersion } from '../discovery/selectors'
import {
HEALTH_STATUS_OK,
getViewableRobots,
getRobotApiVersion,
getRobotByName,
} from '../discovery'
import * as Constants from './constants'

import type { State } from '../types'
Expand All @@ -14,6 +19,15 @@ import type {
RobotSystemType,
} from './types'

// TODO(mc, 2020-08-02): i18n
const UPDATE_SERVER_UNAVAILABLE =
"Unable to update because your robot's update server is not responding."
const OTHER_ROBOT_UPDATING =
'Unable to update because the app is currently updating a different robot.'
const NO_UPDATE_FILES =
'Unable to retrieve update for this robot. Ensure your computer is connected to the internet and try again later.'
const UNAVAILABLE = 'Update unavailable'

export function getBuildrootUpdateVersion(state: State): string | null {
return state.buildroot.version || null
}
Expand Down Expand Up @@ -87,19 +101,16 @@ export const getBuildrootRobot: State => ViewableRobot | null = createSelector(
}
)

export function getBuildrootUpdateAvailable(
state: State,
robot: ViewableRobot
): BuildrootUpdateType | null {
const updateVersion = getBuildrootUpdateVersion(state)
const currentVersion = getRobotApiVersion(robot)

const getBuildrootUpdateType = (
currentVersion: string | null,
updateVersion: string | null
): BuildrootUpdateType | null => {
const validCurrent: string | null = semver.valid(currentVersion)
const validUpdate: string | null = semver.valid(updateVersion)
let type = null

if (validUpdate) {
if (!validCurrent || semver.gt(validUpdate, validCurrent)) {
if (validUpdate && validCurrent) {
if (semver.gt(validUpdate, validCurrent)) {
type = Constants.UPGRADE
} else if (semver.lt(validUpdate, validCurrent)) {
type = Constants.DOWNGRADE
Expand All @@ -111,6 +122,55 @@ export function getBuildrootUpdateAvailable(
return type
}

export function getBuildrootUpdateAvailable(
state: State,
robot: ViewableRobot
): BuildrootUpdateType | null {
const currentVersion = getRobotApiVersion(robot)
const updateVersion = getBuildrootUpdateVersion(state)

return getBuildrootUpdateType(currentVersion, updateVersion)
}

export const getBuildrootUpdateDisplayInfo: (
state: State,
robotName: string
) => {|
autoUpdateAction: string,
autoUpdateDisabledReason: string | null,
updateFromFileDisabledReason: string | null,
|} = createSelector(
getRobotByName,
state => getBuildrootRobot(state),
state => getBuildrootUpdateVersion(state),
(robot, currentUpdatingRobot, updateVersion) => {
const robotVersion = robot ? getRobotApiVersion(robot) : null
const autoUpdateType = getBuildrootUpdateType(robotVersion, updateVersion)
const autoUpdateAction = autoUpdateType ?? UNAVAILABLE
let autoUpdateDisabledReason = null
let updateFromFileDisabledReason = null

if (robot?.serverHealthStatus !== HEALTH_STATUS_OK) {
autoUpdateDisabledReason = UPDATE_SERVER_UNAVAILABLE
updateFromFileDisabledReason = UPDATE_SERVER_UNAVAILABLE
} else if (
currentUpdatingRobot !== null &&
currentUpdatingRobot.name !== robot?.name
) {
autoUpdateDisabledReason = OTHER_ROBOT_UPDATING
updateFromFileDisabledReason = OTHER_ROBOT_UPDATING
} else if (autoUpdateType === null) {
autoUpdateDisabledReason = NO_UPDATE_FILES
}

return {
autoUpdateAction,
autoUpdateDisabledReason,
updateFromFileDisabledReason,
}
}
)

export function getRobotSystemType(
robot: ViewableRobot
): RobotSystemType | null {
Expand Down
3 changes: 1 addition & 2 deletions app/src/components/ConnectPanel/RobotItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ export function RobotItemComponent(props: RobotItemProps): React.Node {
return getBuildrootUpdateAvailable(state, robot) === UPGRADE
})
const isConnectable = status === CONNECTABLE
// NOTE(mc, 2020-03-30): redundant && true to satisfy Flow
const isConnected = Boolean(robot.connected && true)
const isConnected = robot.connected
const isSelected = robot.name === match.params.name
const connectInProgress = useSelector(
(state: State) => RobotSelectors.getConnectRequest(state).inProgress
Expand Down
16 changes: 10 additions & 6 deletions app/src/components/RobotSettings/AdvancedSettingsCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
Icon,
} from '@opentrons/components'

import { UploadRobotUpdate } from './UploadRobotUpdate'
import { UpdateFromFileControl } from './UpdateFromFileControl'
import { OpenJupyterControl } from './OpenJupyterControl'

import type { State, Dispatch } from '../../types'
Expand Down Expand Up @@ -62,7 +62,7 @@ export function AdvancedSettingsCard(
)
const robotLogsDownloading = useSelector(getRobotLogsDownloading)
const dispatch = useDispatch<Dispatch>()
const disabled = status !== CONNECTABLE
const controlsDisabled = status !== CONNECTABLE
const logsAvailable = health && health.logs

const showLogOptoutModal = settings.some(
Expand All @@ -76,7 +76,7 @@ export function AdvancedSettingsCard(
}, [dispatch, name])

return (
<Card title={TITLE} disabled={disabled}>
<Card title={TITLE}>
<LabeledButton
label="Download Logs"
buttonProps={{
Expand All @@ -85,7 +85,7 @@ export function AdvancedSettingsCard(
) : (
'Download'
),
disabled: disabled || !logsAvailable || robotLogsDownloading,
disabled: controlsDisabled || !logsAvailable || robotLogsDownloading,
onClick: () => dispatch(downloadLogs(robot)),
}}
>
Expand All @@ -94,26 +94,30 @@ export function AdvancedSettingsCard(
<LabeledButton
label="Factory Reset"
buttonProps={{
disabled,
disabled: controlsDisabled,
Component: Link,
to: resetUrl,
children: 'Reset',
}}
>
<p>Restore robot to factory configuration</p>
</LabeledButton>
<UpdateFromFileControl
robotName={name}
borderBottom={BORDER_SOLID_LIGHT}
/>
<OpenJupyterControl robotIp={ip} borderBottom={BORDER_SOLID_LIGHT} />
{settings.map(({ id, title, description, value }) => (
<LabeledToggle
key={id}
label={title}
toggledOn={value === true}
disabled={controlsDisabled}
onClick={() => dispatch(updateSetting(name, id, !value))}
>
<p>{description}</p>
</LabeledToggle>
))}
<UploadRobotUpdate robotName={name} />
{showLogOptoutModal && (
<Portal>
<AlertModal
Expand Down
8 changes: 5 additions & 3 deletions app/src/components/RobotSettings/ConnectionCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const STATUS_REFRESH_MS = 5000

export function ConnectionCard(props: Props): React.Node {
const { robot } = props
const { name: robotName, status, local } = robot
const { name: robotName, status, local, ip } = robot
const dispatch = useDispatch<Dispatch>()
const internetStatus = useSelector((state: State) =>
getInternetStatus(state, robotName)
Expand All @@ -36,10 +36,12 @@ export function ConnectionCard(props: Props): React.Node {
useInterval(() => dispatch(fetchStatus(robotName)), STATUS_REFRESH_MS, true)

return (
<Card key={robotName} title={CONNECTIVITY} disabled={disabled}>
<Card key={robotName} title={CONNECTIVITY}>
<ConnectionStatusMessage
type={local ? 'USB' : 'Wi-Fi'}
status={internetStatus}
status={status}
internetStatus={internetStatus}
ipAddress={ip}
/>
<ConnectionInfo connection={wifi} title="Wi-Fi" disabled={disabled}>
<SelectNetwork robotName={robotName} />
Expand Down
8 changes: 6 additions & 2 deletions app/src/components/RobotSettings/ControlsCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Props = {|

const TITLE = 'Robot Controls'

const CANNOT_CONNECT = 'Cannot connect to robot'
const CONNECT_TO_ROBOT = 'Connect to robot to control'
const PROTOCOL_IS_RUNNING = 'Protocol is running'
const BAD_DECK_CALIBRATION =
Expand Down Expand Up @@ -76,7 +77,9 @@ export function ControlsCard(props: Props): React.Node {
)

let buttonDisabledReason = null
if (notConnectable || !robot.connected) {
if (notConnectable) {
buttonDisabledReason = CANNOT_CONNECT
} else if (!robot.connected) {
buttonDisabledReason = CONNECT_TO_ROBOT
} else if (isRunning) {
buttonDisabledReason = PROTOCOL_IS_RUNNING
Expand All @@ -95,7 +98,7 @@ export function ControlsCard(props: Props): React.Node {
const buttonDisabled = Boolean(buttonDisabledReason)

return (
<Card title={TITLE} disabled={notConnectable}>
<Card title={TITLE}>
<DeckCalibrationControl
robotName={robotName}
buttonDisabled={buttonDisabled}
Expand Down Expand Up @@ -127,6 +130,7 @@ export function ControlsCard(props: Props): React.Node {
label="Lights"
toggledOn={Boolean(lightsOn)}
onClick={toggleLights}
disabled={buttonDisabled}
>
<p>Control lights on deck.</p>
</LabeledToggle>
Expand Down
Loading

0 comments on commit a996cdc

Please sign in to comment.