@commercetools-frontend-extensions/operations 3.3.0 → 3.5.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 +14 -0
- package/dist/commercetools-frontend-extensions-operations.cjs.dev.js +171 -51
- package/dist/commercetools-frontend-extensions-operations.cjs.prod.js +171 -51
- package/dist/commercetools-frontend-extensions-operations.esm.js +161 -51
- package/dist/declarations/src/@api/file-import-jobs.d.ts +2 -2
- package/dist/declarations/src/@errors/base.d.ts +19 -0
- package/dist/declarations/src/@errors/guards.d.ts +12 -0
- package/dist/declarations/src/@errors/http-error.d.ts +4 -1
- package/dist/declarations/src/@errors/index.d.ts +3 -0
- package/dist/declarations/src/@errors/invalid-response-error.d.ts +4 -1
- package/dist/declarations/src/@errors/no-resources-to-export-error.d.ts +4 -1
- package/dist/declarations/src/@errors/polling-aborted-error.d.ts +4 -1
- package/dist/declarations/src/@errors/polling-timeout-error.d.ts +8 -0
- package/dist/declarations/src/@errors/project-key-not-available-error.d.ts +4 -1
- package/dist/declarations/src/@errors/query-predicate-error.d.ts +5 -2
- package/dist/declarations/src/@errors/unexpected-column-error.d.ts +5 -1
- package/dist/declarations/src/@errors/unexpected-operation-state-error.d.ts +5 -1
- package/dist/declarations/src/@errors/unexpected-resource-type-error.d.ts +5 -1
- package/dist/declarations/src/@hooks/use-fetch-file-import-job.d.ts +0 -1
- package/dist/declarations/src/@hooks/use-file-import-job-upload.d.ts +1 -0
- package/dist/declarations/src/@hooks/use-file-upload.d.ts +1 -0
- package/dist/declarations/src/@types/file-import-job.d.ts +2 -1
- package/package.json +1 -1
- package/src/@api/file-import-jobs.ts +13 -4
- package/src/@api/process-file.ts +0 -1
- package/src/@api/urls.ts +11 -2
- package/src/@errors/base.ts +30 -0
- package/src/@errors/guards.ts +43 -0
- package/src/@errors/http-error.ts +5 -1
- package/src/@errors/index.ts +3 -0
- package/src/@errors/invalid-response-error.ts +6 -1
- package/src/@errors/no-resources-to-export-error.ts +6 -1
- package/src/@errors/polling-aborted-error.ts +6 -1
- package/src/@errors/polling-timeout-error.ts +17 -0
- package/src/@errors/project-key-not-available-error.ts +6 -1
- package/src/@errors/query-predicate-error.ts +6 -2
- package/src/@errors/unexpected-column-error.ts +8 -1
- package/src/@errors/unexpected-operation-state-error.ts +7 -1
- package/src/@errors/unexpected-resource-type-error.ts +8 -1
- package/src/@hooks/use-fetch-file-import-job.spec.ts +0 -7
- package/src/@hooks/use-fetch-file-import-job.ts +0 -1
- package/src/@hooks/use-file-import-job-upload.spec.ts +17 -1
- package/src/@hooks/use-file-import-job-upload.ts +2 -0
- package/src/@hooks/use-file-upload.ts +2 -0
- package/src/@types/file-import-job.ts +2 -1
- package/src/@types/file-upload.ts +1 -1
- package/src/@utils/error-mapping.spec.ts +8 -0
- package/src/@utils/poll-job-until-processing.ts +3 -6
- package/src/@utils/poll-job-until-validated.ts +3 -6
package/package.json
CHANGED
|
@@ -34,12 +34,17 @@ export function createFileImportJob({
|
|
|
34
34
|
importContainerKey,
|
|
35
35
|
payload,
|
|
36
36
|
autoProcess = false,
|
|
37
|
+
operationType,
|
|
37
38
|
onProgress,
|
|
38
39
|
abortSignal,
|
|
39
40
|
}: CreateFileImportJobParameters): Promise<FileImportJob> {
|
|
41
|
+
const urlResourceType = operationType
|
|
42
|
+
? `${resourceType}-${operationType}`
|
|
43
|
+
: resourceType
|
|
44
|
+
|
|
40
45
|
const url = getFileImportJobsURL({
|
|
41
46
|
projectKey,
|
|
42
|
-
resourceType,
|
|
47
|
+
resourceType: urlResourceType,
|
|
43
48
|
importContainerKey,
|
|
44
49
|
})
|
|
45
50
|
|
|
@@ -145,16 +150,20 @@ export async function processFileImportJob({
|
|
|
145
150
|
resourceType,
|
|
146
151
|
importContainerKey,
|
|
147
152
|
jobId,
|
|
148
|
-
|
|
153
|
+
operationType,
|
|
149
154
|
}: ProcessFileImportJobParameters): Promise<ProcessFileImportJobResponse> {
|
|
155
|
+
const urlResourceType = operationType
|
|
156
|
+
? `${resourceType}-${operationType}`
|
|
157
|
+
: resourceType
|
|
158
|
+
|
|
150
159
|
const url = getFileImportJobProcessURL({
|
|
151
160
|
projectKey,
|
|
152
|
-
resourceType,
|
|
161
|
+
resourceType: urlResourceType,
|
|
153
162
|
importContainerKey,
|
|
154
163
|
jobId,
|
|
155
164
|
})
|
|
156
165
|
|
|
157
|
-
const payload =
|
|
166
|
+
const payload = operationType ? { action: operationType } : {}
|
|
158
167
|
|
|
159
168
|
const response = await fetcher<ProcessFileImportJobResponse>({
|
|
160
169
|
url,
|
package/src/@api/process-file.ts
CHANGED
|
@@ -13,7 +13,6 @@ export async function processUploadedFile({
|
|
|
13
13
|
resourceType,
|
|
14
14
|
action,
|
|
15
15
|
}: ProcessUploadedFileRequestParameters): Promise<ProcessFileResponse> {
|
|
16
|
-
// For delete operations with action (like 'delete') -> use different URL and payload structure
|
|
17
16
|
const uri = action
|
|
18
17
|
? getImportContainerTasksURL({ projectKey, importContainerKey })
|
|
19
18
|
: getProccessFileURL({ projectKey, resourceType, importContainerKey })
|
package/src/@api/urls.ts
CHANGED
|
@@ -6,6 +6,15 @@ import type {
|
|
|
6
6
|
ExportOperationQueryParams,
|
|
7
7
|
} from '../@types'
|
|
8
8
|
|
|
9
|
+
const OPERATION_RESOURCE_TYPES = ['product-delete', 'product-unpublish']
|
|
10
|
+
|
|
11
|
+
function pluralizeResourceType(resourceType: string): string {
|
|
12
|
+
if (OPERATION_RESOURCE_TYPES.includes(resourceType)) {
|
|
13
|
+
return resourceType
|
|
14
|
+
}
|
|
15
|
+
return plural(resourceType)
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export function getImportContainersURL({
|
|
10
19
|
projectKey,
|
|
11
20
|
queryParams,
|
|
@@ -126,7 +135,7 @@ export function getFileImportJobsURL({
|
|
|
126
135
|
resourceType: string
|
|
127
136
|
importContainerKey: string
|
|
128
137
|
}): string {
|
|
129
|
-
return `/${projectKey}/${
|
|
138
|
+
return `/${projectKey}/${pluralizeResourceType(
|
|
130
139
|
toImportApiResourceType(resourceType)
|
|
131
140
|
)}/import-containers/${importContainerKey}/file-import-jobs`
|
|
132
141
|
}
|
|
@@ -166,7 +175,7 @@ export function getFileImportJobProcessURL({
|
|
|
166
175
|
importContainerKey: string
|
|
167
176
|
jobId: string
|
|
168
177
|
}): string {
|
|
169
|
-
return `/${projectKey}/${
|
|
178
|
+
return `/${projectKey}/${pluralizeResourceType(
|
|
170
179
|
toImportApiResourceType(resourceType)
|
|
171
180
|
)}/import-containers/${importContainerKey}/file-import-jobs/${jobId}/process`
|
|
172
181
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const ErrorCode = {
|
|
2
|
+
HTTP_ERROR: 'HTTP_ERROR',
|
|
3
|
+
POLLING_TIMEOUT: 'POLLING_TIMEOUT',
|
|
4
|
+
POLLING_ABORTED: 'POLLING_ABORTED',
|
|
5
|
+
|
|
6
|
+
INVALID_RESPONSE: 'INVALID_RESPONSE',
|
|
7
|
+
UNEXPECTED_COLUMN: 'UNEXPECTED_COLUMN',
|
|
8
|
+
QUERY_PREDICATE_ERROR: 'QUERY_PREDICATE_ERROR',
|
|
9
|
+
|
|
10
|
+
UNEXPECTED_OPERATION_STATE: 'UNEXPECTED_OPERATION_STATE',
|
|
11
|
+
UNEXPECTED_RESOURCE_TYPE: 'UNEXPECTED_RESOURCE_TYPE',
|
|
12
|
+
|
|
13
|
+
PROJECT_KEY_NOT_AVAILABLE: 'PROJECT_KEY_NOT_AVAILABLE',
|
|
14
|
+
|
|
15
|
+
NO_RESOURCES_TO_EXPORT: 'NO_RESOURCES_TO_EXPORT',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
export type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode]
|
|
19
|
+
|
|
20
|
+
export abstract class OperationsError extends Error {
|
|
21
|
+
abstract readonly code: ErrorCodeType
|
|
22
|
+
abstract readonly isRetryable: boolean
|
|
23
|
+
|
|
24
|
+
readonly timestamp: number
|
|
25
|
+
|
|
26
|
+
constructor(message: string) {
|
|
27
|
+
super(message)
|
|
28
|
+
this.timestamp = Date.now()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ErrorCode, type ErrorCodeType } from './base'
|
|
2
|
+
|
|
3
|
+
type ErrorWithCode = {
|
|
4
|
+
code: ErrorCodeType
|
|
5
|
+
isRetryable: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function hasErrorCode(error: unknown): error is ErrorWithCode {
|
|
9
|
+
return (
|
|
10
|
+
typeof error === 'object' &&
|
|
11
|
+
error !== null &&
|
|
12
|
+
'code' in error &&
|
|
13
|
+
typeof (error as ErrorWithCode).code === 'string'
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isOperationsError(error: unknown): error is ErrorWithCode {
|
|
18
|
+
return hasErrorCode(error)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isRetryableError(error: unknown): boolean {
|
|
22
|
+
return hasErrorCode(error) && error.isRetryable === true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isNetworkError(error: unknown): boolean {
|
|
26
|
+
return (
|
|
27
|
+
hasErrorCode(error) &&
|
|
28
|
+
(error.code === ErrorCode.HTTP_ERROR ||
|
|
29
|
+
error.code === ErrorCode.POLLING_TIMEOUT)
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isPollingAbortedError(error: unknown): boolean {
|
|
34
|
+
return hasErrorCode(error) && error.code === ErrorCode.POLLING_ABORTED
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isPollingTimeoutError(error: unknown): boolean {
|
|
38
|
+
return hasErrorCode(error) && error.code === ErrorCode.POLLING_TIMEOUT
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isHttpError(error: unknown): boolean {
|
|
42
|
+
return hasErrorCode(error) && error.code === ErrorCode.HTTP_ERROR
|
|
43
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { BasicErrorDataType } from '../@types'
|
|
2
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
3
|
|
|
3
|
-
export class HttpError<T = BasicErrorDataType> extends
|
|
4
|
+
export class HttpError<T = BasicErrorDataType> extends OperationsError {
|
|
5
|
+
readonly code = ErrorCode.HTTP_ERROR
|
|
4
6
|
readonly statusCode: number
|
|
5
7
|
readonly errorData?: T
|
|
8
|
+
readonly isRetryable: boolean
|
|
6
9
|
|
|
7
10
|
constructor(statusCode: number, statusText?: string, errorData?: T) {
|
|
8
11
|
super(
|
|
@@ -13,5 +16,6 @@ export class HttpError<T = BasicErrorDataType> extends Error {
|
|
|
13
16
|
this.name = 'HttpError'
|
|
14
17
|
this.statusCode = statusCode
|
|
15
18
|
this.errorData = errorData
|
|
19
|
+
this.isRetryable = statusCode >= 500 || statusCode === 408
|
|
16
20
|
}
|
|
17
21
|
}
|
package/src/@errors/index.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export * from './base'
|
|
2
|
+
export * from './guards'
|
|
1
3
|
export * from './http-error'
|
|
2
4
|
export * from './invalid-response-error'
|
|
3
5
|
export * from './no-resources-to-export-error'
|
|
4
6
|
export * from './polling-aborted-error'
|
|
7
|
+
export * from './polling-timeout-error'
|
|
5
8
|
export * from './project-key-not-available-error'
|
|
6
9
|
export * from './query-predicate-error'
|
|
7
10
|
export * from './unexpected-column-error'
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class InvalidResponseError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.INVALID_RESPONSE
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
|
|
2
7
|
constructor(message: string) {
|
|
3
8
|
super(message)
|
|
4
9
|
this.name = 'InvalidResponseError'
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class NoResourcesToExportError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.NO_RESOURCES_TO_EXPORT
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
|
|
2
7
|
constructor(message: string = 'There are no resources to export.') {
|
|
3
8
|
super(message)
|
|
4
9
|
this.name = 'NoResourcesToExportError'
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class PollingAbortedError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.POLLING_ABORTED
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
|
|
2
7
|
constructor() {
|
|
3
8
|
super('Polling was aborted')
|
|
4
9
|
this.name = 'PollingAbortedError'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class PollingTimeoutError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.POLLING_TIMEOUT
|
|
5
|
+
readonly isRetryable = true
|
|
6
|
+
readonly maxAttempts: number
|
|
7
|
+
readonly totalTimeSeconds: number
|
|
8
|
+
|
|
9
|
+
constructor(maxAttempts: number, totalTimeSeconds: number) {
|
|
10
|
+
super(
|
|
11
|
+
`Polling timeout after ${maxAttempts} attempts (${totalTimeSeconds}s)`
|
|
12
|
+
)
|
|
13
|
+
this.name = 'PollingTimeoutError'
|
|
14
|
+
this.maxAttempts = maxAttempts
|
|
15
|
+
this.totalTimeSeconds = totalTimeSeconds
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class ProjectKeyNotAvailableError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.PROJECT_KEY_NOT_AVAILABLE
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
|
|
2
7
|
constructor(message: string = 'Project key is not available') {
|
|
3
8
|
super(message)
|
|
4
9
|
this.name = 'ProjectKeyNotAvailableError'
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class QueryPredicateError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.QUERY_PREDICATE_ERROR
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
readonly field = 'queryPredicate'
|
|
3
7
|
|
|
4
8
|
constructor(
|
|
5
9
|
message: string = 'There is an error with the query predicate. Make sure the syntax is correct.'
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class UnexpectedColumnError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.UNEXPECTED_COLUMN
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
readonly columnName: string
|
|
7
|
+
|
|
2
8
|
constructor(columnName: string) {
|
|
3
9
|
super(`Unexpected column "${columnName}"`)
|
|
4
10
|
this.name = 'UnexpectedColumnError'
|
|
11
|
+
this.columnName = columnName
|
|
5
12
|
}
|
|
6
13
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ProcessingState } from '@commercetools/importapi-sdk'
|
|
2
|
+
import { ErrorCode, OperationsError } from './base'
|
|
3
|
+
|
|
4
|
+
export class UnexpectedOperationStateError extends OperationsError {
|
|
5
|
+
readonly code = ErrorCode.UNEXPECTED_OPERATION_STATE
|
|
6
|
+
readonly isRetryable = false
|
|
7
|
+
readonly state: ProcessingState
|
|
2
8
|
|
|
3
|
-
export class UnexpectedOperationStateError extends Error {
|
|
4
9
|
constructor(state: ProcessingState) {
|
|
5
10
|
super(`Unexpected operation state "${state}"`)
|
|
6
11
|
this.name = 'UnexpectedOperationStateError'
|
|
12
|
+
this.state = state
|
|
7
13
|
}
|
|
8
14
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { ErrorCode, OperationsError } from './base'
|
|
2
|
+
|
|
3
|
+
export class UnexpectedResourceTypeError extends OperationsError {
|
|
4
|
+
readonly code = ErrorCode.UNEXPECTED_RESOURCE_TYPE
|
|
5
|
+
readonly isRetryable = false
|
|
6
|
+
readonly resourceType: string
|
|
7
|
+
|
|
2
8
|
constructor(resourceType: string) {
|
|
3
9
|
super(`Unexpected resource type "${resourceType}"`)
|
|
4
10
|
this.name = 'UnexpectedResourceTypeError'
|
|
11
|
+
this.resourceType = resourceType
|
|
5
12
|
}
|
|
6
13
|
}
|
|
@@ -16,7 +16,6 @@ const mockGetFileImportJob = getFileImportJob as jest.MockedFunction<
|
|
|
16
16
|
|
|
17
17
|
describe('useFetchFileImportJob', () => {
|
|
18
18
|
const projectKey = 'test-with-big-data'
|
|
19
|
-
const resourceType = 'product'
|
|
20
19
|
const importContainerKey = 'test-container'
|
|
21
20
|
const jobId = 'test-job-id'
|
|
22
21
|
|
|
@@ -45,7 +44,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
45
44
|
const { result } = renderHook(() =>
|
|
46
45
|
useFetchFileImportJob({
|
|
47
46
|
projectKey,
|
|
48
|
-
resourceType,
|
|
49
47
|
importContainerKey,
|
|
50
48
|
jobId,
|
|
51
49
|
})
|
|
@@ -57,7 +55,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
57
55
|
|
|
58
56
|
expect(mockGetFileImportJob).toHaveBeenCalledWith({
|
|
59
57
|
projectKey,
|
|
60
|
-
resourceType,
|
|
61
58
|
importContainerKey,
|
|
62
59
|
jobId,
|
|
63
60
|
})
|
|
@@ -70,7 +67,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
70
67
|
const { result } = renderHook(() =>
|
|
71
68
|
useFetchFileImportJob({
|
|
72
69
|
projectKey,
|
|
73
|
-
resourceType,
|
|
74
70
|
importContainerKey,
|
|
75
71
|
jobId,
|
|
76
72
|
})
|
|
@@ -92,7 +88,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
92
88
|
const { result } = renderHook(() =>
|
|
93
89
|
useFetchFileImportJob({
|
|
94
90
|
projectKey,
|
|
95
|
-
resourceType,
|
|
96
91
|
importContainerKey,
|
|
97
92
|
jobId,
|
|
98
93
|
pollingInterval,
|
|
@@ -106,7 +101,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
106
101
|
|
|
107
102
|
expect(mockGetFileImportJob).toHaveBeenCalledWith({
|
|
108
103
|
projectKey,
|
|
109
|
-
resourceType,
|
|
110
104
|
importContainerKey,
|
|
111
105
|
jobId,
|
|
112
106
|
})
|
|
@@ -116,7 +110,6 @@ describe('useFetchFileImportJob', () => {
|
|
|
116
110
|
const { result } = renderHook(() =>
|
|
117
111
|
useFetchFileImportJob({
|
|
118
112
|
projectKey: '',
|
|
119
|
-
resourceType,
|
|
120
113
|
importContainerKey,
|
|
121
114
|
jobId,
|
|
122
115
|
})
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
createFileImportJob,
|
|
6
6
|
} from '../@api'
|
|
7
7
|
import { ProjectKeyNotAvailableError } from '../@errors'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
encodeFileNameWithTimestampToContainerKey,
|
|
10
|
+
getFileImportJobFileType,
|
|
11
|
+
} from '../@utils'
|
|
9
12
|
import {
|
|
10
13
|
useFileImportJobUpload,
|
|
11
14
|
UseFileImportJobUploadConfig,
|
|
@@ -28,6 +31,10 @@ const mockEncodeFileNameWithTimestampToContainerKey =
|
|
|
28
31
|
encodeFileNameWithTimestampToContainerKey as jest.MockedFunction<
|
|
29
32
|
typeof encodeFileNameWithTimestampToContainerKey
|
|
30
33
|
>
|
|
34
|
+
const mockGetFileImportJobFileType =
|
|
35
|
+
getFileImportJobFileType as jest.MockedFunction<
|
|
36
|
+
typeof getFileImportJobFileType
|
|
37
|
+
>
|
|
31
38
|
|
|
32
39
|
describe('useFileImportJobUpload', () => {
|
|
33
40
|
const projectKey = 'test-with-big-data'
|
|
@@ -53,6 +60,9 @@ describe('useFileImportJobUpload', () => {
|
|
|
53
60
|
mockEncodeFileNameWithTimestampToContainerKey.mockReturnValue(
|
|
54
61
|
importContainerKey
|
|
55
62
|
)
|
|
63
|
+
mockGetFileImportJobFileType.mockImplementation((resourceType) =>
|
|
64
|
+
resourceType === 'custom-object' ? 'json' : 'csv'
|
|
65
|
+
)
|
|
56
66
|
mockCreateImportContainerForFileUpload.mockResolvedValue({
|
|
57
67
|
key: importContainerKey,
|
|
58
68
|
})
|
|
@@ -112,7 +122,10 @@ describe('useFileImportJobUpload', () => {
|
|
|
112
122
|
fileName: 'test.csv',
|
|
113
123
|
file: mockFile,
|
|
114
124
|
},
|
|
125
|
+
autoProcess: undefined,
|
|
126
|
+
operationType: undefined,
|
|
115
127
|
onProgress: expect.any(Function),
|
|
128
|
+
abortSignal: undefined,
|
|
116
129
|
})
|
|
117
130
|
|
|
118
131
|
expect(onSuccess).toHaveBeenCalledWith('job-123', importContainerKey)
|
|
@@ -147,7 +160,10 @@ describe('useFileImportJobUpload', () => {
|
|
|
147
160
|
fileName: 'test.json',
|
|
148
161
|
file: mockJsonFile,
|
|
149
162
|
},
|
|
163
|
+
autoProcess: undefined,
|
|
164
|
+
operationType: undefined,
|
|
150
165
|
onProgress: expect.any(Function),
|
|
166
|
+
abortSignal: undefined,
|
|
151
167
|
})
|
|
152
168
|
|
|
153
169
|
expect(onSuccess).toHaveBeenCalledWith('job-123', importContainerKey)
|
|
@@ -18,6 +18,7 @@ export type UseFileImportJobUploadConfig = {
|
|
|
18
18
|
resourceType: ResourceTypeId
|
|
19
19
|
settings?: ExtendedImportContainerDraft['settings']
|
|
20
20
|
autoProcess?: boolean
|
|
21
|
+
operationType?: 'delete'
|
|
21
22
|
onSuccess: (jobId: string, importContainerKey: string) => void
|
|
22
23
|
onError?: (error: unknown) => void
|
|
23
24
|
onProgress?: (progress: number) => void
|
|
@@ -66,6 +67,7 @@ export const useFileImportJobUpload = ({
|
|
|
66
67
|
file: config.file,
|
|
67
68
|
},
|
|
68
69
|
autoProcess: config.autoProcess,
|
|
70
|
+
operationType: config.operationType,
|
|
69
71
|
onProgress: (uploadProgress) => {
|
|
70
72
|
setProgress(uploadProgress)
|
|
71
73
|
config.onProgress?.(uploadProgress)
|
|
@@ -22,6 +22,7 @@ export type FileUploadConfig = {
|
|
|
22
22
|
settings?: ExtendedImportContainerDraft['settings']
|
|
23
23
|
autoProcess?: boolean
|
|
24
24
|
skipValidationPolling?: boolean
|
|
25
|
+
operationType?: 'delete'
|
|
25
26
|
onSuccess: (result: FileUploadResult) => void
|
|
26
27
|
onError?: (error: unknown) => void
|
|
27
28
|
onProgress?: (progress: number) => void
|
|
@@ -86,6 +87,7 @@ export const useFileUpload = ({
|
|
|
86
87
|
resourceType: config.resourceType,
|
|
87
88
|
settings: config.settings,
|
|
88
89
|
autoProcess: config.autoProcess,
|
|
90
|
+
operationType: config.operationType,
|
|
89
91
|
abortSignal: config.abortSignal,
|
|
90
92
|
onSuccess: async (jobId, containerKey) => {
|
|
91
93
|
if (config.skipValidationPolling) {
|
|
@@ -57,6 +57,7 @@ export interface CreateFileImportJobParameters {
|
|
|
57
57
|
importContainerKey: string
|
|
58
58
|
payload: CreateFileImportJobPayload
|
|
59
59
|
autoProcess?: boolean
|
|
60
|
+
operationType?: 'delete'
|
|
60
61
|
onProgress?: (progress: number) => void
|
|
61
62
|
abortSignal?: AbortSignal
|
|
62
63
|
}
|
|
@@ -100,7 +101,7 @@ export interface ProcessFileImportJobParameters {
|
|
|
100
101
|
resourceType: string
|
|
101
102
|
importContainerKey: string
|
|
102
103
|
jobId: string
|
|
103
|
-
|
|
104
|
+
operationType?: 'delete'
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export interface ProcessFileImportJobResponse {
|
|
@@ -53,7 +53,7 @@ export interface ProcessUploadedFileRequestParameters {
|
|
|
53
53
|
projectKey: string
|
|
54
54
|
importContainerKey: string
|
|
55
55
|
resourceType: ResourceTypeId
|
|
56
|
-
action?: 'delete'
|
|
56
|
+
action?: 'delete'
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export type ProcessFileResponse = {
|
|
@@ -6,6 +6,8 @@ type RowErrorsResponse = {
|
|
|
6
6
|
errors: Array<{ field: string; code: string; message: string }>
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// Note: Both `row` and `index` are included in the output for backwards compatibility
|
|
10
|
+
// Old flow uses `row`, new flow uses `index`. After migration, `row` will be removed
|
|
9
11
|
describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
10
12
|
it('should return an empty array if uploadFileErrorsResponse is undefined', () => {
|
|
11
13
|
const result = mapUploadFileErrorsResponseToUploadFileErrorRows(undefined)
|
|
@@ -47,6 +49,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
47
49
|
"code": "InvalidField",
|
|
48
50
|
"field": "name.de2",
|
|
49
51
|
"id": "1",
|
|
52
|
+
"index": 1,
|
|
50
53
|
"row": 1,
|
|
51
54
|
"validationMessage": ""name.de2" is not allowed",
|
|
52
55
|
},
|
|
@@ -54,6 +57,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
54
57
|
"code": "InvalidField",
|
|
55
58
|
"field": "name.enn",
|
|
56
59
|
"id": "2",
|
|
60
|
+
"index": 1,
|
|
57
61
|
"row": 1,
|
|
58
62
|
"validationMessage": ""name.enn" is not allowed",
|
|
59
63
|
},
|
|
@@ -61,6 +65,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
61
65
|
"code": "InvalidField",
|
|
62
66
|
"field": "slug.RU",
|
|
63
67
|
"id": "3",
|
|
68
|
+
"index": 2,
|
|
64
69
|
"row": 2,
|
|
65
70
|
"validationMessage": ""slug.RU" is not allowed",
|
|
66
71
|
},
|
|
@@ -104,6 +109,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
104
109
|
"field": "name.de2",
|
|
105
110
|
"id": "1",
|
|
106
111
|
"index": 0,
|
|
112
|
+
"row": 0,
|
|
107
113
|
"validationMessage": ""name.de2" is not allowed",
|
|
108
114
|
},
|
|
109
115
|
{
|
|
@@ -111,6 +117,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
111
117
|
"field": "name.enn",
|
|
112
118
|
"id": "2",
|
|
113
119
|
"index": 0,
|
|
120
|
+
"row": 0,
|
|
114
121
|
"validationMessage": ""name.enn" is not allowed",
|
|
115
122
|
},
|
|
116
123
|
{
|
|
@@ -118,6 +125,7 @@ describe('mapUploadFileErrorsResponseToUploadFileErrorRows', () => {
|
|
|
118
125
|
"field": "slug.RU",
|
|
119
126
|
"id": "3",
|
|
120
127
|
"index": 1,
|
|
128
|
+
"row": 1,
|
|
121
129
|
"validationMessage": ""slug.RU" is not allowed",
|
|
122
130
|
},
|
|
123
131
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getFileImportJob } from '../@api'
|
|
2
|
-
import { PollingAbortedError } from '../@errors'
|
|
2
|
+
import { PollingAbortedError, PollingTimeoutError } from '../@errors'
|
|
3
3
|
import type { FileImportJob } from '../@types'
|
|
4
4
|
import { hasImportJobStartedProcessing } from './file-import-job-helpers'
|
|
5
5
|
|
|
@@ -64,9 +64,6 @@ export const pollJobUntilProcessing = async ({
|
|
|
64
64
|
attempts++
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
(maxAttempts * pollingInterval) / 1000
|
|
70
|
-
}s)`
|
|
71
|
-
)
|
|
67
|
+
const totalTimeSeconds = (maxAttempts * pollingInterval) / 1000
|
|
68
|
+
throw new PollingTimeoutError(maxAttempts, totalTimeSeconds)
|
|
72
69
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getFileImportJob } from '../@api'
|
|
2
|
-
import { PollingAbortedError } from '../@errors'
|
|
2
|
+
import { PollingAbortedError, PollingTimeoutError } from '../@errors'
|
|
3
3
|
import type { FileImportJob } from '../@types'
|
|
4
4
|
import { isImportJobTerminal } from './file-import-job-helpers'
|
|
5
5
|
|
|
@@ -68,9 +68,6 @@ export const pollJobUntilValidated = async ({
|
|
|
68
68
|
attempts++
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
(maxAttempts * pollingInterval) / 1000
|
|
74
|
-
}s)`
|
|
75
|
-
)
|
|
71
|
+
const totalTimeSeconds = (maxAttempts * pollingInterval) / 1000
|
|
72
|
+
throw new PollingTimeoutError(maxAttempts, totalTimeSeconds)
|
|
76
73
|
}
|