@commercetools-frontend-extensions/operations 3.4.0 → 3.6.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +2 -0
  3. package/dist/commercetools-frontend-extensions-operations.cjs.dev.js +153 -42
  4. package/dist/commercetools-frontend-extensions-operations.cjs.prod.js +153 -42
  5. package/dist/commercetools-frontend-extensions-operations.esm.js +144 -43
  6. package/dist/declarations/src/@errors/base.d.ts +19 -0
  7. package/dist/declarations/src/@errors/guards.d.ts +12 -0
  8. package/dist/declarations/src/@errors/http-error.d.ts +4 -1
  9. package/dist/declarations/src/@errors/index.d.ts +3 -0
  10. package/dist/declarations/src/@errors/invalid-response-error.d.ts +4 -1
  11. package/dist/declarations/src/@errors/no-resources-to-export-error.d.ts +4 -1
  12. package/dist/declarations/src/@errors/polling-aborted-error.d.ts +4 -1
  13. package/dist/declarations/src/@errors/polling-timeout-error.d.ts +8 -0
  14. package/dist/declarations/src/@errors/project-key-not-available-error.d.ts +4 -1
  15. package/dist/declarations/src/@errors/query-predicate-error.d.ts +5 -2
  16. package/dist/declarations/src/@errors/unexpected-column-error.d.ts +5 -1
  17. package/dist/declarations/src/@errors/unexpected-operation-state-error.d.ts +5 -1
  18. package/dist/declarations/src/@errors/unexpected-resource-type-error.d.ts +5 -1
  19. package/dist/declarations/src/@hooks/use-file-import-job-upload.d.ts +1 -0
  20. package/dist/declarations/src/@hooks/use-file-upload.d.ts +1 -0
  21. package/package.json +1 -1
  22. package/src/@errors/base.ts +30 -0
  23. package/src/@errors/guards.ts +43 -0
  24. package/src/@errors/http-error.ts +5 -1
  25. package/src/@errors/index.ts +3 -0
  26. package/src/@errors/invalid-response-error.ts +6 -1
  27. package/src/@errors/no-resources-to-export-error.ts +6 -1
  28. package/src/@errors/polling-aborted-error.ts +6 -1
  29. package/src/@errors/polling-timeout-error.ts +17 -0
  30. package/src/@errors/project-key-not-available-error.ts +6 -1
  31. package/src/@errors/query-predicate-error.ts +6 -2
  32. package/src/@errors/unexpected-column-error.ts +8 -1
  33. package/src/@errors/unexpected-operation-state-error.ts +7 -1
  34. package/src/@errors/unexpected-resource-type-error.ts +8 -1
  35. package/src/@hooks/use-file-import-job-upload.spec.ts +49 -0
  36. package/src/@hooks/use-file-import-job-upload.ts +5 -1
  37. package/src/@hooks/use-file-upload.ts +2 -0
  38. package/src/@utils/poll-job-until-processing.ts +3 -6
  39. package/src/@utils/poll-job-until-validated.ts +3 -6
@@ -1,4 +1,9 @@
1
- export class InvalidResponseError extends Error {
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
- export class NoResourcesToExportError extends Error {
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
- export class PollingAbortedError extends Error {
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
- export class ProjectKeyNotAvailableError extends Error {
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
- export class QueryPredicateError extends Error {
2
- field = 'queryPredicate'
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
- export class UnexpectedColumnError extends Error {
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
- export class UnexpectedResourceTypeError extends Error {
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
  }
@@ -262,6 +262,55 @@ describe('useFileImportJobUpload', () => {
262
262
  expect(result.current.progress).toBe(100)
263
263
  })
264
264
 
265
+ it('should use explicit fileType override instead of deriving from resourceType', async () => {
266
+ const onSuccess = jest.fn()
267
+
268
+ const { result } = renderHook(() => useFileImportJobUpload({ projectKey }))
269
+
270
+ await act(async () => {
271
+ await result.current.upload({
272
+ file: mockFile,
273
+ resourceType: 'product',
274
+ fileType: 'json',
275
+ onSuccess,
276
+ })
277
+ })
278
+
279
+ expect(mockCreateFileImportJob).toHaveBeenCalledWith(
280
+ expect.objectContaining({
281
+ payload: expect.objectContaining({
282
+ fileType: 'json',
283
+ }),
284
+ })
285
+ )
286
+
287
+ expect(mockGetFileImportJobFileType).not.toHaveBeenCalled()
288
+ expect(onSuccess).toHaveBeenCalledWith('job-123', importContainerKey)
289
+ })
290
+
291
+ it('should fall back to derived fileType when fileType override is not provided', async () => {
292
+ const onSuccess = jest.fn()
293
+
294
+ const { result } = renderHook(() => useFileImportJobUpload({ projectKey }))
295
+
296
+ await act(async () => {
297
+ await result.current.upload({
298
+ file: mockFile,
299
+ resourceType: 'product',
300
+ onSuccess,
301
+ })
302
+ })
303
+
304
+ expect(mockGetFileImportJobFileType).toHaveBeenCalledWith('product')
305
+ expect(mockCreateFileImportJob).toHaveBeenCalledWith(
306
+ expect.objectContaining({
307
+ payload: expect.objectContaining({
308
+ fileType: 'csv',
309
+ }),
310
+ })
311
+ )
312
+ })
313
+
265
314
  it('should work without optional settings', async () => {
266
315
  const onSuccess = jest.fn()
267
316
 
@@ -16,6 +16,7 @@ import type { ExtendedImportContainerDraft } from '../@types'
16
16
  export type UseFileImportJobUploadConfig = {
17
17
  file: File
18
18
  resourceType: ResourceTypeId
19
+ fileType?: 'csv' | 'json'
19
20
  settings?: ExtendedImportContainerDraft['settings']
20
21
  autoProcess?: boolean
21
22
  operationType?: 'delete'
@@ -57,12 +58,15 @@ export const useFileImportJobUpload = ({
57
58
  projectKey,
58
59
  })
59
60
 
61
+ const fileType =
62
+ config.fileType ?? getFileImportJobFileType(config.resourceType)
63
+
60
64
  const jobResponse = await createFileImportJob({
61
65
  projectKey,
62
66
  resourceType: config.resourceType,
63
67
  importContainerKey,
64
68
  payload: {
65
- fileType: getFileImportJobFileType(config.resourceType),
69
+ fileType,
66
70
  fileName: config.file.name,
67
71
  file: config.file,
68
72
  },
@@ -19,6 +19,7 @@ export type ValidationProgress = {
19
19
  export type FileUploadConfig = {
20
20
  file: File
21
21
  resourceType: ResourceTypeId
22
+ fileType?: 'csv' | 'json'
22
23
  settings?: ExtendedImportContainerDraft['settings']
23
24
  autoProcess?: boolean
24
25
  skipValidationPolling?: boolean
@@ -85,6 +86,7 @@ export const useFileUpload = ({
85
86
  await jobUpload.upload({
86
87
  file: config.file,
87
88
  resourceType: config.resourceType,
89
+ fileType: config.fileType,
88
90
  settings: config.settings,
89
91
  autoProcess: config.autoProcess,
90
92
  operationType: config.operationType,
@@ -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
- throw new Error(
68
- `Job did not start processing after ${maxAttempts} attempts (${
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
- throw new Error(
72
- `Job validation timeout after ${maxAttempts} attempts (${
73
- (maxAttempts * pollingInterval) / 1000
74
- }s)`
75
- )
71
+ const totalTimeSeconds = (maxAttempts * pollingInterval) / 1000
72
+ throw new PollingTimeoutError(maxAttempts, totalTimeSeconds)
76
73
  }