@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.
- package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -1
- package/dist/attachments-streaming/attachments-streaming-pool.js +4 -6
- package/dist/attachments-streaming/attachments-streaming-pool.test.js +0 -65
- package/dist/common/control-protocol.d.ts +3 -2
- package/dist/common/control-protocol.d.ts.map +1 -1
- package/dist/common/control-protocol.js +2 -4
- package/dist/common/control-protocol.test.d.ts +2 -0
- package/dist/common/control-protocol.test.d.ts.map +1 -0
- package/dist/common/control-protocol.test.js +126 -0
- package/dist/http/axios-client-internal.d.ts.map +1 -1
- package/dist/http/axios-client-internal.js +19 -1
- package/dist/mock-server/mock-server.d.ts +4 -0
- package/dist/mock-server/mock-server.d.ts.map +1 -1
- package/dist/mock-server/mock-server.interfaces.d.ts +9 -4
- package/dist/mock-server/mock-server.interfaces.d.ts.map +1 -1
- package/dist/mock-server/mock-server.js +30 -18
- package/dist/multithreading/spawn/spawn.d.ts +1 -1
- package/dist/multithreading/spawn/spawn.d.ts.map +1 -1
- package/dist/multithreading/spawn/spawn.js +11 -6
- package/dist/multithreading/spawn/spawn.test.js +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts +11 -5
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.emit.test.js +90 -9
- package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.js +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.js +93 -64
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts +2 -0
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts.map +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.js +135 -0
- package/dist/repo/repo.d.ts +11 -2
- package/dist/repo/repo.d.ts.map +1 -1
- package/dist/repo/repo.interfaces.d.ts +2 -0
- package/dist/repo/repo.interfaces.d.ts.map +1 -1
- package/dist/repo/repo.js +37 -11
- package/dist/repo/repo.test.js +184 -0
- package/dist/types/extraction.d.ts +8 -1
- package/dist/types/extraction.d.ts.map +1 -1
- package/dist/types/workers.d.ts +4 -0
- package/dist/types/workers.d.ts.map +1 -1
- package/dist/uploader/uploader.d.ts.map +1 -1
- package/dist/uploader/uploader.helpers.d.ts +6 -1
- package/dist/uploader/uploader.helpers.d.ts.map +1 -1
- package/dist/uploader/uploader.helpers.js +65 -0
- package/dist/uploader/uploader.helpers.test.js +148 -7
- package/dist/uploader/uploader.interfaces.d.ts +11 -0
- package/dist/uploader/uploader.interfaces.d.ts.map +1 -1
- package/dist/uploader/uploader.interfaces.js +11 -0
- package/dist/uploader/uploader.js +2 -5
- package/dist/uploader/uploader.test.js +54 -3
- 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
|
|
117
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
+
await repo.upload();
|
|
267
229
|
this.artifacts.push(...repo.uploadedArtifacts);
|
|
268
|
-
|
|
269
|
-
|
|
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 @@
|
|
|
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
|
+
});
|
package/dist/repo/repo.d.ts
CHANGED
|
@@ -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
|
|
24
|
+
upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void>;
|
|
16
25
|
push(items: Item[]): Promise<boolean>;
|
|
17
26
|
}
|
|
18
27
|
//# sourceMappingURL=repo.d.ts.map
|
package/dist/repo/repo.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
}
|