@outliant/sunrise-utils 1.0.5 → 1.0.7

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/.eslintrc CHANGED
@@ -42,7 +42,25 @@
42
42
  "import/no-extraneous-dependencies": "off",
43
43
  "no-shadow": "off",
44
44
  "no-else-return": "off",
45
- "no-restricted-globals": "off"
45
+ "no-restricted-globals": "off",
46
+ "semi": ["error", "always"],
47
+ "quote-props": ["error", "as-needed"],
48
+ "space-infix-ops": "error",
49
+ "object-curly-spacing": ["error", "always"],
50
+ "indent": [
51
+ "error",
52
+ 2,
53
+ {
54
+ "SwitchCase": 1
55
+ }
56
+ ],
57
+ "no-multiple-empty-lines": [
58
+ "error",
59
+ {
60
+ "max": 1,
61
+ "maxEOF": 0
62
+ }
63
+ ]
46
64
  },
47
65
  "parser": "@babel/eslint-parser",
48
66
  "parserOptions": {
package/CHANGELOG.md CHANGED
@@ -4,8 +4,24 @@ 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.0.7](https://github.com/outliant/sunrise-utils/compare/1.0.6...1.0.7)
8
+
9
+ - chore: support multiple sorting #860px39yc [`133144d`](https://github.com/outliant/sunrise-utils/commit/133144d805126d301543e1df50510b982ea0f130)
10
+
11
+ #### [1.0.6](https://github.com/outliant/sunrise-utils/compare/1.0.5...1.0.6)
12
+
13
+ > 7 March 2023
14
+
15
+ - Task/string contains filters #860pq6e4d [`#2`](https://github.com/outliant/sunrise-utils/pull/2)
16
+ - chore: string contains filters [`2d80b31`](https://github.com/outliant/sunrise-utils/commit/2d80b31ac08ff5c1710373ccaae7c641333b5557)
17
+ - chore: add contains any/none filters [`eeec3bd`](https://github.com/outliant/sunrise-utils/commit/eeec3bdbce07ce6a9a782db3953c6926bfd24e4d)
18
+ - chore: revert pushes to main [`21cba44`](https://github.com/outliant/sunrise-utils/commit/21cba44219188192dc806fed59df2bdab1b63e88)
19
+
7
20
  #### [1.0.5](https://github.com/outliant/sunrise-utils/compare/1.0.4...1.0.5)
8
21
 
22
+ > 7 March 2023
23
+
24
+ - chore(release): 1.0.5 [`51f1f27`](https://github.com/outliant/sunrise-utils/commit/51f1f2766ad028f6769ed43625dd6b7eb977c4de)
9
25
  - chore: update custom sorting to be prepended on the list [`6d8c7e3`](https://github.com/outliant/sunrise-utils/commit/6d8c7e3f665fad6203b700fb4a104a8670572cb0)
10
26
 
11
27
  #### [1.0.4](https://github.com/outliant/sunrise-utils/compare/1.0.3...1.0.4)
@@ -1,8 +1,33 @@
1
1
  const moment = require('moment');
2
2
  const { escapeElasticQuery } = require('../es');
3
3
 
4
+ function getContainsMappingValue (filter) {
5
+ if (['text', 'textarea'].includes(filter.field_type)) {
6
+ return {
7
+ bool: {
8
+ must: [
9
+ {
10
+ match: {
11
+ [`fields.text.analyzed`]: {
12
+ query: filter.value,
13
+ operator: 'and'
14
+ }
15
+ }
16
+ }
17
+ ]
18
+ }
19
+ };
20
+ }
21
+
22
+ return {};
23
+ }
24
+
4
25
  module.exports = (filter) => {
5
26
  switch (filter.condition) {
27
+ case 'contains_any':
28
+ return module.exports.containsAny(filter);
29
+ case 'contains_none':
30
+ return module.exports.containsNone(filter);
6
31
  case 'is_empty':
7
32
  return module.exports.isEmpty(filter);
8
33
  case 'is_not_empty':
@@ -30,6 +55,94 @@ module.exports = (filter) => {
30
55
  }
31
56
  };
32
57
 
58
+ module.exports.containsAny = (filter) => {
59
+ if (!!filter.field_id) {
60
+ const mapping = getContainsMappingValue(filter);
61
+
62
+ if (!mapping) {
63
+ return null;
64
+ }
65
+
66
+ return {
67
+ bool: {
68
+ must: [
69
+ {
70
+ nested: {
71
+ path: 'fields',
72
+ query: {
73
+ bool: {
74
+ must: [
75
+ {
76
+ term: {
77
+ 'fields.id': {
78
+ value: filter.field_id
79
+ }
80
+ }
81
+ }
82
+ ]
83
+ }
84
+ }
85
+ }
86
+ },
87
+ {
88
+ nested: {
89
+ path: 'fields',
90
+ query: mapping
91
+ }
92
+ }
93
+ ]
94
+ }
95
+ };
96
+ }
97
+
98
+ return null;
99
+ };
100
+
101
+ module.exports.containsNone = (filter) => {
102
+ if (!!filter.field_id) {
103
+ const mapping = getContainsMappingValue(filter);
104
+
105
+ if (!mapping) {
106
+ return null;
107
+ }
108
+
109
+ return {
110
+ bool: {
111
+ must: [
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
+ must_not: [
132
+ {
133
+ nested: {
134
+ path: 'fields',
135
+ query: mapping
136
+ }
137
+ }
138
+ ]
139
+ }
140
+ };
141
+ }
142
+
143
+ return null;
144
+ };
145
+
33
146
  module.exports.isAnyOf = (filter) => {
34
147
  let value = filter.value;
35
148
 
@@ -2,6 +2,10 @@ const moment = require('moment');
2
2
 
3
3
  module.exports = (filter) => {
4
4
  switch (filter.condition) {
5
+ case 'contains_any':
6
+ return module.exports.containsAny(filter);
7
+ case 'contains_none':
8
+ return module.exports.containsNone(filter);
5
9
  case 'is_empty':
6
10
  return module.exports.isEmpty(filter);
7
11
  case 'is_any_of':
@@ -29,6 +33,63 @@ module.exports = (filter) => {
29
33
  }
30
34
  };
31
35
 
36
+
37
+ module.exports.containsAny = (filter) => {
38
+ /**
39
+ * Field type is for custom columns
40
+ */
41
+ const type = filter.field_type || filter.type;
42
+
43
+ if (['text', 'textarea'].includes(type)) {
44
+ const value = String(filter.value || '').split(';');
45
+
46
+ return {
47
+ bool: {
48
+ should: value.map(val => {
49
+ return {
50
+ match: {
51
+ [filter.type]: {
52
+ query: val,
53
+ operator: 'and'
54
+ }
55
+ }
56
+ };
57
+ })
58
+ }
59
+ };
60
+ }
61
+
62
+ return null;
63
+ };
64
+
65
+ module.exports.containsNone = (filter) => {
66
+ /**
67
+ * Field type is for custom columns
68
+ */
69
+ const type = filter.field_type || filter.type;
70
+
71
+ if (['text', 'textarea'].includes(type)) {
72
+ const value = String(filter.value || '').split(';');
73
+
74
+ return {
75
+ bool: {
76
+ must_not: value.map(val => {
77
+ return {
78
+ match: {
79
+ [filter.type]: {
80
+ query: val,
81
+ operator: 'and'
82
+ }
83
+ }
84
+ };
85
+ })
86
+ }
87
+ };
88
+ }
89
+
90
+ return null;
91
+ };
92
+
32
93
  module.exports.isAnyOf = (filter) => {
33
94
  let value = filter.value;
34
95
 
@@ -19,6 +19,14 @@ module.exports.conditions = {
19
19
  {
20
20
  label: 'Is any of',
21
21
  value: 'is_any_of'
22
+ },
23
+ {
24
+ label: 'Contains Any',
25
+ value: 'contains_any'
26
+ },
27
+ {
28
+ label: 'Contains None',
29
+ value: 'contains_none'
22
30
  }
23
31
  ],
24
32
  textarea: [
@@ -41,6 +49,14 @@ module.exports.conditions = {
41
49
  {
42
50
  label: 'Is any of',
43
51
  value: 'is_any_of'
52
+ },
53
+ {
54
+ label: 'Contains Any',
55
+ value: 'contains_any'
56
+ },
57
+ {
58
+ label: 'Contains None',
59
+ value: 'contains_none'
44
60
  }
45
61
  ],
46
62
  number: [
@@ -65,13 +65,13 @@ class ProjectPipeline {
65
65
  }
66
66
 
67
67
  buildSortScript(query = {}, customSort = []) {
68
- const sort = query.sort || 'desc';
69
- const sortBy = query.sortBy || 'created_at';
70
- const sortByMultiple = sortBy.split(',');
68
+ const sortMultiple = (query.sort || 'desc').split(',');
69
+ const sortByMultiple = (query.sortBy || 'created_at').split(',');
71
70
  const projectPipelinesSort = [];
72
71
 
73
72
  if (sortByMultiple.length > 0) {
74
- sortByMultiple.forEach((s) => {
73
+ sortByMultiple.forEach((s, i) => {
74
+ const sort = sortMultiple[i] || 'desc';
75
75
  if (isUuid(s)) {
76
76
  projectPipelinesSort.push(sortScript.projectFieldNumeric(sort, s));
77
77
  projectPipelinesSort.push(sortScript.projectFieldText(sort, s));
@@ -81,11 +81,11 @@ class ProjectPipeline {
81
81
  projectPipelinesSort.push(sortScript.criticalPathStage(sort));
82
82
  } else if (s === 'name') {
83
83
  projectPipelinesSort.push({
84
- 'name.raw': { order: query.sort, missing: '_last' }
84
+ 'name.raw': { order: sort, missing: '_last' }
85
85
  });
86
86
  } else {
87
87
  projectPipelinesSort.push({
88
- [s]: { order: query.sort, missing: '_last' }
88
+ [s]: { order: sort, missing: '_last' }
89
89
  });
90
90
  }
91
91
  });
@@ -84,46 +84,41 @@ class TaskPipeline {
84
84
  }
85
85
 
86
86
  buildSortScript(query = {}, customSort = []) {
87
- const sort = query.sort || 'desc';
88
- const sortBy = query.sortBy || 'created_at';
89
- const sortByMultiple = sortBy.split(',');
87
+ const sortMultiple = (query.sort || 'desc').split(',');
88
+ const sortByMultiple = (query.sortBy || 'created_at').split(',');
90
89
  let taskPipelinesSort = [];
91
90
 
92
- if (sortByMultiple.length > 0) {
93
- if (sortByMultiple[0] === 'default') {
94
- taskPipelinesSort = sortScript.default;
95
- } else {
96
- sortByMultiple.forEach((s) => {
97
- if (s === 'region') {
98
- taskPipelinesSort.push(sortScript.region(sort));
99
- } else if (s === 'owner' || s === 'completed_by') {
100
- taskPipelinesSort.push(sortScript.owner(sort));
101
- } else if (s === 'status') {
102
- taskPipelinesSort.push(sortScript.status(sort));
103
- } else if (s === 'critical_path_stage') {
104
- taskPipelinesSort.push(sortScript.criticalPathStage(sort));
105
- } else if (s === 'project_id') {
106
- // Sort numeric part of the Project ID
107
- taskPipelinesSort.push(sortScript.projectId(sort));
108
- } else if (s === 'ready_at') {
109
- taskPipelinesSort.push(sortScript.readyAt(sort));
110
- } else if (s === 'was_marked_incomplete') {
111
- taskPipelinesSort.push(sortScript.wasMarkedIncomplete(sort));
112
- } else if (s === 'last_comment_date') {
113
- taskPipelinesSort.push(sortScript.lastCommentDate(sort));
114
- } else if (isUuid(s)) {
115
- taskPipelinesSort.push(sortScript.projectFieldNumeric(sort, s));
116
- taskPipelinesSort.push(sortScript.projectFieldText(sort, s));
117
- } else {
118
- taskPipelinesSort.push({
119
- [s]: { order: sort, missing: '_last' }
120
- });
121
- }
122
- });
123
- }
124
- } else {
125
- // Default sorting
91
+ if (sortByMultiple[0] === 'default') {
126
92
  taskPipelinesSort = sortScript.default;
93
+ } else {
94
+ sortByMultiple.forEach((s, i) => {
95
+ const sort = sortMultiple[i] || 'desc';
96
+ if (s === 'region') {
97
+ taskPipelinesSort.push(sortScript.region(sort));
98
+ } else if (s === 'owner' || s === 'completed_by') {
99
+ taskPipelinesSort.push(sortScript.owner(sort));
100
+ } else if (s === 'status') {
101
+ taskPipelinesSort.push(sortScript.status(sort));
102
+ } else if (s === 'critical_path_stage') {
103
+ taskPipelinesSort.push(sortScript.criticalPathStage(sort));
104
+ } else if (s === 'project_id') {
105
+ // Sort numeric part of the Project ID
106
+ taskPipelinesSort.push(sortScript.projectId(sort));
107
+ } else if (s === 'ready_at') {
108
+ taskPipelinesSort.push(sortScript.readyAt(sort));
109
+ } else if (s === 'was_marked_incomplete') {
110
+ taskPipelinesSort.push(sortScript.wasMarkedIncomplete(sort));
111
+ } else if (s === 'last_comment_date') {
112
+ taskPipelinesSort.push(sortScript.lastCommentDate(sort));
113
+ } else if (isUuid(s)) {
114
+ taskPipelinesSort.push(sortScript.projectFieldNumeric(sort, s));
115
+ taskPipelinesSort.push(sortScript.projectFieldText(sort, s));
116
+ } else {
117
+ taskPipelinesSort.push({
118
+ [s]: { order: sort, missing: '_last' }
119
+ });
120
+ }
121
+ });
127
122
  }
128
123
 
129
124
  if (customSort.length > 0) {
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.5",
4
+ "version": "1.0.7",
5
5
  "license": "ISC",
6
6
  "author": "Outliant",
7
7
  "main": "index.js",
@@ -159,4 +159,222 @@ describe('taskPipeline', function () {
159
159
  done();
160
160
  });
161
161
  });
162
+
163
+ describe('buildSortScript', () => {
164
+ it('should format sorting with default', (done) => {
165
+ const sort = taskPipeline.buildSortScript({
166
+ sort: 'desc',
167
+ sortBy: 'default'
168
+ });
169
+ expect(sort).to.deep.equal([
170
+ {
171
+ _script: {
172
+ type: 'number',
173
+ script: {
174
+ lang: 'painless',
175
+ source:
176
+ "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == false) {\n return 1;\n }\n\n return 0;\n } catch (Exception err) {\n return 0;\n }\n "
177
+ },
178
+ order: 'desc'
179
+ }
180
+ },
181
+ {
182
+ _script: {
183
+ type: 'string',
184
+ script: {
185
+ lang: 'painless',
186
+ source:
187
+ "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == false) {\n return doc['name'].value.raw;\n }\n\n return 'zzzzzzzz';\n } catch (Exception err) {\n return 'zzzzzzzz';\n }\n "
188
+ },
189
+ order: 'asc'
190
+ }
191
+ },
192
+ {
193
+ _script: {
194
+ type: 'number',
195
+ script: {
196
+ lang: 'painless',
197
+ source:
198
+ "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == true) {\n return 1;\n }\n\n return 0;\n } catch (Exception err) {\n return 0;\n }\n "
199
+ },
200
+ order: 'desc'
201
+ }
202
+ },
203
+ {
204
+ _script: {
205
+ type: 'string',
206
+ script: {
207
+ lang: 'painless',
208
+ source:
209
+ "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == true) {\n return doc['name'].value.raw;\n }\n\n return 'zzzzzzzz';\n } catch (Exception err) {\n return 'zzzzzzzz';\n }\n "
210
+ },
211
+ order: 'asc'
212
+ }
213
+ },
214
+ {
215
+ _script: {
216
+ type: 'number',
217
+ script: {
218
+ lang: 'painless',
219
+ source:
220
+ "\n try {\n boolean isReady = doc['is_ready'].value;\n boolean isComplete = doc['is_complete'].value;\n\n if (isReady == true && isComplete == true) {\n return doc['completed_at'].value.millis;\n }\n\n return 0;\n } catch (Exception err) {\n return 0;\n }\n "
221
+ },
222
+ order: 'desc'
223
+ }
224
+ },
225
+ { 'name.raw': { order: 'asc' } },
226
+ { id: { order: 'asc' } }
227
+ ]);
228
+ done();
229
+ });
230
+ it('should format sorting and custom sort', (done) => {
231
+ const sort = taskPipeline.buildSortScript(
232
+ {
233
+ sort: 'asc,asc,asc',
234
+ sortBy:
235
+ 'region,owner,completed_by,status,critical_path_stage,project_id,ready_at,was_marked_incomplete,last_comment_date,38d423bf-fc24-4183-b8b7-15420c89e318,id'
236
+ },
237
+ [
238
+ {
239
+ _script: {
240
+ type: 'number',
241
+ script: {
242
+ lang: 'painless',
243
+ source: `
244
+ if (params.starredTaskIds.contains(doc._id.value)) {
245
+ return 1;
246
+ } else {
247
+ return 0;
248
+ }
249
+ `,
250
+ params: {
251
+ starredTaskIds: ['task-1', 'task-2']
252
+ }
253
+ },
254
+ order: 'desc'
255
+ }
256
+ }
257
+ ]
258
+ );
259
+ expect(sort).to.deep.equal([
260
+ {
261
+ _script: {
262
+ type: 'number',
263
+ script: {
264
+ lang: 'painless',
265
+ source:
266
+ '\n if (params.starredTaskIds.contains(doc._id.value)) {\n return 1;\n } else {\n return 0;\n }\n ',
267
+ params: { starredTaskIds: ['task-1', 'task-2'] }
268
+ },
269
+ order: 'desc'
270
+ }
271
+ },
272
+ { region_name: { order: 'asc' } },
273
+ {
274
+ _script: {
275
+ type: 'string',
276
+ script: {
277
+ lang: 'painless',
278
+ source:
279
+ "\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 "
280
+ },
281
+ order: 'asc'
282
+ }
283
+ },
284
+ {
285
+ _script: {
286
+ type: 'string',
287
+ script: {
288
+ lang: 'painless',
289
+ source:
290
+ "\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 "
291
+ },
292
+ order: 'asc'
293
+ }
294
+ },
295
+ {
296
+ _script: {
297
+ type: 'string',
298
+ script: {
299
+ lang: 'painless',
300
+ source:
301
+ "\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 "
302
+ },
303
+ order: 'desc'
304
+ }
305
+ },
306
+ {
307
+ _script: {
308
+ type: 'number',
309
+ script: {
310
+ lang: 'painless',
311
+ source:
312
+ "\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 "
313
+ },
314
+ order: 'desc'
315
+ }
316
+ },
317
+ {
318
+ _script: {
319
+ type: 'number',
320
+ script: {
321
+ lang: 'painless',
322
+ source:
323
+ "\n def projectNumber = params._source.project_id;\n projectNumber = /[^0-9]/.matcher(projectNumber).replaceAll('');\n return Integer.parseInt(projectNumber);\n "
324
+ },
325
+ order: 'desc'
326
+ }
327
+ },
328
+ {
329
+ _script: {
330
+ type: 'number',
331
+ script: {
332
+ lang: 'painless',
333
+ source:
334
+ "\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 "
335
+ },
336
+ order: 'desc'
337
+ }
338
+ },
339
+ {
340
+ _script: {
341
+ type: 'number',
342
+ script: {
343
+ lang: 'painless',
344
+ source:
345
+ "\n return doc['was_marked_incomplete'].empty\n ? 0\n : doc['was_marked_incomplete'].value ? 1 : 0;\n "
346
+ },
347
+ order: 'desc'
348
+ }
349
+ },
350
+ { 'last_comment_data.created_at': { order: 'desc' } },
351
+ {
352
+ _script: {
353
+ type: 'number',
354
+ script: {
355
+ lang: 'painless',
356
+ source:
357
+ '\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 ',
358
+ params: { value: '38d423bf-fc24-4183-b8b7-15420c89e318' }
359
+ },
360
+ order: 'desc'
361
+ }
362
+ },
363
+ {
364
+ _script: {
365
+ type: 'string',
366
+ script: {
367
+ lang: 'painless',
368
+ source:
369
+ "\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 }\n\n }\n\n return textInfo;\n } catch (Exception err) {\n return '';\n }\n ",
370
+ params: { value: '38d423bf-fc24-4183-b8b7-15420c89e318' }
371
+ },
372
+ order: 'desc'
373
+ }
374
+ },
375
+ { id: { order: 'desc', missing: '_last' } }
376
+ ]);
377
+ done();
378
+ });
379
+ });
162
380
  });