Skip to content

Commit

Permalink
feat(app, api-client, react-api-client): add api-client function for …
Browse files Browse the repository at this point in the history
…`getCsvFile` (#15926)

Add new api-client function for `getCsvFile`, which takes a fileId and
returns `CsvFileData`:
```
{
  id: string
  createdAt: string
  name: string
}
```
Wrap in react query hook and implement in `HistoricalProtocolRunDrawer`.
  • Loading branch information
ncdiehl11 authored Aug 7, 2024
1 parent 503f33d commit 57eed21
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 48 deletions.
12 changes: 12 additions & 0 deletions api-client/src/dataFiles/getCsvFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { GET, request } from '../request'

import type { CsvFileDataResponse } from './types'
import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'

export function getCsvFile(
config: HostConfig,
fileId: string
): ResponsePromise<CsvFileDataResponse> {
return request<CsvFileDataResponse>(GET, `/dataFiles/${fileId}`, null, config)
}
1 change: 1 addition & 0 deletions api-client/src/dataFiles/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { getCsvFile } from './getCsvFile'
export { getCsvFileRaw } from './getCsvFileRaw'
export { uploadCsvFile } from './uploadCsvFile'

Expand Down
4 changes: 3 additions & 1 deletion api-client/src/dataFiles/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ export interface CsvFileData {
name: string
}

export interface UploadedCsvFileResponse {
export interface CsvFileDataResponse {
data: CsvFileData
}

export type UploadedCsvFileResponse = CsvFileDataResponse

export interface UploadedCsvFilesResponse {
data: CsvFileData[]
}
Expand Down
9 changes: 4 additions & 5 deletions app/src/organisms/Devices/HistoricalProtocolRun.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
SPACING,
LegacyStyledText,
} from '@opentrons/components'
import { useAllCsvFilesQuery } from '@opentrons/react-api-client'
import { formatInterval } from '../RunTimeControl/utils'
import { formatTimestamp } from './utils'
import { EMPTY_TIMESTAMP } from './constants'
Expand Down Expand Up @@ -41,9 +40,9 @@ export function HistoricalProtocolRun(
const { t } = useTranslation('run_details')
const { run, protocolName, robotIsBusy, robotName, protocolKey } = props
const [drawerOpen, setDrawerOpen] = React.useState(false)
const { data: protocolFileData } = useAllCsvFilesQuery(run.protocolId ?? '')
const allProtocolDataFiles =
protocolFileData != null ? protocolFileData.data : []
const countRunDataFiles = run.runTimeParameters.filter(
parameter => parameter.type === 'csv_file'
).length
const runStatus = run.status
const runDisplayName = formatTimestamp(run.createdAt)
let duration = EMPTY_TIMESTAMP
Expand Down Expand Up @@ -92,7 +91,7 @@ export function HistoricalProtocolRun(
width="5%"
data-testid={`RecentProtocolRuns_Files_${protocolKey}`}
>
{allProtocolDataFiles.length}
{countRunDataFiles}
</LegacyStyledText>
<LegacyStyledText
as="p"
Expand Down
98 changes: 57 additions & 41 deletions app/src/organisms/Devices/HistoricalProtocolRunDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
getLoadedLabwareDefinitionsByUri,
getModuleDisplayName,
} from '@opentrons/shared-data'
import { useAllCsvFilesQuery } from '@opentrons/react-api-client'
import { useCsvFileQuery } from '@opentrons/react-api-client'
import { DownloadCsvFileLink } from './DownloadCsvFileLink'
import { Banner } from '../../atoms/Banner'
import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis'
Expand All @@ -46,8 +46,15 @@ export function HistoricalProtocolRunDrawer(
const allLabwareOffsets = run.labwareOffsets?.sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
)
const { data } = useAllCsvFilesQuery(run.protocolId ?? '')
const allProtocolDataFiles = data != null ? data.data : []
const runDataFileIds = run.runTimeParameters.reduce<string[]>(
(acc, parameter) => {
if (parameter.type === 'csv_file') {
return parameter.file?.id != null ? [...acc, parameter.file?.id] : acc
}
return acc
},
[]
)
const uniqueLabwareOffsets = allLabwareOffsets?.filter(
(offset, index, array) => {
return (
Expand Down Expand Up @@ -94,7 +101,7 @@ export function HistoricalProtocolRunDrawer(
) : null

const protocolFilesData =
allProtocolDataFiles.length === 1 ? (
runDataFileIds.length === 0 ? (
<InfoScreen contentType="noFiles" t={t} backgroundColor={COLORS.grey35} />
) : (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
Expand Down Expand Up @@ -133,43 +140,8 @@ export function HistoricalProtocolRunDrawer(
</Box>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
{allProtocolDataFiles.map((fileData, index) => {
const { createdAt, name: fileName, id: fileId } = fileData
return (
<Flex
key={`csv_file_${index}`}
justifyContent={JUSTIFY_FLEX_START}
alignItems={ALIGN_CENTER}
padding={SPACING.spacing12}
backgroundColor={COLORS.white}
borderRadius={BORDERS.borderRadius4}
gridGap={SPACING.spacing24}
>
<Flex
width="33%"
gridGap={SPACING.spacing4}
alignItems={ALIGN_CENTER}
>
<LegacyStyledText
as="p"
css={css`
overflow: ${OVERFLOW_HIDDEN};
text-overflow: ellipsis;
`}
>
{fileName}
</LegacyStyledText>
</Flex>
<Box width="33%">
<LegacyStyledText as="p">
{format(new Date(createdAt), 'M/d/yy HH:mm:ss')}
</LegacyStyledText>
</Box>
<Box width="34%">
<DownloadCsvFileLink fileId={fileId} fileName={fileName} />
</Box>
</Flex>
)
{runDataFileIds.map((fileId, index) => {
return <CsvFileDataRow key={`csv_file_${index}`} fileId={fileId} />
})}
</Flex>
</Flex>
Expand Down Expand Up @@ -293,3 +265,47 @@ export function HistoricalProtocolRunDrawer(
</Flex>
)
}

interface CsvFileDataRowProps {
fileId: string
}

function CsvFileDataRow(props: CsvFileDataRowProps): JSX.Element | null {
const { fileId } = props

const { data: fileData } = useCsvFileQuery(fileId)
if (fileData == null) {
return null
}
const { name, createdAt } = fileData.data
return (
<Flex
justifyContent={JUSTIFY_FLEX_START}
alignItems={ALIGN_CENTER}
padding={SPACING.spacing12}
backgroundColor={COLORS.white}
borderRadius={BORDERS.borderRadius4}
gridGap={SPACING.spacing24}
>
<Flex width="33%" gridGap={SPACING.spacing4} alignItems={ALIGN_CENTER}>
<LegacyStyledText
as="p"
css={css`
overflow: ${OVERFLOW_HIDDEN};
text-overflow: ellipsis;
`}
>
{name}
</LegacyStyledText>
</Flex>
<Box width="33%">
<LegacyStyledText as="p">
{format(new Date(createdAt), 'M/d/yy HH:mm:ss')}
</LegacyStyledText>
</Box>
<Box width="34%">
<DownloadCsvFileLink fileId={fileId} fileName={name} />
</Box>
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/
import { useRunStatus, useRunTimestamps } from '../../RunTimeControl/hooks'
import { HistoricalProtocolRun } from '../HistoricalProtocolRun'
import { HistoricalProtocolRunOverflowMenu } from '../HistoricalProtocolRunOverflowMenu'

import type { RunStatus, RunData } from '@opentrons/api-client'
import type { RunTimeParameter } from '@opentrons/shared-data'

vi.mock('../../../redux/protocol-storage')
vi.mock('../../RunTimeControl/hooks')
Expand All @@ -20,6 +22,7 @@ const run = {
id: 'test_id',
protocolId: 'test_protocol_id',
status: 'succeeded' as RunStatus,
runTimeParameters: [] as RunTimeParameter[],
} as RunData

const render = (props: React.ComponentProps<typeof HistoricalProtocolRun>) => {
Expand Down
78 changes: 78 additions & 0 deletions react-api-client/src/dataFiles/__tests__/useCsvFileQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { QueryClient, QueryClientProvider } from 'react-query'
import { renderHook, waitFor } from '@testing-library/react'
import { getCsvFile } from '@opentrons/api-client'
import { useHost } from '../../api'
import { useCsvFileQuery } from '..'

import type {
CsvFileData,
CsvFileDataResponse,
HostConfig,
Response,
} from '@opentrons/api-client'

vi.mock('@opentrons/api-client')
vi.mock('../../api/useHost')

const HOST_CONFIG: HostConfig = { hostname: 'localhost' }
const FILE_ID = 'file123'
const FILE_NAME = 'my_file.csv'
const FILE_CONTENT_RESPONSE = {
data: {
name: FILE_NAME,
id: FILE_ID,
createdAt: '2024-06-07T19:19:56.268029+00:00',
} as CsvFileData,
} as CsvFileDataResponse

describe('useCsvFileQuery hook', () => {
let wrapper: React.FunctionComponent<{ children: React.ReactNode }>

beforeEach(() => {
const queryClient = new QueryClient()
const clientProvider: React.FunctionComponent<{
children: React.ReactNode
}> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)

wrapper = clientProvider
})

it('should return no data if no host', () => {
vi.mocked(useHost).mockReturnValue(null)

const { result } = renderHook(() => useCsvFileQuery(FILE_ID), {
wrapper,
})

expect(result.current.data).toBeUndefined()
})

it('should return no data if the get file request fails', () => {
vi.mocked(useHost).mockReturnValue(HOST_CONFIG)
vi.mocked(getCsvFile).mockRejectedValue('oh no')

const { result } = renderHook(() => useCsvFileQuery(FILE_ID), {
wrapper,
})
expect(result.current.data).toBeUndefined()
})

it('should return file data if successful request', async () => {
vi.mocked(useHost).mockReturnValue(HOST_CONFIG)
vi.mocked(getCsvFile).mockResolvedValue({
data: FILE_CONTENT_RESPONSE,
} as Response<CsvFileDataResponse>)

const { result } = renderHook(() => useCsvFileQuery(FILE_ID), {
wrapper,
})

await waitFor(() => {
expect(result.current.data).toEqual(FILE_CONTENT_RESPONSE)
})
})
})
1 change: 1 addition & 0 deletions react-api-client/src/dataFiles/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { useCsvFileQuery } from './useCsvFileQuery'
export { useCsvFileRawQuery } from './useCsvFileRawQuery'
export { useUploadCsvFileMutation } from './useUploadCsvFileMutation'
25 changes: 25 additions & 0 deletions react-api-client/src/dataFiles/useCsvFileQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useQuery } from 'react-query'
import { getCsvFile } from '@opentrons/api-client'
import { useHost } from '../api'

import type { UseQueryOptions, UseQueryResult } from 'react-query'
import type { HostConfig, CsvFileDataResponse } from '@opentrons/api-client'

export function useCsvFileQuery(
fileId: string,
options?: UseQueryOptions<CsvFileDataResponse>
): UseQueryResult<CsvFileDataResponse> {
const host = useHost()
const allOptions: UseQueryOptions<CsvFileDataResponse> = {
...options,
enabled: host !== null && fileId !== null,
}

const query = useQuery<CsvFileDataResponse>(
[host, 'dataFiles', fileId],
() =>
getCsvFile(host as HostConfig, fileId).then(response => response.data),
allOptions
)
return query
}
2 changes: 1 addition & 1 deletion react-api-client/src/dataFiles/useCsvFileRawQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useCsvFileRawQuery(
}

const query = useQuery<DownloadedCsvFileResponse>(
[host, `/dataFiles/${fileId}/download`],
[host, 'dataFiles', fileId, 'download'],
() =>
getCsvFileRaw(host as HostConfig, fileId).then(response => response.data),
allOptions
Expand Down

0 comments on commit 57eed21

Please sign in to comment.