From 089365a7f787a87761d42d656ca2772ebc6eb6fd Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 14 Mar 2024 09:14:15 -0400 Subject: [PATCH 1/4] feat(app): add Parameters tab to odd protocolDetails closes AUTH-113, AUTH-124 --- .../localization/en/protocol_details.json | 4 + .../pages/ProtocolDetails/EmptySection.tsx | 15 +-- app/src/pages/ProtocolDetails/Parameters.tsx | 103 ++++++++++++++++++ .../__tests__/EmptySection.test.tsx | 22 ++-- .../__tests__/Parameters.test.tsx | 3 + .../__tests__/ProtocolDetails.test.tsx | 6 + app/src/pages/ProtocolDetails/index.tsx | 7 +- 7 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 app/src/pages/ProtocolDetails/Parameters.tsx create mode 100644 app/src/pages/ProtocolDetails/__tests__/Parameters.test.tsx diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index 229aa43cc907..1be941dcf905 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -7,6 +7,10 @@ "connection_status": "connection status", "creation_method": "creation method", "deck_view": "Deck View", + "name": "name", + "default_value": "default value", + "no_parameters": "no parameters are specified in this protocol", + "range": "range", "delete_protocol_perm": "{{name}} and its run history will be permanently deleted.", "delete_protocol": "Delete Protocol", "delete_this_protocol": "Delete this protocol?", diff --git a/app/src/pages/ProtocolDetails/EmptySection.tsx b/app/src/pages/ProtocolDetails/EmptySection.tsx index 6386ad731e4e..308cfd8f0a70 100644 --- a/app/src/pages/ProtocolDetails/EmptySection.tsx +++ b/app/src/pages/ProtocolDetails/EmptySection.tsx @@ -14,13 +14,19 @@ import { useTranslation } from 'react-i18next' import { StyledText } from '../../atoms/text' interface EmptySectionProps { - section: 'hardware' | 'labware' | 'liquids' + section: 'hardware' | 'labware' | 'liquids' | 'parameters' } export const EmptySection = (props: EmptySectionProps): JSX.Element => { const { section } = props const { t, i18n } = useTranslation('protocol_details') + let sectionText: string = t('not_in_protocol', { section: section }) + if (section === 'liquids') { + sectionText = t('liquids_not_in_protocol') + } else if (section === 'parameters') { + sectionText = t('no_parameters') + } return ( { aria-label="EmptySection_ot-alert" /> - {i18n.format( - section === 'liquids' - ? t('liquids_not_in_protocol') - : t('not_in_protocol', { section: section }), - 'capitalize' - )} + {i18n.format(sectionText, 'capitalize')} ) diff --git a/app/src/pages/ProtocolDetails/Parameters.tsx b/app/src/pages/ProtocolDetails/Parameters.tsx new file mode 100644 index 000000000000..932879d253cc --- /dev/null +++ b/app/src/pages/ProtocolDetails/Parameters.tsx @@ -0,0 +1,103 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' +import { + BORDERS, + COLORS, + Flex, + SPACING, + TYPOGRAPHY, + WRAP, +} from '@opentrons/components' +import { StyledText } from '../../atoms/text' +import { useRequiredProtocolHardware } from '../Protocols/hooks' +import { EmptySection } from './EmptySection' + +const Table = styled('table')` + ${TYPOGRAPHY.labelRegular} + table-layout: auto; + width: 100%; + border-spacing: 0 ${SPACING.spacing8}; + margin: ${SPACING.spacing16} 0; + text-align: ${TYPOGRAPHY.textAlignLeft}; +` +const TableHeader = styled('th')` + padding: ${SPACING.spacing4}; + color: ${COLORS.grey60}; +` + +const TableRow = styled('tr')` + background-color: ${COLORS.grey35}; + border: 1px ${COLORS.white} solid; + height: 4.75rem; +` + +const TableDatum = styled('td')` + padding: ${SPACING.spacing4}; + white-space: break-spaces; + text-overflow: ${WRAP}; + &:first-child { + border-top-left-radius: ${BORDERS.borderRadiusSize4}; + border-bottom-left-radius: ${BORDERS.borderRadiusSize4}; + } + &:last-child { + border-top-right-radius: ${BORDERS.borderRadiusSize4}; + border-bottom-right-radius: ${BORDERS.borderRadiusSize4}; + } +` + +export const Parameters = (props: { protocolId: string }): JSX.Element => { + // TODO(Jr, 3/14/24): replace hook with correct hook to get parameters + const { requiredProtocolHardware } = useRequiredProtocolHardware( + props.protocolId + ) + const { t, i18n } = useTranslation('protocol_details') + + return requiredProtocolHardware.length === 0 ? ( + + ) : ( + + + + + + {i18n.format(t('name'), 'capitalize')} + + + + + {i18n.format(t('default_value'), 'capitalize')} + + + + + {i18n.format(t('range'), 'capitalize')} + + + + + + + + TODO + + + TODO + + + TODO + + + +
+ ) +} diff --git a/app/src/pages/ProtocolDetails/__tests__/EmptySection.test.tsx b/app/src/pages/ProtocolDetails/__tests__/EmptySection.test.tsx index 3561bc661178..f46c65cb4f04 100644 --- a/app/src/pages/ProtocolDetails/__tests__/EmptySection.test.tsx +++ b/app/src/pages/ProtocolDetails/__tests__/EmptySection.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { it, describe } from 'vitest' +import { screen } from '@testing-library/react' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' import { EmptySection } from '../EmptySection' @@ -17,22 +18,29 @@ describe('EmptySection', () => { props = { section: 'labware', } - const { getByText, getByLabelText } = render(props) - getByLabelText('EmptySection_ot-alert') - getByText('No labware is specified for this protocol') + render(props) + screen.getByLabelText('EmptySection_ot-alert') + screen.getByText('No labware is specified for this protocol') }) it('should render text for liquid', () => { props = { section: 'liquids', } - const { getByText } = render(props) - getByText('No liquids are specified for this protocol') + render(props) + screen.getByText('No liquids are specified for this protocol') }) it('should render text for hardware', () => { props = { section: 'hardware', } - const { getByText } = render(props) - getByText('No hardware is specified for this protocol') + render(props) + screen.getByText('No hardware is specified for this protocol') + }) + it('should render text for paramters', () => { + props = { + section: 'parameters', + } + render(props) + screen.getByText('No parameters are specified in this protocol') }) }) diff --git a/app/src/pages/ProtocolDetails/__tests__/Parameters.test.tsx b/app/src/pages/ProtocolDetails/__tests__/Parameters.test.tsx new file mode 100644 index 000000000000..e24533f10c74 --- /dev/null +++ b/app/src/pages/ProtocolDetails/__tests__/Parameters.test.tsx @@ -0,0 +1,3 @@ +import { it } from 'vitest' + +it.todo('renders the parameters labels and mock data') diff --git a/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index 1c44e41685ea..12ad7ef870f7 100644 --- a/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -26,6 +26,7 @@ import { ProtocolDetails } from '..' import { Deck } from '../Deck' import { Hardware } from '../Hardware' import { Labware } from '../Labware' +import { Parameters } from '../Parameters' // Mock IntersectionObserver class IntersectionObserver { @@ -50,6 +51,7 @@ vi.mock('../../Protocols/hooks') vi.mock('../Deck') vi.mock('../Hardware') vi.mock('../Labware') +vi.mock('../Parameters') const MOCK_HOST_CONFIG = {} as HostConfig const mockCreateRun = vi.fn((id: string) => {}) @@ -181,7 +183,11 @@ describe('ODDProtocolDetails', () => { vi.mocked(Hardware).mockReturnValue(
Mock Hardware
) vi.mocked(Labware).mockReturnValue(
Mock Labware
) vi.mocked(Deck).mockReturnValue(
Mock Initial Deck Layout
) + vi.mocked(Parameters).mockReturnValue(
Mock Parameters
) + render() + const parametersButton = screen.getByRole('button', { name: 'Parameters' }) + fireEvent.click(parametersButton) const hardwareButton = screen.getByRole('button', { name: 'Hardware' }) fireEvent.click(hardwareButton) screen.getByText('Mock Hardware') diff --git a/app/src/pages/ProtocolDetails/index.tsx b/app/src/pages/ProtocolDetails/index.tsx index 806332e624b9..d8237b1ac4b6 100644 --- a/app/src/pages/ProtocolDetails/index.tsx +++ b/app/src/pages/ProtocolDetails/index.tsx @@ -45,7 +45,9 @@ import { getPinnedProtocolIds, updateConfigValue, } from '../../redux/config' +import { useOffsetCandidatesForAnalysis } from '../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../Protocols/hooks' +import { Parameters } from './Parameters' import { Deck } from './Deck' import { Hardware } from './Hardware' import { Labware } from './Labware' @@ -56,7 +58,6 @@ import type { Protocol } from '@opentrons/api-client' import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' import type { Dispatch } from '../../redux/types' import type { OnDeviceRouteParams } from '../../App/types' -import { useOffsetCandidatesForAnalysis } from '../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' interface ProtocolHeaderProps { title?: string | null @@ -156,6 +157,7 @@ const ProtocolHeader = ({ const protocolSectionTabOptions = [ 'Summary', + 'Parameters', 'Hardware', 'Labware', 'Liquids', @@ -256,6 +258,9 @@ const ProtocolSectionContent = ({ /> ) break + case 'Parameters': + protocolSection = + break case 'Hardware': protocolSection = break From 1bb440d67c21903623ccfe7aefa63397aab15858 Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 14 Mar 2024 09:18:50 -0400 Subject: [PATCH 2/4] alphabetize i18n --- app/src/assets/localization/en/protocol_details.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index 1be941dcf905..fba0d5381182 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -1,5 +1,6 @@ { "author": "author", + "both_mounts": "Both Mounts", "choose_robot_to_run": "Choose Robot to Run\n{{protocol_name}}", "clear_and_proceed_to_setup": "Clear and proceed to setup", "connect_modules_to_see_controls": "Connect modules to see controls", @@ -7,10 +8,7 @@ "connection_status": "connection status", "creation_method": "creation method", "deck_view": "Deck View", - "name": "name", "default_value": "default value", - "no_parameters": "no parameters are specified in this protocol", - "range": "range", "delete_protocol_perm": "{{name}} and its run history will be permanently deleted.", "delete_protocol": "Delete Protocol", "delete_this_protocol": "Delete this protocol?", @@ -24,14 +22,15 @@ "labware": "labware", "last_analyzed": "last analyzed", "last_updated": "last updated", - "both_mounts": "Both Mounts", "left_mount": "left mount", "liquid_name": "liquid name", "liquids_not_in_protocol": "no liquids are specified for this protocol", "liquids": "liquids", "location": "location", "modules": "modules", + "name": "name", "no_available_robots_found": "No available robots found", + "no_parameters": "no parameters are specified in this protocol", "no_summary": "no summary specified for this protocol.", "not_connected": "not connected", "not_in_protocol": "no {{section}} is specified for this protocol", @@ -48,6 +47,7 @@ "protocol_outdated_app_analysis": "This protocol's analysis is out of date. It may produce different results if you run it now.", "python_api_version": "Python API {{version}}", "quantity": "Quantity", + "range": "range", "read_less": "read less", "read_more": "read more", "right_mount": "right mount", From ffcce6233e5b631f732f90e470bdf98731291d4c Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 14 Mar 2024 09:34:12 -0400 Subject: [PATCH 3/4] add snackbar to paramters --- app/src/assets/localization/en/protocol_details.json | 1 + app/src/pages/ProtocolDetails/Parameters.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index fba0d5381182..0ca3c115448e 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -60,6 +60,7 @@ "sending": "Sending", "show_in_folder": "Show in folder", "slot": "Slot {{slotName}}", + "start_setup_to_customize": "Start setup to customize values", "start_setup": "Start setup", "successfully_sent": "Successfully sent", "total_volume": "total volume", diff --git a/app/src/pages/ProtocolDetails/Parameters.tsx b/app/src/pages/ProtocolDetails/Parameters.tsx index 932879d253cc..4838c226bdce 100644 --- a/app/src/pages/ProtocolDetails/Parameters.tsx +++ b/app/src/pages/ProtocolDetails/Parameters.tsx @@ -10,6 +10,7 @@ import { WRAP, } from '@opentrons/components' import { StyledText } from '../../atoms/text' +import { useToaster } from '../../organisms/ToasterOven' import { useRequiredProtocolHardware } from '../Protocols/hooks' import { EmptySection } from './EmptySection' @@ -51,12 +52,16 @@ export const Parameters = (props: { protocolId: string }): JSX.Element => { const { requiredProtocolHardware } = useRequiredProtocolHardware( props.protocolId ) + const { makeSnackbar } = useToaster() const { t, i18n } = useTranslation('protocol_details') + const makeSnack = (): void => { + makeSnackbar(t('start_setup_to_customize')) + } return requiredProtocolHardware.length === 0 ? ( ) : ( - +
From 97cec55720494531edcf3b23b7feb4686c22db96 Mon Sep 17 00:00:00 2001 From: Jethary Date: Thu, 14 Mar 2024 12:02:43 -0400 Subject: [PATCH 4/4] put behind ff --- .../__tests__/ProtocolDetails.test.tsx | 3 +++ app/src/pages/ProtocolDetails/index.tsx | 24 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index 12ad7ef870f7..0e6bfb0da8ca 100644 --- a/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -21,6 +21,7 @@ import { i18n } from '../../../i18n' import { useHardwareStatusText } from '../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' import { useOffsetCandidatesForAnalysis } from '../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../../Protocols/hooks' +import { useFeatureFlag } from '../../../redux/config' import { formatTimeWithUtcLabel } from '../../../resources/runs' import { ProtocolDetails } from '..' import { Deck } from '../Deck' @@ -52,6 +53,7 @@ vi.mock('../Deck') vi.mock('../Hardware') vi.mock('../Labware') vi.mock('../Parameters') +vi.mock('../../../redux/config') const MOCK_HOST_CONFIG = {} as HostConfig const mockCreateRun = vi.fn((id: string) => {}) @@ -90,6 +92,7 @@ const render = (path = '/protocols/fakeProtocolId') => { describe('ODDProtocolDetails', () => { beforeEach(() => { + vi.mocked(useFeatureFlag).mockReturnValue(true) vi.mocked(useCreateRunMutation).mockReturnValue({ createRun: mockCreateRun, } as any) diff --git a/app/src/pages/ProtocolDetails/index.tsx b/app/src/pages/ProtocolDetails/index.tsx index d8237b1ac4b6..758d5f018a8c 100644 --- a/app/src/pages/ProtocolDetails/index.tsx +++ b/app/src/pages/ProtocolDetails/index.tsx @@ -44,6 +44,7 @@ import { getApplyHistoricOffsets, getPinnedProtocolIds, updateConfigValue, + useFeatureFlag, } from '../../redux/config' import { useOffsetCandidatesForAnalysis } from '../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../Protocols/hooks' @@ -163,8 +164,17 @@ const protocolSectionTabOptions = [ 'Liquids', 'Deck', ] as const +const protocolSectionTabOptionsWithoutParameters = [ + 'Summary', + 'Hardware', + 'Labware', + 'Liquids', + 'Deck', +] as const -type TabOption = typeof protocolSectionTabOptions[number] +type TabOption = + | typeof protocolSectionTabOptions[number] + | typeof protocolSectionTabOptionsWithoutParameters[number] interface ProtocolSectionTabsProps { currentOption: TabOption @@ -175,9 +185,13 @@ const ProtocolSectionTabs = ({ currentOption, setCurrentOption, }: ProtocolSectionTabsProps): JSX.Element => { + const enableRtpFF = useFeatureFlag('enableRunTimeParameters') + const options = enableRtpFF + ? protocolSectionTabOptions + : protocolSectionTabOptionsWithoutParameters return ( - {protocolSectionTabOptions.map(option => { + {options.map(option => { return ( () const { missingProtocolHardware, @@ -305,8 +320,11 @@ export function ProtocolDetails(): JSX.Element | null { const { makeSnackbar } = useToaster() const queryClient = useQueryClient() const [currentOption, setCurrentOption] = React.useState( - protocolSectionTabOptions[0] + enableRtpFF + ? protocolSectionTabOptions[0] + : protocolSectionTabOptionsWithoutParameters[0] ) + const [showMaxPinsAlert, setShowMaxPinsAlert] = React.useState(false) const { data: protocolRecord,