Skip to content

Commit

Permalink
Feat: add async validation on uploading file (#150)
Browse files Browse the repository at this point in the history
* feat: Update bulk-import-ui

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* feat: Add useValidateBulkImport

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* fix: refresh interval time

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* feat: Add Uploading Screen async Validation

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* fix: Import List Data filter

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* chore: Add Changelog

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* chore: Add Changelog

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* fix: fix useBulkImportDetailsQuery onSuccess prop

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* fix: Try fix ci/cd

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* fix: Try fix ci/cd

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

* ci: update quality-egineering version

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>

---------

Signed-off-by: Arthur Andrade <arthurfelandrade@gmail.com>
  • Loading branch information
ArthurTriis1 authored Jan 26, 2024
1 parent 318d56d commit 7a083d6
Show file tree
Hide file tree
Showing 22 changed files with 224 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/qe-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
jobs:
quality-engineering:
name: QE
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2.1.14
with:
cypress: true
cyRunnerBranch: ${{ inputs.cyRunnerBranch }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qe-pull-request-target.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
jobs:
quality-engineering:
name: QE
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2.1.14
with:
danger: true
dangerRequireChangelog: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qe-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
jobs:
quality-engineering:
name: QE
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2.1.14
with:
danger: true
dangerRequireChangelog: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qe-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
quality-engineering:
name: QE
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2.1.14
with:
nodeLint: true
nodeTest: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qe-schedule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
quality-engineering:
name: QE
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2
uses: vtex-apps/usqa/.github/workflows/quality-engineering.yml@v2.1.14
with:
cypress: true
cyRunnerTimeOut: 45
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Refactor Bulk import Uploading Modal to Async Validation

## [1.29.2] - 2024-01-12

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
} from '../../types/BulkImport'
import useStartBulkImport from '../../hooks/useStartBulkImport'
import ReportDownloadLink from '../ReportDownloadLink/ReportDownloadLink'
import { ValidationScreen } from '../UploadingScreen'

const CreateOrganizationButton = () => {
const { formatMessage } = useTranslate()
Expand Down Expand Up @@ -70,6 +71,7 @@ const CreateOrganizationButton = () => {
onOpenChange={setUploadModalOpen}
uploadFile={uploadBulkImportFile}
onUploadFinish={handleUploadFinish}
uploadingScreen={props => <ValidationScreen {...props} />}
errorScreen={props => (
<ReportErrorScreen {...(props.data as BulkImportUploadError)} />
)}
Expand Down
2 changes: 1 addition & 1 deletion react/components/ImportReportModal/ImportReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ImportReportModal = ({
}: ImportReportModalProps) => {
const { translate: t, formatDate } = useTranslate()

const { data, error } = useBulkImportDetailsQuery(importId)
const { data, error } = useBulkImportDetailsQuery({ importId })

const reportDownloadLink = data?.importResult?.reportDownloadLink

Expand Down
58 changes: 58 additions & 0 deletions react/components/UploadingScreen/UploadingScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react'
import { UploadingScreen as BulkImportUploadingScreen } from '@vtex/bulk-import-ui'

import type { UploadFileData } from '../../types/BulkImport'
import ValidatingScreen from './ValidatingScreen'
import useValidateBulkImport from '../../hooks/useValidateBulkImport'

export type UploadingScreenProps = {
name: string
size: number
uploadFile: () => Promise<UploadFileData>
onUploadFinished: (data: UploadFileData) => void
}

export type UploadingStep = 'UPLOADING' | 'VALIDATING'

const UploadingScreen = ({
uploadFile,
onUploadFinished: onUploadFinishedProp,
...otherProps
}: UploadingScreenProps) => {
const [step, setStep] = useState<UploadingStep>('UPLOADING')

const [importId, setImportId] = useState<string | undefined>(undefined)

const { startBulkImportValidation } = useValidateBulkImport({
onSuccess: () => {
setStep('VALIDATING')
},
})

const onUploadFinished = (data: UploadFileData) => {
if (data.status === 'error') {
onUploadFinishedProp(data)

return
}

startBulkImportValidation({ importId: data?.data?.fileData?.importId })
setImportId(data?.data?.fileData?.importId)
}

return step === 'UPLOADING' ? (
<BulkImportUploadingScreen
{...otherProps}
uploadFile={uploadFile}
onUploadFinished={onUploadFinished}
/>
) : (
<ValidatingScreen
{...otherProps}
importId={importId}
onUploadFinished={onUploadFinishedProp}
/>
)
}

export default UploadingScreen
80 changes: 80 additions & 0 deletions react/components/UploadingScreen/ValidatingScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import { Flex, Spinner, Text, csx } from '@vtex/admin-ui'

import { useTranslate } from '../../hooks'
import { bytesToSize } from '../utils/bytesToSize'
import type { UploadFileData } from '../../types/BulkImport'
import useBulkImportDetailsQuery from '../../hooks/useBulkImportDetailsQuery'

export type ValidatingScreenProps = {
importId?: string
name: string
size: number
onUploadFinished: (data: UploadFileData) => void
}

const ValidatingScreen = ({
name,
size,
importId,
onUploadFinished,
}: ValidatingScreenProps) => {
const { translate: t } = useTranslate()

useBulkImportDetailsQuery({
importId,
refreshInterval: 30 * 1000,
onSuccess: data => {
if (data.importState === 'ReadyToImport') {
onUploadFinished({
status: 'success',
data: {
fileData: {
...data,
percentage: data.percentage.toString(),
},
},
})

return
}

onUploadFinished({
status: 'error',
showReport: data?.importState === 'ValidationFailed',
data: {
error: 'FieldValidationError',
errorDownloadLink: data?.validationResult?.reportDownloadLink ?? '',
validationResult: data?.validationResult?.validationResult ?? [],
fileName: data.fileName,
},
})
},
})

return (
<Flex
className={csx({ backgroundColor: '$gray05', height: '100%' })}
align="center"
direction="column"
justify="center"
>
<Spinner className={csx({ color: '$blue40' })} size={120} />
<Text
className={csx({ marginBottom: '$space-3', marginTop: '$space-10' })}
variant="pageTitle"
>
{t('uploading')}
</Text>
<div>
<Text>{name}</Text>
<Text tone="secondary">
{' · '}
{bytesToSize(size)}
</Text>
</div>
</Flex>
)
}

export default ValidatingScreen
2 changes: 2 additions & 0 deletions react/components/UploadingScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as ValidationScreen } from './UploadingScreen'
export type { UploadingScreenProps } from './UploadingScreen'
10 changes: 10 additions & 0 deletions react/components/utils/bytesToSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const bytesToSize = (bytes: number) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']

if (bytes === 0) return '0 Bytes'
const i = Math.floor(Math.log(bytes) / Math.log(1024))

if (i === 0) return `${bytes} ${sizes[i]}`

return `${Math.round(bytes / 1024 ** i)}${sizes[i]}`
}
15 changes: 14 additions & 1 deletion react/hooks/useBulkImportDetailsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import useSWR from 'swr'

import { getBulkImportDetails } from '../services'
import type { BulkImportDetails } from '../services/getBulkImportDetails'

const useBulkImportDetailsQuery = (importId: string) => {
export type UseBulkImportDetailsQueryProps = {
importId?: string
onSuccess?: (data: BulkImportDetails) => void
refreshInterval?: number
}

const useBulkImportDetailsQuery = ({
importId,
onSuccess,
refreshInterval = 0,
}: UseBulkImportDetailsQueryProps) => {
return useSWR(
importId ? `/buyer-orgs/${importId}` : null,
() => getBulkImportDetails(importId),
{
refreshInterval,
revalidateOnFocus: false,
onSuccess,
}
)
}
Expand Down
2 changes: 1 addition & 1 deletion react/hooks/useBulkImportsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const useBulkImportQuery = (
account ? '/buyer-orgs' : null,
() => getBulkImportList(account),
{
refreshInterval: shouldPoll ? 5 * 1000 : 0, // 30 seconds
refreshInterval: shouldPoll ? 30 * 1000 : 0, // 30 seconds
onError: errorData => {
const status = errorData?.response?.status ?? 0

Expand Down
18 changes: 18 additions & 0 deletions react/hooks/useValidateBulkImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import useSWRMutation from 'swr/mutation'

import { validateBulkImport } from '../services'

const useValidateBulkImport = ({ onSuccess }: { onSuccess?: () => void }) => {
const { trigger } = useSWRMutation(
'/buyer-orgs/start',
(_, { arg }: { arg: { importId: string } }) =>
validateBulkImport(arg.importId),
{
onSuccess,
}
)

return { startBulkImportValidation: trigger }
}

export default useValidateBulkImport
2 changes: 1 addition & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"dependencies": {
"@vtex/admin-ui": "^0.136.1",
"@vtex/bulk-import-ui": "1.1.6",
"@vtex/bulk-import-ui": "1.1.8",
"@vtex/css-handles": "^1.0.0",
"apollo-client": "^2.6.10",
"axios": "1.4.0",
Expand Down
10 changes: 5 additions & 5 deletions react/services/getBulkImportDetails.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import bulkImportClient from '.'
import type { ImportDetails, ImportReportData } from '../types/BulkImport'

type BulkImportList = Omit<ImportDetails, 'percentage'> & {
export type BulkImportDetails = Omit<ImportDetails, 'percentage'> & {
importReportList: ImportReportData[]
percentage: number
}

const getBulkImportList = async (importId: string): Promise<BulkImportList> => {
const getBulkImportDetails = async (
importId?: string
): Promise<BulkImportDetails> => {
const importListResponse = await bulkImportClient.get<ImportDetails>(
`/buyer-orgs/${importId}`
)
Expand All @@ -15,8 +17,6 @@ const getBulkImportList = async (importId: string): Promise<BulkImportList> => {

const { importResult } = data

if (!importResult?.imports) throw Error('Import result not provided')

const importList = importResult?.imports ?? []

const [totalSuccess, totalError] = importList.reduce(
Expand Down Expand Up @@ -49,4 +49,4 @@ const getBulkImportList = async (importId: string): Promise<BulkImportList> => {
}
}

export default getBulkImportList
export default getBulkImportDetails
7 changes: 4 additions & 3 deletions react/services/getBulkImportList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ const getBulkImportList = async (account: string) => {
const importListData = importListResponse.data as ImportDetails[]

return importListData
.filter(
item =>
!['ReadyToImport', 'Failed'].some(status => status === item.importState)
.filter(item =>
['InProgress', 'Completed', 'CompletedWithError'].some(
status => status === item.importState
)
)
.map(item => ({
importId: item.importId,
Expand Down
1 change: 1 addition & 0 deletions react/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as getBulkImportList } from './getBulkImportList'
export { default as getBulkImportDetails } from './getBulkImportDetails'
export { default as uploadBulkImportFile } from './uploadBulkImportFile'
export { default as startBulkImport } from './startBulkImport'
export { default as validateBulkImport } from './validateBulkImport'
7 changes: 7 additions & 0 deletions react/services/validateBulkImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import bulkImportClient from '.'

const validateBulkImport = async (importId?: string): Promise<unknown> => {
return bulkImportClient.post(`/buyer-orgs/validate/${importId}`)
}

export default validateBulkImport
8 changes: 7 additions & 1 deletion react/types/BulkImport.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ type ImportState =
| 'ReadyToImport'
| 'InProgress'
| 'Completed'
| 'InValidation'
| 'ValidationFailed'
| 'CompletedWithError'
| 'Failed'

Expand Down Expand Up @@ -56,6 +58,10 @@ export type ImportDetails = {
importedAt: string
importedUserEmail: string
importedUserName: string
validationResult?: {
reportDownloadLink: string
validationResult: ValidationResult[]
}
}

export type UploadFileResult = {
Expand All @@ -70,7 +76,7 @@ export type ValidationResult = {
}

export type FieldValidationError = {
description: string
description?: string
error: 'FieldValidationError'
errorDownloadLink: string
validationResult: ValidationResult[]
Expand Down
Loading

0 comments on commit 7a083d6

Please sign in to comment.