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): handle empty rtp file #15435

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions app/src/assets/localization/en/protocol_details.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"author": "author",
"add_required_csv_file": "Add the required CSV file to continue.",
"both_mounts": "Both Mounts",
"choices": "{{count}} choices",
"choose_robot_to_run": "Choose Robot to Run\n{{protocol_name}}",
Expand Down
23 changes: 21 additions & 2 deletions app/src/organisms/ChooseProtocolSlideout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
StyledText,
TYPOGRAPHY,
useTooltip,
useHoverTooltip,
} from '@opentrons/components'
import { ApiHostProvider } from '@opentrons/react-api-client'
import { sortRuntimeParameters } from '@opentrons/shared-data'
Expand Down Expand Up @@ -85,6 +86,7 @@ export function ChooseProtocolSlideoutComponent(
const history = useHistory()
const logger = useLogger(new URL('', import.meta.url).pathname)
const [targetProps, tooltipProps] = useTooltip()
const [targetPropsHover, tooltipPropsHover] = useHoverTooltip()
const [
showRestoreValuesTooltip,
setShowRestoreValuesTooltip,
Expand All @@ -103,6 +105,11 @@ export function ChooseProtocolSlideoutComponent(
] = React.useState<RunTimeParameter[]>([])
const [currentPage, setCurrentPage] = React.useState<number>(1)
const [hasParamError, setHasParamError] = React.useState<boolean>(false)
const [hasMissingFileParam, setHasMissingFileParam] = React.useState<boolean>(
runTimeParametersOverrides?.some(
parameter => parameter.type === 'csv_file'
) ?? false
)
const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
const enableCsvFile = useFeatureFlag('enableCsvFile')

Expand All @@ -113,6 +120,12 @@ export function ChooseProtocolSlideoutComponent(
}, [selectedProtocol])
React.useEffect(() => {
setHasParamError(errors.length > 0)
setHasMissingFileParam(
runTimeParametersOverrides.some(
parameter =>
parameter.type === 'csv_file' && parameter.file?.file == null
)
)
}, [runTimeParametersOverrides])

const runTimeParametersFromAnalysis =
Expand Down Expand Up @@ -194,7 +207,7 @@ export function ChooseProtocolSlideoutComponent(
const isRestoreDefaultsLinkEnabled =
runTimeParametersOverrides?.some(parameter =>
parameter.type === 'csv_file'
? true
? parameter.file != null
: parameter.value !== parameter.default
) ?? false

Expand Down Expand Up @@ -369,7 +382,7 @@ export function ChooseProtocolSlideoutComponent(
const error =
runtimeParam.file?.file?.type === 'text/csv'
? null
: t('protocol_details:file_must_be_csv')
: t('protocol_details:csv_file_type_required')
if (error != null) {
errors.push(error)
}
Expand Down Expand Up @@ -541,13 +554,19 @@ export function ChooseProtocolSlideoutComponent(
width="49%"
onClick={handleProceed}
disabled={hasParamError}
{...targetPropsHover}
>
{isCreatingRun ? (
<Icon name="ot-spinner" spin size="1rem" />
) : (
t('shared:confirm_values')
)}
</PrimaryButton>
{hasMissingFileParam ? (
<Tooltip tooltipProps={tooltipPropsHover}>
{t('protocol_details:add_required_csv_file')}
</Tooltip>
) : null}
</Flex>
)

Expand Down
5 changes: 5 additions & 0 deletions app/src/organisms/ChooseRobotSlideout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ interface ChooseRobotSlideoutProps
multiSlideout?: { currentPage: number } | null
setHasParamError?: (isError: boolean) => void
resetRunTimeParameters?: () => void
setHasMissingFileParam?: (isMissing: boolean) => void
}

export function ChooseRobotSlideout(
Expand Down Expand Up @@ -150,6 +151,7 @@ export function ChooseRobotSlideout(
setRunTimeParametersOverrides,
setHasParamError,
resetRunTimeParameters,
setHasMissingFileParam,
} = props
const enableCsvFile = useFeatureFlag('enableCsvFile')

Expand Down Expand Up @@ -520,6 +522,9 @@ export function ChooseRobotSlideout(
</Flex>
)
} else if (runtimeParam.type === 'csv_file') {
if (runtimeParam.file?.file != null) {
setHasMissingFileParam?.(false)
}
const error =
runtimeParam.file?.file?.type === 'text/csv'
? null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import {
mockUnreachableRobot,
} from '../../../redux/discovery/__fixtures__'
import { getNetworkInterfaces } from '../../../redux/networking'
import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/protocol-storage/__fixtures__'
import {
storedProtocolData as storedProtocolDataFixture,
storedProtocolDataWithCsvRunTimeParameter,
} from '../../../redux/protocol-storage/__fixtures__'
import { useCreateRunFromProtocol } from '../useCreateRunFromProtocol'
import { useOffsetCandidatesForAnalysis } from '../../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis'
import { ChooseRobotToRunProtocolSlideout } from '../'
Expand Down Expand Up @@ -125,6 +128,12 @@ describe('ChooseRobotToRunProtocolSlideout', () => {
expect.any(String)
)
.thenReturn([])
when(vi.mocked(useOffsetCandidatesForAnalysis))
.calledWith(
storedProtocolDataWithCsvRunTimeParameter.mostRecentAnalysis,
expect.any(String)
)
.thenReturn([])
when(vi.mocked(getNetworkInterfaces))
.calledWith({} as State, expect.any(String))
.thenReturn({ wifi: null, ethernet: null })
Expand Down Expand Up @@ -417,4 +426,23 @@ describe('ChooseRobotToRunProtocolSlideout', () => {
'Labware offset data references previous protocol run labware locations to save you time. If all the labware in this protocol have been checked in previous runs, that data will be applied to this run.'
)
})

it('Disables confirm values button if file parameter missing', () => {
vi.mocked(useOffsetCandidatesForAnalysis).mockReturnValue([])
render({
storedProtocolData: storedProtocolDataWithCsvRunTimeParameter,
onCloseClick: vi.fn(),
showSlideout: true,
})
const proceedButton = screen.getByRole('button', {
name: 'Continue to parameters',
})
fireEvent.click(proceedButton)
const confirm = screen.getByRole('button', { name: 'Confirm values' })
fireEvent.mouseOver(confirm)
setTimeout(
() => screen.getByText('Add the required CSV file to continue.'),
200 // for tooltip to show up
)
})
})
118 changes: 67 additions & 51 deletions app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
DIRECTION_ROW,
SecondaryButton,
SPACING,
useHoverTooltip,
} from '@opentrons/components'

import { Tooltip } from '../../atoms/Tooltip'
import { getRobotUpdateDisplayInfo } from '../../redux/robot-update'
import { OPENTRONS_USB } from '../../redux/discovery'
import { appShellRequestor } from '../../redux/shell/remote'
Expand Down Expand Up @@ -67,6 +69,11 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
setRunTimeParametersOverrides,
] = React.useState<RunTimeParameter[]>(runTimeParameters)
const [hasParamError, setHasParamError] = React.useState<boolean>(false)
const [hasMissingFileParam, setHasMissingFileParam] = React.useState<boolean>(
runTimeParameters?.some(parameter => parameter.type === 'csv_file') ?? false
)

const [targetProps, tooltipProps] = useHoverTooltip()

const offsetCandidates = useOffsetCandidatesForAnalysis(
mostRecentAnalysis,
Expand Down Expand Up @@ -193,6 +200,64 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
/>
)

const footer = (
<Flex flexDirection={DIRECTION_COLUMN}>
{hasRunTimeParameters ? (
currentPage === 1 ? (
<>
{offsetsComponent}
<PrimaryButton
onClick={() => {
setCurrentPage(2)
}}
width="100%"
disabled={
isCreatingRun ||
selectedRobot == null ||
isSelectedRobotOnDifferentSoftwareVersion
}
>
{t('shared:continue_to_param')}
</PrimaryButton>
</>
) : (
<Flex gridGap={SPACING.spacing8} flexDirection={DIRECTION_ROW}>
<SecondaryButton
onClick={() => {
setCurrentPage(1)
}}
width="50%"
>
{t('shared:change_robot')}
</SecondaryButton>
<PrimaryButton
width="50%"
onClick={handleProceed}
disabled={hasParamError || hasMissingFileParam}
{...targetProps}
>
{isCreatingRun ? (
<Icon name="ot-spinner" spin size="1rem" />
) : (
t('shared:confirm_values')
)}
</PrimaryButton>
{hasMissingFileParam ? (
<Tooltip tooltipProps={tooltipProps}>
{t('add_required_csv_file')}
</Tooltip>
) : null}
</Flex>
)
) : (
<>
{offsetsComponent}
{singlePageButton}
</>
)}
</Flex>
)

const resetRunTimeParameters = (): void => {
const clone = runTimeParametersOverrides.map(parameter =>
parameter.type === 'csv_file'
Expand Down Expand Up @@ -226,57 +291,7 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
}
runTimeParametersOverrides={runTimeParametersOverrides}
setRunTimeParametersOverrides={setRunTimeParametersOverrides}
footer={
<Flex flexDirection={DIRECTION_COLUMN}>
{hasRunTimeParameters ? (
currentPage === 1 ? (
<>
{offsetsComponent}
<PrimaryButton
onClick={() => {
setCurrentPage(2)
}}
width="100%"
disabled={
isCreatingRun ||
selectedRobot == null ||
isSelectedRobotOnDifferentSoftwareVersion
}
>
{t('shared:continue_to_param')}
</PrimaryButton>
</>
) : (
<Flex gridGap={SPACING.spacing8} flexDirection={DIRECTION_ROW}>
<SecondaryButton
onClick={() => {
setCurrentPage(1)
}}
width="50%"
>
{t('shared:change_robot')}
</SecondaryButton>
<PrimaryButton
width="50%"
onClick={handleProceed}
disabled={hasParamError}
>
{isCreatingRun ? (
<Icon name="ot-spinner" spin size="1rem" />
) : (
t('shared:confirm_values')
)}
</PrimaryButton>
</Flex>
)
) : (
<>
{offsetsComponent}
{singlePageButton}
</>
)}
</Flex>
}
footer={footer}
selectedRobot={selectedRobot}
setSelectedRobot={setSelectedRobot}
robotType={robotType}
Expand All @@ -287,6 +302,7 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
showIdleOnly
setHasParamError={setHasParamError}
resetRunTimeParameters={resetRunTimeParameters}
setHasMissingFileParam={setHasMissingFileParam}
/>
)
}
Expand Down
20 changes: 20 additions & 0 deletions app/src/redux/protocol-storage/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ export const storedProtocolData: StoredProtocolData = {
modified: 123456789,
}

export const storedProtocolDataWithCsvRunTimeParameter: StoredProtocolData = {
protocolKey: 'protocolKeyStub',
mostRecentAnalysis: ({
...simpleAnalysisFileFixture,
runTimeParameters: [
...simpleAnalysisFileFixture.runTimeParameters,
{
displayName: 'mock csv rtp',
variable_name: 'my_csv_param',
description: '',
type: 'csv_file',
file: null,
},
],
} as any) as ProtocolAnalysisOutput,
srcFileNames: ['fakeSrcFileName'],
srcFiles: ['fakeSrcFile' as any],
modified: 123456789,
}

export const storedProtocolDataWithoutRunTimeParameters: StoredProtocolData = {
protocolKey: 'protocolKeyStub',
mostRecentAnalysis: ({
Expand Down
Loading