Skip to content

Commit

Permalink
feat: add application section, update translation files
Browse files Browse the repository at this point in the history
  • Loading branch information
fbelginetw committed Oct 22, 2024
1 parent 16cd6af commit 73ec6c2
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"application_title": "Application",
"application_scientific_dropdown_label": "What's your scientific application?",
"application_scientific_dropdown_title": "What's your scientific application?",
"application_scientific_dropdown_placeholder": "Select an option",
"application_describe_label": "Describe what you are trying to do",
"application_describe_example": "Example: “The protocol performs automated liquid handling for Pierce BCA Protein Assay Kit to determine protein concentrations in various sample types, such as cell lysates and eluates of purification process.",
"basic_aliquoting": "Basic aliquoting",
"pcr": "PCR",
"other": "Other",
"application_other_title": "Other application",
"application_other_caption": "Example: “cherrypicking” or “serial dilution”",
"application_describe_title": "Describe what you are trying to do",
"application_describe_caption": "Example: “The protocol performs automated liquid handling for Pierce BCA Protein Assay Kit to determine protein concentrations in various sample types, such as cell lysates and eluates of purification process.",
"section_confirm_button": "Confirm"
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,5 @@
"landing_page_button_new_protocol": "Create a new protocol",
"landing_page_button_update_protocol": "Update an existing protocol",
"prompt_preview_submit_button": "Submit prompt",
"prompt_preview_placeholder_message": "As you complete the sections on the left, your prompt will be built here. When all requirements are met you will be able to generate the protocol.",
"create_protocol_page_application_title": "Application",
"create_protocol_page_application_scientific_dropdown_label": "What's your scientific application?",
"create_protocol_page_application_scientific_dropdown_placeholder": "Select an option",
"create_protocol_page_application_describe_label": "Describe what you are trying to do",
"create_protocol_page_application_describe_example": "Example: “The protocol performs automated liquid handling for Pierce BCA Protein Assay Kit to determine protein concentrations in various sample types, such as cell lysates and eluates of purification process.",
"create_protocol_page_section_confirm_button": "Confirm"
"prompt_preview_placeholder_message": "As you complete the sections on the left, your prompt will be built here. When all requirements are met you will be able to generate the protocol."
}
6 changes: 4 additions & 2 deletions opentrons-ai-client/src/atoms/ControlledInputField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
import { Controller } from 'react-hook-form'

interface ControlledInputFieldProps {
id?: string
name: string
rules?: any
title?: string
caption?: string
}

export function ControlledInputField({
id,
name,
rules,
title,
Expand All @@ -27,8 +29,8 @@ export function ControlledInputField({
<InputField
title={title}
caption={caption}
id="otherApplication"
name="otherApplication"
id={id}
name={name}
type="text"
onChange={field.onChange}
value={field.value}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { fireEvent, renderHook, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
import { ApplicationSection } from '..'
import { FormProvider, useForm, useFormContext } from 'react-hook-form'

// TODO move to __testing-utils__
const TestFormProviderComponent = () => {
const methods = useForm({
defaultValues: {},
})

return (
<FormProvider {...methods}>
<ApplicationSection />
</FormProvider>
)
}

const render = (): ReturnType<typeof renderWithProviders> => {
return renderWithProviders(<TestFormProviderComponent />, {
i18nInstance: i18n,
})
}

describe('ApplicationSection', () => {
beforeEach(() => {
const { result } = renderHook(() => useFormContext())
const methods = result.current
})

it('should render scientific application dropdown, describe input and confirm button', () => {
render()

expect(
screen.getByText("What's your scientific application?")
).toBeInTheDocument()
expect(
screen.getByText('Describe what you are trying to do')
).toBeInTheDocument()
expect(screen.getByText('Confirm')).toBeInTheDocument()
})

it('should not render other application dropdown if Other option is not selected', () => {
render()

expect(screen.queryByText('Other application')).not.toBeInTheDocument()
})

it('should render other application dropdown if Other option is selected', () => {
render()

const applicationDropdown = screen.getByText('Select an option')
fireEvent.click(applicationDropdown)

const otherOption = screen.getByText('Other')
fireEvent.click(otherOption)

expect(screen.getByText('Other application')).toBeInTheDocument()
})

it('should enable confirm button when all fields are filled', async () => {
render()

const applicationDropdown = screen.getByText('Select an option')
fireEvent.click(applicationDropdown)

const basicAliquotingOption = screen.getByText('Basic aliquoting')
fireEvent.click(basicAliquotingOption)

const describeInput = screen.getByRole('textbox')
fireEvent.change(describeInput, { target: { value: 'Test description' } })

const confirmButton = screen.getByRole('button')
await waitFor(() => expect(confirmButton).toBeEnabled())
})

it('should disable confirm button when all fields are not filled', () => {
render()

const confirmButton = screen.getByRole('button')
expect(confirmButton).toBeDisabled()
})
})
83 changes: 83 additions & 0 deletions opentrons-ai-client/src/organisms/ApplicationSection/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
DIRECTION_COLUMN,
DISPLAY_FLEX,
Flex,
JUSTIFY_FLEX_END,
LargeButton,
SPACING,
} from '@opentrons/components'
import { useFormContext } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { ControlledDropdownMenu } from '../../atoms/ControlledDropdownMenu'
import { ControlledInputField } from '../../atoms/ControlledInputField'

export const BASIC_ALIQUOTING = 'basic_aliquoting'
export const PCR = 'pcr'
export const OTHER = 'other'
export const APPLICATION_SCIENTIFIC_APPLICATION =
'application.scientificApplication'
export const APPLICATION_OTHER_APPLICATION = 'application.otherApplication'
export const APPLICATION_DESCRIBE = 'application.describe'

export function ApplicationSection(): JSX.Element | null {
const { t } = useTranslation('create_protocol')
const {
watch,
formState: { isValid },
} = useFormContext()

const options = [
{ name: t(BASIC_ALIQUOTING), value: BASIC_ALIQUOTING },
{ name: t(PCR), value: PCR },
{ name: t(OTHER), value: OTHER },
]

const isOtherSelected = watch(APPLICATION_SCIENTIFIC_APPLICATION) === OTHER

return (
<Flex
flexDirection={DIRECTION_COLUMN}
height="100%"
gap={SPACING.spacing16}
>
<ControlledDropdownMenu
width="100%"
dropdownType="neutral"
name={APPLICATION_SCIENTIFIC_APPLICATION}
title={t('application_scientific_dropdown_title')}
options={options}
placeholder={t('application_scientific_dropdown_placeholder')}
rules={{ required: true }}
/>

{isOtherSelected && (
<ControlledInputField
name={APPLICATION_OTHER_APPLICATION}
title={t('application_other_title')}
caption={t('application_other_caption')}
rules={{ required: isOtherSelected, minLength: 3 }}
/>
)}

<ControlledInputField
name={APPLICATION_DESCRIBE}
title={t('application_describe_title')}
caption={t('application_describe_caption')}
rules={{ required: true, minLength: 3 }}
/>

<ButtonContainer>
<LargeButton
disabled={!isValid}
buttonText={t('section_confirm_button')}
></LargeButton>
</ButtonContainer>
</Flex>
)
}

const ButtonContainer = styled.div`
display: ${DISPLAY_FLEX};
justify-content: ${JUSTIFY_FLEX_END};
`
121 changes: 43 additions & 78 deletions opentrons-ai-client/src/pages/CreateProtocol/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import {
DIRECTION_COLUMN,
DISPLAY_FLEX,
DropdownMenu,
Flex,
InputField,
JUSTIFY_FLEX_END,
JUSTIFY_SPACE_EVENLY,
LargeButton,
POSITION_RELATIVE,
SPACING,
StyledText,
} from '@opentrons/components'
import { useTranslation } from 'react-i18next'
import { Accordion } from '../../molecules/Accordion'
import { useState } from 'react'
import styled from 'styled-components'
import { PromptPreview } from '../../molecules/PromptPreview'
import { ApplicationSection } from '../../organisms/ApplicationSection'
import { useForm, FormProvider } from 'react-hook-form'

export interface InputType {
userPrompt: string
interface CreateProtocolFormData {
application: {
scientificApplication: string
otherApplication?: string
description: string
}
}

export function CreateProtocol(): JSX.Element | null {
Expand All @@ -27,83 +27,48 @@ export function CreateProtocol(): JSX.Element | null {
true
)

const methods = useForm<CreateProtocolFormData>({
defaultValues: {
application: {
scientificApplication: '',
otherApplication: '',
description: '',
},
},
})

return (
<Flex
position={POSITION_RELATIVE}
justifyContent={JUSTIFY_SPACE_EVENLY}
gap={SPACING.spacing32}
margin={`${SPACING.spacing16} ${SPACING.spacing16}`}
height="100%"
>
<ProtocolSections>
<Accordion
heading={t('application_title')}
isOpen={applicationAccordionIsOpen}
handleClick={function (): void {
<FormProvider {...methods}>
<Flex
position={POSITION_RELATIVE}
justifyContent={JUSTIFY_SPACE_EVENLY}
gap={SPACING.spacing32}
margin={`${SPACING.spacing16} ${SPACING.spacing16}`}
height="100%"
>
<ProtocolSections>
<Accordion
heading={t('application_title')}
isOpen={applicationAccordionIsOpen}
handleClick={function (): void {
throw new Error('Function not implemented.')
}}
>
<ApplicationSection />
</Accordion>
</ProtocolSections>
<PromptPreview
handleSubmit={function (): void {
throw new Error('Function not implemented.')
}}
>
<Flex flexDirection={DIRECTION_COLUMN} height="100%">
<FormField>
<StyledText desktopStyle="bodyDefaultRegular">
{t('application_scientific_dropdown_label')}
</StyledText>
<DropdownMenu
width="100%"
dropdownType="neutral"
filterOptions={[
{ name: 'test', value: 'test' },
{ name: 'test2', value: 'test2' },
]}
onClick={function (value: string): void {
throw new Error('Function not implemented.')
}}
currentOption={{ name: 'test', value: 'test' }}
></DropdownMenu>
</FormField>

<FormField>
<StyledText desktopStyle="bodyDefaultRegular">
{t('application_describe_label')}
</StyledText>
<InputField></InputField>
<StyledText desktopStyle="bodyDefaultRegular">
{t('application_describe_example')}
</StyledText>
</FormField>

<ButtonContainer>
<LargeButton
disabled={true}
buttonText={t('section_confirm_button')}
></LargeButton>
</ButtonContainer>
</Flex>
</Accordion>
</ProtocolSections>
<PromptPreview
handleSubmit={function (): void {
throw new Error('Function not implemented.')
}}
promptPreviewData={[]}
/>
</Flex>
promptPreviewData={[]}
/>
</Flex>
</FormProvider>
)
}

const ProtocolSections = styled(Flex)`
flex-direction: ${DIRECTION_COLUMN};
width: 100%;
`

const ButtonContainer = styled.div`
display: ${DISPLAY_FLEX};
justify-content: ${JUSTIFY_FLEX_END};
`

const FormField = styled.div`
display: ${DISPLAY_FLEX};
flex-direction: ${DIRECTION_COLUMN};
gap: ${SPACING.spacing4};
margin-bottom: ${SPACING.spacing16};
`

0 comments on commit 73ec6c2

Please sign in to comment.