@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commercetools-frontend-extensions-operations.cjs.dev.js +171 -51
  3. package/dist/commercetools-frontend-extensions-operations.cjs.prod.js +171 -51
  4. package/dist/commercetools-frontend-extensions-operations.esm.js +161 -51
  5. package/dist/declarations/src/@api/file-import-jobs.d.ts +2 -2
  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-fetch-file-import-job.d.ts +0 -1
  20. package/dist/declarations/src/@hooks/use-file-import-job-upload.d.ts +1 -0
  21. package/dist/declarations/src/@hooks/use-file-upload.d.ts +1 -0
  22. package/dist/declarations/src/@types/file-import-job.d.ts +2 -1
  23. package/package.json +1 -1
  24. package/src/@api/file-import-jobs.ts +13 -4
  25. package/src/@api/process-file.ts +0 -1
  26. package/src/@api/urls.ts +11 -2
  27. package/src/@errors/base.ts +30 -0
  28. package/src/@errors/guards.ts +43 -0
  29. package/src/@errors/http-error.ts +5 -1
  30. package/src/@errors/index.ts +3 -0
  31. package/src/@errors/invalid-response-error.ts +6 -1
  32. package/src/@errors/no-resources-to-export-error.ts +6 -1
  33. package/src/@errors/polling-aborted-error.ts +6 -1
  34. package/src/@errors/polling-timeout-error.ts +17 -0
  35. package/src/@errors/project-key-not-available-error.ts +6 -1
  36. package/src/@errors/query-predicate-error.ts +6 -2
  37. package/src/@errors/unexpected-column-error.ts +8 -1
  38. package/src/@errors/unexpected-operation-state-error.ts +7 -1
  39. package/src/@errors/unexpected-resource-type-error.ts +8 -1
  40. package/src/@hooks/use-fetch-file-import-job.spec.ts +0 -7
  41. package/src/@hooks/use-fetch-file-import-job.ts +0 -1
  42. package/src/@hooks/use-file-import-job-upload.spec.ts +17 -1
  43. package/src/@hooks/use-file-import-job-upload.ts +2 -0
  44. package/src/@hooks/use-file-upload.ts +2 -0
  45. package/src/@types/file-import-job.ts +2 -1
  46. package/src/@types/file-upload.ts +1 -1
  47. package/src/@utils/error-mapping.spec.ts +8 -0
  48. package/src/@utils/poll-job-until-processing.ts +3 -6
  49. package/src/@utils/poll-job-until-validated.ts +3 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend-extensions/operations",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "license": "Proprietary",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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
- action,
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 = action ? { action } : {}
166
+ const payload = operationType ? { action: operationType } : {}
158
167
 
159
168
  const response = await fetcher<ProcessFileImportJobResponse>({
160
169
  url,
@@ -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}/${plural(
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}/${plural(
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 Error {
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
  }
@@ -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
- 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
  }
@@ -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
  })
@@ -6,7 +6,6 @@ import { useFetch } from './use-fetch'
6
6
 
7
7
  type UseFetchFileImportJobConfig = {
8
8
  projectKey: string
9
- resourceType: string
10
9
  importContainerKey: string
11
10
  jobId: string
12
11
  pollingInterval?: number
@@ -5,7 +5,10 @@ import {
5
5
  createFileImportJob,
6
6
  } from '../@api'
7
7
  import { ProjectKeyNotAvailableError } from '../@errors'
8
- import { encodeFileNameWithTimestampToContainerKey } from '../@utils'
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
- action?: 'delete'
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' // Currently only 'delete' action is supported
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
- 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
  }