-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(app): add module calibration section to robotsettings (#13374)
* feat(app): add Module calibration section to RobotSettings
- Loading branch information
Showing
17 changed files
with
635 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import * as React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import styled, { css } from 'styled-components' | ||
|
||
import { BORDERS, COLORS, SPACING, TYPOGRAPHY } from '@opentrons/components' | ||
import { getModuleDisplayName } from '@opentrons/shared-data/js/modules' | ||
|
||
import { StyledText } from '../../../atoms/text' | ||
import { formatLastCalibrated } from './utils' | ||
import { ModuleCalibrationOverflowMenu } from './ModuleCalibrationOverflowMenu' | ||
|
||
import type { AttachedModule } from '@opentrons/api-client' | ||
|
||
interface ModuleCalibrationItemsProps { | ||
attachedModules: AttachedModule[] | ||
updateRobotStatus: (isRobotBusy: boolean) => void | ||
} | ||
|
||
export function ModuleCalibrationItems({ | ||
attachedModules, | ||
updateRobotStatus, | ||
}: ModuleCalibrationItemsProps): JSX.Element { | ||
const { t } = useTranslation('device_settings') | ||
|
||
return ( | ||
<StyledTable> | ||
<thead> | ||
<tr> | ||
<StyledTableHeader>{t('module')}</StyledTableHeader> | ||
<StyledTableHeader>{t('serial')}</StyledTableHeader> | ||
<StyledTableHeader>{t('last_calibrated_label')}</StyledTableHeader> | ||
</tr> | ||
</thead> | ||
<tbody css={BODY_STYLE}> | ||
{attachedModules.map(attachedModule => ( | ||
<StyledTableRow key={attachedModule.id}> | ||
<StyledTableCell> | ||
<StyledText as="p"> | ||
{getModuleDisplayName(attachedModule.moduleModel)} | ||
</StyledText> | ||
</StyledTableCell> | ||
<StyledTableCell> | ||
<StyledText as="p">{attachedModule.serialNumber}</StyledText> | ||
</StyledTableCell> | ||
<StyledTableCell> | ||
<StyledText as="p"> | ||
{attachedModule.moduleOffset?.last_modified != null | ||
? formatLastCalibrated( | ||
attachedModule.moduleOffset?.last_modified | ||
) | ||
: t('not_calibrated_short')} | ||
</StyledText> | ||
</StyledTableCell> | ||
<StyledTableCell> | ||
<ModuleCalibrationOverflowMenu | ||
isCalibrated={ | ||
attachedModule.moduleOffset?.last_modified != null | ||
} | ||
attachedModule={attachedModule} | ||
updateRobotStatus={updateRobotStatus} | ||
/> | ||
</StyledTableCell> | ||
</StyledTableRow> | ||
))} | ||
</tbody> | ||
</StyledTable> | ||
) | ||
} | ||
|
||
const StyledTable = styled.table` | ||
width: 100%; | ||
border-collapse: collapse; | ||
text-align: left; | ||
` | ||
|
||
const StyledTableHeader = styled.th` | ||
${TYPOGRAPHY.labelSemiBold} | ||
padding: ${SPACING.spacing8}; | ||
` | ||
|
||
const StyledTableRow = styled.tr` | ||
padding: ${SPACING.spacing8}; | ||
border-bottom: ${BORDERS.lineBorder}; | ||
` | ||
|
||
const StyledTableCell = styled.td` | ||
padding: ${SPACING.spacing8}; | ||
text-overflow: wrap; | ||
` | ||
|
||
const BODY_STYLE = css` | ||
box-shadow: 0 0 0 1px ${COLORS.medGreyEnabled}; | ||
border-radius: 3px; | ||
` |
113 changes: 113 additions & 0 deletions
113
...c/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import * as React from 'react' | ||
|
||
import { useTranslation } from 'react-i18next' | ||
|
||
import { | ||
Flex, | ||
COLORS, | ||
POSITION_ABSOLUTE, | ||
DIRECTION_COLUMN, | ||
POSITION_RELATIVE, | ||
ALIGN_FLEX_END, | ||
useOnClickOutside, | ||
} from '@opentrons/components' | ||
|
||
import { Divider } from '../../../atoms/structure' | ||
import { OverflowBtn } from '../../../atoms/MenuList/OverflowBtn' | ||
import { MenuItem } from '../../../atoms/MenuList/MenuItem' | ||
import { useMenuHandleClickOutside } from '../../../atoms/MenuList/hooks' | ||
import { useRunStatuses } from '../../Devices/hooks' | ||
import { ModuleWizardFlows } from '../../ModuleWizardFlows' | ||
|
||
import type { AttachedModule } from '../../../redux/modules/types' | ||
|
||
interface ModuleCalibrationOverflowMenuProps { | ||
isCalibrated: boolean | ||
attachedModule: AttachedModule | ||
updateRobotStatus: (isRobotBusy: boolean) => void | ||
} | ||
|
||
export function ModuleCalibrationOverflowMenu({ | ||
isCalibrated, | ||
attachedModule, | ||
updateRobotStatus, | ||
}: ModuleCalibrationOverflowMenuProps): JSX.Element { | ||
const { t } = useTranslation(['device_settings', 'robot_calibration']) | ||
|
||
const { | ||
menuOverlay, | ||
handleOverflowClick, | ||
showOverflowMenu, | ||
setShowOverflowMenu, | ||
} = useMenuHandleClickOutside() | ||
|
||
const [showModuleWizard, setShowModuleWizard] = React.useState<boolean>(false) | ||
const { isRunRunning: isRunning } = useRunStatuses() | ||
|
||
const OverflowMenuRef = useOnClickOutside<HTMLDivElement>({ | ||
onClickOutside: () => setShowOverflowMenu(false), | ||
}) | ||
|
||
const handleCalibration = (): void => { | ||
setShowOverflowMenu(false) | ||
setShowModuleWizard(true) | ||
} | ||
|
||
const handleDeleteCalibration = (): void => { | ||
// ToDo (kk:08/23/2023) | ||
// call a custom hook to delete calibration data | ||
} | ||
|
||
React.useEffect(() => { | ||
if (isRunning) { | ||
updateRobotStatus(true) | ||
} | ||
}, [isRunning, updateRobotStatus]) | ||
|
||
return ( | ||
<Flex flexDirection={DIRECTION_COLUMN} position={POSITION_RELATIVE}> | ||
<OverflowBtn | ||
alignSelf={ALIGN_FLEX_END} | ||
aria-label="ModuleCalibrationOverflowMenu" | ||
onClick={handleOverflowClick} | ||
disabled={isRunning} | ||
/> | ||
{showModuleWizard ? ( | ||
<ModuleWizardFlows | ||
attachedModule={attachedModule} | ||
slotName="A1" | ||
closeFlow={() => { | ||
setShowModuleWizard(false) | ||
}} | ||
/> | ||
) : null} | ||
{showOverflowMenu ? ( | ||
<Flex | ||
ref={OverflowMenuRef} | ||
whiteSpace="nowrap" | ||
zIndex="5" | ||
borderRadius="4px 4px 0px 0px" | ||
boxShadow="0px 1px 3px rgba(0, 0, 0, 0.2)" | ||
position={POSITION_ABSOLUTE} | ||
backgroundColor={COLORS.white} | ||
top="2.3rem" | ||
right="0" | ||
flexDirection={DIRECTION_COLUMN} | ||
> | ||
<MenuItem onClick={handleCalibration}> | ||
{isCalibrated ? t('recalibrate_module') : t('calibrate_module')} | ||
</MenuItem> | ||
{isCalibrated ? ( | ||
<> | ||
<Divider /> | ||
<MenuItem onClick={handleDeleteCalibration} disabled={false}> | ||
{t('clear_calibration_data')} | ||
</MenuItem> | ||
</> | ||
) : null} | ||
</Flex> | ||
) : null} | ||
{menuOverlay} | ||
</Flex> | ||
) | ||
} |
102 changes: 102 additions & 0 deletions
102
...sms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import * as React from 'react' | ||
|
||
import { renderWithProviders } from '@opentrons/components' | ||
|
||
import { i18n } from '../../../../i18n' | ||
import { mockFetchModulesSuccessActionPayloadModules } from '../../../../redux/modules/__fixtures__' | ||
import { ModuleCalibrationOverflowMenu } from '../ModuleCalibrationOverflowMenu' | ||
import { formatLastCalibrated } from '../utils' | ||
import { ModuleCalibrationItems } from '../ModuleCalibrationItems' | ||
|
||
import type { AttachedModule } from '@opentrons/api-client' | ||
|
||
jest.mock('../ModuleCalibrationOverflowMenu') | ||
|
||
const mockModuleCalibrationOverflowMenu = ModuleCalibrationOverflowMenu as jest.MockedFunction< | ||
typeof ModuleCalibrationOverflowMenu | ||
> | ||
|
||
const mockCalibratedModule = { | ||
id: '1436cd6085f18e5c315d65bd835d899a631cc2ba', | ||
serialNumber: 'TC2PVT2023040702', | ||
firmwareVersion: 'v1.0.4', | ||
hardwareRevision: 'Opentrons-thermocycler-gen2', | ||
hasAvailableUpdate: false, | ||
moduleType: 'thermocyclerModuleType', | ||
moduleModel: 'thermocyclerModuleV2', | ||
moduleOffset: { | ||
offset: { | ||
x: 0.1640625, | ||
y: -1.2421875, | ||
z: -1.759999999999991, | ||
}, | ||
slot: '7', | ||
last_modified: '2023-06-01T14:42:20.131798+00:00', | ||
}, | ||
data: { | ||
status: 'holding at target', | ||
currentTemperature: 10, | ||
targetTemperature: 10, | ||
lidStatus: 'open', | ||
lidTemperatureStatus: 'holding at target', | ||
lidTemperature: 100, | ||
lidTargetTemperature: 100, | ||
holdTime: 0, | ||
currentCycleIndex: 1, | ||
totalCycleCount: 1, | ||
currentStepIndex: 1, | ||
totalStepCount: 1, | ||
}, | ||
usbPort: { | ||
port: 3, | ||
portGroup: 'left', | ||
hub: false, | ||
path: '1.0/tty/ttyACM3/dev', | ||
}, | ||
} | ||
|
||
const render = ( | ||
props: React.ComponentProps<typeof ModuleCalibrationItems> | ||
): ReturnType<typeof renderWithProviders> => { | ||
return renderWithProviders(<ModuleCalibrationItems {...props} />, { | ||
i18nInstance: i18n, | ||
}) | ||
} | ||
|
||
describe('ModuleCalibrationItems', () => { | ||
let props: React.ComponentProps<typeof ModuleCalibrationItems> | ||
|
||
beforeEach(() => { | ||
props = { | ||
attachedModules: mockFetchModulesSuccessActionPayloadModules, | ||
updateRobotStatus: jest.fn(), | ||
} | ||
mockModuleCalibrationOverflowMenu.mockReturnValue( | ||
<div>mock ModuleCalibrationOverflowMenu</div> | ||
) | ||
}) | ||
|
||
it('should render module information and overflow menu', () => { | ||
const [{ getByText, getAllByText }] = render(props) | ||
getByText('Module') | ||
getByText('Serial') | ||
getByText('Last Calibrated') | ||
getByText('Magnetic Module GEN1') | ||
getByText('def456') | ||
getByText('Temperature Module GEN1') | ||
getByText('abc123') | ||
getByText('Thermocycler Module GEN1') | ||
getByText('ghi789') | ||
expect(getAllByText('Not calibrated').length).toBe(3) | ||
expect(getAllByText('mock ModuleCalibrationOverflowMenu').length).toBe(3) | ||
}) | ||
|
||
it('should display last calibrated time if a module is calibrated', () => { | ||
props = { | ||
...props, | ||
attachedModules: [mockCalibratedModule] as AttachedModule[], | ||
} | ||
const [{ getByText }] = render(props) | ||
getByText(formatLastCalibrated('2023-06-01T14:42:20.131798+00:00')) | ||
}) | ||
}) |
Oops, something went wrong.