Skip to content

Commit

Permalink
feat(app): delete runs and protocols from ODD (#12319)
Browse files Browse the repository at this point in the history
  • Loading branch information
shlokamin authored Apr 17, 2023
1 parent f193430 commit aab3b51
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 54 deletions.
9 changes: 8 additions & 1 deletion api-client/src/protocols/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ export interface ProtocolMetadata {
}

export interface Protocol {
links?: ResourceLinks
links?: {
referencingRunIds: [
{
id: string
href: string
}
]
}
data: ProtocolResource
}

Expand Down
61 changes: 44 additions & 17 deletions app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useQueryClient } from 'react-query'
import { useHistory } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import {
Expand All @@ -13,10 +14,8 @@ import {
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import {
useCreateRunMutation,
// TODO useDeleteProtocolMutation,
} from '@opentrons/react-api-client'
import { deleteProtocol, deleteRun, getProtocol } from '@opentrons/api-client'
import { useCreateRunMutation, useHost } from '@opentrons/react-api-client'

import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants'
import { StyledText } from '../../../atoms/text'
Expand All @@ -27,20 +26,21 @@ import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config'

import type { Dispatch } from '../../../redux/types'
import type { UseLongPressResult } from '@opentrons/components'
import type { ProtocolResource } from '@opentrons/shared-data'

export function LongPressModal(props: {
longpress: UseLongPressResult
protocol: ProtocolResource
protocolId: string
}): JSX.Element {
const { longpress, protocol } = props
const { longpress, protocolId } = props
const history = useHistory()
const host = useHost()
const queryClient = useQueryClient()
let pinnedProtocolIds = useSelector(getPinnedProtocolIds) ?? []
const { t } = useTranslation(['protocol_info', 'shared'])
const dispatch = useDispatch<Dispatch>()
const { makeSnackbar } = useToaster()

const pinned = pinnedProtocolIds.includes(protocol.id)
const pinned = pinnedProtocolIds.includes(protocolId)

const [showMaxPinsAlert, setShowMaxPinsAlert] = React.useState<boolean>(false)

Expand All @@ -65,34 +65,58 @@ export function LongPressModal(props: {
longpress.setIsLongPressed(false)
}

// TODO const { deleteProtocol } = useDeleteProtocolMutation(protocol.id)

const handleDeleteClick = (): void => {
longpress.setIsLongPressed(false)
// TODO: deleteProtocol()
console.log(`deleted protocol with id ${protocol.id}`)
history.go(0)
if (host != null) {
getProtocol(host, protocolId)
.then(
response =>
response.data.links?.referencingRunIds.map(({ id }) => id) ?? []
)
.then(referencingRunIds => {
return Promise.all(
referencingRunIds?.map(runId => deleteRun(host, runId))
)
})
.then(() => deleteProtocol(host, protocolId))
.then(() =>
queryClient
.invalidateQueries([host, 'protocols'])
.catch((e: Error) =>
console.error(`error invalidating runs query: ${e.message}`)
)
)
.then(() => longpress.setIsLongPressed(false))
.catch((e: Error) => {
console.error(`error deleting resources: ${e.message}`)
longpress.setIsLongPressed(false)
})
} else {
console.error(
'could not delete resources because the robot host is unknown'
)
longpress.setIsLongPressed(false)
}
}

const handlePinClick = (): void => {
if (!pinned) {
if (pinnedProtocolIds.length === MAXIMUM_PINNED_PROTOCOLS) {
setShowMaxPinsAlert(true)
} else {
pinnedProtocolIds.push(protocol.id)
pinnedProtocolIds.push(protocolId)
handlePinnedProtocolIds(pinnedProtocolIds)
makeSnackbar(t('pinned_protocol'))
}
} else {
pinnedProtocolIds = pinnedProtocolIds.filter(p => p !== protocol.id)
pinnedProtocolIds = pinnedProtocolIds.filter(p => p !== protocolId)
handlePinnedProtocolIds(pinnedProtocolIds)
makeSnackbar(t('unpinned_protocol'))
}
}

const handleRunClick = (): void => {
longpress.setIsLongPressed(false)
createRun({ protocolId: protocol.id })
createRun({ protocolId: protocolId })
}

const handlePinnedProtocolIds = (pinnedProtocolIds: string[]): void => {
Expand Down Expand Up @@ -128,6 +152,7 @@ export function LongPressModal(props: {
height="4.875rem"
padding={SPACING.spacing5}
onClick={handleRunClick}
as="button"
>
<Icon name="play-circle" size="1.75rem" color={COLORS.black} />
<StyledText
Expand All @@ -145,6 +170,7 @@ export function LongPressModal(props: {
height="4.875rem"
padding={SPACING.spacing5}
onClick={handlePinClick}
as="button"
>
<Icon name="pin" size="1.875rem" color={COLORS.black} />
<StyledText
Expand All @@ -163,6 +189,7 @@ export function LongPressModal(props: {
height="4.875rem"
padding={SPACING.spacing5}
onClick={handleDeleteClick}
as="button"
>
<Icon name="trash" size="1.875rem" color={COLORS.white} />
<StyledText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function PinnedProtocol(props: {
</StyledText>
</Flex>
{longpress.isLongPressed === true && (
<LongPressModal longpress={longpress} protocol={protocol} />
<LongPressModal longpress={longpress} protocolId={protocol.id} />
)}
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
import * as React from 'react'
import { when, resetAllWhenMocks } from 'jest-when'
import { MemoryRouter } from 'react-router-dom'
import { fireEvent } from '@testing-library/react'
import { renderHook } from '@testing-library/react-hooks'

import { renderWithProviders, useLongPress } from '@opentrons/components'
import { useCreateRunMutation } from '@opentrons/react-api-client'
import {
getProtocol,
deleteProtocol,
deleteRun,
HostConfig,
} from '@opentrons/api-client'
import { useCreateRunMutation, useHost } from '@opentrons/react-api-client'

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

import type { UseLongPressResult } from '@opentrons/components'
import type { ProtocolResource } from '@opentrons/shared-data'

const MOCK_HOST_CONFIG = {} as HostConfig
const mockCreateRun = jest.fn((id: string) => {})
const mockUseCreateRunMutation = useCreateRunMutation as jest.MockedFunction<
typeof useCreateRunMutation
>
const mockuseHost = useHost as jest.MockedFunction<typeof useHost>
const mockGetProtocol = getProtocol as jest.MockedFunction<typeof getProtocol>
const mockDeleteProtocol = deleteProtocol as jest.MockedFunction<
typeof deleteProtocol
>
const mockDeleteRun = deleteRun as jest.MockedFunction<typeof deleteRun>

jest.mock('react-router-dom', () => {
const reactRouterDom = jest.requireActual('react-router-dom')
return {
...reactRouterDom,
}
})
jest.mock('@opentrons/api-client')
jest.mock('@opentrons/react-api-client')

const mockProtocol: ProtocolResource = {
id: 'mockProtocol1',
createdAt: '2022-05-03T21:36:12.494778+00:00',
protocolType: 'json',
metadata: {
protocolName: 'yay mock protocol',
author: 'engineering',
description: 'A short mock protocol',
created: 1606853851893,
tags: ['unitTest'],
},
analysisSummaries: [],
files: [],
key: '26ed5a82-502f-4074-8981-57cdda1d066d',
}

const render = (longPress: UseLongPressResult) => {
return renderWithProviders(
<MemoryRouter>
<LongPressModal longpress={longPress} protocol={mockProtocol} />
<LongPressModal longpress={longPress} protocolId={'mockProtocol1'} />
</MemoryRouter>,
{
i18nInstance: i18n,
Expand All @@ -53,6 +51,12 @@ const render = (longPress: UseLongPressResult) => {
}

describe('Long Press Modal', () => {
beforeEach(() => {
when(mockuseHost).calledWith().mockReturnValue(MOCK_HOST_CONFIG)
})
afterEach(() => {
resetAllWhenMocks()
})
it('should display the three options', () => {
const { result } = renderHook(() => useLongPress())
result.current.isLongPressed = true
Expand All @@ -62,6 +66,28 @@ describe('Long Press Modal', () => {
getByText('Delete protocol')
})

it('should delete delete the protocol and all referenced runs', async () => {
when(mockGetProtocol)
.calledWith(MOCK_HOST_CONFIG, 'mockProtocol1')
.mockResolvedValue({
data: { links: { referencingRunIds: [{ id: '1' }, { id: '2' }] } },
} as any)
const { result } = renderHook(() => useLongPress())
result.current.isLongPressed = true
const [{ getByText }] = render(result.current)
getByText('Delete protocol')
const deleteButton = getByText('Delete protocol').closest('button')
deleteButton?.click()
// flush promises and then make assertions
await new Promise(setImmediate)
expect(mockDeleteRun).toHaveBeenCalledWith(MOCK_HOST_CONFIG, '1')
expect(mockDeleteRun).toHaveBeenCalledWith(MOCK_HOST_CONFIG, '2')
expect(mockDeleteProtocol).toHaveBeenCalledWith(
MOCK_HOST_CONFIG,
'mockProtocol1'
)
})

it('should launch protocol run when clicking run protocol button', () => {
mockUseCreateRunMutation.mockReturnValue({
createRun: mockCreateRun,
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/OnDeviceDisplay/ProtocolDashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export function ProtocolCard(props: {
{format(new Date(protocol.createdAt), 'Pp')}
</StyledText>
{longpress.isLongPressed && (
<LongPressModal longpress={longpress} protocol={protocol} />
<LongPressModal longpress={longpress} protocolId={protocol.id} />
)}
</Flex>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import * as React from 'react'
import { when, resetAllWhenMocks } from 'jest-when'
import { Route } from 'react-router'
import { MemoryRouter } from 'react-router-dom'
import { format } from 'date-fns'
import '@testing-library/jest-dom'
import { renderWithProviders } from '@opentrons/components'
import {
deleteProtocol,
deleteRun,
getProtocol,
HostConfig,
} from '@opentrons/api-client'
import {
useCreateRunMutation,
useDeleteProtocolMutation,
useHost,
useProtocolQuery,
} from '@opentrons/react-api-client'
import { i18n } from '../../../../i18n'
Expand All @@ -15,22 +22,26 @@ import { Deck } from '../Deck'
import { Hardware } from '../Hardware'
import { Labware } from '../Labware'

jest.mock('@opentrons/api-client')
jest.mock('@opentrons/react-api-client')
jest.mock('../Deck')
jest.mock('../Hardware')
jest.mock('../Labware')

const MOCK_HOST_CONFIG = {} as HostConfig
const mockCreateRun = jest.fn((id: string) => {})
const mockDeleteProtocol = jest.fn((id: string) => {})
const mockHardware = Hardware as jest.MockedFunction<typeof Hardware>
const mockLabware = Labware as jest.MockedFunction<typeof Labware>
const mockDeck = Deck as jest.MockedFunction<typeof Deck>
const mockUseCreateRunMutation = useCreateRunMutation as jest.MockedFunction<
typeof useCreateRunMutation
>
const mockUseDeleteProtocolMutation = useDeleteProtocolMutation as jest.MockedFunction<
typeof useDeleteProtocolMutation
const mockuseHost = useHost as jest.MockedFunction<typeof useHost>
const mockGetProtocol = getProtocol as jest.MockedFunction<typeof getProtocol>
const mockDeleteProtocol = deleteProtocol as jest.MockedFunction<
typeof deleteProtocol
>
const mockDeleteRun = deleteRun as jest.MockedFunction<typeof deleteRun>
const mockUseProtocolQuery = useProtocolQuery as jest.MockedFunction<
typeof useProtocolQuery
>
Expand All @@ -53,9 +64,7 @@ describe('ODDProtocolDetails', () => {
mockUseCreateRunMutation.mockReturnValue({
createRun: mockCreateRun,
} as any)
mockUseDeleteProtocolMutation.mockReturnValue({
deleteProtocol: mockDeleteProtocol,
} as any)

mockUseProtocolQuery.mockReturnValue({
data: {
data: {
Expand All @@ -76,6 +85,10 @@ describe('ODDProtocolDetails', () => {
},
},
} as any)
when(mockuseHost).calledWith().mockReturnValue(MOCK_HOST_CONFIG)
})
afterEach(() => {
resetAllWhenMocks()
})

it('renders protocol truncated name that expands when clicked', () => {
Expand Down Expand Up @@ -113,9 +126,23 @@ describe('ODDProtocolDetails', () => {
const [{ getByText }] = render()
getByText('Pin protocol')
})
it('renders the delete protocol button', () => {
it('renders the delete protocol button', async () => {
when(mockGetProtocol)
.calledWith(MOCK_HOST_CONFIG, 'fakeProtocolId')
.mockResolvedValue({
data: { links: { referencingRunIds: [{ id: '1' }, { id: '2' }] } },
} as any)
const [{ getByText }] = render()
getByText('Delete protocol')
const deleteButton = getByText('Delete protocol').closest('button')
deleteButton?.click()
// flush promises and then make assertions
await new Promise(setImmediate)
expect(mockDeleteRun).toHaveBeenCalledWith(MOCK_HOST_CONFIG, '1')
expect(mockDeleteRun).toHaveBeenCalledWith(MOCK_HOST_CONFIG, '2')
expect(mockDeleteProtocol).toHaveBeenCalledWith(
MOCK_HOST_CONFIG,
'fakeProtocolId'
)
})
it('renders the navigation buttons', () => {
mockHardware.mockReturnValue(<div>Mock Hardware</div>)
Expand Down
Loading

0 comments on commit aab3b51

Please sign in to comment.