Skip to content

Commit

Permalink
feat(app): add setup instructions modal (#12933)
Browse files Browse the repository at this point in the history
Add setup instructions modal to protocolsetup modules screen.
  • Loading branch information
koji authored Jun 21, 2023
1 parent 589b7e3 commit c5b813c
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 12 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"example": "Example",
"extension_mount": "extension mount",
"extra_attention_warning_title": "Secure labware and modules before proceeding to run",
"extra_module_attached": "Extra module attached",
"feedback_form_link": "Let us know!",
"get_labware_offset_data": "Get Labware Offset Data",
"heater_shaker_extra_attention": "Use latch controls for easy placement of labware.",
Expand Down Expand Up @@ -95,7 +96,6 @@
"module_connected": "Connected",
"module_disconnected": "Disconnected",
"module_mismatch_body": "Check that the modules connected to this robot are of the right type and generation",
"extra_module_attached": "Extra module attached",
"module_name": "Module Name",
"module_not_connected": "Not connected",
"module_setup_step_description_plural": "Plug in and turn on the required modules via the robot's USB Ports. Place the modules as shown in the deck map.",
Expand Down Expand Up @@ -173,7 +173,8 @@
"secure_labware_modal": "Securing labware to the {{name}}",
"secure": "Secure",
"setup_for_run": "Setup for Run",
"setup_instructions": "Setup Instructions",
"setup_instructions_description": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box or scan the QR code to visit the modules section of the Opentrons Help Center.",
"setup_instructions": "setup instructions",
"setup_is_view_only": "Setup is view-only once run has started",
"slot_location": "Slot {{slotName}}",
"slot_number": "Slot Number",
Expand Down
2 changes: 1 addition & 1 deletion app/src/assets/localization/en/run_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"comment_step": "Comment",
"comment": "Comment",
"complete_protocol_to_download": "Complete the protocol to download the run log",
"contact_information": "Please contact [email protected] with relevant information for assistance with troubleshooting.",
"contact_information": "Download the run logs from the Opentrons App and send it to [email protected] for assistance.",
"current_step_pause_timer": "Timer",
"current_step_pause": "Current Step - Paused by User",
"current_step": "Current Step",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
TYPOGRAPHY,
DIRECTION_COLUMN,
ALIGN_FLEX_START,
COLORS,
BORDERS,
} from '@opentrons/components'
import { useStopRunMutation } from '@opentrons/react-api-client'

Expand Down Expand Up @@ -81,6 +83,8 @@ export function RunFailedModal({
gridGap={SPACING.spacing8}
overflowY="scroll"
maxHeight="5.375rem"
backgroundColor={COLORS.light1}
borderRadius={BORDERS.borderRadiusSize3}
>
{errors?.map(error => (
<StyledText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ describe('RunFailedModal', () => {
getByText(
'ProtocolEngineError [line 16]: ModuleNotAttachedError: No available'
)
getByText(
'Download the run logs from the Opentrons App and send it to [email protected] for assistance.'
)
getByText('Close')
})

Expand Down
68 changes: 68 additions & 0 deletions app/src/organisms/ProtocolSetupModules/SetupInstructionsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'

import {
ALIGN_STRETCH,
BORDERS,
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
Flex,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import { StyledText } from '../../atoms/text'
import { Modal } from '../../molecules/Modal'

import type { ModalHeaderBaseProps } from '../../molecules/Modal/types'

import imgSrc from '../../assets/images/on-device-display/setup_instructions_qr_code.png'

const INSTRUCTIONS_URL = 'support.opentrons.com/s/modules'

interface SetupInstructionsModalProps {
setShowSetupInstructionsModal: (showSetupInstructionsModal: boolean) => void
}
export function SetupInstructionsModal({
setShowSetupInstructionsModal,
}: SetupInstructionsModalProps): JSX.Element {
const { i18n, t } = useTranslation('protocol_setup')
const modalHeader: ModalHeaderBaseProps = {
title: i18n.format(t('setup_instructions'), 'capitalize'),
iconName: 'information',
iconColor: COLORS.darkBlack100,
hasExitIcon: true,
}

return (
<Modal
header={modalHeader}
onOutsideClick={() => setShowSetupInstructionsModal(false)}
>
<Flex
flexDirection={DIRECTION_ROW}
alignSelf={ALIGN_STRETCH}
gridGap={SPACING.spacing40}
>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing24}>
<StyledText as="p">{t('setup_instructions_description')}</StyledText>
<Flex
backgroundColor={COLORS.light1}
borderRadius={BORDERS.borderRadiusSize3}
padding={`${SPACING.spacing16} ${SPACING.spacing24}`}
>
<StyledText as="p" fontWeight={TYPOGRAPHY.fontWeightSemiBold}>
{INSTRUCTIONS_URL}
</StyledText>
</Flex>
</Flex>
<img
src={imgSrc}
alt="Setup Instructions QR Code"
width="178px"
height="178px"
/>
</Flex>
</Modal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getAttachedProtocolModuleMatches,
getUnmatchedModulesForProtocol,
} from '../utils'
import { SetupInstructionsModal } from '../SetupInstructionsModal'
import { ProtocolSetupModules } from '..'

jest.mock('@opentrons/shared-data/js/helpers')
Expand All @@ -24,6 +25,7 @@ jest.mock(
)
jest.mock('../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo')
jest.mock('../utils')
jest.mock('../SetupInstructionsModal')

const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction<
typeof getDeckDefFromRobotType
Expand All @@ -43,6 +45,9 @@ const mockGetUnmatchedModulesForProtocol = getUnmatchedModulesForProtocol as jes
const mockUseMostRecentCompletedAnalysis = useMostRecentCompletedAnalysis as jest.MockedFunction<
typeof useMostRecentCompletedAnalysis
>
const mockSetupInstructionsModal = SetupInstructionsModal as jest.MockedFunction<
typeof SetupInstructionsModal
>

const RUN_ID = "otie's run"
const mockSetSetupScreen = jest.fn()
Expand Down Expand Up @@ -79,6 +84,9 @@ describe('ProtocolSetupModules', () => {
when(mockGetDeckDefFromRobotType)
.calledWith('OT-3 Standard')
.mockReturnValue(ot3StandardDeckDef as any)
mockSetupInstructionsModal.mockReturnValue(
<div>mock SetupInstructionsModal</div>
)
})

afterEach(() => {
Expand All @@ -105,6 +113,6 @@ describe('ProtocolSetupModules', () => {
const [{ getByText }] = render()

getByText('Setup Instructions').click()
getByText('TODO: setup instructions modal')
getByText('mock SetupInstructionsModal')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react'

import { renderWithProviders } from '@opentrons/components'

import { i18n } from '../../../i18n'

import { SetupInstructionsModal } from '../SetupInstructionsModal'

const mockSetShowSetupInstructionsModal = jest.fn()
const QR_CODE_IMAGE_FILE = 'setup_instructions_qr_code.png'

const render = (props: React.ComponentProps<typeof SetupInstructionsModal>) => {
return renderWithProviders(<SetupInstructionsModal {...props} />, {
i18nInstance: i18n,
})
}

describe('SetupInstructionsModal', () => {
let props: React.ComponentProps<typeof SetupInstructionsModal>

beforeEach(() => {
props = {
setShowSetupInstructionsModal: mockSetShowSetupInstructionsModal,
}
})

it('should render text and image', () => {
const [{ getByText, getByRole }] = render(props)
getByText('Setup instructions')
getByText(
'For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box or scan the QR code to visit the modules section of the Opentrons Help Center.'
)
getByText('support.opentrons.com/s/modules')
expect(getByRole('img').getAttribute('src')).toEqual(QR_CODE_IMAGE_FILE)
})

it('should call mock function when tapping close icon', () => {
const [{ getByLabelText }] = render(props)
getByLabelText('closeIcon').click()
expect(mockSetShowSetupInstructionsModal).toHaveBeenCalled()
})
})
14 changes: 6 additions & 8 deletions app/src/organisms/ProtocolSetupModules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
getAttachedProtocolModuleMatches,
getUnmatchedModulesForProtocol,
} from './utils'
import { SetupInstructionsModal } from './SetupInstructionsModal'

import type { SetupScreens } from '../../pages/OnDeviceDisplay/ProtocolSetup'
import type { AttachedProtocolModuleMatch } from './utils'
Expand Down Expand Up @@ -145,7 +146,7 @@ export function ProtocolSetupModules({
runId,
setSetupScreen,
}: ProtocolSetupModulesProps): JSX.Element {
const { t } = useTranslation('protocol_setup')
const { i18n, t } = useTranslation('protocol_setup')
const [
showMultipleModulesModal,
setShowMultipleModulesModal,
Expand Down Expand Up @@ -193,12 +194,9 @@ export function ProtocolSetupModules({
/>
) : null}
{showSetupInstructionsModal ? (
<LegacyModal
title={t('setup_instructions')}
onClose={() => setShowSetupInstructionsModal(false)}
>
TODO: setup instructions modal
</LegacyModal>
<SetupInstructionsModal
setShowSetupInstructionsModal={setShowSetupInstructionsModal}
/>
) : null}
{showDeckMapModal ? (
<LegacyModal
Expand Down Expand Up @@ -247,7 +245,7 @@ export function ProtocolSetupModules({
/>
<SmallButton
alignSelf={ALIGN_FLEX_END}
buttonText={t('setup_instructions')}
buttonText={i18n.format(t('setup_instructions'), 'titleCase')}
buttonType="tertiaryLowLight"
iconName="information"
iconPlacement="startIcon"
Expand Down

0 comments on commit c5b813c

Please sign in to comment.