@devrev/ts-adaas 1.19.8 → 1.19.9-beta.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 (50) hide show
  1. package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -1
  2. package/dist/attachments-streaming/attachments-streaming-pool.js +4 -6
  3. package/dist/attachments-streaming/attachments-streaming-pool.test.js +0 -65
  4. package/dist/common/control-protocol.d.ts +3 -2
  5. package/dist/common/control-protocol.d.ts.map +1 -1
  6. package/dist/common/control-protocol.js +2 -4
  7. package/dist/common/control-protocol.test.d.ts +2 -0
  8. package/dist/common/control-protocol.test.d.ts.map +1 -0
  9. package/dist/common/control-protocol.test.js +126 -0
  10. package/dist/http/axios-client-internal.d.ts.map +1 -1
  11. package/dist/http/axios-client-internal.js +19 -1
  12. package/dist/mock-server/mock-server.d.ts +4 -0
  13. package/dist/mock-server/mock-server.d.ts.map +1 -1
  14. package/dist/mock-server/mock-server.interfaces.d.ts +9 -4
  15. package/dist/mock-server/mock-server.interfaces.d.ts.map +1 -1
  16. package/dist/mock-server/mock-server.js +30 -18
  17. package/dist/multithreading/spawn/spawn.d.ts +1 -1
  18. package/dist/multithreading/spawn/spawn.d.ts.map +1 -1
  19. package/dist/multithreading/spawn/spawn.js +11 -6
  20. package/dist/multithreading/spawn/spawn.test.js +1 -0
  21. package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +1 -1
  22. package/dist/multithreading/worker-adapter/worker-adapter.d.ts +11 -5
  23. package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -1
  24. package/dist/multithreading/worker-adapter/worker-adapter.emit.test.js +90 -9
  25. package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.js +1 -1
  26. package/dist/multithreading/worker-adapter/worker-adapter.js +93 -64
  27. package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts +2 -0
  28. package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts.map +1 -0
  29. package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.js +135 -0
  30. package/dist/repo/repo.d.ts +11 -2
  31. package/dist/repo/repo.d.ts.map +1 -1
  32. package/dist/repo/repo.interfaces.d.ts +2 -0
  33. package/dist/repo/repo.interfaces.d.ts.map +1 -1
  34. package/dist/repo/repo.js +37 -11
  35. package/dist/repo/repo.test.js +184 -0
  36. package/dist/types/extraction.d.ts +8 -1
  37. package/dist/types/extraction.d.ts.map +1 -1
  38. package/dist/types/workers.d.ts +4 -0
  39. package/dist/types/workers.d.ts.map +1 -1
  40. package/dist/uploader/uploader.d.ts.map +1 -1
  41. package/dist/uploader/uploader.helpers.d.ts +6 -1
  42. package/dist/uploader/uploader.helpers.d.ts.map +1 -1
  43. package/dist/uploader/uploader.helpers.js +65 -0
  44. package/dist/uploader/uploader.helpers.test.js +148 -7
  45. package/dist/uploader/uploader.interfaces.d.ts +11 -0
  46. package/dist/uploader/uploader.interfaces.d.ts.map +1 -1
  47. package/dist/uploader/uploader.interfaces.js +11 -0
  48. package/dist/uploader/uploader.js +2 -5
  49. package/dist/uploader/uploader.test.js +54 -3
  50. package/package.json +1 -1
@@ -43,6 +43,7 @@ function makeAdapter(eventType = types_1.EventType.StartExtractingData) {
43
43
  const adapter = new worker_adapter_1.WorkerAdapter({ event, adapterState });
44
44
  return { adapter, event, adapterState };
45
45
  }
46
+ const iso = (ms) => new Date(ms).toISOString();
46
47
  describe(`${worker_adapter_1.WorkerAdapter.name}.emit`, () => {
47
48
  let adapter;
48
49
  let mockPostMessage;
@@ -113,19 +114,25 @@ describe(`${worker_adapter_1.WorkerAdapter.name}.emit`, () => {
113
114
  // Assert
114
115
  expect(mockPostMessage).toHaveBeenCalledTimes(1);
115
116
  });
116
- it('should correctly emit one event even if uploadAllRepos errors', async () => {
117
- // Arrange
117
+ it('should emit phase extraction error when uploadAllRepos fails', async () => {
118
+ const { emit: mockEmit } = require('../../common/control-protocol');
118
119
  adapter['adapterState'].postState = jest.fn().mockResolvedValue(undefined);
119
120
  adapter.uploadAllRepos = jest
120
121
  .fn()
121
122
  .mockRejectedValue(new Error('uploadAllRepos error'));
122
- // Act
123
- await adapter.emit(types_1.ExtractorEventType.MetadataExtractionError, {
124
- reports: [],
125
- processed_files: [],
126
- });
127
- // Assert
128
- expect(mockPostMessage).toHaveBeenCalledTimes(1);
123
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionDone);
124
+ expect(mockEmit).toHaveBeenCalledWith(expect.objectContaining({
125
+ eventType: types_1.ExtractorEventType.DataExtractionError,
126
+ data: expect.objectContaining({
127
+ error: expect.objectContaining({
128
+ message: expect.stringContaining('uploadAllRepos error'),
129
+ }),
130
+ }),
131
+ }));
132
+ expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({
133
+ subject: 'emit',
134
+ payload: { eventType: types_1.ExtractorEventType.DataExtractionError },
135
+ }));
129
136
  });
130
137
  it('should include artifacts in data for extraction events', async () => {
131
138
  // Arrange
@@ -262,6 +269,80 @@ describe(`${worker_adapter_1.WorkerAdapter.name}.emit`, () => {
262
269
  expect(emittedMessage.startsWith('E'.repeat(100))).toBe(true);
263
270
  });
264
271
  });
272
+ describe(`${worker_adapter_1.WorkerAdapter.name}.emit — worker_metadata`, () => {
273
+ let adapter;
274
+ beforeEach(() => {
275
+ jest.clearAllMocks();
276
+ ({ adapter } = makeAdapter());
277
+ adapter['adapterState'].postState = jest.fn().mockResolvedValue(undefined);
278
+ adapter.uploadAllRepos = jest.fn().mockResolvedValue(undefined);
279
+ });
280
+ function setupRepos(adapterInstance, lastExtractedItemType) {
281
+ adapterInstance['repos'] = [
282
+ {
283
+ itemType: 'issues',
284
+ dateRanges: {
285
+ creationDate: { oldest: 100, newest: 200 },
286
+ modifiedDate: { oldest: 150, newest: 250 },
287
+ },
288
+ },
289
+ {
290
+ itemType: 'tasks',
291
+ dateRanges: {
292
+ creationDate: { oldest: 300, newest: 400 },
293
+ modifiedDate: { oldest: 350, newest: 450 },
294
+ },
295
+ },
296
+ ];
297
+ adapterInstance['lastExtractedItemType'] = lastExtractedItemType;
298
+ }
299
+ it('should emit flat worker_metadata for the latest extracted item type only', async () => {
300
+ setupRepos(adapter, 'tasks');
301
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionProgress);
302
+ const { emit: mockEmit } = require('../../common/control-protocol');
303
+ expect(mockEmit.mock.calls[0][0].worker_metadata).toEqual({
304
+ item_type: 'tasks',
305
+ oldest_created_date: iso(300),
306
+ newest_created_date: iso(400),
307
+ oldest_modified_date: iso(350),
308
+ newest_modified_date: iso(450),
309
+ });
310
+ });
311
+ it('should omit unset RFC3339 bounds from worker_metadata', async () => {
312
+ adapter['repos'] = [
313
+ {
314
+ itemType: 'tasks',
315
+ dateRanges: {
316
+ creationDate: { oldest: 0, newest: 0 },
317
+ modifiedDate: { oldest: 0, newest: 0 },
318
+ },
319
+ },
320
+ ];
321
+ adapter['lastExtractedItemType'] = 'tasks';
322
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionProgress);
323
+ const { emit: mockEmit } = require('../../common/control-protocol');
324
+ expect(mockEmit.mock.calls[0][0].worker_metadata).toEqual({
325
+ item_type: 'tasks',
326
+ });
327
+ });
328
+ it('should send empty worker_metadata when no item type has been extracted', async () => {
329
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionProgress);
330
+ const { emit: mockEmit } = require('../../common/control-protocol');
331
+ expect(mockEmit.mock.calls[0][0].worker_metadata).toEqual({});
332
+ });
333
+ it('should send empty worker_metadata for loader events', async () => {
334
+ setupRepos(adapter, 'tasks');
335
+ await adapter.emit(types_1.LoaderEventType.DataLoadingProgress);
336
+ const { emit: mockEmit } = require('../../common/control-protocol');
337
+ expect(mockEmit.mock.calls[0][0].worker_metadata).toEqual({});
338
+ });
339
+ it('should send empty worker_metadata for non-progress extraction events', async () => {
340
+ setupRepos(adapter, 'tasks');
341
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionError);
342
+ const { emit: mockEmit } = require('../../common/control-protocol');
343
+ expect(mockEmit.mock.calls[0][0].worker_metadata).toEqual({});
344
+ });
345
+ });
265
346
  describe(`${worker_adapter_1.WorkerAdapter.name}.emit — ExternalSyncUnitExtractionDone legacy path`, () => {
266
347
  it('should upload ESUs via a repo and strip external_sync_units from the emitted payload', async () => {
267
348
  // Arrange
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const attachments_streaming_pool_1 = require("../../attachments-streaming/attachments-streaming-pool");
4
+ const test_utils_1 = require("../../common/test-utils");
4
5
  const state_1 = require("../../state/state");
5
6
  const jest_setup_1 = require("../../tests/jest.setup");
6
- const test_utils_1 = require("../../common/test-utils");
7
7
  const types_1 = require("../../types");
8
8
  const worker_adapter_1 = require("./worker-adapter");
9
9
  /* eslint-disable @typescript-eslint/no-require-imports */
@@ -22,6 +22,13 @@ const workers_1 = require("../../types/workers");
22
22
  const uploader_1 = require("../../uploader/uploader");
23
23
  const event_type_translation_1 = require("../../common/event-type-translation");
24
24
  const helpers_1 = require("../../common/helpers");
25
+ const spawn_helpers_1 = require("../spawn/spawn.helpers");
26
+ function toRfc3339Timestamp(ms) {
27
+ if (!Number.isFinite(ms) || ms === 0) {
28
+ return undefined;
29
+ }
30
+ return new Date(ms).toISOString();
31
+ }
25
32
  function createWorkerAdapter({ event, adapterState, options, }) {
26
33
  return new WorkerAdapter({
27
34
  event,
@@ -46,10 +53,6 @@ function createWorkerAdapter({ event, adapterState, options, }) {
46
53
  */
47
54
  class WorkerAdapter {
48
55
  constructor({ event, adapterState, options, }) {
49
- this._isTimeout = false;
50
- this.timeoutSignal = new Promise((resolve) => {
51
- this.resolveTimeoutSignal = resolve;
52
- });
53
56
  this.repos = [];
54
57
  this.currentEventDataLength = 0;
55
58
  this.event = event;
@@ -57,6 +60,7 @@ class WorkerAdapter {
57
60
  this.adapterState = adapterState;
58
61
  this._artifacts = [];
59
62
  this.hasWorkerEmitted = false;
63
+ this.isTimeout = false;
60
64
  // Loader
61
65
  this.loaderReports = [];
62
66
  this._processedFiles = [];
@@ -69,15 +73,6 @@ class WorkerAdapter {
69
73
  options,
70
74
  });
71
75
  }
72
- get isTimeout() {
73
- return this._isTimeout;
74
- }
75
- set isTimeout(value) {
76
- this._isTimeout = value;
77
- if (value) {
78
- this.resolveTimeoutSignal();
79
- }
80
- }
81
76
  get state() {
82
77
  return this.adapterState.state;
83
78
  }
@@ -114,6 +109,7 @@ class WorkerAdapter {
114
109
  repo.itemType !== constants_1.SSOR_ATTACHMENT;
115
110
  return new repo_1.Repo(Object.assign(Object.assign({ event: this.event, itemType: repo.itemType }, (shouldNormalize && { normalize: repo.normalize })), { onUpload: (artifact) => {
116
111
  var _a;
112
+ this.lastExtractedItemType = repo.itemType;
117
113
  // We need to store artifacts ids in state for later use when streaming attachments
118
114
  if (repo.itemType === constants_1.AirSyncDefaultItemTypes.ATTACHMENTS) {
119
115
  (_a = this.state.toDevRev) === null || _a === void 0 ? void 0 : _a.attachmentsMetadata.artifactIds.push(artifact.id);
@@ -158,7 +154,7 @@ class WorkerAdapter {
158
154
  */
159
155
  async emit(newEventType, data) {
160
156
  return (0, logger_context_1.runWithSdkLogContext)(async () => {
161
- var _a, _b;
157
+ var _a;
162
158
  newEventType = (0, event_type_translation_1.translateOutgoingEventType)(newEventType);
163
159
  if (this.hasWorkerEmitted) {
164
160
  console.warn(`Trying to emit event with event type: ${newEventType}. Ignoring emit request because it has already been emitted.`);
@@ -190,8 +186,13 @@ class WorkerAdapter {
190
186
  }
191
187
  catch (error) {
192
188
  console.error('Error while uploading repos', error);
193
- node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
194
- this.hasWorkerEmitted = true;
189
+ for (const repo of this.repos) {
190
+ this.artifacts.push(...repo.uploadedArtifacts);
191
+ }
192
+ const { eventType } = (0, spawn_helpers_1.getTimeoutErrorEventType)(this.event.payload.event_type);
193
+ await this.emitToPlatform(eventType, {
194
+ error: { message: (0, logger_1.serializeError)(error) },
195
+ });
195
196
  return;
196
197
  }
197
198
  // If the extraction is done, we want to save the timestamp of the last successful sync
@@ -219,55 +220,87 @@ class WorkerAdapter {
219
220
  this.state.workersNewest = extractionEnd;
220
221
  }
221
222
  }
222
- // We want to save the state every time we emit an event, except for the start and delete events
223
- if (!constants_1.STATELESS_EVENT_TYPES.includes(this.event.payload.event_type)) {
224
- console.log(`Saving state before emitting event with event type: ${newEventType}.`);
225
- try {
226
- await this.adapterState.postState(this.state);
227
- }
228
- catch (error) {
229
- console.error('Error while posting state', error);
230
- node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
231
- this.hasWorkerEmitted = true;
232
- return;
233
- }
234
- }
235
- try {
236
- // Always prune error messages to make them shorter before emit
237
- if ((_b = data === null || data === void 0 ? void 0 : data.error) === null || _b === void 0 ? void 0 : _b.message) {
238
- data.error.message = (0, helpers_1.truncateMessage)(data.error.message);
239
- }
240
- const isExtractionEvent = Object.values(extraction_1.ExtractorEventType).includes(newEventType);
241
- const isLoaderEvent = Object.values(loading_1.LoaderEventType).includes(newEventType);
242
- await (0, control_protocol_1.emit)({
243
- eventType: newEventType,
244
- event: this.event,
245
- data: Object.assign(Object.assign(Object.assign({}, data), (isExtractionEvent ? { artifacts: this.artifacts } : {})), (isLoaderEvent
246
- ? { reports: this.reports, processed_files: this.processedFiles }
247
- : {})),
248
- });
249
- const message = {
250
- subject: workers_1.WorkerMessageSubject.WorkerMessageEmitted,
251
- payload: { eventType: newEventType },
252
- };
253
- this.artifacts = [];
254
- node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(message);
255
- this.hasWorkerEmitted = true;
256
- }
257
- catch (error) {
258
- console.error(`Error while emitting event with event type: ${newEventType}.`, (0, logger_1.serializeError)(error));
259
- node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
260
- this.hasWorkerEmitted = true;
261
- }
223
+ await this.emitToPlatform(newEventType, data);
262
224
  });
263
225
  }
264
226
  async uploadAllRepos() {
265
227
  for (const repo of this.repos) {
266
- const error = await repo.upload();
228
+ await repo.upload();
267
229
  this.artifacts.push(...repo.uploadedArtifacts);
268
- if (error) {
269
- throw error;
270
- }
230
+ }
231
+ }
232
+ /**
233
+ * Persists adapter state before emitting, except for stateless worker phases.
234
+ */
235
+ async postStateBeforeEmit(eventType) {
236
+ if (constants_1.STATELESS_EVENT_TYPES.includes(this.event.payload.event_type)) {
237
+ return true;
238
+ }
239
+ console.log(`Saving state before emitting event with event type: ${eventType}.`);
240
+ try {
241
+ await this.adapterState.postState(this.state);
242
+ return true;
243
+ }
244
+ catch (error) {
245
+ console.error('Error while posting state', error);
246
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
247
+ this.hasWorkerEmitted = true;
248
+ return false;
249
+ }
250
+ }
251
+ /**
252
+ * Posts state (when required), emits to the platform, and notifies the parent
253
+ * worker that an event was emitted.
254
+ */
255
+ async emitToPlatform(eventType, data) {
256
+ var _a;
257
+ const posted = await this.postStateBeforeEmit(eventType);
258
+ if (!posted) {
259
+ return;
260
+ }
261
+ try {
262
+ if ((_a = data === null || data === void 0 ? void 0 : data.error) === null || _a === void 0 ? void 0 : _a.message) {
263
+ data.error.message = (0, helpers_1.truncateMessage)(data.error.message);
264
+ }
265
+ const isExtractionEvent = Object.values(extraction_1.ExtractorEventType).includes(eventType);
266
+ const isLoaderEvent = Object.values(loading_1.LoaderEventType).includes(eventType);
267
+ const progress_data = {};
268
+ if (isExtractionEvent &&
269
+ (eventType == extraction_1.ExtractorEventType.DataExtractionDone ||
270
+ eventType == extraction_1.ExtractorEventType.DataExtractionProgress ||
271
+ eventType == extraction_1.ExtractorEventType.AttachmentExtractionDone ||
272
+ eventType == extraction_1.ExtractorEventType.AttachmentExtractionProgress)) {
273
+ const repo = this.lastExtractedItemType
274
+ ? this.repos.find((r) => r.itemType === this.lastExtractedItemType)
275
+ : undefined;
276
+ if (repo) {
277
+ progress_data.item_type = repo.itemType;
278
+ progress_data.newest_created_date = toRfc3339Timestamp(repo.dateRanges.creationDate.newest);
279
+ progress_data.oldest_created_date = toRfc3339Timestamp(repo.dateRanges.creationDate.oldest);
280
+ progress_data.newest_modified_date = toRfc3339Timestamp(repo.dateRanges.modifiedDate.newest);
281
+ progress_data.oldest_modified_date = toRfc3339Timestamp(repo.dateRanges.modifiedDate.oldest);
282
+ }
283
+ }
284
+ await (0, control_protocol_1.emit)({
285
+ eventType,
286
+ event: this.event,
287
+ data: Object.assign(Object.assign(Object.assign({}, data), (isExtractionEvent ? { artifacts: this.artifacts } : {})), (isLoaderEvent
288
+ ? { reports: this.reports, processed_files: this.processedFiles }
289
+ : {})),
290
+ worker_metadata: Object.assign({}, progress_data),
291
+ });
292
+ const message = {
293
+ subject: workers_1.WorkerMessageSubject.WorkerMessageEmitted,
294
+ payload: { eventType },
295
+ };
296
+ this.artifacts = [];
297
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(message);
298
+ this.hasWorkerEmitted = true;
299
+ }
300
+ catch (error) {
301
+ console.error(`Error while emitting event with event type: ${eventType}.`, (0, logger_1.serializeError)(error));
302
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
303
+ this.hasWorkerEmitted = true;
271
304
  }
272
305
  }
273
306
  async loadItemTypes({ itemTypesToLoad, }) {
@@ -703,10 +736,6 @@ class WorkerAdapter {
703
736
  else if (attachment.inline === false) {
704
737
  ssorAttachment.inline = false;
705
738
  }
706
- if (this.isTimeout) {
707
- this.destroyHttpStream(httpStream);
708
- return;
709
- }
710
739
  await ((_c = this.getRepo('ssor_attachment')) === null || _c === void 0 ? void 0 : _c.push([ssorAttachment]));
711
740
  return;
712
741
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker-adapter.upload-failure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-adapter.upload-failure.test.d.ts","sourceRoot":"","sources":["../../../src/multithreading/worker-adapter/worker-adapter.upload-failure.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const state_1 = require("../../state/state");
4
+ const jest_setup_1 = require("../../tests/jest.setup");
5
+ const test_helpers_1 = require("../../tests/test-helpers");
6
+ const test_utils_1 = require("../../common/test-utils");
7
+ const types_1 = require("../../types");
8
+ const worker_adapter_1 = require("./worker-adapter");
9
+ /* eslint-disable @typescript-eslint/no-require-imports */
10
+ jest.mock('../../common/control-protocol', () => ({
11
+ emit: jest.fn().mockResolvedValue({}),
12
+ }));
13
+ jest.mock('../../mappers/mappers');
14
+ jest.mock('node:worker_threads', () => ({
15
+ parentPort: { postMessage: jest.fn() },
16
+ }));
17
+ jest.mock('../../attachments-streaming/attachments-streaming-pool', () => ({
18
+ AttachmentsStreamingPool: jest.fn().mockImplementation(() => ({
19
+ streamAll: jest.fn().mockResolvedValue(undefined),
20
+ })),
21
+ }));
22
+ const UPLOAD_URL_PATH = '/internal/airdrop.artifacts.upload-url';
23
+ function makeAdapter() {
24
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
25
+ payload: { event_type: types_1.EventType.StartExtractingData },
26
+ });
27
+ const initialState = {
28
+ attachments: { completed: false },
29
+ lastSyncStarted: '',
30
+ lastSuccessfulSyncStarted: '',
31
+ snapInVersionId: '',
32
+ toDevRev: {
33
+ attachmentsMetadata: {
34
+ artifactIds: [],
35
+ lastProcessed: 0,
36
+ lastProcessedAttachmentsIdsList: [],
37
+ },
38
+ },
39
+ };
40
+ const adapterState = new state_1.State({ event, initialState });
41
+ const adapter = new worker_adapter_1.WorkerAdapter({ event, adapterState });
42
+ const workerThreads = require('node:worker_threads');
43
+ const mockPostMessage = jest.fn();
44
+ if (workerThreads.parentPort) {
45
+ jest
46
+ .spyOn(workerThreads.parentPort, 'postMessage')
47
+ .mockImplementation(mockPostMessage);
48
+ }
49
+ else {
50
+ workerThreads.parentPort = { postMessage: mockPostMessage };
51
+ }
52
+ return { adapter, mockPostMessage };
53
+ }
54
+ describe(`${worker_adapter_1.WorkerAdapter.name} upload failure (near-integration)`, () => {
55
+ let adapter;
56
+ let mockPostMessage;
57
+ beforeEach(() => {
58
+ jest.clearAllMocks();
59
+ jest_setup_1.mockServer.resetRoutes();
60
+ ({ adapter, mockPostMessage } = makeAdapter());
61
+ adapter['adapterState'].postState = jest.fn().mockResolvedValue(undefined);
62
+ });
63
+ afterEach(() => {
64
+ jest.restoreAllMocks();
65
+ });
66
+ it('should throw from repo.push when upload-url returns 400 during batch upload', async () => {
67
+ var _a, _b;
68
+ jest_setup_1.mockServer.setRoute({
69
+ path: UPLOAD_URL_PATH,
70
+ method: 'GET',
71
+ status: 400,
72
+ });
73
+ adapter.initializeRepos([
74
+ {
75
+ itemType: 'tasks',
76
+ overridenOptions: { batchSize: 10 },
77
+ },
78
+ ]);
79
+ await expect((_a = adapter.getRepo('tasks')) === null || _a === void 0 ? void 0 : _a.push((0, test_helpers_1.createItems)(20))).rejects.toThrow('artifact upload URL');
80
+ expect((_b = adapter.getRepo('tasks')) === null || _b === void 0 ? void 0 : _b.getItems().length).toBe(20);
81
+ });
82
+ it('should emit DataExtractionError via emitToPlatform when uploadAllRepos fails', async () => {
83
+ var _a;
84
+ const { emit: mockEmit } = require('../../common/control-protocol');
85
+ jest_setup_1.mockServer.setRoute({
86
+ path: UPLOAD_URL_PATH,
87
+ method: 'GET',
88
+ status: 400,
89
+ });
90
+ adapter.initializeRepos([{ itemType: 'tasks' }]);
91
+ await ((_a = adapter.getRepo('tasks')) === null || _a === void 0 ? void 0 : _a.push((0, test_helpers_1.createItems)(5)));
92
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionDone);
93
+ expect(mockEmit).toHaveBeenCalledWith(expect.objectContaining({
94
+ eventType: types_1.ExtractorEventType.DataExtractionError,
95
+ data: expect.objectContaining({
96
+ error: expect.objectContaining({
97
+ message: expect.stringContaining('artifact upload URL'),
98
+ }),
99
+ }),
100
+ }));
101
+ expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({
102
+ subject: 'emit',
103
+ payload: { eventType: types_1.ExtractorEventType.DataExtractionError },
104
+ }));
105
+ });
106
+ it('should include partial artifacts when push batch succeeded before uploadAllRepos fails', async () => {
107
+ var _a;
108
+ const { emit: mockEmit } = require('../../common/control-protocol');
109
+ jest_setup_1.mockServer.setRoute({
110
+ path: UPLOAD_URL_PATH,
111
+ method: 'GET',
112
+ status: 200,
113
+ succeedThenFail: {
114
+ successCount: 1,
115
+ errorStatus: 400,
116
+ },
117
+ });
118
+ adapter.initializeRepos([
119
+ {
120
+ itemType: 'tasks',
121
+ overridenOptions: { batchSize: 10 },
122
+ },
123
+ ]);
124
+ await ((_a = adapter.getRepo('tasks')) === null || _a === void 0 ? void 0 : _a.push((0, test_helpers_1.createItems)(15)));
125
+ await adapter.emit(types_1.ExtractorEventType.DataExtractionDone);
126
+ expect(mockEmit).toHaveBeenCalledWith(expect.objectContaining({
127
+ eventType: types_1.ExtractorEventType.DataExtractionError,
128
+ data: expect.objectContaining({
129
+ artifacts: expect.arrayContaining([
130
+ expect.objectContaining({ item_count: 10, item_type: 'tasks' }),
131
+ ]),
132
+ }),
133
+ }));
134
+ });
135
+ });
@@ -1,5 +1,4 @@
1
1
  import { Item } from '../repo/repo.interfaces';
2
- import { ErrorRecord } from '../types/common';
3
2
  import { Artifact } from '../uploader/uploader.interfaces';
4
3
  import { NormalizedAttachment, NormalizedItem, RepoFactoryInterface } from './repo.interfaces';
5
4
  export declare class Repo {
@@ -10,9 +9,19 @@ export declare class Repo {
10
9
  private onUpload;
11
10
  private options?;
12
11
  uploadedArtifacts: Artifact[];
12
+ dateRanges: {
13
+ creationDate: {
14
+ oldest: number;
15
+ newest: number;
16
+ };
17
+ modifiedDate: {
18
+ oldest: number;
19
+ newest: number;
20
+ };
21
+ };
13
22
  constructor({ event, itemType, normalize, onUpload, options, }: RepoFactoryInterface);
14
23
  getItems(): (NormalizedItem | NormalizedAttachment | Item)[];
15
- upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void | ErrorRecord>;
24
+ upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void>;
16
25
  push(items: Item[]): Promise<boolean>;
17
26
  }
18
27
  //# sourceMappingURL=repo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/repo/repo.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAI3D,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAmD;IAChE,OAAO,CAAC,SAAS,CAAC,CAAwD;IAC1E,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,OAAO,CAAC,CAAuB;IAChC,iBAAiB,EAAE,QAAQ,EAAE,CAAC;gBAEzB,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,OAAO,GACR,EAAE,oBAAoB;IAUvB,QAAQ,IAAI,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE;IAItD,MAAM,CACV,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE,GACvD,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC;IAqCxB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAyC5C"}
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/repo/repo.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAI3D,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAmB3B,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAmD;IAChE,OAAO,CAAC,SAAS,CAAC,CAAwD;IAC1E,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,OAAO,CAAC,CAAuB;IAChC,iBAAiB,EAAE,QAAQ,EAAE,CAAC;IAC9B,UAAU;;;;;;;;;MAGf;gBAEU,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,OAAO,GACR,EAAE,oBAAoB;IAUvB,QAAQ,IAAI,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE;IAItD,MAAM,CACV,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE,GACvD,OAAO,CAAC,IAAI,CAAC;IAyDV,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAkC5C"}
@@ -39,6 +39,8 @@ export interface NormalizedAttachment {
39
39
  author_id?: string;
40
40
  inline?: boolean;
41
41
  content_type?: string;
42
+ created_date?: string;
43
+ modified_date?: string;
42
44
  grand_parent_id?: number | string;
43
45
  }
44
46
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"repo.interfaces.d.ts","sourceRoot":"","sources":["../../src/repo/repo.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,oBAAoB,CAAC;IACtE,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,oBAAoB,CAAC;IACtE,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAItB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED;;GAEG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"repo.interfaces.d.ts","sourceRoot":"","sources":["../../src/repo/repo.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,oBAAoB,CAAC;IACtE,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,oBAAoB,CAAC;IACtE,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAIvB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED;;GAEG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC"}
package/dist/repo/repo.js CHANGED
@@ -4,8 +4,24 @@ exports.Repo = void 0;
4
4
  const constants_1 = require("../common/constants");
5
5
  const uploader_1 = require("../uploader/uploader");
6
6
  const logger_context_1 = require("../logger/logger.context");
7
+ function updateRange(range, ms) {
8
+ if (range.oldest === 0 || ms < range.oldest) {
9
+ range.oldest = ms;
10
+ }
11
+ if (range.newest === 0 || ms > range.newest) {
12
+ range.newest = ms;
13
+ }
14
+ }
15
+ function toValidTimestamp(value) {
16
+ const ms = new Date(value).getTime();
17
+ return Number.isFinite(ms) ? ms : undefined;
18
+ }
7
19
  class Repo {
8
20
  constructor({ event, itemType, normalize, onUpload, options, }) {
21
+ this.dateRanges = {
22
+ creationDate: { oldest: 0, newest: 0 },
23
+ modifiedDate: { oldest: 0, newest: 0 },
24
+ };
9
25
  this.items = [];
10
26
  this.itemType = itemType;
11
27
  this.normalize = normalize;
@@ -18,13 +34,30 @@ class Repo {
18
34
  return this.items;
19
35
  }
20
36
  async upload(batch) {
37
+ var _a;
21
38
  const itemsToUpload = batch || this.items;
22
39
  if (itemsToUpload.length > 0) {
40
+ for (const item of itemsToUpload) {
41
+ const createdDate = item === null || item === void 0 ? void 0 : item.created_date;
42
+ if (createdDate != null) {
43
+ const createdMs = toValidTimestamp(createdDate);
44
+ if (createdMs !== undefined) {
45
+ updateRange(this.dateRanges.creationDate, createdMs);
46
+ }
47
+ }
48
+ const modifiedDate = item === null || item === void 0 ? void 0 : item.modified_date;
49
+ if (modifiedDate != null && modifiedDate !== '') {
50
+ const modifiedMs = toValidTimestamp(modifiedDate);
51
+ if (modifiedMs !== undefined) {
52
+ updateRange(this.dateRanges.modifiedDate, modifiedMs);
53
+ }
54
+ }
55
+ }
23
56
  console.log(`Uploading ${itemsToUpload.length} items of type ${this.itemType}. `);
24
57
  const { artifact, error } = await this.uploader.upload(this.itemType, itemsToUpload);
25
58
  if (error || !artifact) {
26
59
  console.error('Error while uploading batch', error);
27
- return error;
60
+ throw new Error((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : `Upload failed for item type "${this.itemType}" without artifact.`);
28
61
  }
29
62
  this.onUpload(artifact);
30
63
  this.uploadedArtifacts.push(artifact);
@@ -59,16 +92,9 @@ class Repo {
59
92
  // Upload in batches while the number of items exceeds the batch size
60
93
  const batchSize = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.batchSize) || constants_1.ARTIFACT_BATCH_SIZE;
61
94
  while (this.items.length >= batchSize) {
62
- // Slice out a batch of batchSize items to upload
63
- const batch = this.items.splice(0, batchSize);
64
- try {
65
- // Upload the batch
66
- await this.upload(batch);
67
- }
68
- catch (error) {
69
- console.error('Error while uploading batch', error);
70
- return false;
71
- }
95
+ const batch = this.items.slice(0, batchSize);
96
+ await this.upload(batch);
97
+ this.items.splice(0, batchSize);
72
98
  }
73
99
  return true;
74
100
  }