@outliant/sunrise-utils 1.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 (40) hide show
  1. package/.eslintignore +5 -0
  2. package/.eslintrc +53 -0
  3. package/.github/workflows/pr-dev-workflow.yml +16 -0
  4. package/.nvmrc +1 -0
  5. package/.release-it.js +26 -0
  6. package/.vscode/launch.json +24 -0
  7. package/.vscode/settings.json +7 -0
  8. package/CHANGELOG.md +12 -0
  9. package/README.md +19 -0
  10. package/changelog.hbs +13 -0
  11. package/helpers/es.js +6 -0
  12. package/helpers/projectFilter/projectFieldFilter.js +587 -0
  13. package/helpers/searchFilter.js +86 -0
  14. package/helpers/taskFilter/criticalPathFilter.js +144 -0
  15. package/helpers/taskFilter/index.js +228 -0
  16. package/helpers/taskFilter/onHoldFilter.js +101 -0
  17. package/helpers/taskFilter/ownerFilter.js +148 -0
  18. package/helpers/taskFilter/pathAgeFilter.js +18 -0
  19. package/helpers/taskFilter/regionFilter.js +82 -0
  20. package/helpers/taskFilter/statusFilter.js +177 -0
  21. package/helpers/taskFilter/taskFieldFilter.js +309 -0
  22. package/helpers/taskSortScript.js +356 -0
  23. package/index.d.ts +11 -0
  24. package/index.js +9 -0
  25. package/lib/fieldConditions.js +166 -0
  26. package/lib/logger.js +48 -0
  27. package/lib/taskPipeline.js +137 -0
  28. package/package.json +73 -0
  29. package/test/helpers/projectFilter/projectFieldFilter.spec.js +881 -0
  30. package/test/helpers/taskFilter/criticalPathFilter.spec.js +174 -0
  31. package/test/helpers/taskFilter/index.spec.js +339 -0
  32. package/test/helpers/taskFilter/onHoldFilter.spec.js +112 -0
  33. package/test/helpers/taskFilter/ownerFilter.spec.js +226 -0
  34. package/test/helpers/taskFilter/pathAgeFilter.spec.js +47 -0
  35. package/test/helpers/taskFilter/regionFilter.spec.js +131 -0
  36. package/test/helpers/taskFilter/statusFilter.spec.js +197 -0
  37. package/test/helpers/taskFilter/taskFieldFilter.spec.js +355 -0
  38. package/test/lib/fieldConditions.spec.js +17 -0
  39. package/test/lib/logger.spec.js +117 -0
  40. package/test/lib/taskPipeline.spec.js +162 -0
@@ -0,0 +1,587 @@
1
+ const moment = require('moment');
2
+ const { escapeElasticQuery } = require('../es');
3
+
4
+ module.exports = (filter) => {
5
+ switch (filter.condition) {
6
+ case 'is_empty':
7
+ return module.exports.isEmpty(filter);
8
+ case 'is_not_empty':
9
+ return module.exports.isNotEmpty(filter);
10
+ case 'is_any_of':
11
+ return module.exports.isAnyOf(filter);
12
+ case 'is_equal':
13
+ return module.exports.isEqual(filter);
14
+ case 'not_equal':
15
+ return module.exports.isNotEqual(filter);
16
+ case 'before':
17
+ case 'less_than':
18
+ return module.exports.isBefore(filter);
19
+ case 'after':
20
+ case 'greater_than':
21
+ return module.exports.isAfter(filter);
22
+ case 'less_than_or_equal':
23
+ return module.exports.isBeforeIncludeParam(filter);
24
+ case 'greater_than_or_equal':
25
+ return module.exports.isAfterIncludeParam(filter);
26
+ case 'between':
27
+ return module.exports.isBetween(filter);
28
+ default:
29
+ return null;
30
+ }
31
+ };
32
+
33
+ module.exports.isAnyOf = (filter) => {
34
+ let value = filter.value;
35
+
36
+ if (typeof value === 'string') {
37
+ value = value.split(';');
38
+ }
39
+
40
+ if (filter.field_id) {
41
+ return {
42
+ nested: {
43
+ path: 'fields',
44
+ query: {
45
+ bool: {
46
+ must: [
47
+ {
48
+ term: {
49
+ 'fields.id': {
50
+ value: filter.field_id
51
+ }
52
+ }
53
+ },
54
+ {
55
+ terms: {
56
+ 'fields.text': value
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ };
64
+ }
65
+
66
+ return {
67
+ terms: {
68
+ [filter.type]: value
69
+ }
70
+ };
71
+ };
72
+
73
+ module.exports.isEmpty = (filter) => {
74
+ if (!!filter.field_id) {
75
+ return {
76
+ bool: {
77
+ should: [
78
+ {
79
+ bool: {
80
+ must: [
81
+ {
82
+ nested: {
83
+ path: 'fields',
84
+ query: {
85
+ bool: {
86
+ must: [
87
+ {
88
+ term: {
89
+ 'fields.id': {
90
+ value: filter.field_id
91
+ }
92
+ }
93
+ },
94
+ {
95
+ term: {
96
+ 'fields.text': {
97
+ value: ''
98
+ }
99
+ }
100
+ }
101
+ ]
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ },
109
+ {
110
+ bool: {
111
+ must_not: [
112
+ {
113
+ nested: {
114
+ path: 'fields',
115
+ query: {
116
+ bool: {
117
+ must: [
118
+ {
119
+ term: {
120
+ 'fields.id': {
121
+ value: filter.field_id
122
+ }
123
+ }
124
+ }
125
+ ]
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ]
131
+ }
132
+ }
133
+ ]
134
+ }
135
+ };
136
+ }
137
+
138
+ return null;
139
+ };
140
+
141
+ module.exports.isNotEmpty = (filter) => {
142
+ if (!!filter.field_id) {
143
+ return {
144
+ bool: {
145
+ must: [
146
+ {
147
+ nested: {
148
+ path: 'fields',
149
+ query: {
150
+ bool: {
151
+ must: [
152
+ {
153
+ term: {
154
+ 'fields.id': {
155
+ value: filter.field_id
156
+ }
157
+ }
158
+ },
159
+ {
160
+ exists: {
161
+ field: 'fields.text'
162
+ }
163
+ },
164
+ {
165
+ bool: {
166
+ must_not: [
167
+ {
168
+ term: {
169
+ 'fields.text': {
170
+ value: ''
171
+ }
172
+ }
173
+ }
174
+ ]
175
+ }
176
+ }
177
+ ]
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ]
183
+ }
184
+ };
185
+ }
186
+
187
+ return null;
188
+ };
189
+
190
+ module.exports.isEqual = (filter) => {
191
+ if (!!filter.field_id) {
192
+ let mappingValue = {
193
+ term: {
194
+ 'fields.text': {
195
+ value: filter.value
196
+ }
197
+ }
198
+ };
199
+
200
+ if (filter.field_type === 'date') {
201
+ const startOfDay = moment(filter.value).startOf('day').valueOf();
202
+ const endOfDay = moment(filter.value).endOf('day').valueOf();
203
+
204
+ mappingValue = {
205
+ range: {
206
+ 'fields.number': {
207
+ gte: startOfDay,
208
+ lte: endOfDay
209
+ }
210
+ }
211
+ };
212
+ } else if (filter.field_type === 'number') {
213
+ mappingValue = {
214
+ term: {
215
+ 'fields.number': {
216
+ value: parseInt(filter.value)
217
+ }
218
+ }
219
+ };
220
+ } else if (filter.field_type === 'textarea') {
221
+ mappingValue = {
222
+ query_string: {
223
+ fields: ['fields.text'],
224
+ query: `*${escapeElasticQuery(filter.value.trim())}*`
225
+ }
226
+ };
227
+ } else if (filter.field_type === 'checkbox') {
228
+ let value = filter.value;
229
+
230
+ if (typeof value === 'string') {
231
+ value = value.split(';');
232
+ }
233
+
234
+ mappingValue = {
235
+ bool: {
236
+ should: value.map((x) => {
237
+ return {
238
+ match: {
239
+ 'fields.text.analyzed': {
240
+ query: x,
241
+ operator: 'and'
242
+ }
243
+ }
244
+ };
245
+ })
246
+ }
247
+ };
248
+ }
249
+
250
+ return {
251
+ bool: {
252
+ must: [
253
+ {
254
+ nested: {
255
+ path: 'fields',
256
+ query: {
257
+ bool: {
258
+ must: [
259
+ {
260
+ term: {
261
+ 'fields.id': {
262
+ value: filter.field_id
263
+ }
264
+ }
265
+ },
266
+ mappingValue
267
+ ]
268
+ }
269
+ }
270
+ }
271
+ }
272
+ ]
273
+ }
274
+ };
275
+ }
276
+
277
+ return null;
278
+ };
279
+
280
+ module.exports.isBefore = (filter) => {
281
+ if (!!filter.field_id) {
282
+ let param;
283
+ if (filter.field_type === 'date') {
284
+ param = moment(filter.value).startOf('day').valueOf();
285
+ } else if (filter.field_type === 'number') {
286
+ param = parseInt(filter.value);
287
+ } else {
288
+ return null;
289
+ }
290
+
291
+ return {
292
+ bool: {
293
+ must: [
294
+ {
295
+ nested: {
296
+ path: 'fields',
297
+ query: {
298
+ bool: {
299
+ must: [
300
+ {
301
+ term: {
302
+ 'fields.id': {
303
+ value: filter.field_id
304
+ }
305
+ }
306
+ },
307
+ {
308
+ range: {
309
+ 'fields.number': {
310
+ lt: param
311
+ }
312
+ }
313
+ }
314
+ ]
315
+ }
316
+ }
317
+ }
318
+ }
319
+ ]
320
+ }
321
+ };
322
+ }
323
+
324
+ return null;
325
+ };
326
+
327
+ module.exports.isAfter = (filter) => {
328
+ if (!!filter.field_id) {
329
+ let param;
330
+ if (filter.field_type === 'date') {
331
+ param = moment(filter.value).endOf('day').valueOf();
332
+ } else if (filter.field_type === 'number') {
333
+ param = parseInt(filter.value);
334
+ } else {
335
+ return null;
336
+ }
337
+
338
+ return {
339
+ bool: {
340
+ must: [
341
+ {
342
+ nested: {
343
+ path: 'fields',
344
+ query: {
345
+ bool: {
346
+ must: [
347
+ {
348
+ term: {
349
+ 'fields.id': {
350
+ value: filter.field_id
351
+ }
352
+ }
353
+ },
354
+ {
355
+ range: {
356
+ 'fields.number': {
357
+ gt: param
358
+ }
359
+ }
360
+ }
361
+ ]
362
+ }
363
+ }
364
+ }
365
+ }
366
+ ]
367
+ }
368
+ };
369
+ }
370
+
371
+ return null;
372
+ };
373
+
374
+ module.exports.isBeforeIncludeParam = (filter) => {
375
+ if (!!filter.field_id) {
376
+ let param;
377
+ if (filter.field_type === 'date') {
378
+ param = moment(filter.value).endOf('day').valueOf();
379
+ } else if (filter.field_type === 'number') {
380
+ param = parseInt(filter.value);
381
+ } else {
382
+ return null;
383
+ }
384
+
385
+ return {
386
+ bool: {
387
+ must: [
388
+ {
389
+ nested: {
390
+ path: 'fields',
391
+ query: {
392
+ bool: {
393
+ must: [
394
+ {
395
+ term: {
396
+ 'fields.id': {
397
+ value: filter.field_id
398
+ }
399
+ }
400
+ },
401
+ {
402
+ range: {
403
+ 'fields.number': {
404
+ lte: param
405
+ }
406
+ }
407
+ }
408
+ ]
409
+ }
410
+ }
411
+ }
412
+ }
413
+ ]
414
+ }
415
+ };
416
+ }
417
+
418
+ return null;
419
+ };
420
+
421
+ module.exports.isAfterIncludeParam = (filter) => {
422
+ if (!!filter.field_id) {
423
+ let param;
424
+ if (filter.field_type === 'date') {
425
+ param = moment(filter.value).startOf('day').valueOf();
426
+ } else if (filter.field_type === 'number') {
427
+ param = parseInt(filter.value);
428
+ } else {
429
+ return null;
430
+ }
431
+
432
+ return {
433
+ bool: {
434
+ must: [
435
+ {
436
+ nested: {
437
+ path: 'fields',
438
+ query: {
439
+ bool: {
440
+ must: [
441
+ {
442
+ term: {
443
+ 'fields.id': {
444
+ value: filter.field_id
445
+ }
446
+ }
447
+ },
448
+ {
449
+ range: {
450
+ 'fields.number': {
451
+ gte: param
452
+ }
453
+ }
454
+ }
455
+ ]
456
+ }
457
+ }
458
+ }
459
+ }
460
+ ]
461
+ }
462
+ };
463
+ }
464
+
465
+ return null;
466
+ };
467
+
468
+ module.exports.isBetween = (filter) => {
469
+ if (!!filter.field_id) {
470
+ let firstParam;
471
+ let secondParam;
472
+
473
+ if (filter.field_type === 'date') {
474
+ firstParam = moment(filter.value).startOf('day').valueOf();
475
+ secondParam = moment(filter.second_value).endOf('day').valueOf();
476
+ } else if (filter.field_type === 'number') {
477
+ firstParam = parseInt(filter.value);
478
+ secondParam = parseInt(filter.second_value);
479
+ } else {
480
+ return null;
481
+ }
482
+
483
+ return {
484
+ bool: {
485
+ must: [
486
+ {
487
+ nested: {
488
+ path: 'fields',
489
+ query: {
490
+ bool: {
491
+ must: [
492
+ {
493
+ term: {
494
+ 'fields.id': {
495
+ value: filter.field_id
496
+ }
497
+ }
498
+ },
499
+ {
500
+ range: {
501
+ 'fields.number': {
502
+ gte: firstParam,
503
+ lte: secondParam
504
+ }
505
+ }
506
+ }
507
+ ]
508
+ }
509
+ }
510
+ }
511
+ }
512
+ ]
513
+ }
514
+ };
515
+ }
516
+
517
+ return null;
518
+ };
519
+
520
+ module.exports.isNotEqual = (filter) => {
521
+ if (!!filter.field_id) {
522
+ let mappingValue = {
523
+ term: {
524
+ 'fields.text': {
525
+ value: filter.value
526
+ }
527
+ }
528
+ };
529
+
530
+ if (filter.field_type === 'date') {
531
+ const startOfDay = moment(filter.value).startOf('day').valueOf();
532
+ const endOfDay = moment(filter.value).endOf('day').valueOf();
533
+
534
+ mappingValue = {
535
+ range: {
536
+ 'fields.number': {
537
+ gte: startOfDay,
538
+ lte: endOfDay
539
+ }
540
+ }
541
+ };
542
+ } else if (filter.field_type === 'number') {
543
+ mappingValue = {
544
+ term: {
545
+ 'fields.number': {
546
+ value: parseInt(filter.value)
547
+ }
548
+ }
549
+ };
550
+ }
551
+
552
+ return {
553
+ bool: {
554
+ must: [
555
+ {
556
+ nested: {
557
+ path: 'fields',
558
+ query: {
559
+ bool: {
560
+ must: [
561
+ {
562
+ term: {
563
+ 'fields.id': {
564
+ value: filter.field_id
565
+ }
566
+ }
567
+ }
568
+ ]
569
+ }
570
+ }
571
+ }
572
+ }
573
+ ],
574
+ must_not: [
575
+ {
576
+ nested: {
577
+ path: 'fields',
578
+ query: mappingValue
579
+ }
580
+ }
581
+ ]
582
+ }
583
+ };
584
+ }
585
+
586
+ return null;
587
+ };
@@ -0,0 +1,86 @@
1
+ const { validate: isUuid } = require('uuid');
2
+ const { escapeElasticQuery } = require('./es');
3
+
4
+ module.exports = (query = {}, searchFields = []) => {
5
+ const search = query.search;
6
+ let searchString = search.trim();
7
+
8
+ const parsedQuery = escapeElasticQuery(searchString);
9
+ /* eslint-disable no-useless-escape */
10
+ const isQueryQuoteWrapped = /^\"(.*)\"$/.test(searchString);
11
+
12
+ if (isQueryQuoteWrapped) {
13
+ searchString = searchString.replace(/"/g, '');
14
+ }
15
+
16
+ const should = [];
17
+ const projectFields = searchFields.filter((x) => isUuid(x));
18
+ const localFields = searchFields.filter((x) => x !== '_id' && !isUuid(x));
19
+
20
+ if (searchFields.includes('_id')) {
21
+ should.push({
22
+ term: {
23
+ _id: {
24
+ value: searchString
25
+ }
26
+ }
27
+ });
28
+ }
29
+
30
+ if (localFields.length > 0) {
31
+ if (isQueryQuoteWrapped) {
32
+ should.push({
33
+ multi_match: {
34
+ query: searchString,
35
+ fields: localFields,
36
+ operator: 'and'
37
+ }
38
+ });
39
+ } else {
40
+ should.push({
41
+ query_string: {
42
+ fields: localFields,
43
+ query: `${parsedQuery} OR *${parsedQuery.toLowerCase()}* OR *${parsedQuery.toUpperCase()}*`
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ if (projectFields.length > 0) {
50
+ for (let i = 0; i < projectFields.length; i++) {
51
+ should.push({
52
+ function_score: {
53
+ query: {
54
+ nested: {
55
+ path: 'fields',
56
+ query: {
57
+ bool: {
58
+ must: [
59
+ {
60
+ term: {
61
+ 'fields.id': {
62
+ value: projectFields[i]
63
+ }
64
+ }
65
+ },
66
+ {
67
+ match: {
68
+ 'fields.text.analyzed': {
69
+ query: searchString,
70
+ operator: isQueryQuoteWrapped ? 'and' : 'or'
71
+ }
72
+ }
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ }
78
+ },
79
+ min_score: 15
80
+ }
81
+ });
82
+ }
83
+ }
84
+
85
+ return should;
86
+ };