@cumulus/db 18.3.4 → 19.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.
Files changed (44) hide show
  1. package/README.md +5 -0
  2. package/dist/config.d.ts +19 -0
  3. package/dist/config.js +40 -1
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +15 -4
  6. package/dist/lib/execution.d.ts +5 -0
  7. package/dist/lib/execution.js +16 -1
  8. package/dist/migrations/20240125171703_update_granule_execution_cumulus_id_type.d.ts +4 -0
  9. package/dist/migrations/20240125171703_update_granule_execution_cumulus_id_type.js +22 -0
  10. package/dist/migrations/20240126135619_granules_add_indexes.d.ts +4 -0
  11. package/dist/migrations/20240126135619_granules_add_indexes.js +19 -0
  12. package/dist/migrations/20240126165019_granules_update_constraints.d.ts +4 -0
  13. package/dist/migrations/20240126165019_granules_update_constraints.js +44 -0
  14. package/dist/migrations/20240606060726_alter_async_operations_add_operation_type_bulk_execution_delete.d.ts +4 -0
  15. package/dist/migrations/20240606060726_alter_async_operations_add_operation_type_bulk_execution_delete.js +43 -0
  16. package/dist/migrations/20240613174614_add_execution_parent_and_collection_indexes.d.ts +4 -0
  17. package/dist/migrations/20240613174614_add_execution_parent_and_collection_indexes.js +17 -0
  18. package/dist/migrations/20240617204826_update_executions_deletion_constraint.d.ts +4 -0
  19. package/dist/migrations/20240617204826_update_executions_deletion_constraint.js +14 -0
  20. package/dist/migrations/20240728101230_add_table_indexes.d.ts +4 -0
  21. package/dist/migrations/20240728101230_add_table_indexes.js +53 -0
  22. package/dist/models/execution.d.ts +2 -2
  23. package/dist/models/execution.js +1 -1
  24. package/dist/search/BaseSearch.d.ts +187 -0
  25. package/dist/search/BaseSearch.js +416 -0
  26. package/dist/search/CollectionSearch.d.ts +79 -0
  27. package/dist/search/CollectionSearch.js +162 -0
  28. package/dist/search/ExecutionSearch.d.ts +62 -0
  29. package/dist/search/ExecutionSearch.js +133 -0
  30. package/dist/search/GranuleSearch.d.ts +55 -0
  31. package/dist/search/GranuleSearch.js +109 -0
  32. package/dist/search/StatsSearch.d.ts +111 -0
  33. package/dist/search/StatsSearch.js +214 -0
  34. package/dist/search/field-mapping.d.ts +16 -0
  35. package/dist/search/field-mapping.js +304 -0
  36. package/dist/search/queries.d.ts +10 -0
  37. package/dist/search/queries.js +235 -0
  38. package/dist/translate/executions.d.ts +6 -0
  39. package/dist/translate/executions.js +32 -23
  40. package/dist/translate/granules.d.ts +25 -1
  41. package/dist/translate/granules.js +48 -27
  42. package/dist/types/search.d.ts +52 -0
  43. package/dist/types/search.js +3 -0
  44. package/package.json +9 -9
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.StatsSearch = void 0;
7
+ const omit_1 = __importDefault(require("lodash/omit"));
8
+ const logger_1 = __importDefault(require("@cumulus/logger"));
9
+ const connection_1 = require("../connection");
10
+ const tables_1 = require("../tables");
11
+ const BaseSearch_1 = require("./BaseSearch");
12
+ const log = new logger_1.default({ sender: '@cumulus/db/StatsSearch' });
13
+ const infixMapping = {
14
+ granules: 'granule_id',
15
+ collections: 'name',
16
+ providers: 'name',
17
+ executions: 'arn',
18
+ pdrs: 'name',
19
+ };
20
+ /**
21
+ * A class to query postgres for the STATS and STATS/AGGREGATE endpoints
22
+ */
23
+ class StatsSearch extends BaseSearch_1.BaseSearch {
24
+ constructor(event, type) {
25
+ const { field, ...queryStringParameters } = event.queryStringParameters || {};
26
+ super({ queryStringParameters }, type);
27
+ this.field = field ?? 'status';
28
+ this.dbQueryParameters = (0, omit_1.default)(this.dbQueryParameters, ['limit', 'offset']);
29
+ }
30
+ /**
31
+ * Formats the postgres records into an API stats/aggregate response
32
+ *
33
+ * @param result - the postgres query results
34
+ * @returns the api object with the aggregate statistics
35
+ */
36
+ formatAggregateResult(result) {
37
+ let totalCount = 0;
38
+ const responses = [];
39
+ for (const row of Object.keys(result)) {
40
+ responses.push({
41
+ key: result[row].aggregatedfield,
42
+ count: Number.parseInt(result[row].count, 10),
43
+ });
44
+ totalCount += Number(result[row].count);
45
+ }
46
+ return {
47
+ meta: {
48
+ name: 'cumulus-api',
49
+ count: totalCount,
50
+ field: this.field,
51
+ },
52
+ count: responses,
53
+ };
54
+ }
55
+ /**
56
+ * Formats the postgres results into an API stats/summary response
57
+ *
58
+ * @param result - the knex summary query results
59
+ * @returns the api object with the summary statistics
60
+ */
61
+ formatSummaryResult(result) {
62
+ const timestampTo = this.dbQueryParameters.range?.updated_at?.lte ?? new Date();
63
+ const timestampFrom = this.dbQueryParameters.range?.updated_at?.gte ?? new Date(0);
64
+ const dateto = timestampTo.toISOString();
65
+ const datefrom = timestampFrom.toISOString();
66
+ return {
67
+ errors: {
68
+ dateFrom: datefrom,
69
+ dateTo: dateto,
70
+ value: Number(result.count_errors),
71
+ aggregation: 'count',
72
+ unit: 'error',
73
+ },
74
+ collections: {
75
+ dateFrom: datefrom,
76
+ dateTo: dateto,
77
+ value: Number(result.count_collections),
78
+ aggregation: 'count',
79
+ unit: 'collection',
80
+ },
81
+ processingTime: {
82
+ dateFrom: datefrom,
83
+ dateTo: dateto,
84
+ value: Number(result.avg_processing_time),
85
+ aggregation: 'average',
86
+ unit: 'second',
87
+ },
88
+ granules: {
89
+ dateFrom: datefrom,
90
+ dateTo: dateto,
91
+ value: Number(result.count_granules),
92
+ aggregation: 'count',
93
+ unit: 'granule',
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * Queries postgres for a summary of statistics around the granules in the system
99
+ *
100
+ * @param testKnex - the knex client to be used
101
+ * @returns the postgres aggregations based on query
102
+ */
103
+ async summary(testKnex) {
104
+ const knex = testKnex ?? await (0, connection_1.getKnexClient)();
105
+ const aggregateQuery = knex(this.tableName);
106
+ this.buildRangeQuery({ searchQuery: aggregateQuery });
107
+ aggregateQuery.select(knex.raw(`COUNT(CASE WHEN ${this.tableName}.error ->> 'Error' is not null THEN 1 END) AS count_errors`), knex.raw('COUNT(*) AS count_granules'), knex.raw(`AVG(${this.tableName}.duration) AS avg_processing_time`), knex.raw(`COUNT(DISTINCT ${this.tableName}.collection_cumulus_id) AS count_collections`));
108
+ log.debug(`summary about to execute query: ${aggregateQuery?.toSQL().sql}`);
109
+ const aggregateQueryRes = await aggregateQuery;
110
+ return this.formatSummaryResult(aggregateQueryRes[0]);
111
+ }
112
+ /**
113
+ * Performs joins on the collections/pdrs/providers table if neccessary
114
+ *
115
+ * @param query - the knex query to be joined or not
116
+ */
117
+ joinTables(query) {
118
+ const { collections: collectionsTable, providers: providersTable, pdrs: pdrsTable, } = tables_1.TableNames;
119
+ if (this.searchCollection()) {
120
+ query.join(collectionsTable, `${this.tableName}.collection_cumulus_id`, `${collectionsTable}.cumulus_id`);
121
+ }
122
+ if (this.searchProvider()) {
123
+ query.join(providersTable, `${this.tableName}.provider_cumulus_id`, `${providersTable}.cumulus_id`);
124
+ }
125
+ if (this.searchPdr()) {
126
+ query.join(pdrsTable, `${this.tableName}.pdr_cumulus_id`, `${pdrsTable}.cumulus_id`);
127
+ }
128
+ }
129
+ /**
130
+ * Aggregates the search query based on queryStringParameters
131
+ *
132
+ * @param query - the knex query to be aggregated
133
+ * @param knex - the knex client to be used
134
+ */
135
+ aggregateQueryField(query, knex) {
136
+ if (this.field?.includes('error.Error')) {
137
+ query.select(knex.raw("error ->> 'Error' as aggregatedfield"));
138
+ }
139
+ else {
140
+ query.select(`${this.tableName}.${this.field} as aggregatedfield`);
141
+ }
142
+ query.modify((queryBuilder) => this.joinTables(queryBuilder))
143
+ .count('* as count')
144
+ .groupBy('aggregatedfield')
145
+ .orderBy([{ column: 'count', order: 'desc' }, { column: 'aggregatedfield' }]);
146
+ }
147
+ /**
148
+ * Builds basic query
149
+ *
150
+ * @param knex - the knex client
151
+ * @returns the search query
152
+ */
153
+ buildBasicQuery(knex) {
154
+ const searchQuery = knex(this.tableName);
155
+ this.aggregateQueryField(searchQuery, knex);
156
+ return { searchQuery };
157
+ }
158
+ /**
159
+ * Builds queries for infix and prefix
160
+ *
161
+ * @param params
162
+ * @param params.searchQuery - the search query
163
+ * @param [params.dbQueryParameters] - the db query parameters
164
+ */
165
+ buildInfixPrefixQuery(params) {
166
+ const { searchQuery, dbQueryParameters } = params;
167
+ const { infix, prefix } = dbQueryParameters || this.dbQueryParameters;
168
+ const fieldName = infixMapping[this.tableName];
169
+ if (infix) {
170
+ searchQuery.whereLike(`${this.tableName}.${fieldName}`, `%${infix}%`);
171
+ }
172
+ if (prefix) {
173
+ searchQuery.whereLike(`${this.tableName}.${fieldName}`, `%${prefix}%`);
174
+ }
175
+ }
176
+ /**
177
+ * Builds queries for term fields
178
+ *
179
+ * @param params
180
+ * @param params.searchQuery - the search query
181
+ * @param [params.dbQueryParameters] - the db query parameters
182
+ * @returns the updated search query based on queryStringParams
183
+ */
184
+ buildTermQuery(params) {
185
+ const { dbQueryParameters, searchQuery } = params;
186
+ const { term = {} } = dbQueryParameters ?? this.dbQueryParameters;
187
+ if (this.field?.includes('error.Error')) {
188
+ searchQuery.whereRaw(`${this.tableName}.error ->> 'Error' is not null`);
189
+ }
190
+ return super.buildTermQuery({
191
+ ...params,
192
+ dbQueryParameters: { term: (0, omit_1.default)(term, 'error.Error') },
193
+ });
194
+ }
195
+ /**
196
+ * Executes the aggregate search query
197
+ *
198
+ * @param testKnex - the knex client to be used
199
+ * @returns the aggregate query results in api format
200
+ */
201
+ async aggregate(testKnex) {
202
+ const knex = testKnex ?? await (0, connection_1.getKnexClient)();
203
+ const { searchQuery } = this.buildSearch(knex);
204
+ try {
205
+ const pgRecords = await searchQuery;
206
+ return this.formatAggregateResult(pgRecords);
207
+ }
208
+ catch (error) {
209
+ return error;
210
+ }
211
+ }
212
+ }
213
+ exports.StatsSearch = StatsSearch;
214
+ //# sourceMappingURL=StatsSearch.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Map query string field to db field
3
+ *
4
+ * @param type - query record type
5
+ * @param queryField - query field
6
+ * @param queryField.name - query field value
7
+ * @param [queryField.value] - query field value
8
+ * @returns db field
9
+ */
10
+ export declare const mapQueryStringFieldToDbField: (type: string, queryField: {
11
+ name: string;
12
+ value?: string;
13
+ }) => {
14
+ [key: string]: any;
15
+ } | undefined;
16
+ //# sourceMappingURL=field-mapping.d.ts.map
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.mapQueryStringFieldToDbField = void 0;
7
+ const Collections_1 = require("@cumulus/message/Collections");
8
+ const logger_1 = __importDefault(require("@cumulus/logger"));
9
+ const log = new logger_1.default({ sender: '@cumulus/db/field-mapping' });
10
+ // functions to map the api search string field name and value to postgres db field
11
+ const granuleMapping = {
12
+ beginningDateTime: (value) => ({
13
+ beginning_date_time: value,
14
+ }),
15
+ cmrLink: (value) => ({
16
+ cmr_link: value,
17
+ }),
18
+ createdAt: (value) => ({
19
+ created_at: value && new Date(Number(value)),
20
+ }),
21
+ duration: (value) => ({
22
+ duration: value && Number(value),
23
+ }),
24
+ endingDateTime: (value) => ({
25
+ ending_date_time: value,
26
+ }),
27
+ granuleId: (value) => ({
28
+ granule_id: value,
29
+ }),
30
+ _id: (value) => ({
31
+ granule_id: value,
32
+ }),
33
+ lastUpdateDateTime: (value) => ({
34
+ last_update_date_time: value,
35
+ }),
36
+ processingEndDateTime: (value) => ({
37
+ processing_end_date_time: value,
38
+ }),
39
+ processingStartDateTime: (value) => ({
40
+ processing_start_date_time: value,
41
+ }),
42
+ productionDateTime: (value) => ({
43
+ production_date_time: value,
44
+ }),
45
+ productVolume: (value) => ({
46
+ product_volume: value,
47
+ }),
48
+ published: (value) => ({
49
+ published: (value === 'true'),
50
+ }),
51
+ status: (value) => ({
52
+ status: value,
53
+ }),
54
+ timestamp: (value) => ({
55
+ updated_at: value && new Date(Number(value)),
56
+ }),
57
+ timeToArchive: (value) => ({
58
+ time_to_archive: Number(value),
59
+ }),
60
+ timeToPreprocess: (value) => ({
61
+ time_to_process: Number(value),
62
+ }),
63
+ updatedAt: (value) => ({
64
+ updated_at: value && new Date(Number(value)),
65
+ }),
66
+ error: (value) => ({
67
+ error: value,
68
+ }),
69
+ // nested error field
70
+ 'error.Error': (value) => ({
71
+ 'error.Error': value,
72
+ }),
73
+ 'error.Error.keyword': (value) => ({
74
+ 'error.Error': value,
75
+ }),
76
+ // The following fields require querying other tables
77
+ collectionId: (value) => {
78
+ const { name, version } = (value && (0, Collections_1.deconstructCollectionId)(value)) || {};
79
+ return {
80
+ collectionName: name,
81
+ collectionVersion: version,
82
+ };
83
+ },
84
+ provider: (value) => ({
85
+ providerName: value,
86
+ }),
87
+ pdrName: (value) => ({
88
+ pdrName: value,
89
+ }),
90
+ };
91
+ const asyncOperationMapping = {
92
+ createdAt: (value) => ({
93
+ created_at: value && new Date(Number(value)),
94
+ }),
95
+ id: (value) => ({
96
+ id: value,
97
+ }),
98
+ operationType: (value) => ({
99
+ operation_type: value,
100
+ }),
101
+ status: (value) => ({
102
+ status: value,
103
+ }),
104
+ taskArn: (value) => ({
105
+ task_arn: value,
106
+ }),
107
+ timestamp: (value) => ({
108
+ updated_at: value && new Date(Number(value)),
109
+ }),
110
+ updatedAt: (value) => ({
111
+ updated_at: value && new Date(Number(value)),
112
+ }),
113
+ };
114
+ const collectionMapping = {
115
+ createdAt: (value) => ({
116
+ created_at: value && new Date(Number(value)),
117
+ }),
118
+ name: (value) => ({
119
+ name: value,
120
+ }),
121
+ version: (value) => ({
122
+ version: value,
123
+ }),
124
+ _id: (value) => {
125
+ const { name, version } = (value && (0, Collections_1.deconstructCollectionId)(value)) || {};
126
+ return {
127
+ collectionName: name,
128
+ collectionVersion: version,
129
+ };
130
+ },
131
+ duplicateHandling: (value) => ({
132
+ duplicate_handling: value,
133
+ }),
134
+ granuleId: (value) => ({
135
+ granule_id_validation_regex: value,
136
+ }),
137
+ granuleIdExtraction: (value) => ({
138
+ granule_id_extraction_regex: value,
139
+ }),
140
+ timestamp: (value) => ({
141
+ updated_at: value && new Date(Number(value)),
142
+ }),
143
+ updatedAt: (value) => ({
144
+ updated_at: value && new Date(Number(value)),
145
+ }),
146
+ reportToEms: (value) => ({
147
+ report_to_ems: (value === 'true'),
148
+ }),
149
+ process: (value) => ({
150
+ process: value,
151
+ }),
152
+ sampleFileName: (value) => ({
153
+ sample_file_name: value,
154
+ }),
155
+ url_path: (value) => ({
156
+ url_path: value,
157
+ }),
158
+ };
159
+ // TODO add and verify all queryable fields for the following record types
160
+ const executionMapping = {
161
+ arn: (value) => ({
162
+ arn: value,
163
+ }),
164
+ createdAt: (value) => ({
165
+ created_at: value && new Date(Number(value)),
166
+ }),
167
+ duration: (value) => ({
168
+ duration: value && Number(value),
169
+ }),
170
+ // nested error field
171
+ 'error.Error': (value) => ({
172
+ 'error.Error': value,
173
+ }),
174
+ 'error.Error.keyword': (value) => ({
175
+ 'error.Error': value,
176
+ }),
177
+ execution: (value) => ({
178
+ url: value,
179
+ }),
180
+ type: (value) => ({
181
+ workflow_name: value,
182
+ }),
183
+ status: (value) => ({
184
+ status: value,
185
+ }),
186
+ timestamp: (value) => ({
187
+ updated_at: value && new Date(Number(value)),
188
+ }),
189
+ updatedAt: (value) => ({
190
+ updated_at: value && new Date(Number(value)),
191
+ }),
192
+ // The following fields require querying other tables
193
+ asyncOperationId: (value) => ({
194
+ asyncOperationId: value,
195
+ }),
196
+ parentArn: (value) => ({
197
+ parentArn: value,
198
+ }),
199
+ collectionId: (value) => {
200
+ const { name, version } = (value && (0, Collections_1.deconstructCollectionId)(value)) || {};
201
+ return {
202
+ collectionName: name,
203
+ collectionVersion: version,
204
+ };
205
+ },
206
+ };
207
+ const pdrMapping = {
208
+ createdAt: (value) => ({
209
+ created_at: value && new Date(Number(value)),
210
+ }),
211
+ pdrName: (value) => ({
212
+ name: value,
213
+ }),
214
+ status: (value) => ({
215
+ status: value,
216
+ }),
217
+ timestamp: (value) => ({
218
+ updated_at: value && new Date(Number(value)),
219
+ }),
220
+ updatedAt: (value) => ({
221
+ updated_at: value && new Date(Number(value)),
222
+ }),
223
+ // The following fields require querying other tables
224
+ collectionId: (value) => {
225
+ const { name, version } = (value && (0, Collections_1.deconstructCollectionId)(value)) || {};
226
+ return {
227
+ collectionName: name,
228
+ collectionVersion: version,
229
+ };
230
+ },
231
+ provider: (value) => ({
232
+ providerName: value,
233
+ }),
234
+ };
235
+ const providerMapping = {
236
+ createdAt: (value) => ({
237
+ created_at: value && new Date(Number(value)),
238
+ }),
239
+ id: (value) => ({
240
+ name: value,
241
+ }),
242
+ timestamp: (value) => ({
243
+ updated_at: value && new Date(Number(value)),
244
+ }),
245
+ updatedAt: (value) => ({
246
+ updated_at: value && new Date(Number(value)),
247
+ }),
248
+ };
249
+ const ruleMapping = {
250
+ createdAt: (value) => ({
251
+ created_at: value && new Date(Number(value)),
252
+ }),
253
+ name: (value) => ({
254
+ name: value,
255
+ }),
256
+ state: (value) => ({
257
+ enabled: (value === 'ENABLED'),
258
+ }),
259
+ timestamp: (value) => ({
260
+ updated_at: value && new Date(Number(value)),
261
+ }),
262
+ updatedAt: (value) => ({
263
+ updated_at: value && new Date(Number(value)),
264
+ }),
265
+ // The following fields require querying other tables
266
+ collectionId: (value) => {
267
+ const { name, version } = (value && (0, Collections_1.deconstructCollectionId)(value)) || {};
268
+ return {
269
+ collectionName: name,
270
+ collectionVersion: version,
271
+ };
272
+ },
273
+ provider: (value) => ({
274
+ providerName: value,
275
+ }),
276
+ };
277
+ // type and its mapping
278
+ const supportedMappings = {
279
+ granule: granuleMapping,
280
+ asyncOperation: asyncOperationMapping,
281
+ collection: collectionMapping,
282
+ execution: executionMapping,
283
+ pdr: pdrMapping,
284
+ provider: providerMapping,
285
+ rule: ruleMapping,
286
+ };
287
+ /**
288
+ * Map query string field to db field
289
+ *
290
+ * @param type - query record type
291
+ * @param queryField - query field
292
+ * @param queryField.name - query field value
293
+ * @param [queryField.value] - query field value
294
+ * @returns db field
295
+ */
296
+ const mapQueryStringFieldToDbField = (type, queryField) => {
297
+ if (!(supportedMappings[type] && supportedMappings[type][queryField.name])) {
298
+ log.warn(`No db mapping field found for type: ${type}, field ${JSON.stringify(queryField)}`);
299
+ return undefined;
300
+ }
301
+ return supportedMappings[type] && supportedMappings[type][queryField.name](queryField.value);
302
+ };
303
+ exports.mapQueryStringFieldToDbField = mapQueryStringFieldToDbField;
304
+ //# sourceMappingURL=field-mapping.js.map
@@ -0,0 +1,10 @@
1
+ import { DbQueryParameters, QueryStringParameters } from '../types/search';
2
+ /**
3
+ * Convert api query string parameters to db query parameters
4
+ *
5
+ * @param type - query record type
6
+ * @param queryStringParameters - query string parameters
7
+ * @returns db query parameters
8
+ */
9
+ export declare const convertQueryStringToDbQueryParameters: (type: string, queryStringParameters: QueryStringParameters) => DbQueryParameters;
10
+ //# sourceMappingURL=queries.d.ts.map