@outliant/sunrise-utils 1.1.8 → 1.1.10

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,13 +4,30 @@ 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.10](https://github.com/outliant/sunrise-utils/compare/1.1.9...1.1.10)
8
+
9
+ - chore: add days to complete function [`#15`](https://github.com/outliant/sunrise-utils/pull/15)
10
+ - chore: fix test [`8202713`](https://github.com/outliant/sunrise-utils/commit/8202713cafa50ee4ad6978ffcf588e93e821b429)
11
+ - chore: add type and remove console [`8bb18ae`](https://github.com/outliant/sunrise-utils/commit/8bb18ae2ff3e37fcee5f66f8b57cf7a1d45f3dad)
12
+
13
+ #### [1.1.9](https://github.com/outliant/sunrise-utils/compare/1.1.8...1.1.9)
14
+
15
+ > 3 October 2023
16
+
17
+ - Task/task new columns #85ztbmnkf [`#14`](https://github.com/outliant/sunrise-utils/pull/14)
18
+ - chore: add filter for days to complete [`f2fdf97`](https://github.com/outliant/sunrise-utils/commit/f2fdf977c6b5c5f22c17b3fffb7a6a2024a3c38c)
19
+ - chore: add test [`847deb3`](https://github.com/outliant/sunrise-utils/commit/847deb3d2eebba343b865bb69cb6dc642096160d)
20
+ - chore: fix test [`ad49d34`](https://github.com/outliant/sunrise-utils/commit/ad49d3451ff523426672528f42f65f77f2d05c5f)
21
+
7
22
  #### [1.1.8](https://github.com/outliant/sunrise-utils/compare/1.1.7...1.1.8)
8
23
 
24
+ > 29 September 2023
25
+
9
26
  - chore: use split string helper [`#13`](https://github.com/outliant/sunrise-utils/pull/13)
10
27
  - chore: update logic for user pipeline [`#12`](https://github.com/outliant/sunrise-utils/pull/12)
28
+ - chore(release): 1.1.8 [`aa38c1a`](https://github.com/outliant/sunrise-utils/commit/aa38c1abac2d22fddb329fc18e071881b15837b3)
11
29
  - chore: update users pipeline [`c0de2e6`](https://github.com/outliant/sunrise-utils/commit/c0de2e6bbacbeb1b8b96597473679f7f2424880e)
12
30
  - chore: fix unit test [`84b880d`](https://github.com/outliant/sunrise-utils/commit/84b880d39497aa03257869702ecd0aa9f05b1d77)
13
- - chore: fix test [`d7e1659`](https://github.com/outliant/sunrise-utils/commit/d7e1659d478e1787153deb0c6db79ebdf3105263)
14
31
 
15
32
  #### [1.1.7](https://github.com/outliant/sunrise-utils/compare/1.1.6...1.1.7)
16
33
 
@@ -210,7 +210,7 @@ module.exports.criticalPathStage = (order) => {
210
210
  if (stageIndex != null) {
211
211
  return stageIndex;
212
212
  }
213
-
213
+
214
214
  return 9999999;
215
215
  } catch (Exception err) {
216
216
  return 9999999;
@@ -318,6 +318,39 @@ module.exports.projectFieldNumeric = (order, value) => {
318
318
  };
319
319
  };
320
320
 
321
+ module.exports.daysToComplete = (order) => {
322
+ return {
323
+ _script: {
324
+ type: 'number',
325
+ script: {
326
+ lang: 'painless',
327
+ source: `
328
+ try {
329
+ def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;
330
+ def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;
331
+
332
+ if (isEmptyReadyAt || isEmptyCompletedAt) {
333
+ return -999999;
334
+ }
335
+
336
+ def readyAt = doc['ready_at'].value.toInstant().toEpochMilli();
337
+ def completedAt = doc['completed_at'].value.toInstant().toEpochMilli();
338
+
339
+ if (readyAt == null || completedAt == null) {
340
+ return -999999;
341
+ }
342
+
343
+ return completedAt - readyAt;
344
+ } catch (Exception err) {
345
+ return -999999;
346
+ }
347
+ `
348
+ },
349
+ order
350
+ }
351
+ };
352
+ };
353
+
321
354
  module.exports.projectFieldText = (order, value) => {
322
355
  return {
323
356
  _script: {
@@ -0,0 +1,233 @@
1
+ const _ = require('lodash');
2
+
3
+ const commonOperatorTemplate = (conditionCallback) => {
4
+ const condition = conditionCallback({
5
+ daysToComplete: 'daysToComplete',
6
+ value: 'params.value',
7
+ secondValue: 'params.second_value'
8
+ });
9
+
10
+ return `
11
+ try {
12
+ def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;
13
+ def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;
14
+
15
+ if (isEmptyReadyAt || isEmptyCompletedAt) {
16
+ return false;
17
+ }
18
+
19
+ def readyAt = doc['ready_at'].value
20
+ .toInstant()
21
+ .atZone(ZoneId.of('UTC'))
22
+ .toLocalDate()
23
+ .atStartOfDay(ZoneId.of('UTC'))
24
+ .toInstant()
25
+ .toEpochMilli();
26
+
27
+ def completedAt = doc['completed_at'].value
28
+ .toInstant()
29
+ .atZone(ZoneId.of('UTC'))
30
+ .toLocalDate()
31
+ .atStartOfDay(ZoneId.of('UTC'))
32
+ .toInstant()
33
+ .toEpochMilli();
34
+
35
+ if (readyAt == null || completedAt == null) {
36
+ return false;
37
+ }
38
+
39
+ def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);
40
+
41
+ return ${condition}
42
+ } catch (Exception err) {
43
+ return false;
44
+ }
45
+ `;
46
+ };
47
+
48
+ module.exports = (filter) => {
49
+ filter.value = _.parseInt(filter.value);
50
+ filter.second_value = _.parseInt(filter.second_value);
51
+
52
+ switch (filter.condition) {
53
+ case 'is_equal':
54
+ return module.exports.isEqual(filter);
55
+ case 'not_equal':
56
+ return module.exports.isNotEqual(filter);
57
+ case 'less_than':
58
+ return module.exports.lessThan(filter);
59
+ case 'less_than_or_equal':
60
+ return module.exports.lessThanOrEqual(filter);
61
+ case 'greater_than':
62
+ return module.exports.greaterThan(filter);
63
+ case 'greater_than_or_equal':
64
+ return module.exports.greaterThanOrEqual(filter);
65
+ case 'between':
66
+ return module.exports.between(filter);
67
+ case 'is_empty':
68
+ return module.exports.isEmpty(filter);
69
+ case 'is_not_empty':
70
+ return module.exports.isNotEmpty(filter);
71
+ default:
72
+ return null;
73
+ }
74
+ };
75
+
76
+ module.exports.isEqual = (filter) => {
77
+ return {
78
+ script: {
79
+ script: {
80
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
81
+ return `${daysToComplete} == ${value}`;
82
+ }),
83
+ lang: "painless",
84
+ params: {
85
+ value: filter.value,
86
+ }
87
+ }
88
+ }
89
+ };
90
+
91
+ };
92
+
93
+ module.exports.isNotEqual = (filter) => {
94
+ return {
95
+ script: {
96
+ script: {
97
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
98
+ return `${daysToComplete} != ${value}`;
99
+ }),
100
+ lang: "painless",
101
+ params: {
102
+ value: filter.value,
103
+ }
104
+ }
105
+ }
106
+ };
107
+
108
+ };
109
+
110
+ module.exports.greaterThan = (filter) => {
111
+ return {
112
+ script: {
113
+ script: {
114
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
115
+ return `${daysToComplete} > ${value}`;
116
+ }),
117
+ lang: "painless",
118
+ params: {
119
+ value: filter.value,
120
+ }
121
+ }
122
+ }
123
+ };
124
+ };
125
+
126
+ module.exports.greaterThanOrEqual = (filter) => {
127
+ return {
128
+ script: {
129
+ script: {
130
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
131
+ return `${daysToComplete} >= ${value}`;
132
+ }),
133
+ lang: "painless",
134
+ params: {
135
+ value: filter.value,
136
+ }
137
+ }
138
+ }
139
+ };
140
+ };
141
+
142
+ module.exports.lessThan = (filter) => {
143
+ return {
144
+ script: {
145
+ script: {
146
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
147
+ return `${daysToComplete} < ${value}`;
148
+ }),
149
+ lang: "painless",
150
+ params: {
151
+ value: filter.value,
152
+ }
153
+ }
154
+ }
155
+ };
156
+ };
157
+
158
+ module.exports.lessThanOrEqual = (filter) => {
159
+ return {
160
+ script: {
161
+ script: {
162
+ source: commonOperatorTemplate(({ daysToComplete, value }) => {
163
+ return `${daysToComplete} <= ${value}`;
164
+ }),
165
+ lang: "painless",
166
+ params: {
167
+ value: filter.value,
168
+ second_value: filter.second_value
169
+ }
170
+ }
171
+ }
172
+ };
173
+ };
174
+
175
+ module.exports.between = (filter) => {
176
+ return {
177
+ script: {
178
+ script: {
179
+ source: commonOperatorTemplate(({ daysToComplete, value, secondValue }) => {
180
+ return `
181
+ ${daysToComplete} >= ${value} &&
182
+ ${daysToComplete} <= ${secondValue}
183
+ `;
184
+ }),
185
+ lang: "painless",
186
+ params: {
187
+ value: filter.value,
188
+ second_value: filter.second_value
189
+ }
190
+ }
191
+ }
192
+ };
193
+ };
194
+
195
+ module.exports.isEmpty = () => {
196
+ return {
197
+ script: {
198
+ script: {
199
+ source: `
200
+ try {
201
+ def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;
202
+ def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;
203
+
204
+ return isEmptyReadyAt || isEmptyCompletedAt;
205
+ } catch (Exception err) {
206
+ return false;
207
+ }
208
+ `,
209
+ lang: "painless"
210
+ }
211
+ }
212
+ };
213
+ };
214
+
215
+ module.exports.isNotEmpty = () => {
216
+ return {
217
+ script: {
218
+ script: {
219
+ source: `
220
+ try {
221
+ def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;
222
+ def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;
223
+
224
+ return !isEmptyReadyAt && !isEmptyCompletedAt;
225
+ } catch (Exception err) {
226
+ return false;
227
+ }
228
+ `,
229
+ lang: "painless"
230
+ }
231
+ }
232
+ };
233
+ };
@@ -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 buildDaysToCompleteFilter = require('./daysToComplete');
8
9
  const buildAgeColorFilter = require('./buildAgeColorFilter');
9
10
  const buildProjectFieldFilter = require('../projectFilter/projectFieldFilter');
10
11
  const { PROJECT_BANNERS } = require('../../constants');
@@ -66,6 +67,18 @@ module.exports.defaultCustomColumns = [
66
67
  type: 'ready_at',
67
68
  field_type: 'date'
68
69
  },
70
+ {
71
+ name: 'Days to Complete',
72
+ value: 'days_to_complete',
73
+ type: 'days_to_complete',
74
+ field_type: 'number'
75
+ },
76
+ {
77
+ name: 'Pending Date',
78
+ value: 'pending_date',
79
+ type: 'pending_date',
80
+ field_type: 'date'
81
+ },
69
82
  {
70
83
  name: 'Project ID',
71
84
  value: 'project_id',
@@ -222,11 +235,18 @@ module.exports.buildTaskPipelineFilter = (filter) => {
222
235
  return buildCriticalPathFilter(filter);
223
236
  case 'on_hold':
224
237
  return buildOnHoldFilter(filter);
238
+ case 'days_to_complete':
239
+ return buildDaysToCompleteFilter(filter);
225
240
  // Always expected as is_equal condition
226
241
  case 'path_age':
227
242
  return buildPathAgeFilter(filter);
228
243
  case 'task_age':
229
244
  return buildAgeColorFilter(filter);
245
+ case 'pending_date':
246
+ return buildTaskFieldFilter({
247
+ ...filter,
248
+ type: 'ready_at'
249
+ });
230
250
  // Always expected as is_equal condition with array of task template ids
231
251
  case 'task_templates':
232
252
  let value = filter.value;
package/index.d.ts CHANGED
@@ -54,6 +54,7 @@ declare namespace Utils {
54
54
  export function buildSortScript(query?: Query, customSort?: any[]): any;
55
55
  export function getTaskAge(task: any): any;
56
56
  export function getTaskAgeColor(task: any): any;
57
+ export function getDaysToComplete(task: any): undefined | number;
57
58
  }
58
59
 
59
60
  namespace projectPipeline {
@@ -98,30 +98,61 @@ class TaskPipeline {
98
98
  } else {
99
99
  sortByMultiple.forEach((s, i) => {
100
100
  const sort = sortMultiple[i] || 'desc';
101
- if (s === 'region') {
102
- taskPipelinesSort.push(sortScript.region(sort));
103
- } else if (s === 'owner' || s === 'completed_by') {
104
- taskPipelinesSort.push(sortScript.owner(sort));
105
- } else if (s === 'status') {
106
- taskPipelinesSort.push(sortScript.status(sort));
107
- } else if (s === 'critical_path_stage') {
108
- taskPipelinesSort.push(sortScript.criticalPathStage(sort));
109
- } else if (s === 'project_id') {
110
- // Sort numeric part of the Project ID
111
- taskPipelinesSort.push(sortScript.projectId(sort));
112
- } else if (s === 'ready_at') {
113
- taskPipelinesSort.push(sortScript.readyAt(sort));
114
- } else if (s === 'was_marked_incomplete') {
115
- taskPipelinesSort.push(sortScript.wasMarkedIncomplete(sort));
116
- } else if (s === 'last_comment_date') {
117
- taskPipelinesSort.push(sortScript.lastCommentDate(sort));
118
- } else if (isUuid(s)) {
119
- taskPipelinesSort.push(sortScript.projectFieldNumeric(sort, s));
120
- taskPipelinesSort.push(sortScript.projectFieldText(sort, s));
121
- } else {
122
- taskPipelinesSort.push({
123
- [s]: { order: sort, missing: '_last' }
124
- });
101
+
102
+ switch (true) {
103
+ case s === 'region':
104
+ taskPipelinesSort.push(sortScript.region(sort));
105
+ break;
106
+
107
+ case s === 'owner' :
108
+ case s === 'completed_by' :
109
+ taskPipelinesSort.push(sortScript.owner(sort));
110
+ break;
111
+
112
+ case s === 'status':
113
+ taskPipelinesSort.push(sortScript.status(sort));
114
+ break;
115
+
116
+ case s === 'critical_path_stage':
117
+ taskPipelinesSort.push(sortScript.criticalPathStage(sort));
118
+ break;
119
+
120
+ case s === 'project_id':
121
+ taskPipelinesSort.push(sortScript.projectId(sort));
122
+ break;
123
+
124
+ case s === 'ready_at':
125
+ taskPipelinesSort.push(sortScript.readyAt(sort));
126
+ break;
127
+
128
+ case s === 'pending_date':
129
+ taskPipelinesSort.push({
130
+ ready_at: { order: sort, missing: '_last' }
131
+ });
132
+ break;
133
+
134
+ case s === 'days_to_complete':
135
+ taskPipelinesSort.push(sortScript.daysToComplete(sort));
136
+ break;
137
+
138
+ case s === 'was_marked_incomplete':
139
+ taskPipelinesSort.push(sortScript.wasMarkedIncomplete(sort));
140
+ break;
141
+
142
+ case s === 'last_comment_date':
143
+ taskPipelinesSort.push(sortScript.lastCommentDate(sort));
144
+ break;
145
+
146
+ case isUuid(s):
147
+ taskPipelinesSort.push(sortScript.projectFieldNumeric(sort, s));
148
+ taskPipelinesSort.push(sortScript.projectFieldText(sort, s));
149
+ break;
150
+
151
+ default:
152
+ taskPipelinesSort.push({
153
+ [s]: { order: sort, missing: '_last' }
154
+ });
155
+ break;
125
156
  }
126
157
  });
127
158
  }
@@ -141,6 +172,21 @@ class TaskPipeline {
141
172
  return null;
142
173
  }
143
174
 
175
+ getDaysToComplete = (task) => {
176
+ if (!task.ready_at || !task.completed_at) {
177
+ return;
178
+ }
179
+
180
+ const readyAt = moment(task.ready_at)
181
+ .utc()
182
+ .startOf('day');
183
+ const completedAt = moment(task.completed_at)
184
+ .utc()
185
+ .startOf('day');
186
+
187
+ return completedAt.diff(readyAt, 'days');
188
+ };
189
+
144
190
  getTaskAgeColor(task) {
145
191
  const taskAge = this.getTaskAge(task);
146
192
 
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.1.8",
4
+ "version": "1.1.10",
5
5
  "license": "ISC",
6
6
  "author": "Outliant",
7
7
  "main": "index.js",
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ /* eslint-env node, mocha */
4
+ const { expect } = require('chai');
5
+
6
+ // To test
7
+ const daysToComplete = require('../../../helpers/taskFilter/daysToComplete');
8
+
9
+ describe('taskFilter/daysToComplete', function () {
10
+ const commonInput = {
11
+ field_id: '',
12
+ type: 'days_to_complete',
13
+ field_type: 'number',
14
+ value: 1,
15
+ second_value: 5
16
+ };
17
+
18
+ const filters = [
19
+ {
20
+ input: {
21
+ ...commonInput,
22
+ condition: 'not_equal'
23
+ },
24
+ expected: {
25
+ script: {
26
+ script: {
27
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return daysToComplete != params.value\n } catch (Exception err) {\n return false;\n }\n ",
28
+ lang: "painless",
29
+ params: {
30
+ value: 1
31
+ }
32
+ }
33
+ }
34
+ }
35
+ },
36
+ {
37
+ input: {
38
+ ...commonInput,
39
+ condition: 'less_than'
40
+ },
41
+ expected: {
42
+ script: {
43
+ script: {
44
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return daysToComplete < params.value\n } catch (Exception err) {\n return false;\n }\n ",
45
+ lang: "painless",
46
+ params: {
47
+ value: 1
48
+ }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ {
54
+ input: {
55
+ ...commonInput,
56
+ condition: 'less_than_or_equal'
57
+ },
58
+ expected: {
59
+ script: {
60
+ script: {
61
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return daysToComplete <= params.value\n } catch (Exception err) {\n return false;\n }\n ",
62
+ lang: "painless",
63
+ params: {
64
+ value: 1,
65
+ second_value: 5
66
+ }
67
+ }
68
+ }
69
+ }
70
+ },
71
+ {
72
+ input: {
73
+ ...commonInput,
74
+ condition: 'greater_than'
75
+ },
76
+ expected: {
77
+ script: {
78
+ script: {
79
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return daysToComplete > params.value\n } catch (Exception err) {\n return false;\n }\n ",
80
+ lang: "painless",
81
+ params: {
82
+ value: 1
83
+ }
84
+ }
85
+ }
86
+ }
87
+ },
88
+ {
89
+ input: {
90
+ ...commonInput,
91
+ condition: 'greater_than_or_equal'
92
+ },
93
+ expected: {
94
+ script: {
95
+ script: {
96
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return daysToComplete >= params.value\n } catch (Exception err) {\n return false;\n }\n ",
97
+ lang: "painless",
98
+ params: {
99
+ value: 1
100
+ }
101
+ }
102
+ }
103
+ }
104
+ },
105
+ {
106
+ input: {
107
+ ...commonInput,
108
+ condition: 'between'
109
+ },
110
+ expected: {
111
+ script: {
112
+ script: {
113
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n if (isEmptyReadyAt || isEmptyCompletedAt) {\n return false;\n }\n\n def readyAt = doc['ready_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n def completedAt = doc['completed_at'].value\n .toInstant()\n .atZone(ZoneId.of('UTC'))\n .toLocalDate()\n .atStartOfDay(ZoneId.of('UTC'))\n .toInstant()\n .toEpochMilli();\n\n if (readyAt == null || completedAt == null) {\n return false;\n }\n\n def daysToComplete = (completedAt - readyAt) / (1000 * 3600 * 24);\n\n return \n daysToComplete >= params.value &&\n daysToComplete <= params.second_value\n \n } catch (Exception err) {\n return false;\n }\n ",
114
+ lang: "painless",
115
+ params: {
116
+ value: 1,
117
+ second_value: 5
118
+ }
119
+ }
120
+ }
121
+ }
122
+ },
123
+ {
124
+ input: {
125
+ ...commonInput,
126
+ condition: 'is_empty'
127
+ },
128
+ expected: {
129
+ script: {
130
+ script: {
131
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n return isEmptyReadyAt || isEmptyCompletedAt;\n } catch (Exception err) {\n return false;\n }\n ",
132
+ lang: "painless"
133
+ }
134
+ }
135
+ }
136
+ },
137
+ {
138
+ input: {
139
+ ...commonInput,
140
+ condition: 'is_not_empty'
141
+ },
142
+ expected: {
143
+ script: {
144
+ script: {
145
+ source: "\n try {\n def isEmptyReadyAt = doc.containsKey('ready_at') && doc['ready_at'].empty;\n def isEmptyCompletedAt = doc.containsKey('completed_at') && doc['completed_at'].empty;\n\n return !isEmptyReadyAt && !isEmptyCompletedAt;\n } catch (Exception err) {\n return false;\n }\n ",
146
+ lang: "painless"
147
+ }
148
+ }
149
+ }
150
+ }
151
+ ];
152
+
153
+ filters.forEach((filter) => {
154
+ it(filter.input.condition, (done) => {
155
+ expect(daysToComplete(filter.input)).to.deep.equal(filter.expected);
156
+
157
+ done();
158
+ });
159
+ });
160
+ });
@@ -257,123 +257,136 @@ describe('taskPipeline', function () {
257
257
  }
258
258
  ]
259
259
  );
260
+
260
261
  expect(sort).to.deep.equal([
261
262
  {
262
263
  _script: {
263
- type: 'number',
264
+ type: "number",
264
265
  script: {
265
- lang: 'painless',
266
- source:
267
- '\n if (params.starredTaskIds.contains(doc._id.value)) {\n return 1;\n } else {\n return 0;\n }\n ',
268
- params: { starredTaskIds: ['task-1', 'task-2'] }
266
+ lang: "painless",
267
+ source: "\n if (params.starredTaskIds.contains(doc._id.value)) {\n return 1;\n } else {\n return 0;\n }\n ",
268
+ params: {
269
+ starredTaskIds: [
270
+ "task-1",
271
+ "task-2"
272
+ ]
273
+ }
269
274
  },
270
- order: 'desc'
275
+ order: "desc"
276
+ }
277
+ },
278
+ {
279
+ region_name: {
280
+ order: "asc"
271
281
  }
272
282
  },
273
- { region_name: { order: 'asc' } },
274
283
  {
275
284
  _script: {
276
- type: 'string',
285
+ type: "string",
277
286
  script: {
278
- lang: 'painless',
279
- source:
280
- "\n try {\n String owner = doc['owner'].value;\n\n if (owner != '' && owner != null) {\n return owner.toLowerCase();\n }\n } catch (Exception err) {}\n\n return '';\n "
287
+ lang: "painless",
288
+ source: "\n try {\n String owner = doc['owner'].value;\n\n if (owner != '' && owner != null) {\n return owner.toLowerCase();\n }\n } catch (Exception err) {}\n\n return '';\n "
281
289
  },
282
- order: 'asc'
290
+ order: "asc"
283
291
  }
284
292
  },
285
293
  {
286
294
  _script: {
287
- type: 'string',
295
+ type: "string",
288
296
  script: {
289
- lang: 'painless',
290
- source:
291
- "\n try {\n String owner = doc['owner'].value;\n\n if (owner != '' && owner != null) {\n return owner.toLowerCase();\n }\n } catch (Exception err) {}\n\n return '';\n "
297
+ lang: "painless",
298
+ source: "\n try {\n String owner = doc['owner'].value;\n\n if (owner != '' && owner != null) {\n return owner.toLowerCase();\n }\n } catch (Exception err) {}\n\n return '';\n "
292
299
  },
293
- order: 'asc'
300
+ order: "asc"
294
301
  }
295
302
  },
296
303
  {
297
304
  _script: {
298
- type: 'string',
305
+ type: "string",
299
306
  script: {
300
- lang: 'painless',
301
- source:
302
- "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true) {\n if (isComplete == true) {\n return 'completed';\n }\n\n return 'pending';\n }\n\n return 'new';\n } catch (Exception err) {\n return '';\n }\n "
307
+ lang: "painless",
308
+ source: "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true) {\n if (isComplete == true) {\n return 'completed';\n }\n\n return 'pending';\n }\n\n return 'new';\n } catch (Exception err) {\n return '';\n }\n "
303
309
  },
304
- order: 'desc'
310
+ order: "desc"
305
311
  }
306
312
  },
307
313
  {
308
314
  _script: {
309
- type: 'number',
315
+ type: "number",
310
316
  script: {
311
- lang: 'painless',
312
- source:
313
- "\n try {\n def stageIndex = doc['critical_path.stage_index'].value;\n if (stageIndex != null) {\n return stageIndex;\n }\n \n return 9999999;\n } catch (Exception err) {\n return 9999999;\n }\n "
317
+ lang: "painless",
318
+ source: "\n try {\n def stageIndex = doc['critical_path.stage_index'].value;\n if (stageIndex != null) {\n return stageIndex;\n }\n\n return 9999999;\n } catch (Exception err) {\n return 9999999;\n }\n "
314
319
  },
315
- order: 'desc'
320
+ order: "desc"
316
321
  }
317
322
  },
318
323
  {
319
324
  _script: {
320
- type: 'number',
325
+ type: "number",
321
326
  script: {
322
- lang: 'painless',
323
- source:
324
- "\n def projectNumber = params._source.project_id;\n projectNumber = /[^0-9]/.matcher(projectNumber).replaceAll('');\n return Integer.parseInt(projectNumber);\n "
327
+ lang: "painless",
328
+ source: "\n def projectNumber = params._source.project_id;\n projectNumber = /[^0-9]/.matcher(projectNumber).replaceAll('');\n return Integer.parseInt(projectNumber);\n "
325
329
  },
326
- order: 'desc'
330
+ order: "desc"
327
331
  }
328
332
  },
329
333
  {
330
334
  _script: {
331
- type: 'number',
335
+ type: "number",
332
336
  script: {
333
- lang: 'painless',
334
- source:
335
- "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == true && doc.containsKey('completed_at') && !doc['completed_at'].empty) {\n return doc['completed_at'].value.millis;\n } else if (isReady == true && doc.containsKey('ready_at') && !doc['ready_at'].empty) {\n return doc['ready_at'].value.millis;\n }\n\n return doc['created_at'].value.millis;\n } catch (Exception err) {\n return 0;\n }\n "
337
+ lang: "painless",
338
+ source: "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == true && doc.containsKey('completed_at') && !doc['completed_at'].empty) {\n return doc['completed_at'].value.millis;\n } else if (isReady == true && doc.containsKey('ready_at') && !doc['ready_at'].empty) {\n return doc['ready_at'].value.millis;\n }\n\n return doc['created_at'].value.millis;\n } catch (Exception err) {\n return 0;\n }\n "
336
339
  },
337
- order: 'desc'
340
+ order: "desc"
338
341
  }
339
342
  },
340
343
  {
341
344
  _script: {
342
- type: 'number',
345
+ type: "number",
343
346
  script: {
344
- lang: 'painless',
345
- source:
346
- "\n return doc['was_marked_incomplete'].empty\n ? 0\n : doc['was_marked_incomplete'].value ? 1 : 0;\n "
347
+ lang: "painless",
348
+ source: "\n return doc['was_marked_incomplete'].empty\n ? 0\n : doc['was_marked_incomplete'].value ? 1 : 0;\n "
347
349
  },
348
- order: 'desc'
350
+ order: "desc"
351
+ }
352
+ },
353
+ {
354
+ "last_comment_data.created_at": {
355
+ order: "desc"
349
356
  }
350
357
  },
351
- { 'last_comment_data.created_at': { order: 'desc' } },
352
358
  {
353
359
  _script: {
354
- type: 'number',
360
+ type: "number",
355
361
  script: {
356
- lang: 'painless',
357
- source:
358
- '\n try {\n def fields = params._source.fields;\n\n for (int i=0; i<fields.size(); i++) {\n if (fields[i].id.equals(params.value)) {\n if (fields[i].number != null) {\n return fields[i].number;\n } else {\n return -999999;\n }\n }\n }\n\n return -999999;\n } catch (Exception err) {\n return -999999;\n }\n ',
359
- params: { value: '38d423bf-fc24-4183-b8b7-15420c89e318' }
362
+ lang: "painless",
363
+ source: "\n try {\n def fields = params._source.fields;\n\n for (int i=0; i<fields.size(); i++) {\n if (fields[i].id.equals(params.value)) {\n if (fields[i].number != null) {\n return fields[i].number;\n } else {\n return -999999;\n }\n }\n }\n\n return -999999;\n } catch (Exception err) {\n return -999999;\n }\n ",
364
+ params: {
365
+ value: "38d423bf-fc24-4183-b8b7-15420c89e318"
366
+ }
360
367
  },
361
- order: 'desc'
368
+ order: "desc"
362
369
  }
363
370
  },
364
371
  {
365
372
  _script: {
366
- type: 'string',
373
+ type: "string",
367
374
  script: {
368
- lang: 'painless',
369
- source:
370
- "\n try {\n String textInfo = '';\n def fields = params._source.fields;\n\n for (int i=0; i<fields.size(); i++) {\n if (fields[i].id.equals(params.value)) {\n\n if (fields[i].number != null) {\n return '';\n }\n\n textInfo = fields[i].text == null ? '' : fields[i].text;\n if (fields[i].containsKey('computedText')) {\n textInfo = fields[i].computedText == null ? textInfo : fields[i].computedText;\n }\n }\n }\n\n return textInfo;\n } catch (Exception err) {\n return '';\n }\n ",
371
- params: { value: '38d423bf-fc24-4183-b8b7-15420c89e318' }
375
+ lang: "painless",
376
+ source: "\n try {\n String textInfo = '';\n def fields = params._source.fields;\n\n for (int i=0; i<fields.size(); i++) {\n if (fields[i].id.equals(params.value)) {\n\n if (fields[i].number != null) {\n return '';\n }\n\n textInfo = fields[i].text == null ? '' : fields[i].text;\n if (fields[i].containsKey('computedText')) {\n textInfo = fields[i].computedText == null ? textInfo : fields[i].computedText;\n }\n }\n }\n\n return textInfo;\n } catch (Exception err) {\n return '';\n }\n ",
377
+ params: {
378
+ value: "38d423bf-fc24-4183-b8b7-15420c89e318"
379
+ }
372
380
  },
373
- order: 'desc'
381
+ order: "desc"
374
382
  }
375
383
  },
376
- { id: { order: 'desc', missing: '_last' } }
384
+ {
385
+ id: {
386
+ order: "desc",
387
+ missing: "_last"
388
+ }
389
+ }
377
390
  ]);
378
391
  done();
379
392
  });