@outliant/sunrise-utils 1.0.10 → 1.1.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/CHANGELOG.md CHANGED
@@ -4,9 +4,25 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [1.1.0](https://github.com/outliant/sunrise-utils/compare/1.0.11...1.1.0)
8
+
9
+ - chore: support task age color #860q9c397 [`50d79a7`](https://github.com/outliant/sunrise-utils/commit/50d79a78e309c70d7eac0f5533686bd65c1167a7)
10
+
11
+ #### [1.0.11](https://github.com/outliant/sunrise-utils/compare/1.0.10...1.0.11)
12
+
13
+ > 31 March 2023
14
+
15
+ - (task/additional date filters #860q65z6h [`#4`](https://github.com/outliant/sunrise-utils/pull/4)
16
+ - chore: add last n days [`bb2f94a`](https://github.com/outliant/sunrise-utils/commit/bb2f94aade4ed11845ca29cf7f9b970cac51ae42)
17
+ - chore: add new filters for task [`3fa42e5`](https://github.com/outliant/sunrise-utils/commit/3fa42e52bc6076dfd35272b91558d9696729cfbf)
18
+ - chore(release): 1.0.11 [`8795194`](https://github.com/outliant/sunrise-utils/commit/879519458081a5c38786c9ba344748883a026796)
19
+
7
20
  #### [1.0.10](https://github.com/outliant/sunrise-utils/compare/1.0.9...1.0.10)
8
21
 
22
+ > 30 March 2023
23
+
9
24
  - chore: added created and updated on project list columns #860qbm263 [`8f664f1`](https://github.com/outliant/sunrise-utils/commit/8f664f117644d97e5338f11c0f4e1a4396375b07)
25
+ - chore(release): 1.0.10 [`08ef0d6`](https://github.com/outliant/sunrise-utils/commit/08ef0d6b47ff16e853249d21d5d0b0a962caf44b)
10
26
 
11
27
  #### [1.0.9](https://github.com/outliant/sunrise-utils/compare/1.0.8...1.0.9)
12
28
 
@@ -55,6 +55,10 @@ module.exports = (filter) => {
55
55
  return module.exports.isAfterIncludeParam(filter);
56
56
  case 'between':
57
57
  return module.exports.isBetween(filter);
58
+ case 'next_n_days':
59
+ return module.exports.nextNDays(filter);
60
+ case 'last_n_days':
61
+ return module.exports.lastNDays(filter);
58
62
  default:
59
63
  return null;
60
64
  }
@@ -495,6 +499,111 @@ module.exports.isAfter = (filter) => {
495
499
  return null;
496
500
  };
497
501
 
502
+ module.exports.nextNDays = (filter) => {
503
+ if (!!filter.field_id) {
504
+ if (isNaN(filter.value)) {
505
+ return null;
506
+ }
507
+
508
+ const today = moment()
509
+ .startOf('day');
510
+
511
+ const nextNDays = moment()
512
+ .add(filter.value, 'd')
513
+ .endOf('day');
514
+
515
+ const res = {
516
+ bool: {
517
+ must: [
518
+ {
519
+ nested: {
520
+ path: 'fields',
521
+ query: {
522
+ bool: {
523
+ must: [
524
+ {
525
+ term: {
526
+ 'fields.id': {
527
+ value: filter.field_id
528
+ }
529
+ }
530
+ },
531
+ {
532
+ range: {
533
+ 'fields.number': {
534
+ gt: today.valueOf(),
535
+ lt: nextNDays.valueOf()
536
+ }
537
+ }
538
+ }
539
+ ]
540
+ }
541
+ }
542
+ }
543
+ }
544
+ ]
545
+ }
546
+ };
547
+
548
+ return res;
549
+ }
550
+
551
+ return null;
552
+ };
553
+
554
+ module.exports.lastNDays = (filter) => {
555
+ if (!!filter.field_id) {
556
+ if (isNaN(filter.value)) {
557
+ return null;
558
+ }
559
+
560
+ const today = moment()
561
+ .startOf('day');
562
+
563
+ const lastNDays = moment()
564
+ .subtract(filter.value, 'd')
565
+ .endOf('day');
566
+
567
+ const res = {
568
+ bool: {
569
+ must: [
570
+ {
571
+ nested: {
572
+ path: 'fields',
573
+ query: {
574
+ bool: {
575
+ must: [
576
+ {
577
+ term: {
578
+ 'fields.id': {
579
+ value: filter.field_id
580
+ }
581
+ }
582
+ },
583
+ {
584
+ range: {
585
+ 'fields.number': {
586
+ lt: today.valueOf(),
587
+ gt: lastNDays.valueOf()
588
+ }
589
+ }
590
+ }
591
+ ]
592
+ }
593
+ }
594
+ }
595
+ }
596
+ ]
597
+ }
598
+ };
599
+
600
+ console.log(JSON.stringify(res, null, 2));
601
+ return res;
602
+ }
603
+
604
+ return null;
605
+ };
606
+
498
607
  module.exports.isBeforeIncludeParam = (filter) => {
499
608
  if (!!filter.field_id) {
500
609
  let param;
@@ -0,0 +1,17 @@
1
+ module.exports.taskAgeColorRangeMap = {
2
+ green: {
3
+ min: 0,
4
+ max: 5
5
+ },
6
+ yellow: {
7
+ min: 6,
8
+ max: 10
9
+ },
10
+ red: {
11
+ min: 11,
12
+ max: 30
13
+ },
14
+ black: {
15
+ min: 31
16
+ }
17
+ };
@@ -0,0 +1,45 @@
1
+ const { taskAgeColorRangeMap } = require('../task');
2
+
3
+ module.exports = (filter) => {
4
+ switch (filter.condition) {
5
+ case 'is_equal':
6
+ return module.exports.isEqual(filter);
7
+ default:
8
+ return null;
9
+ }
10
+ };
11
+
12
+ module.exports.isEqual = (filter) => {
13
+ const range = taskAgeColorRangeMap[filter.value];
14
+ if (!range) {
15
+ return null;
16
+ }
17
+
18
+ return {
19
+ script: {
20
+ script: {
21
+ source: `
22
+ try {
23
+ def now = new Date().getTime();
24
+ def readyDate = doc['ready_at'].value.millis;
25
+
26
+ long elapsedDays = (now - readyDate)/(1000 * 3600 * 24);
27
+ boolean valid = elapsedDays >= params.min;
28
+
29
+ if (params.containsKey('max')) {
30
+ valid = valid && elapsedDays <= params.max;
31
+ }
32
+
33
+ return valid;
34
+ } catch(Exception err){
35
+ return false;
36
+ }`,
37
+ lang: 'painless',
38
+ params: {
39
+ min: range.min,
40
+ max: range.max
41
+ }
42
+ }
43
+ }
44
+ };
45
+ };
@@ -5,6 +5,7 @@ const buildRegionFilter = require('./regionFilter');
5
5
  const buildTaskFieldFilter = require('./taskFieldFilter');
6
6
  const buildCriticalPathFilter = require('./criticalPathFilter');
7
7
  const buildOnHoldFilter = require('./onHoldFilter');
8
+ const buildAgeColorFilter = require('./buildAgeColorFilter');
8
9
  const buildProjectFieldFilter = require('../projectFilter/projectFieldFilter');
9
10
 
10
11
  module.exports.taskStatusOptions = [
@@ -30,6 +31,25 @@ module.exports.taskStatusOptions = [
30
31
  }
31
32
  ];
32
33
 
34
+ module.exports.taskAgeColorOptions = [
35
+ {
36
+ label: 'Green',
37
+ value: 'green'
38
+ },
39
+ {
40
+ label: 'Yellow',
41
+ value: 'yellow'
42
+ },
43
+ {
44
+ label: 'Red',
45
+ value: 'red'
46
+ },
47
+ {
48
+ label: 'Black',
49
+ value: 'black'
50
+ }
51
+ ];
52
+
33
53
  module.exports.defaultCustomColumns = [
34
54
  {
35
55
  name: 'Owner',
@@ -196,6 +216,8 @@ module.exports.buildTaskPipelineFilter = (filter) => {
196
216
  // Always expected as is_equal condition
197
217
  case 'path_age':
198
218
  return buildPathAgeFilter(filter);
219
+ case 'task_age':
220
+ return buildAgeColorFilter(filter);
199
221
  // Always expected as is_equal condition with array of task template ids
200
222
  case 'task_templates':
201
223
  let value = filter.value;
@@ -28,12 +28,15 @@ module.exports = (filter) => {
28
28
  return module.exports.isAfterIncludeParam(filter);
29
29
  case 'between':
30
30
  return module.exports.isBetween(filter);
31
+ case 'next_n_days':
32
+ return module.exports.nextNDays(filter);
33
+ case 'last_n_days':
34
+ return module.exports.lastNDays(filter);
31
35
  default:
32
36
  return null;
33
37
  }
34
38
  };
35
39
 
36
-
37
40
  module.exports.containsAny = (filter) => {
38
41
  /**
39
42
  * Field type is for custom columns
@@ -291,6 +294,61 @@ module.exports.isAfter = (filter) => {
291
294
  };
292
295
  };
293
296
 
297
+ module.exports.nextNDays = (filter) => {
298
+ if (isNaN(filter.value)) {
299
+ return null;
300
+ }
301
+
302
+ const today = moment()
303
+ .startOf('day');
304
+
305
+ const nextNDays = moment()
306
+ .add(filter.value, 'd')
307
+ .endOf('day');
308
+
309
+ return {
310
+ range: {
311
+ [filter.type]: {
312
+ gt: today.valueOf(),
313
+ lt: nextNDays.valueOf()
314
+ }
315
+ }
316
+ };
317
+ };
318
+
319
+ module.exports.lastNDays = (filter) => {
320
+ if (isNaN(filter.value)) {
321
+ return null;
322
+ }
323
+
324
+ const today = moment()
325
+ .startOf('day');
326
+
327
+ const lastNDays = moment()
328
+ .subtract(filter.value, 'd')
329
+ .endOf('day');
330
+
331
+ const info = {
332
+ today: {
333
+ format: today.format('YYYY-MM-DD'),
334
+ value: today.valueOf()
335
+ },
336
+ lastNDays: {
337
+ format: lastNDays.format('YYYY-MM-DD'),
338
+ value: lastNDays.valueOf()
339
+ }
340
+ };
341
+
342
+ return {
343
+ range: {
344
+ [filter.type]: {
345
+ lt: today.valueOf(),
346
+ gt: lastNDays.valueOf()
347
+ }
348
+ }
349
+ };
350
+ };
351
+
294
352
  module.exports.isBeforeIncludeParam = (filter) => {
295
353
  let param;
296
354
 
package/index.d.ts CHANGED
@@ -41,6 +41,7 @@ declare namespace Utils {
41
41
 
42
42
  namespace taskPipeline {
43
43
  export const taskStatusOptions: FilterConditionOption[];
44
+ export const taskAgeColorOptions: FilterConditionOption[];
44
45
  export const defaultCustomColumns: Column[];
45
46
  export function buildFilter(filter: Filter): any;
46
47
  export function buildFiltersQuery(
@@ -51,6 +52,8 @@ declare namespace Utils {
51
52
  searchFields?: string[]
52
53
  ): any;
53
54
  export function buildSortScript(query?: Query, customSort?: any[]): any;
55
+ export function getTaskAge(task: any): any;
56
+ export function getTaskAgeColor(task: any): any;
54
57
  }
55
58
 
56
59
  namespace projectPipeline {
@@ -165,6 +165,14 @@ module.exports.conditions = {
165
165
  {
166
166
  label: 'Is Not Empty',
167
167
  value: 'is_not_empty'
168
+ },
169
+ {
170
+ label: 'In The Next N Days',
171
+ value: 'next_n_days'
172
+ },
173
+ {
174
+ label: 'In The Last N Days',
175
+ value: 'last_n_days'
168
176
  }
169
177
  ]
170
178
  };
@@ -1,16 +1,21 @@
1
1
  const { validate: isUuid } = require('uuid');
2
+ const moment = require('moment');
3
+
2
4
  const {
3
5
  taskStatusOptions,
6
+ taskAgeColorOptions,
4
7
  defaultCustomColumns,
5
8
  defaultTaskFilter,
6
9
  buildTaskPipelineFilter
7
10
  } = require('../helpers/taskFilter');
8
11
  const searchFilter = require('../helpers/searchFilter');
9
12
  const sortScript = require('../helpers/sortScript');
13
+ const taskHelper = require('../helpers/task');
10
14
 
11
15
  class TaskPipeline {
12
16
  taskStatusOptions = taskStatusOptions;
13
17
  defaultCustomColumns = defaultCustomColumns;
18
+ taskAgeColorOptions = taskAgeColorOptions;
14
19
  buildFilter = buildTaskPipelineFilter;
15
20
 
16
21
  buildFiltersQuery(
@@ -127,6 +132,37 @@ class TaskPipeline {
127
132
 
128
133
  return taskPipelinesSort;
129
134
  }
135
+
136
+ getTaskAge(task) {
137
+ if (!task.is_complete && task.is_ready && task.ready_at) {
138
+ return moment().diff(moment(task.ready_at), 'days');
139
+ }
140
+
141
+ return null;
142
+ }
143
+
144
+ getTaskAgeColor(task) {
145
+ const taskAge = this.getTaskAge(task);
146
+
147
+ if (taskAge === null) {
148
+ return null;
149
+ }
150
+
151
+ let ageColor = null;
152
+
153
+ Object.keys(taskHelper.taskAgeColorRangeMap).forEach((color) => {
154
+ const range = taskHelper.taskAgeColorRangeMap[color];
155
+ const isValid =
156
+ taskAge >= range.min &&
157
+ (!range.max || (range.max && taskAge <= range.max));
158
+
159
+ if (!ageColor && isValid) {
160
+ ageColor = color;
161
+ }
162
+ });
163
+
164
+ return ageColor;
165
+ }
130
166
  }
131
167
 
132
168
  module.exports = new TaskPipeline();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@outliant/sunrise-utils",
3
3
  "description": "Helper functions for project Sunrise",
4
- "version": "1.0.10",
4
+ "version": "1.1.0",
5
5
  "license": "ISC",
6
6
  "author": "Outliant",
7
7
  "main": "index.js",
@@ -137,6 +137,28 @@ describe('taskFilter', function () {
137
137
  done();
138
138
  });
139
139
 
140
+ it('should handle task_age filter', (done) => {
141
+ const filter = taskFilter.buildTaskPipelineFilter({
142
+ condition: 'is_equal',
143
+ field_id: '',
144
+ field_type: 'select',
145
+ type: 'task_age',
146
+ value: 'red'
147
+ });
148
+
149
+ expect(filter).to.be.deep.equal({
150
+ script: {
151
+ script: {
152
+ source:
153
+ "\n try {\n def now = new Date().getTime();\n def readyDate = doc['ready_at'].value.millis;\n\n long elapsedDays = (now - readyDate)/(1000 * 3600 * 24);\n boolean valid = elapsedDays >= params.min;\n\n if (params.containsKey('max')) {\n valid = valid && elapsedDays <= params.max;\n }\n\n return valid;\n } catch(Exception err){\n return false;\n }",
154
+ lang: 'painless',
155
+ params: { min: 11, max: 30 }
156
+ }
157
+ }
158
+ });
159
+ done();
160
+ });
161
+
140
162
  it('should handle task_templates array filter', (done) => {
141
163
  const filter = taskFilter.buildTaskPipelineFilter({
142
164
  condition: 'is_equal',
@@ -2,6 +2,7 @@
2
2
 
3
3
  /* eslint-env node, mocha */
4
4
  const { expect } = require('chai');
5
+ const moment = require('moment');
5
6
 
6
7
  // To test
7
8
  const taskPipeline = require('../../lib/taskPipeline');
@@ -377,4 +378,124 @@ describe('taskPipeline', function () {
377
378
  done();
378
379
  });
379
380
  });
381
+
382
+ describe('getTaskAge', () => {
383
+ it('should calculate task age', (done) => {
384
+ const task = {
385
+ is_complete: false,
386
+ is_ready: true,
387
+ ready_at: moment().subtract(2, 'days').valueOf()
388
+ };
389
+
390
+ const age = taskPipeline.getTaskAge(task);
391
+
392
+ expect(age).to.equal(2);
393
+ done();
394
+ });
395
+
396
+ it('should return null if task is completed', (done) => {
397
+ const task = {
398
+ is_complete: true,
399
+ is_ready: true,
400
+ ready_at: moment().subtract(2, 'days').valueOf()
401
+ };
402
+
403
+ const age = taskPipeline.getTaskAge(task);
404
+
405
+ expect(age).to.equal(null);
406
+ done();
407
+ });
408
+
409
+ it('should return null if task is blocked', (done) => {
410
+ const task = {
411
+ is_complete: false,
412
+ is_ready: false,
413
+ ready_at: null
414
+ };
415
+
416
+ const age = taskPipeline.getTaskAge(task);
417
+
418
+ expect(age).to.equal(null);
419
+ done();
420
+ });
421
+ });
422
+
423
+ describe('getTaskAgeColor', () => {
424
+ it('should calculate task age color green', (done) => {
425
+ const testNumbers = [0, 1, 2, 3, 4, 5];
426
+ for (let i = 0; i < testNumbers.length; i++) {
427
+ const testNumber = testNumbers[i];
428
+ const task = {
429
+ is_complete: false,
430
+ is_ready: true,
431
+ ready_at: moment().subtract(testNumber, 'days').valueOf()
432
+ };
433
+ const color = taskPipeline.getTaskAgeColor(task);
434
+
435
+ expect(color).to.equal('green');
436
+ }
437
+ done();
438
+ });
439
+
440
+ it('should calculate task age color yellow', (done) => {
441
+ const testNumbers = [6, 7, 8, 9, 10];
442
+ for (let i = 0; i < testNumbers.length; i++) {
443
+ const testNumber = testNumbers[i];
444
+ const task = {
445
+ is_complete: false,
446
+ is_ready: true,
447
+ ready_at: moment().subtract(testNumber, 'days').valueOf()
448
+ };
449
+ const color = taskPipeline.getTaskAgeColor(task);
450
+
451
+ expect(color).to.equal('yellow');
452
+ }
453
+ done();
454
+ });
455
+
456
+ it('should calculate task age color red', (done) => {
457
+ const testNumbers = [11, 12, 15, 20, 25, 30];
458
+ for (let i = 0; i < testNumbers.length; i++) {
459
+ const testNumber = testNumbers[i];
460
+ const task = {
461
+ is_complete: false,
462
+ is_ready: true,
463
+ ready_at: moment().subtract(testNumber, 'days').valueOf()
464
+ };
465
+ const color = taskPipeline.getTaskAgeColor(task);
466
+
467
+ expect(color).to.equal('red');
468
+ }
469
+ done();
470
+ });
471
+
472
+ it('should calculate task age color black', (done) => {
473
+ const testNumbers = [31, 40, 55, 360];
474
+ for (let i = 0; i < testNumbers.length; i++) {
475
+ const testNumber = testNumbers[i];
476
+ const task = {
477
+ is_complete: false,
478
+ is_ready: true,
479
+ ready_at: moment().subtract(testNumber, 'days').valueOf()
480
+ };
481
+ const color = taskPipeline.getTaskAgeColor(task);
482
+
483
+ expect(color).to.equal('black');
484
+ }
485
+ done();
486
+ });
487
+
488
+ it('should return null if task is not ready', (done) => {
489
+ const task = {
490
+ is_complete: true,
491
+ is_ready: true,
492
+ ready_at: moment().subtract(2, 'days')
493
+ };
494
+
495
+ const age = taskPipeline.getTaskAgeColor(task);
496
+
497
+ expect(age).to.equal(null);
498
+ done();
499
+ });
500
+ });
380
501
  });