@commercetools-frontend-extensions/operations 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +169 -0
- package/babel.config.js +6 -0
- package/dist/commercetools-frontend-extensions-operations.cjs.d.ts +2 -0
- package/dist/commercetools-frontend-extensions-operations.cjs.dev.js +2469 -0
- package/dist/commercetools-frontend-extensions-operations.cjs.js +7 -0
- package/dist/commercetools-frontend-extensions-operations.cjs.prod.js +2461 -0
- package/dist/commercetools-frontend-extensions-operations.esm.js +2316 -0
- package/dist/declarations/src/@api/export-operations.d.ts +5 -0
- package/dist/declarations/src/@api/fetcher.d.ts +17 -0
- package/dist/declarations/src/@api/file-upload.d.ts +3 -0
- package/dist/declarations/src/@api/import-containers.d.ts +35 -0
- package/dist/declarations/src/@api/import-operations.d.ts +6 -0
- package/dist/declarations/src/@api/index.d.ts +8 -0
- package/dist/declarations/src/@api/process-file.d.ts +3 -0
- package/dist/declarations/src/@api/test-fixtures.d.ts +272 -0
- package/dist/declarations/src/@api/urls.d.ts +44 -0
- package/dist/declarations/src/@components/file-drop-area/active-drag-drop-area.d.ts +10 -0
- package/dist/declarations/src/@components/file-drop-area/disabled-drop-area.d.ts +5 -0
- package/dist/declarations/src/@components/file-drop-area/drop-area-wrapper.d.ts +11 -0
- package/dist/declarations/src/@components/file-drop-area/enabled-drop-area.d.ts +7 -0
- package/dist/declarations/src/@components/file-drop-area/file-drop-area.d.ts +14 -0
- package/dist/declarations/src/@components/file-drop-area/file-dropped-area.d.ts +6 -0
- package/dist/declarations/src/@components/file-drop-area/index.d.ts +7 -0
- package/dist/declarations/src/@components/file-drop-area/styles.d.ts +9 -0
- package/dist/declarations/src/@components/icons/file-icon.d.ts +2 -0
- package/dist/declarations/src/@components/icons/index.d.ts +2 -0
- package/dist/declarations/src/@components/icons/lock-icon.d.ts +2 -0
- package/dist/declarations/src/@components/index.d.ts +6 -0
- package/dist/declarations/src/@components/info-box/index.d.ts +1 -0
- package/dist/declarations/src/@components/info-box/info-box.d.ts +7 -0
- package/dist/declarations/src/@components/upload-separator/index.d.ts +1 -0
- package/dist/declarations/src/@components/upload-separator/upload-separator.d.ts +12 -0
- package/dist/declarations/src/@components/upload-settings/index.d.ts +1 -0
- package/dist/declarations/src/@components/upload-settings/upload-settings.d.ts +11 -0
- package/dist/declarations/src/@components/uploading-modal/index.d.ts +1 -0
- package/dist/declarations/src/@components/uploading-modal/uploading-modal.d.ts +12 -0
- package/dist/declarations/src/@constants/delimiters.d.ts +8 -0
- package/dist/declarations/src/@constants/import-tags.d.ts +7 -0
- package/dist/declarations/src/@constants/index.d.ts +4 -0
- package/dist/declarations/src/@constants/resource-links.d.ts +10 -0
- package/dist/declarations/src/@constants/upload-limits.d.ts +10 -0
- package/dist/declarations/src/@errors/http-error.d.ts +6 -0
- package/dist/declarations/src/@errors/index.d.ts +8 -0
- package/dist/declarations/src/@errors/invalid-response-error.d.ts +3 -0
- package/dist/declarations/src/@errors/no-resources-to-export-error.d.ts +3 -0
- package/dist/declarations/src/@errors/project-key-not-available-error.d.ts +3 -0
- package/dist/declarations/src/@errors/query-predicate-error.d.ts +4 -0
- package/dist/declarations/src/@errors/unexpected-column-error.d.ts +3 -0
- package/dist/declarations/src/@errors/unexpected-operation-state-error.d.ts +4 -0
- package/dist/declarations/src/@errors/unexpected-resource-type-error.d.ts +3 -0
- package/dist/declarations/src/@hooks/index.d.ts +5 -0
- package/dist/declarations/src/@hooks/use-fetch-export-operations.d.ts +15 -0
- package/dist/declarations/src/@hooks/use-fetch-import-container-details.d.ts +15 -0
- package/dist/declarations/src/@hooks/use-fetch-import-operations.d.ts +16 -0
- package/dist/declarations/src/@hooks/use-fetch-import-summaries.d.ts +20 -0
- package/dist/declarations/src/@hooks/use-import-container-upload.d.ts +18 -0
- package/dist/declarations/src/@types/api.d.ts +13 -0
- package/dist/declarations/src/@types/basic-error-data-type.d.ts +5 -0
- package/dist/declarations/src/@types/export-operation.d.ts +95 -0
- package/dist/declarations/src/@types/file-upload.d.ts +63 -0
- package/dist/declarations/src/@types/import-container.d.ts +53 -0
- package/dist/declarations/src/@types/import-operation.d.ts +13 -0
- package/dist/declarations/src/@types/import-states.d.ts +9 -0
- package/dist/declarations/src/@types/import-summary.d.ts +15 -0
- package/dist/declarations/src/@types/index.d.ts +9 -0
- package/dist/declarations/src/@types/shared.d.ts +7 -0
- package/dist/declarations/src/@utils/error-mapping.d.ts +19 -0
- package/dist/declarations/src/@utils/file-upload.d.ts +46 -0
- package/dist/declarations/src/@utils/form.d.ts +1 -0
- package/dist/declarations/src/@utils/format.d.ts +5 -0
- package/dist/declarations/src/@utils/import-container.d.ts +8 -0
- package/dist/declarations/src/@utils/index.d.ts +6 -0
- package/dist/declarations/src/@utils/url.d.ts +6 -0
- package/dist/declarations/src/index.d.ts +26 -0
- package/index.js +1 -0
- package/jest.test.config.js +11 -0
- package/package.json +63 -0
- package/src/@api/export-operations.ts +26 -0
- package/src/@api/fetcher.spec.ts +51 -0
- package/src/@api/fetcher.ts +127 -0
- package/src/@api/file-upload.spec.ts +83 -0
- package/src/@api/file-upload.ts +46 -0
- package/src/@api/import-containers.ts +256 -0
- package/src/@api/import-operations.ts +33 -0
- package/src/@api/index.ts +8 -0
- package/src/@api/process-file.spec.ts +74 -0
- package/src/@api/process-file.ts +53 -0
- package/src/@api/test-fixtures.ts +772 -0
- package/src/@api/urls.ts +118 -0
- package/src/@components/file-drop-area/active-drag-drop-area.tsx +33 -0
- package/src/@components/file-drop-area/disabled-drop-area.tsx +17 -0
- package/src/@components/file-drop-area/drop-area-wrapper.tsx +38 -0
- package/src/@components/file-drop-area/enabled-drop-area.tsx +27 -0
- package/src/@components/file-drop-area/file-drop-area.tsx +74 -0
- package/src/@components/file-drop-area/file-dropped-area.tsx +29 -0
- package/src/@components/file-drop-area/index.ts +7 -0
- package/src/@components/file-drop-area/styles.ts +67 -0
- package/src/@components/icons/file-icon.tsx +30 -0
- package/src/@components/icons/index.ts +2 -0
- package/src/@components/icons/lock-icon.tsx +34 -0
- package/src/@components/index.ts +6 -0
- package/src/@components/info-box/index.ts +1 -0
- package/src/@components/info-box/info-box.tsx +23 -0
- package/src/@components/upload-separator/index.ts +1 -0
- package/src/@components/upload-separator/upload-separator.tsx +61 -0
- package/src/@components/upload-settings/index.ts +1 -0
- package/src/@components/upload-settings/upload-settings.tsx +36 -0
- package/src/@components/uploading-modal/index.ts +1 -0
- package/src/@components/uploading-modal/uploading-modal.tsx +64 -0
- package/src/@constants/delimiters.ts +14 -0
- package/src/@constants/import-tags.ts +9 -0
- package/src/@constants/index.ts +4 -0
- package/src/@constants/resource-links.ts +61 -0
- package/src/@constants/upload-limits.ts +11 -0
- package/src/@errors/http-error.ts +17 -0
- package/src/@errors/index.ts +8 -0
- package/src/@errors/invalid-response-error.ts +6 -0
- package/src/@errors/no-resources-to-export-error.ts +6 -0
- package/src/@errors/project-key-not-available-error.ts +6 -0
- package/src/@errors/query-predicate-error.ts +10 -0
- package/src/@errors/unexpected-column-error.ts +6 -0
- package/src/@errors/unexpected-operation-state-error.ts +8 -0
- package/src/@errors/unexpected-resource-type-error.ts +6 -0
- package/src/@hooks/index.ts +5 -0
- package/src/@hooks/messages.ts +11 -0
- package/src/@hooks/use-fetch-export-operations.ts +34 -0
- package/src/@hooks/use-fetch-import-container-details.ts +31 -0
- package/src/@hooks/use-fetch-import-operations.ts +42 -0
- package/src/@hooks/use-fetch-import-summaries.ts +47 -0
- package/src/@hooks/use-fetch.spec.ts +76 -0
- package/src/@hooks/use-fetch.ts +80 -0
- package/src/@hooks/use-import-container-upload.spec.ts +294 -0
- package/src/@hooks/use-import-container-upload.ts +126 -0
- package/src/@types/api.ts +14 -0
- package/src/@types/basic-error-data-type.ts +5 -0
- package/src/@types/export-operation.ts +144 -0
- package/src/@types/file-upload.ts +81 -0
- package/src/@types/import-container.ts +104 -0
- package/src/@types/import-operation.ts +31 -0
- package/src/@types/import-states.ts +9 -0
- package/src/@types/import-summary.ts +22 -0
- package/src/@types/index.ts +9 -0
- package/src/@types/shared.ts +52 -0
- package/src/@utils/error-mapping.spec.ts +126 -0
- package/src/@utils/error-mapping.ts +39 -0
- package/src/@utils/file-upload.spec.ts +151 -0
- package/src/@utils/file-upload.ts +150 -0
- package/src/@utils/form.ts +20 -0
- package/src/@utils/format.spec.ts +62 -0
- package/src/@utils/format.ts +53 -0
- package/src/@utils/import-container.spec.ts +26 -0
- package/src/@utils/import-container.ts +34 -0
- package/src/@utils/index.ts +6 -0
- package/src/@utils/url.spec.ts +75 -0
- package/src/@utils/url.ts +18 -0
- package/src/index.ts +27 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useIntl } from 'react-intl'
|
|
2
|
+
import { InfoDialog } from '@commercetools-frontend/application-components'
|
|
3
|
+
import {
|
|
4
|
+
Constraints,
|
|
5
|
+
ProgressBar,
|
|
6
|
+
SecondaryButton,
|
|
7
|
+
Spacings,
|
|
8
|
+
Text,
|
|
9
|
+
} from '@commercetools-frontend/ui-kit'
|
|
10
|
+
import { convertFileSizeToKB } from '../../@utils'
|
|
11
|
+
import { FileIcon } from '../icons/file-icon'
|
|
12
|
+
|
|
13
|
+
type UploadingModalProps = {
|
|
14
|
+
isOpen: boolean
|
|
15
|
+
title: string
|
|
16
|
+
fileName: string
|
|
17
|
+
fileSize: number
|
|
18
|
+
progress: number
|
|
19
|
+
cancelLabel: string
|
|
20
|
+
onCancel: () => void
|
|
21
|
+
onClose: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const UploadingModal = ({
|
|
25
|
+
isOpen,
|
|
26
|
+
title,
|
|
27
|
+
fileName,
|
|
28
|
+
fileSize,
|
|
29
|
+
progress,
|
|
30
|
+
cancelLabel,
|
|
31
|
+
onCancel,
|
|
32
|
+
onClose,
|
|
33
|
+
}: UploadingModalProps) => {
|
|
34
|
+
const intl = useIntl()
|
|
35
|
+
return (
|
|
36
|
+
<InfoDialog size={16} isOpen={isOpen} title={title} onClose={onClose}>
|
|
37
|
+
<Spacings.Stack scale="m">
|
|
38
|
+
<Spacings.Inline alignItems="center" justifyContent="space-between">
|
|
39
|
+
<Spacings.Inline alignItems="center">
|
|
40
|
+
<FileIcon />
|
|
41
|
+
<Spacings.Inline>
|
|
42
|
+
<Constraints.Horizontal max={10}>
|
|
43
|
+
<Text.Body truncate isBold>
|
|
44
|
+
{fileName}
|
|
45
|
+
</Text.Body>
|
|
46
|
+
</Constraints.Horizontal>
|
|
47
|
+
</Spacings.Inline>
|
|
48
|
+
<Text.Body tone="secondary">
|
|
49
|
+
({intl.formatNumber(convertFileSizeToKB(fileSize))} KB)
|
|
50
|
+
</Text.Body>
|
|
51
|
+
</Spacings.Inline>
|
|
52
|
+
<SecondaryButton
|
|
53
|
+
tone="secondary"
|
|
54
|
+
size="medium"
|
|
55
|
+
label={cancelLabel}
|
|
56
|
+
onClick={onCancel}
|
|
57
|
+
/>
|
|
58
|
+
</Spacings.Inline>
|
|
59
|
+
<ProgressBar barWidth="scale" height="10" progress={progress} />
|
|
60
|
+
<div />
|
|
61
|
+
</Spacings.Stack>
|
|
62
|
+
</InfoDialog>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ResourceTypeId } from '@commercetools/importapi-sdk'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CommerceTools API documentation base URL
|
|
5
|
+
*/
|
|
6
|
+
export const CT_API_DOCS_URL = 'https://docs.commercetools.com/api/'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Template download links for each resource type
|
|
10
|
+
*/
|
|
11
|
+
export const RESOURCE_TYPE_TEMPLATE_DOWNLOAD_LINKS: Record<
|
|
12
|
+
ResourceTypeId,
|
|
13
|
+
string
|
|
14
|
+
> = {
|
|
15
|
+
category:
|
|
16
|
+
'https://docs.commercetools.com/merchant-center/downloads/category_import_template.csv',
|
|
17
|
+
'custom-object':
|
|
18
|
+
'https://docs.commercetools.com/merchant-center/downloads/custom_object_import_template.json',
|
|
19
|
+
product:
|
|
20
|
+
'https://docs.commercetools.com/merchant-center/import-data#download-a-template',
|
|
21
|
+
'inventory-entry':
|
|
22
|
+
'https://docs.commercetools.com/merchant-center/downloads/inventory_entry_import_template.csv',
|
|
23
|
+
// TODO: remove `inventory` after aligning the resource type names in the Import API and Export API
|
|
24
|
+
inventory:
|
|
25
|
+
'https://docs.commercetools.com/merchant-center/downloads/inventory_entry_import_template.csv',
|
|
26
|
+
'discount-code':
|
|
27
|
+
'https://docs.commercetools.com/merchant-center/downloads/discount_code_import_template.csv',
|
|
28
|
+
customer:
|
|
29
|
+
'https://docs.commercetools.com/merchant-center/import-data#download-a-template',
|
|
30
|
+
order:
|
|
31
|
+
'https://docs.commercetools.com/merchant-center/import-data#download-a-template',
|
|
32
|
+
'product-type':
|
|
33
|
+
'https://docs.commercetools.com/merchant-center/downloads/product_type_import_template.csv',
|
|
34
|
+
'business-unit':
|
|
35
|
+
'https://docs.commercetools.com/merchant-center/downloads/business_unit_import_template.csv',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const RESOURCE_TYPE_DOCUMENTATION_LINKS: Record<ResourceTypeId, string> =
|
|
39
|
+
{
|
|
40
|
+
category:
|
|
41
|
+
'https://docs.commercetools.com/merchant-center/import-categories#supported-headers-and-values',
|
|
42
|
+
'custom-object':
|
|
43
|
+
'https://docs.commercetools.com/merchant-center/import-custom-objects#supported-fields-and-values',
|
|
44
|
+
product:
|
|
45
|
+
'https://docs.commercetools.com/merchant-center/import-products#supported-headers-and-values',
|
|
46
|
+
'inventory-entry':
|
|
47
|
+
'https://docs.commercetools.com/merchant-center/import-inventory#supported-headers-and-values',
|
|
48
|
+
// TODO: remove `inventory` after aligning the resource type names in the Import API and Export API
|
|
49
|
+
inventory:
|
|
50
|
+
'https://docs.commercetools.com/merchant-center/import-inventory#supported-headers-and-values',
|
|
51
|
+
'discount-code':
|
|
52
|
+
'https://docs.commercetools.com/merchant-center/import-discount-codes#supported-headers-and-values',
|
|
53
|
+
customer:
|
|
54
|
+
'https://docs.commercetools.com/merchant-center/import-customers#supported-headers-and-values',
|
|
55
|
+
order:
|
|
56
|
+
'https://docs.commercetools.com/merchant-center/import-orders#supported-headers-and-values',
|
|
57
|
+
'product-type':
|
|
58
|
+
'https://docs.commercetools.com/merchant-center/import-product-types#supported-headers-and-values',
|
|
59
|
+
'business-unit':
|
|
60
|
+
'https://docs.commercetools.com/merchant-center/import-business-units#supported-headers-and-values',
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum file size for imports.
|
|
3
|
+
* Recommended by backend, enforced in frontend validation.
|
|
4
|
+
*/
|
|
5
|
+
export const MAX_FILE_SIZE_MB = 35
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maximum row count for imports.
|
|
9
|
+
* Recommended by backend, enforced in frontend validation.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_ROW_COUNT = 80_000
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BasicErrorDataType } from '../@types'
|
|
2
|
+
|
|
3
|
+
export class HttpError<T = BasicErrorDataType> extends Error {
|
|
4
|
+
readonly statusCode: number
|
|
5
|
+
readonly errorData?: T
|
|
6
|
+
|
|
7
|
+
constructor(statusCode: number, statusText?: string, errorData?: T) {
|
|
8
|
+
super(
|
|
9
|
+
`HTTP Error! Status code: ${statusCode}, message: "${
|
|
10
|
+
statusText ? statusText : ''
|
|
11
|
+
}"`
|
|
12
|
+
)
|
|
13
|
+
this.name = 'HttpError'
|
|
14
|
+
this.statusCode = statusCode
|
|
15
|
+
this.errorData = errorData
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './unexpected-column-error'
|
|
2
|
+
export * from './unexpected-operation-state-error'
|
|
3
|
+
export * from './unexpected-resource-type-error'
|
|
4
|
+
export * from './http-error'
|
|
5
|
+
export * from './invalid-response-error'
|
|
6
|
+
export * from './query-predicate-error'
|
|
7
|
+
export * from './no-resources-to-export-error'
|
|
8
|
+
export * from './project-key-not-available-error'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ProcessingState } from '@commercetools/importapi-sdk'
|
|
2
|
+
|
|
3
|
+
export class UnexpectedOperationStateError extends Error {
|
|
4
|
+
constructor(state: ProcessingState) {
|
|
5
|
+
super(`Unexpected operation state "${state}"`)
|
|
6
|
+
this.name = 'UnexpectedOperationStateError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl'
|
|
2
|
+
|
|
3
|
+
export default defineMessages({
|
|
4
|
+
unexpectedError: {
|
|
5
|
+
id: 'operations.fetch.unexpectedError',
|
|
6
|
+
description:
|
|
7
|
+
'Generic error message displayed when an unexpected error occurs during data fetching',
|
|
8
|
+
defaultMessage:
|
|
9
|
+
'An unexpected error occurred while fetching the data. Please try again. If the problem persists, please contact support.',
|
|
10
|
+
},
|
|
11
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fetchExportOperations } from '../@api'
|
|
3
|
+
import { ProjectKeyNotAvailableError } from '../@errors'
|
|
4
|
+
import type {
|
|
5
|
+
PaginatedExportOperationResponse,
|
|
6
|
+
ExportOperationQueryParams,
|
|
7
|
+
} from '../@types'
|
|
8
|
+
import { useFetch } from './use-fetch'
|
|
9
|
+
|
|
10
|
+
type UseFetchExportOperationsConfig = {
|
|
11
|
+
projectKey: string
|
|
12
|
+
queryParams: ExportOperationQueryParams
|
|
13
|
+
pollingInterval?: number
|
|
14
|
+
shouldContinuePolling?: (data: PaginatedExportOperationResponse) => boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useFetchExportOperations = ({
|
|
18
|
+
projectKey,
|
|
19
|
+
queryParams,
|
|
20
|
+
pollingInterval,
|
|
21
|
+
shouldContinuePolling,
|
|
22
|
+
}: UseFetchExportOperationsConfig) => {
|
|
23
|
+
const fetchData = React.useCallback(() => {
|
|
24
|
+
if (!projectKey) {
|
|
25
|
+
return Promise.reject(new ProjectKeyNotAvailableError())
|
|
26
|
+
}
|
|
27
|
+
return fetchExportOperations({ projectKey, queryParams })
|
|
28
|
+
}, [projectKey, queryParams])
|
|
29
|
+
|
|
30
|
+
return useFetch(fetchData, {
|
|
31
|
+
pollingInterval,
|
|
32
|
+
shouldContinuePolling,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fetchImportContainerDetails } from '../@api'
|
|
3
|
+
import { ProjectKeyNotAvailableError } from '../@errors'
|
|
4
|
+
import { useFetch } from './use-fetch'
|
|
5
|
+
import { type ImportContainerDetails } from '../@types'
|
|
6
|
+
|
|
7
|
+
type UseFetchImportContainerDetailsConfig = {
|
|
8
|
+
projectKey: string
|
|
9
|
+
importContainerKey: string
|
|
10
|
+
pollingInterval?: number
|
|
11
|
+
shouldContinuePolling?: (data: ImportContainerDetails) => boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useFetchImportContainerDetails = ({
|
|
15
|
+
projectKey,
|
|
16
|
+
importContainerKey,
|
|
17
|
+
pollingInterval,
|
|
18
|
+
shouldContinuePolling,
|
|
19
|
+
}: UseFetchImportContainerDetailsConfig) => {
|
|
20
|
+
const fetchData = React.useCallback(() => {
|
|
21
|
+
if (!projectKey) {
|
|
22
|
+
return Promise.reject(new ProjectKeyNotAvailableError())
|
|
23
|
+
}
|
|
24
|
+
return fetchImportContainerDetails({ projectKey, importContainerKey })
|
|
25
|
+
}, [projectKey, importContainerKey])
|
|
26
|
+
|
|
27
|
+
return useFetch(fetchData, {
|
|
28
|
+
pollingInterval,
|
|
29
|
+
shouldContinuePolling,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fetchImportOperations } from '../@api'
|
|
3
|
+
import { ProjectKeyNotAvailableError } from '../@errors'
|
|
4
|
+
import type {
|
|
5
|
+
ExtendedImportOperationPagedResponse,
|
|
6
|
+
ImportOperationQueryParams,
|
|
7
|
+
} from '../@types'
|
|
8
|
+
import { useFetch } from './use-fetch'
|
|
9
|
+
|
|
10
|
+
type UseFetchImportOperationsConfig = {
|
|
11
|
+
projectKey: string
|
|
12
|
+
importContainerKey: string
|
|
13
|
+
queryParams: ImportOperationQueryParams
|
|
14
|
+
pollingInterval?: number
|
|
15
|
+
shouldContinuePolling?: (
|
|
16
|
+
data: ExtendedImportOperationPagedResponse
|
|
17
|
+
) => boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useFetchImportOperations = ({
|
|
21
|
+
projectKey,
|
|
22
|
+
importContainerKey,
|
|
23
|
+
queryParams,
|
|
24
|
+
pollingInterval,
|
|
25
|
+
shouldContinuePolling,
|
|
26
|
+
}: UseFetchImportOperationsConfig) => {
|
|
27
|
+
const fetchData = React.useCallback(() => {
|
|
28
|
+
if (!projectKey) {
|
|
29
|
+
return Promise.reject(new ProjectKeyNotAvailableError())
|
|
30
|
+
}
|
|
31
|
+
return fetchImportOperations({
|
|
32
|
+
projectKey,
|
|
33
|
+
importContainerKey,
|
|
34
|
+
queryParams,
|
|
35
|
+
})
|
|
36
|
+
}, [projectKey, importContainerKey, queryParams])
|
|
37
|
+
|
|
38
|
+
return useFetch(fetchData, {
|
|
39
|
+
pollingInterval,
|
|
40
|
+
shouldContinuePolling,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { fetchImportSummaries } from '../@api'
|
|
3
|
+
import { ProjectKeyNotAvailableError } from '../@errors'
|
|
4
|
+
import type {
|
|
5
|
+
ImportContainerQueryParams,
|
|
6
|
+
ImportContainerDetails,
|
|
7
|
+
} from '../@types'
|
|
8
|
+
import { useFetch } from './use-fetch'
|
|
9
|
+
|
|
10
|
+
export type ImportSummariesResult = {
|
|
11
|
+
results: ImportContainerDetails[]
|
|
12
|
+
count: number
|
|
13
|
+
total: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type UseFetchImportSummariesConfig = {
|
|
17
|
+
projectKey: string
|
|
18
|
+
queryParams: ImportContainerQueryParams
|
|
19
|
+
pollingInterval?: number
|
|
20
|
+
shouldContinuePolling?: (data: ImportSummariesResult) => boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useFetchImportSummaries = ({
|
|
24
|
+
projectKey,
|
|
25
|
+
queryParams,
|
|
26
|
+
pollingInterval,
|
|
27
|
+
shouldContinuePolling,
|
|
28
|
+
}: UseFetchImportSummariesConfig) => {
|
|
29
|
+
const fetchData =
|
|
30
|
+
React.useCallback(async (): Promise<ImportSummariesResult> => {
|
|
31
|
+
if (!projectKey) {
|
|
32
|
+
return Promise.reject(new ProjectKeyNotAvailableError())
|
|
33
|
+
}
|
|
34
|
+
const summary = await fetchImportSummaries({ projectKey, queryParams })
|
|
35
|
+
const resolvedResults = await Promise.all(summary.results)
|
|
36
|
+
return {
|
|
37
|
+
results: resolvedResults,
|
|
38
|
+
count: summary.count,
|
|
39
|
+
total: summary.total,
|
|
40
|
+
}
|
|
41
|
+
}, [projectKey, queryParams])
|
|
42
|
+
|
|
43
|
+
return useFetch<ImportSummariesResult>(fetchData, {
|
|
44
|
+
pollingInterval,
|
|
45
|
+
shouldContinuePolling,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react'
|
|
2
|
+
import { useIntl } from 'react-intl'
|
|
3
|
+
import { HttpError } from '../@errors'
|
|
4
|
+
import { useFetch } from './use-fetch'
|
|
5
|
+
|
|
6
|
+
jest.mock('react-intl', () => ({
|
|
7
|
+
useIntl: jest.fn(),
|
|
8
|
+
defineMessages: jest.fn(),
|
|
9
|
+
}))
|
|
10
|
+
jest.mock('@commercetools-frontend/sentry', () => ({
|
|
11
|
+
reportErrorToSentry: jest.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
describe('useFetch', () => {
|
|
15
|
+
const mockIntl = {
|
|
16
|
+
formatMessage: jest.fn((message) => message.defaultMessage),
|
|
17
|
+
}
|
|
18
|
+
const mockFetchFunction = jest.fn()
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
;(useIntl as jest.Mock).mockReturnValue(mockIntl)
|
|
21
|
+
jest.clearAllMocks()
|
|
22
|
+
})
|
|
23
|
+
it('should fetch data successfully', async () => {
|
|
24
|
+
const data = { data: 'some data' }
|
|
25
|
+
mockFetchFunction.mockResolvedValueOnce(data)
|
|
26
|
+
const { result } = renderHook(() => useFetch(mockFetchFunction, {}))
|
|
27
|
+
expect(result.current.isLoading).toBe(true)
|
|
28
|
+
await waitFor(() => {
|
|
29
|
+
expect(result.current.isLoading).toBe(false)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(result.current.data).toEqual(data)
|
|
33
|
+
expect(result.current.error).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
it('should handle HttpError', async () => {
|
|
36
|
+
const error = new HttpError(500, 'Http error')
|
|
37
|
+
mockFetchFunction.mockRejectedValueOnce(error)
|
|
38
|
+
const { result } = renderHook(() => useFetch(mockFetchFunction, {}))
|
|
39
|
+
await waitFor(() => {
|
|
40
|
+
expect(result.current.isLoading).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
expect(result.current.data).toBeNull()
|
|
43
|
+
expect(result.current.error).toEqual(error)
|
|
44
|
+
})
|
|
45
|
+
it('should refetch data', async () => {
|
|
46
|
+
const data = { data: 'some data' }
|
|
47
|
+
mockFetchFunction.mockResolvedValueOnce(data)
|
|
48
|
+
const { result } = renderHook(() => useFetch(mockFetchFunction, {}))
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
expect(result.current.data).toEqual(data)
|
|
51
|
+
})
|
|
52
|
+
const newData = { data: 'new data' }
|
|
53
|
+
mockFetchFunction.mockResolvedValueOnce(newData)
|
|
54
|
+
act(() => {
|
|
55
|
+
result.current.refetch()
|
|
56
|
+
})
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(result.current.data).toEqual(newData)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
it('should handle polling', async () => {
|
|
62
|
+
const data = { data: 'some data' }
|
|
63
|
+
mockFetchFunction.mockResolvedValue(data)
|
|
64
|
+
const { result } = renderHook(() =>
|
|
65
|
+
useFetch(mockFetchFunction, { pollingInterval: 50 })
|
|
66
|
+
)
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
expect(result.current.data).toEqual(data)
|
|
69
|
+
})
|
|
70
|
+
const newData = { data: 'new data' }
|
|
71
|
+
mockFetchFunction.mockResolvedValue(newData)
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(result.current.data).toEqual(newData)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useIntl } from 'react-intl'
|
|
3
|
+
import { reportErrorToSentry } from '@commercetools-frontend/sentry'
|
|
4
|
+
import { HttpError } from '../@errors'
|
|
5
|
+
import messages from './messages'
|
|
6
|
+
|
|
7
|
+
export const useFetch = <Data>(
|
|
8
|
+
fetchFunction: () => Promise<Data>,
|
|
9
|
+
config: {
|
|
10
|
+
pollingInterval?: number
|
|
11
|
+
shouldContinuePolling?: (data: Data) => boolean
|
|
12
|
+
} = {}
|
|
13
|
+
) => {
|
|
14
|
+
const intl = useIntl()
|
|
15
|
+
const [data, setData] = React.useState<Data | null>(null)
|
|
16
|
+
const [error, setError] = React.useState<Error | null>(null)
|
|
17
|
+
const [isLoading, setIsLoading] = React.useState(false)
|
|
18
|
+
const [refetchCount, setRefetchCount] = React.useState(0)
|
|
19
|
+
const [lastFetchTime, setLastFetchTime] = React.useState<Date>(new Date())
|
|
20
|
+
|
|
21
|
+
const refetch = React.useCallback(() => {
|
|
22
|
+
setRefetchCount((count) => count + 1)
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
let pollingId: NodeJS.Timeout | null = null
|
|
27
|
+
const fetchData = async () => {
|
|
28
|
+
setIsLoading(true)
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetchFunction()
|
|
31
|
+
setData(response)
|
|
32
|
+
setLastFetchTime(new Date())
|
|
33
|
+
if (
|
|
34
|
+
config.shouldContinuePolling &&
|
|
35
|
+
!config.shouldContinuePolling(response)
|
|
36
|
+
) {
|
|
37
|
+
if (pollingId) {
|
|
38
|
+
clearInterval(pollingId)
|
|
39
|
+
pollingId = null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof HttpError) {
|
|
44
|
+
setError(err)
|
|
45
|
+
} else {
|
|
46
|
+
setError(new Error(intl.formatMessage(messages.unexpectedError)))
|
|
47
|
+
reportErrorToSentry(
|
|
48
|
+
new Error('An unexpected error occurred in the `useFetch` hook'),
|
|
49
|
+
{
|
|
50
|
+
extra: {
|
|
51
|
+
error: err,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
} finally {
|
|
57
|
+
setIsLoading(false)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
fetchData()
|
|
61
|
+
if (config.pollingInterval && config.pollingInterval > 0) {
|
|
62
|
+
pollingId = setInterval(fetchData, config.pollingInterval)
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
if (pollingId) {
|
|
66
|
+
clearInterval(pollingId)
|
|
67
|
+
}
|
|
68
|
+
setError(null)
|
|
69
|
+
setIsLoading(false)
|
|
70
|
+
}
|
|
71
|
+
}, [
|
|
72
|
+
fetchFunction,
|
|
73
|
+
refetchCount,
|
|
74
|
+
intl,
|
|
75
|
+
config.pollingInterval,
|
|
76
|
+
config.shouldContinuePolling,
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
return { data, error, isLoading, refetch, lastFetchTime }
|
|
80
|
+
}
|