@cumulus/es-client 13.4.0 → 14.0.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/README.md CHANGED
@@ -8,6 +8,13 @@ Utilities for working with Elasticsearch.
8
8
  npm install @cumulus/es-client
9
9
  ```
10
10
 
11
+ ## Notes
12
+
13
+ ### Sorting
14
+
15
+ It is possible to sort the Elastic Search results by specifying `sort_by`, `sort_key`, and `order` params.
16
+ If not provided, the default sorting will be applied (`{ timestamp: { order: 'desc' } }` as of this writing).
17
+
11
18
  ## About Cumulus
12
19
 
13
20
  Cumulus is a cloud-based data ingest, archive, distribution and management prototype for NASA's
package/esDefaults.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const defaultESScrollSize = 1000;
2
2
  const defaultESScrollDuration = '6m';
3
3
 
4
- const defaultSortParams = [{ timestamp: 'desc' }, { _uid: 'asc' }];
4
+ const defaultSortParams = [{ timestamp: { order: 'desc' } }];
5
5
 
6
6
  module.exports = {
7
7
  defaultESScrollSize,
package/indexer.js CHANGED
@@ -11,11 +11,13 @@
11
11
  'use strict';
12
12
 
13
13
  const cloneDeep = require('lodash/cloneDeep');
14
+ const isEqual = require('lodash/isEqual');
14
15
 
15
16
  const Logger = require('@cumulus/logger');
16
17
  const { inTestMode } = require('@cumulus/common/test-utils');
17
- const { IndexExistsError } = require('@cumulus/errors');
18
+ const { IndexExistsError, ValidationError } = require('@cumulus/errors');
18
19
  const { constructCollectionId } = require('@cumulus/message/Collections');
20
+ const { removeNilProperties } = require('@cumulus/common/util');
19
21
 
20
22
  const { Search, defaultIndexAlias } = require('./search');
21
23
  const mappings = require('./config/mappings.json');
@@ -284,15 +286,26 @@ async function indexGranule(esClient, payload, index = defaultIndexAlias, type =
284
286
  );
285
287
  }
286
288
 
289
+ const granuleInvalidNullFields = [
290
+ 'granuleId',
291
+ 'collectionId',
292
+ 'status',
293
+ 'updatedAt',
294
+ 'execution',
295
+ 'createdAt',
296
+ ];
297
+
287
298
  /**
288
299
  * Upserts a granule in Elasticsearch
289
300
  *
290
301
  * @param {Object} params
291
- * @param {Object} params.esClient - Elasticsearch Connection object
292
- * @param {Object} params.updates - Updates to make
293
- * @param {string} params.index - Elasticsearch index alias (default defined in search.js)
294
- * @param {string} params.type - Elasticsearch type (default: granule)
295
- * @param {string} [params.refresh] - whether to refresh the index on update or not
302
+ * @param {Object} params.esClient - Elasticsearch Connection object
303
+ * @param {Object} params.updates - Updates to make
304
+ * @param {string} params.index - Elasticsearch index alias (default defined in search.js)
305
+ * @param {string} params.type - Elasticsearch type (default: granule)
306
+ * @param {string} [params.refresh] - whether to refresh the index on update or not
307
+ * @param {boolean} writeConstraints - boolean toggle restricting if conditionals should
308
+ * be used to determine write eligibility
296
309
  * @returns {Promise} Elasticsearch response
297
310
  */
298
311
  async function upsertGranule({
@@ -301,7 +314,12 @@ async function upsertGranule({
301
314
  index = defaultIndexAlias,
302
315
  type = 'granule',
303
316
  refresh,
304
- }) {
317
+ }, writeConstraints = true) {
318
+ Object.keys(updates).forEach((key) => {
319
+ if (updates[key] === null && granuleInvalidNullFields.includes(key)) {
320
+ throw new ValidationError(`Attempted DynamoDb write with invalid key ${key} set to null. Please remove or change this field and retry`);
321
+ }
322
+ });
305
323
  // If the granule exists in 'deletedgranule', delete it first before inserting the granule
306
324
  // into ES. Ignore 404 error, so the deletion still succeeds if the record doesn't exist.
307
325
  const delGranParams = {
@@ -313,7 +331,49 @@ async function upsertGranule({
313
331
  };
314
332
  await esClient.delete(delGranParams, { ignore: [404] });
315
333
 
316
- const upsertDoc = updates;
334
+ // Remove nils in case there isn't a collision
335
+ const upsertDoc = removeNilProperties(updates);
336
+ let removeString = '';
337
+
338
+ // Set field removal for null values
339
+ Object.entries(updates).forEach(([fieldName, value]) => {
340
+ // File removal is a special case as null gets set to []
341
+ if (fieldName === 'files' && isEqual(value, [])) {
342
+ removeString += `ctx._source.remove('${fieldName}'); `;
343
+ }
344
+ if (value === null) {
345
+ removeString += `ctx._source.remove('${fieldName}'); `;
346
+ }
347
+ });
348
+
349
+ let inlineDocWriteString = 'ctx._source.putAll(params.doc);';
350
+ if (removeString !== '') {
351
+ inlineDocWriteString += removeString;
352
+ }
353
+ let inline = inlineDocWriteString;
354
+
355
+ if (writeConstraints === true) {
356
+ // Because both API write and message write chains use the granule model to store records, in
357
+ // cases where createdAt does not exist on the granule, we assume overwrite protections are
358
+ // undesired behavior via business logic on the message write logic
359
+ inline = `
360
+ if ((ctx._source.createdAt === null || params.doc.createdAt >= ctx._source.createdAt)
361
+ && (params.doc.status != 'running' || (params.doc.status == 'running' && params.doc.execution != ctx._source.execution))) {
362
+ ${inlineDocWriteString}
363
+ } else {
364
+ ctx.op = 'none';
365
+ }
366
+ `;
367
+ if (!updates.createdAt) {
368
+ inline = `
369
+ if (params.doc.status != 'running' || (params.doc.status == 'running' && params.doc.execution != ctx._source.execution)) {
370
+ ${inlineDocWriteString}
371
+ } else {
372
+ ctx.op = 'none';
373
+ }
374
+ `;
375
+ }
376
+ }
317
377
 
318
378
  return await esClient.update({
319
379
  index,
@@ -323,14 +383,7 @@ async function upsertGranule({
323
383
  body: {
324
384
  script: {
325
385
  lang: 'painless',
326
- inline: `
327
- if ((ctx._source.createdAt === null || params.doc.createdAt >= ctx._source.createdAt)
328
- && (params.doc.status != 'running' || (params.doc.status == 'running' && params.doc.execution != ctx._source.execution))) {
329
- ctx._source.putAll(params.doc);
330
- } else {
331
- ctx.op = 'none';
332
- }
333
- `,
386
+ inline,
334
387
  params: {
335
388
  doc: upsertDoc,
336
389
  },
@@ -709,4 +762,5 @@ module.exports = {
709
762
  deleteGranule,
710
763
  deleteExecution,
711
764
  deleteReconciliationReport,
765
+ granuleInvalidNullFields,
712
766
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cumulus/es-client",
3
- "version": "13.4.0",
3
+ "version": "14.0.0",
4
4
  "description": "Utilities for working with Elasticsearch",
5
5
  "keywords": [
6
6
  "CUMULUS",
@@ -30,10 +30,10 @@
30
30
  "author": "Cumulus Authors",
31
31
  "license": "Apache-2.0",
32
32
  "dependencies": {
33
- "@cumulus/common": "13.4.0",
34
- "@cumulus/errors": "13.4.0",
35
- "@cumulus/logger": "13.4.0",
36
- "@cumulus/message": "13.4.0",
33
+ "@cumulus/common": "14.0.0",
34
+ "@cumulus/errors": "14.0.0",
35
+ "@cumulus/logger": "14.0.0",
36
+ "@cumulus/message": "14.0.0",
37
37
  "@elastic/elasticsearch": "^5.6.20",
38
38
  "aws-elasticsearch-connector": "8.2.0",
39
39
  "aws-sdk": "^2.585.0",
@@ -42,9 +42,9 @@
42
42
  "p-limit": "^1.2.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@cumulus/aws-client": "13.4.0",
46
- "@cumulus/test-data": "13.4.0",
45
+ "@cumulus/aws-client": "14.0.0",
46
+ "@cumulus/test-data": "14.0.0",
47
47
  "p-each-series": "^2.1.0"
48
48
  },
49
- "gitHead": "cd4377ccf97a1daba19775fb95f9f7fa0f2a29f3"
49
+ "gitHead": "bc0cd0857e007b64e4e8809f7de16a4aeb1e6840"
50
50
  }