@terascope/elasticsearch-api 3.4.0 → 3.5.1

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/index.js CHANGED
@@ -278,7 +278,6 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
278
278
  */
279
279
  function _filterRetryRecords(actionRecords, result) {
280
280
  const retry = [];
281
- const deadLetter = [];
282
281
  const { items } = result;
283
282
 
284
283
  let nonRetriableError = false;
@@ -312,9 +311,10 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
312
311
  reason = `${item.error.type}--${item.error.reason}`;
313
312
 
314
313
  if (config._dead_letter_action === 'kafka_dead_letter') {
315
- deadLetter.push({ doc: actionRecords[i].data, reason });
314
+ actionRecords[i].data.setMetadata('_bulk_sender_rejection', reason);
316
315
  continue;
317
316
  }
317
+
318
318
  break;
319
319
  }
320
320
  } else if (item.status == null || item.status < 400) {
@@ -323,8 +323,11 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
323
323
  }
324
324
 
325
325
  if (nonRetriableError) {
326
+ // if dlq active still attempt the retries
327
+ const retryOnError = config._dead_letter_action === 'kafka_dead_letter' ? retry : [];
328
+
326
329
  return {
327
- retry: [], successful, error: true, reason, deadLetter
330
+ retry: retryOnError, successful, error: true, reason
328
331
  };
329
332
  }
330
333
 
@@ -337,7 +340,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
337
340
 
338
341
  /**
339
342
  * @param data {Array<{ action: data }>}
340
- * @returns {Promise<{ recordCount: number, deadLetter: record[] }>}
343
+ * @returns {Promise<number>}
341
344
  */
342
345
  async function _bulkSend(actionRecords, previousCount = 0, previousRetryDelay = 0) {
343
346
  const body = actionRecords.flatMap((record, index) => {
@@ -349,7 +352,18 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
349
352
  throw new Error(`Bulk send record is missing the action property${dbg}`);
350
353
  }
351
354
 
352
- if (!isElasticsearch6()) return _nonEs6Prep(record);
355
+ if (!isElasticsearch6()) {
356
+ const actionKey = getFirstKey(record.action);
357
+ const { _type, ...withoutTypeAction } = record.action[actionKey];
358
+ // if data is specified return both
359
+ return record.data ? [{
360
+ ...record.action,
361
+ [actionKey]: withoutTypeAction
362
+ }, record.data] : [{
363
+ ...record.action,
364
+ [actionKey]: withoutTypeAction
365
+ }];
366
+ }
353
367
 
354
368
  // if data is specified return both
355
369
  return record.data ? [record.action, record.data] : [record.action];
@@ -358,51 +372,41 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
358
372
  const response = await _clientRequest('bulk', { body });
359
373
  const results = response.body ? response.body : response;
360
374
 
361
- if (!results.errors) return { recordCount: _affectedRowsCount(results) };
375
+ if (!results.errors) {
376
+ return results.items.reduce((c, item) => {
377
+ const [value] = Object.values(item);
378
+ // ignore non-successful status codes
379
+ if (value.status != null && value.status >= 400) return c;
380
+ return c + 1;
381
+ }, 0);
382
+ }
362
383
 
363
384
  const {
364
- retry, successful, error, reason, deadLetter
385
+ retry, successful, error, reason
365
386
  } = _filterRetryRecords(actionRecords, results);
366
387
 
367
- if (error) {
368
- if (config._dead_letter_action === 'kafka_dead_letter') {
369
- return {
370
- recordCount: previousCount + successful,
371
- deadLetter
372
- };
373
- }
374
-
388
+ if (error && config._dead_letter_action !== 'kafka_dead_letter') {
375
389
  throw new Error(`bulk send error: ${reason}`);
376
390
  }
377
391
 
378
392
  if (retry.length === 0) {
379
- return { recordCount: previousCount + successful };
393
+ return previousCount + successful;
380
394
  }
381
395
 
382
- warning();
383
-
384
- const nextRetryDelay = await _awaitRetry(previousRetryDelay);
385
- return _bulkSend(retry, previousCount + successful, nextRetryDelay);
396
+ return _handleRetries(retry, previousCount + successful, previousRetryDelay);
386
397
  }
387
398
 
388
- function _nonEs6Prep(record) {
389
- const actionKey = getFirstKey(record.action);
390
-
391
- const { _type, ...withoutTypeAction } = record.action[actionKey];
392
- // if data is specified return both
393
-
394
- const body = [{ ...record.action, [actionKey]: withoutTypeAction }];
395
-
396
- if (record.data != null) body.push(record.data);
399
+ async function _handleRetries(retry, affectedCount, previousRetryDelay) {
400
+ warning();
397
401
 
398
- return body;
402
+ const nextRetryDelay = await _awaitRetry(previousRetryDelay);
403
+ return _bulkSend(retry, affectedCount, nextRetryDelay);
399
404
  }
400
405
 
401
406
  /**
402
407
  * The new and improved bulk send with proper retry support
403
408
  *
404
- * @returns {Promise<{ recordCount: number, deadLetter: record[] }>}
405
- * the number of affected rows and records for kafka dead letter queue
409
+ * @returns {Promise<number>} the number of affected rows
406
410
  */
407
411
  function bulkSend(data) {
408
412
  if (!Array.isArray(data)) {
@@ -412,15 +416,6 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
412
416
  return Promise.resolve(_bulkSend(data));
413
417
  }
414
418
 
415
- function _affectedRowsCount(results) {
416
- return results.items.reduce((c, item) => {
417
- const [value] = Object.values(item);
418
- // ignore non-successful status codes
419
- if (value.status != null && value.status >= 400) return c;
420
- return c + 1;
421
- }, 0);
422
- }
423
-
424
419
  function _warn(warnLogger, msg) {
425
420
  let _lastTime = null;
426
421
  return () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@terascope/elasticsearch-api",
3
3
  "displayName": "Elasticsearch API",
4
- "version": "3.4.0",
4
+ "version": "3.5.1",
5
5
  "description": "Elasticsearch client api used across multiple services, handles retries and exponential backoff",
6
6
  "homepage": "https://github.com/terascope/teraslice/tree/master/packages/elasticsearch-api#readme",
7
7
  "bugs": {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@terascope/types": "^0.11.2",
26
- "@terascope/utils": "^0.45.5",
26
+ "@terascope/utils": "^0.45.6",
27
27
  "bluebird": "^3.7.2",
28
28
  "setimmediate": "^1.0.5"
29
29
  },
@@ -31,7 +31,7 @@
31
31
  "@opensearch-project/opensearch": "^1.1.0",
32
32
  "@types/elasticsearch": "^5.0.40",
33
33
  "elasticsearch": "^15.4.1",
34
- "elasticsearch-store": "^0.65.5",
34
+ "elasticsearch-store": "^0.66.1",
35
35
  "elasticsearch6": "npm:@elastic/elasticsearch@^6.7.0",
36
36
  "elasticsearch7": "npm:@elastic/elasticsearch@^7.0.0",
37
37
  "elasticsearch8": "npm:@elastic/elasticsearch@^8.0.0"
package/test/api-spec.js CHANGED
@@ -768,7 +768,7 @@ describe('elasticsearch-api', () => {
768
768
  { delete: { _index: 'some_index', _type: 'events', _id: 5 } }
769
769
  ]
770
770
  });
771
- return expect(result).toEqual({ recordCount: 2 });
771
+ return expect(result).toBe(2);
772
772
  });
773
773
 
774
774
  it('can remove type from bulkSend', async () => {
@@ -877,7 +877,7 @@ describe('elasticsearch-api', () => {
877
877
  });
878
878
  const result = await api.bulkSend(myBulkData);
879
879
 
880
- expect(result).toEqual({ recordCount: 2 });
880
+ expect(result).toBe(2);
881
881
 
882
882
  bulkError = ['some_thing_else', 'some_thing_else'];
883
883
 
@@ -14,7 +14,9 @@ const {
14
14
  createMappingFromDatatype
15
15
  } = ElasticsearchTestHelpers;
16
16
 
17
- jest.setTimeout(10000);
17
+ const THREE_MINUTES = 3 * 60 * 1000;
18
+
19
+ jest.setTimeout(THREE_MINUTES + 60000);
18
20
 
19
21
  function formatUploadData(
20
22
  index, data, isES8ClientTest = false
@@ -72,35 +74,48 @@ describe('bulkSend', () => {
72
74
  await cleanupIndex(client, index);
73
75
  });
74
76
 
75
- it('returns records that cannot be tried again if dlq config is set', async () => {
77
+ it('should send records if no issues and dlq not set', async () => {
78
+ const diffApi = elasticsearchAPI(client, logger);
79
+
80
+ const docs = cloneDeep(EvenDateData.data.slice(0, 2));
81
+
82
+ const result = await diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8));
83
+
84
+ expect(result).toBe(2);
85
+ });
86
+
87
+ it('should throw an error if dlq is not set', async () => {
88
+ const diffApi = elasticsearchAPI(client, logger);
89
+
76
90
  const docs = cloneDeep(EvenDateData.data.slice(0, 2));
77
91
 
78
92
  docs[0].bytes = 'this is a bad value';
79
93
 
80
- const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
94
+ await expect(diffApi.bulkSend(formatUploadData(index, docs, isElasticsearch8)))
95
+ .rejects.toThrow();
96
+ });
97
+
98
+ it('should update metadata for records that are not retryable and return results for successful updates if dlq is set', async () => {
99
+ const docs = cloneDeep(EvenDateData.data.slice(0, 2))
100
+ .map((doc, i) => DataEntity.make(doc, { _key: i + 1 }));
81
101
 
82
- expect(result.recordCount).toBe(1);
102
+ docs[0].bytes = 'this is a bad value';
103
+
104
+ const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
83
105
 
84
- expect(result.deadLetter[0].doc).toEqual(DataEntity.make({
85
- ip: '120.67.248.156',
86
- userAgent: 'Mozilla/5.0 (Windows; U; Windows NT 6.1) AppleWebKit/533.1.2 (KHTML, like Gecko) Chrome/35.0.894.0 Safari/533.1.2',
87
- url: 'http://lucious.biz',
88
- uuid: 'b23a8550-0081-453f-9e80-93a90782a5bd',
89
- created: '2019-04-26T15:00:23.225+00:00',
90
- ipv6: '9e79:7798:585a:b847:f1c4:81eb:0c3d:7eb8',
91
- location: '50.15003, -94.89355',
92
- bytes: 'this is a bad value'
93
- }));
106
+ // 1 good doc - so only 1 row affected
107
+ expect(result).toBe(1);
94
108
 
95
- expect(result.deadLetter[0].reason).toBeDefined();
109
+ expect(docs[0].getMetadata('_bulk_sender_rejection')).toInclude('mapper_parsing_exception--failed to parse field [bytes]');
110
+ expect(docs[1].getMetadata('_bulk_sender_rejection')).toBeUndefined();
96
111
  });
97
112
 
98
- it('should return a count if not un-retryable records', async () => {
113
+ it('should return a count if not un-retryable records if dlq is set', async () => {
99
114
  const docs = cloneDeep(EvenDateData.data.slice(0, 2));
100
115
 
101
116
  const result = await api.bulkSend(formatUploadData(index, docs, isElasticsearch8));
102
117
 
103
- expect(result).toEqual({ recordCount: 2 });
118
+ expect(result).toBe(2);
104
119
  });
105
120
  });
106
121
  });
package/types/index.d.ts CHANGED
@@ -32,9 +32,9 @@ declare namespace elasticsearchAPI {
32
32
  /**
33
33
  * The new and improved bulk send with proper retry support
34
34
  *
35
- * @returns the number of affected rows, and deadLetter records if config is set
35
+ * @returns the number of affected rows
36
36
  */
37
- bulkSend: (data: BulkRecord[]) => Promise<{ recordCount: number; deadLetter?: any[]; }>;
37
+ bulkSend: (data: BulkRecord[]) => Promise<number>;
38
38
  nodeInfo: (query: any) => Promise<any>;
39
39
  nodeStats: (query: any) => Promise<any>;
40
40
  buildQuery: (opConfig: Config, msg: any) => ClientParams.SearchParams;