@mixd-id/web-scaffold 0.2.240702 → 0.2.240704

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 (95) hide show
  1. package/docs/schema/user-action.json +266 -0
  2. package/package.json +6 -2
  3. package/public/assets/dashboard/bar.png +0 -0
  4. package/public/assets/dashboard/doughnut.png +0 -0
  5. package/public/assets/dashboard/metric.png +0 -0
  6. package/public/assets/dashboard/pie.png +0 -0
  7. package/public/assets/dashboard/polar-area.png +0 -0
  8. package/public/assets/dashboard/virtual-table.png +0 -0
  9. package/public/static/dashboard/bar.png +0 -0
  10. package/public/static/dashboard/doughnut.png +0 -0
  11. package/public/static/dashboard/metric.png +0 -0
  12. package/public/static/dashboard/pie.png +0 -0
  13. package/public/static/dashboard/polar-area.png +0 -0
  14. package/public/static/dashboard/virtual-table.png +0 -0
  15. package/src/components/Button.vue +179 -160
  16. package/src/components/Checkbox.vue +0 -1
  17. package/src/components/Datepicker.vue +8 -6
  18. package/src/components/GHeatMaps.vue +317 -0
  19. package/src/components/GmapsDirection.vue +191 -0
  20. package/src/components/Grid.vue +2 -0
  21. package/src/components/HTMLEditor.vue +2 -2
  22. package/src/components/List.vue +384 -308
  23. package/src/components/Modal.vue +2 -3
  24. package/src/components/PresetSelectorFilterItem.vue +15 -2
  25. package/src/components/Switch.vue +3 -0
  26. package/src/components/Tabs.vue +1 -1
  27. package/src/components/TextWithTag.vue +67 -25
  28. package/src/components/Textbox.vue +5 -0
  29. package/src/components/VirtualGrid.vue +224 -228
  30. package/src/components/VirtualTable.vue +46 -28
  31. package/src/configs/dashboard/bar.js +10 -0
  32. package/src/configs/dashboard/collection-1.js +5 -0
  33. package/src/configs/dashboard/doughnut.js +7 -0
  34. package/src/configs/dashboard/gheatmaps.js +9 -0
  35. package/src/configs/dashboard/grid-2.js +34 -0
  36. package/src/configs/dashboard/grid-3.js +34 -0
  37. package/src/configs/dashboard/grid-4.js +34 -0
  38. package/src/configs/dashboard/grid.js +15 -0
  39. package/src/configs/dashboard/metric.js +10 -0
  40. package/src/configs/dashboard/pie.js +7 -0
  41. package/src/configs/dashboard/polar-area.js +7 -0
  42. package/src/configs/dashboard/virtual-table.js +9 -0
  43. package/src/defs/dashboard-preset.js +22 -0
  44. package/src/index.js +37 -23
  45. package/src/mixin/ready-state.js +37 -0
  46. package/src/stores/datasource.js +11 -0
  47. package/src/themes/default/index.js +1 -1
  48. package/src/utils/dashboard.js +1080 -0
  49. package/src/utils/event-bus.js +8 -0
  50. package/src/utils/helpers.js +56 -8
  51. package/src/utils/helpers.mjs +35 -1
  52. package/src/utils/preset-selector.js +5 -2
  53. package/src/utils/preset-selector.mjs +23 -13
  54. package/src/widgets/BotEditor/BotEditorActions.vue +162 -0
  55. package/src/widgets/BotEditor.vue +228 -0
  56. package/src/widgets/Dashboard/BarChart.vue +330 -0
  57. package/src/widgets/Dashboard/BarChartSetting.vue +317 -0
  58. package/src/widgets/Dashboard/DatasourceFilterSharing.vue +93 -0
  59. package/src/widgets/Dashboard/DatasourcePreview.vue +93 -0
  60. package/src/widgets/Dashboard/DatasourceSelector.vue +122 -0
  61. package/src/widgets/Dashboard/Doughnut.vue +157 -0
  62. package/src/widgets/Dashboard/DoughnutSetting.vue +196 -0
  63. package/src/widgets/Dashboard/GHeatMapsSetting.vue +108 -0
  64. package/src/widgets/Dashboard/InteractionEdit.vue +228 -0
  65. package/src/widgets/Dashboard/Metric.vue +76 -0
  66. package/src/widgets/Dashboard/MetricSetting.vue +174 -0
  67. package/src/widgets/Dashboard/Pie.vue +139 -0
  68. package/src/widgets/Dashboard/PieSetting.vue +247 -0
  69. package/src/widgets/Dashboard/PolarArea.vue +159 -0
  70. package/src/widgets/Dashboard/PolarAreaSetting.vue +195 -0
  71. package/src/widgets/Dashboard/SharingModal.vue +116 -0
  72. package/src/widgets/Dashboard/ViewSelector.vue +183 -0
  73. package/src/widgets/Dashboard/VirtualColumnEdit.vue +97 -0
  74. package/src/widgets/Dashboard/VirtualTableSetting.vue +234 -0
  75. package/src/widgets/Dashboard.vue +1773 -0
  76. package/src/widgets/PresetBar.vue +136 -175
  77. package/src/widgets/PresetBarPivot.vue +186 -0
  78. package/src/widgets/PresetSelector.vue +2 -2
  79. package/src/widgets/UserActionBuilder/UserActionCondition.vue +99 -0
  80. package/src/widgets/UserActionBuilder/UserActionConsole.vue +77 -0
  81. package/src/widgets/UserActionBuilder/UserActionItem.vue +166 -58
  82. package/src/widgets/UserActionBuilder/UserActionOutput.vue +35 -9
  83. package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +27 -0
  84. package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +28 -0
  85. package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +135 -0
  86. package/src/widgets/UserActionBuilder/UserActionProps.vue +213 -0
  87. package/src/widgets/UserActionBuilder.vue +68 -199
  88. package/src/widgets/WebPageBuilder4/GridSetting.vue +123 -73
  89. package/src/widgets/WebPageBuilder4/HeightSetting.vue +14 -11
  90. package/src/widgets/WebPageBuilder4/MarginSetting.vue +4 -1
  91. package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +12 -4
  92. package/src/widgets/WebPageBuilder4/PaddingSetting.vue +4 -0
  93. package/src/widgets/WebPageBuilder4/TreeView.vue +6 -3
  94. package/src/widgets/WebPageBuilder4/TreeViewItem.vue +32 -58
  95. package/tailwind.config.js +2 -2
@@ -0,0 +1,1080 @@
1
+ const {presetToSequelizeList} = require("./preset-selector");
2
+
3
+ const backgroundColors = [
4
+ "#FF5733", // Bright Red
5
+ "#33FF57", // Bright Green
6
+ "#3357FF", // Bright Blue
7
+ "#FFC300", // Bright Yellow
8
+ "#28B463", // Emerald Green
9
+ "#2874A6", // Deep Blue
10
+ "#F39C12", // Orange
11
+ "#8E44AD", // Purple
12
+ "#1ABC9C", // Aqua
13
+ "#3498DB", // Sky Blue
14
+ "#2ECC71", // Green
15
+ "#F1C40F", // Yellow
16
+ "#34495E", // Dark Slate
17
+ "#7D3C98", // Amethyst
18
+ "#16A085", // Teal
19
+ "#E67E22", // Carrot Orange
20
+ "#2C3E50", // Dark Blue
21
+ "#D35400", // Pumpkin Orange
22
+ "#7FB3D5", // Light Blue
23
+ "#B03A2E", // Rust
24
+ "#1F618D", // Steel Blue
25
+ "#A93226", // Dark Red
26
+ "#48C9B0", // Light Teal
27
+ "#E74C3C", // Red
28
+ "#AF7AC5", // Lavender
29
+ "#45B39D", // Seafoam
30
+ "#5D6D7E", // Gray Blue
31
+ "#52BE80", // Light Green
32
+ "#F5B041", // Saffron
33
+ "#BDC3C7" // Light Gray
34
+
35
+ /*'#5D9CEC',
36
+ '#A0D468',
37
+ '#FFCE54',
38
+ '#FC6E51',
39
+ '#48CFAD',
40
+ '#AC92EC',
41
+ '#4FC1E9',
42
+ '#FFCE54',
43
+ '#ED5565',
44
+ '#EC87C0'*/
45
+ ]
46
+
47
+ const getModelFromDatasource = async (datasource, opt) => {
48
+
49
+ switch(datasource.type){
50
+
51
+ case 2:
52
+ return opt.conn.models[datasource.key]
53
+
54
+ default:
55
+ if(!opt.conn.models[datasource.datasourceUid]){
56
+ const tableColumns = await getSequelizeColumns(datasource.columns, opt)
57
+
58
+ opt.conn.define(datasource.datasourceUid, tableColumns, {
59
+ tableName: `datasource_${datasource.datasourceUid}`,
60
+ timestamps: false
61
+ })
62
+ }
63
+
64
+ return opt.conn.models[datasource.datasourceUid]
65
+ }
66
+ }
67
+
68
+ const getSequelizeColumns = async (columns, opt) => {
69
+
70
+ const { DataTypes } = opt.Sequelize
71
+
72
+ const tableColumns = {
73
+ id: {
74
+ type: DataTypes.INTEGER,
75
+ autoIncrement: true,
76
+ primaryKey: true,
77
+ allowNull: false,
78
+ }
79
+ }
80
+
81
+ for(let column of columns){
82
+ if(column.key === 'id') continue
83
+
84
+ let type
85
+ switch(column.type){
86
+ case 'date':
87
+ type = DataTypes.DATE
88
+ break
89
+ case 'number':
90
+ type = DataTypes.BIGINT
91
+ break
92
+
93
+ default:
94
+ type = DataTypes.STRING(column.maxLength ?? 40)
95
+ break
96
+ }
97
+
98
+ tableColumns[column.key] = {
99
+ type
100
+ }
101
+ }
102
+
103
+ return tableColumns
104
+ }
105
+
106
+ const getDatasourceItems = async (datasource, opt) => {
107
+
108
+ if(!datasource.itemsPerPage)
109
+ datasource.itemsPerPage = 20
110
+
111
+ const datasourceOpt = DatasourceService.getDatasourceParamsByUid(datasource.datasourceUid)
112
+
113
+ const Model = await getModelFromDatasource(datasource, opt)
114
+ const config = {
115
+ columns: datasource.columns
116
+ }
117
+
118
+ const findOpt = await presetToSequelizeList(datasource, {
119
+ config,
120
+ model: Model,
121
+ conn: opt.conn,
122
+ limit: 100,
123
+ ...datasourceOpt
124
+ })
125
+
126
+ const items = (await Model.findAll({
127
+ ...findOpt
128
+ }))
129
+ .map(_ => _.toJSON())
130
+
131
+ let hasNext = false
132
+ if(!datasource.pivot?.enabled && items.length > 20){
133
+ items.pop()
134
+ hasNext = true
135
+ }
136
+
137
+ if(!datasource.pivot?.enabled && datasource.computedColumns){
138
+ items.forEach(item => {
139
+ for(let column of datasource.computedColumns){
140
+ const value = column.value.replace(/{([^}]+)}/g, (match, key) => {
141
+ return item[key] ?? 0
142
+ })
143
+
144
+ const valueObj = parser.parse(value)
145
+
146
+ item[column.key] = valueObj?.result
147
+ }
148
+ })
149
+ }
150
+
151
+ return {
152
+ items,
153
+ hasNext
154
+ }
155
+ }
156
+
157
+
158
+ const loadBarChart = async(params, opt) => {
159
+ if(params.xAxes.filter(_ => !_).length > 0 ||
160
+ params.yAxes.filter(_ => !_).length > 0)
161
+ return { labels:[], datasets:[] }
162
+
163
+ const { items } = params
164
+
165
+ if(items.length === 200001)
166
+ throw new ValidationError({ count:[ 'Unable to load bar chart, max items count is 200000' ]})
167
+
168
+ const columns = (params.datasource.columns ?? []).reduce((res, cur) => {
169
+ res[cur['key']] = cur
170
+ return res
171
+ }, {})
172
+
173
+ let labels = []
174
+ let datasets = []
175
+
176
+ let xAxes
177
+ if(params.xAxeMultiple){
178
+ xAxes = params.xAxes
179
+ }
180
+ else{
181
+ const xAxesKey = params.xAxes[0].key
182
+ const xAxeModifier = params.xAxes[0].modifier
183
+
184
+ let reduced = {}
185
+ switch(xAxeModifier){
186
+
187
+ case 'hour':
188
+ for(let i = 0 ; i < 24 ; i++){
189
+ reduced[i] = { key:i.toString().padStart(2, '0'), parentKey:xAxesKey, modifier:xAxeModifier }
190
+ }
191
+ break
192
+
193
+ case 'minute':
194
+ for(let i = 0 ; i < 60 ; i++){
195
+ reduced[i] = { key:i.toString().padStart(2, '0'), parentKey:xAxesKey, modifier:xAxeModifier }
196
+ }
197
+ break
198
+
199
+ case 'month':
200
+ for(let i = 1 ; i <= 12 ; i++){
201
+ reduced[i] = { key:dayjs(`2020-${i.toString().padStart(2, '0')}-15`).format('MMM'), parentKey:xAxesKey, modifier:xAxeModifier }
202
+ }
203
+ break
204
+
205
+ case 'year':
206
+ const currentYear = parseInt(dayjs().format('YYYY'))
207
+ for(let i = currentYear - 20 ; i <= currentYear ; i++){
208
+ reduced[i] = { key:i.toString().padStart(2, '0'), parentKey:xAxesKey, modifier:xAxeModifier }
209
+ }
210
+ break
211
+
212
+ case 'date':
213
+ for(let i = 1 ; i <= 31 ; i++){
214
+ reduced[i] = { key:i.toString().padStart(2, '0'), parentKey:xAxesKey, modifier:xAxeModifier }
215
+ }
216
+ break
217
+
218
+ case 'last60Minutes':
219
+ let last60Minutes = dayjs()
220
+ for(let i = 60 ; i > 0 ; i--){
221
+ const cur = last60Minutes.subtract(i, 'minute').format('YYYYMMDDHHmm00')
222
+ reduced[cur] = { key:cur, parentKey:xAxesKey, modifier:xAxeModifier }
223
+ }
224
+ break
225
+
226
+ case 'last12Hours':
227
+ let last12Hours = dayjs()
228
+ for(let i = 11 ; i >= 0 ; i--){
229
+ const cur = last12Hours.subtract(i, 'hour')
230
+ const key = cur.format('YYYYMMDDHH0000')
231
+ const alias = cur.format('HH')
232
+ reduced[key] = { key, alias, parentKey:xAxesKey, modifier:xAxeModifier }
233
+ }
234
+ break
235
+
236
+ case 'last24Hours':
237
+ let last24Hours = dayjs()
238
+ for(let i = 23 ; i >= 0 ; i--){
239
+ const cur = last24Hours.subtract(i, 'hour')
240
+ const key = cur.format('YYYYMMDDHH0000')
241
+ const alias = cur.format('HH')
242
+ reduced[key] = { key, alias, parentKey:xAxesKey, modifier:xAxeModifier }
243
+ }
244
+ break
245
+
246
+ default:
247
+ reduced = items.reduce((acc, item) => {
248
+
249
+ let key = item[xAxesKey]
250
+ switch(params.xAxes[0].modifier){
251
+
252
+ }
253
+
254
+ acc[key] = { key, parentKey:xAxesKey, modifier:params.xAxes[0].modifier }
255
+ return acc
256
+ }, {})
257
+ break
258
+
259
+ }
260
+
261
+ xAxes = Object.values(reduced)
262
+ }
263
+
264
+ let yAxes
265
+ if(params.yAxeMultiple){
266
+ const yAxesKey = params.yAxes[0].key
267
+ const reduced = items.reduce((acc, item) => {
268
+ const key = item[yAxesKey]
269
+ acc[key] = { key, parentKey:yAxesKey, modifier:params.yAxes[0].modifier }
270
+ return acc
271
+ }, {})
272
+
273
+ yAxes = Object.values(reduced)
274
+ }
275
+ else{
276
+ yAxes = [ params.yAxes[0] ]
277
+ }
278
+
279
+ const yAxe0Column = params.datasource.columns.find(_ => _.key === params.yAxes[0].key)
280
+
281
+ switch(yAxe0Column?.type){
282
+
283
+ case 'enum':
284
+ if(yAxe0Column?.typeParams){
285
+ yAxes.map(yAxe => {
286
+ const obj = yAxe0Column?.typeParams.find(_ => _.value === yAxe.key)
287
+ yAxe.alias = obj?.text ?? yAxe.key
288
+ })
289
+ }
290
+ else if(yAxe0Column?.enumSrc){
291
+ const [ path, key, name ] = yAxe0Column.enumSrc.split(':')
292
+ if(typeof opt.enumFn === 'function'){
293
+ const { items } = await opt.enumFn(path, {}, opt.socket) ?? {}
294
+
295
+ if(Array.isArray(items)){
296
+ const itemsKey = items.reduce((res, cur) => {
297
+ res[cur[key]] = cur[name]
298
+ return res
299
+ }, {})
300
+
301
+ yAxes = yAxes.map(yAxe => {
302
+ yAxe.alias = itemsKey[yAxe.key] ?? yAxe.key
303
+ return yAxe
304
+ })
305
+ }
306
+ }
307
+ }
308
+ break
309
+
310
+ }
311
+
312
+ let idx = 0
313
+ const yLegends = []
314
+
315
+ const enums = {}
316
+ for(let xAxe of params.xAxes){
317
+ const datasourceColumn = columns[xAxe.key]
318
+ if(datasourceColumn){
319
+ if(datasourceColumn.type === 'enum'){
320
+ if(datasourceColumn.typeParams){
321
+ enums[xAxe.key] = datasourceColumn.typeParams.reduce((res, cur) => {
322
+ res[cur.value] = cur.text
323
+ return res
324
+ }, {})
325
+ }
326
+ else if(datasourceColumn.enumSrc){
327
+ const [ path, key, name ] = datasourceColumn.enumSrc.split(':')
328
+ if(typeof opt.enumFn === 'function'){
329
+ const { items } = await opt.enumFn(path, {}, opt.socket) ?? {}
330
+
331
+ if(Array.isArray(items)){
332
+ enums[xAxe.key] = items.reduce((res, cur) => {
333
+ res[cur[key]] = cur[name]
334
+ return res
335
+ }, {})
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ for(let yAxe of yAxes){
344
+
345
+ let data
346
+
347
+ let yAxeItems = items
348
+
349
+ if(params.yAxeMultiple){
350
+ yAxeItems = yAxeItems.filter(_ => _[yAxe.parentKey] === yAxe.key)
351
+ }
352
+
353
+ if(params.xAxeMultiple){
354
+ data = xAxes.map(_ => {
355
+ let value
356
+ switch(yAxe.modifier){
357
+ case 'count':
358
+ value = yAxeItems.length
359
+ break
360
+
361
+ case 'sum':
362
+ default:
363
+ value = yAxeItems.reduce((acc, item) => {
364
+ const value = parseFloat(item[params.yAxeMultiple ? _.key : yAxe.key])
365
+ return acc + (isNaN(value) ? 0 : value)
366
+ }, 0)
367
+ }
368
+
369
+ return value
370
+ })
371
+ }
372
+ else{
373
+ data = xAxes.map(_ => {
374
+
375
+ let curItems
376
+ switch(_.modifier){
377
+ case 'date':
378
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('DD') === _.key)
379
+ break
380
+
381
+ case 'month':
382
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('MMM') === _.key)
383
+ break
384
+
385
+ case 'year':
386
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('YYYY') === _.key)
387
+ break
388
+
389
+ case 'hour':
390
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('HH') === _.key)
391
+ break
392
+
393
+ case 'minute':
394
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('mm') === _.key)
395
+ break
396
+
397
+ case 'last12Hours':
398
+ case 'last24Hours':
399
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('YYYYMMDDHH0000') === _.key)
400
+ break
401
+
402
+ case 'last60Minutes':
403
+ curItems = yAxeItems.filter(__ => dayjs(__[_.parentKey]).format('YYYYMMDDHHmm00') === _.key)
404
+ break
405
+
406
+ default:
407
+ curItems = yAxeItems.filter(__ => __[_.parentKey] === _.key)
408
+ }
409
+
410
+ let value
411
+ switch(yAxe.modifier){
412
+ case 'count':
413
+ value = curItems.length
414
+ break
415
+
416
+ case 'countDistinct':
417
+ value = curItems.reduce((acc, item) => {
418
+ if(!acc.includes(item[yAxe.key]))
419
+ acc.push(item[yAxe.key])
420
+ return acc
421
+ }, []).length
422
+ break
423
+
424
+ case 'sum':
425
+ default:
426
+ value = curItems.reduce((acc, item) => {
427
+ const value = parseFloat(item[yAxe.key])
428
+ return acc + (isNaN(value) ? 0 : value)
429
+ }, 0)
430
+ }
431
+
432
+ return value
433
+ })
434
+ }
435
+
436
+ let label = yAxe.key
437
+ if(params.yAxeMultiple){
438
+ label = yAxe.alias ?? yAxe.key
439
+
440
+ if(columns[yAxe.parentKey]?.type === 'enum'){
441
+ if(columns[yAxe.parentKey]?.typeParams){
442
+ const obj = columns[yAxe.parentKey]?.typeParams.find(_ => _.value === label)
443
+ label = obj?.text ?? label
444
+ }
445
+ }
446
+ }
447
+
448
+ datasets.push({
449
+ label,
450
+ data,
451
+ backgroundColor: yAxes.length > 1 ?
452
+ [ backgroundColors[idx % backgroundColors.length] ]:
453
+ [ backgroundColors[0] ],
454
+ borderColor: yAxes.length > 1 ?
455
+ [ backgroundColors[idx % backgroundColors.length] ]:
456
+ [ backgroundColors[0] ],
457
+ borderWidth: 1
458
+ })
459
+
460
+ yLegends.push({
461
+ key: label ?? '(empty)',
462
+ color: backgroundColors[idx % backgroundColors.length]
463
+ })
464
+
465
+ idx++
466
+ }
467
+
468
+ labels = xAxes.map(_ => {
469
+ if(enums[_.parentKey])
470
+ return enums[_.parentKey][_.key] ?? (_.alias ?? _.key)
471
+ return _.alias ?? _.key
472
+ })
473
+
474
+ if(datasets.length > 0 && params.yAxeMultiple && params.usePercentage){
475
+
476
+ params.datasets = datasets
477
+
478
+ const segments = []
479
+ let segmentMax = -1
480
+ for(let i in datasets[0].data){
481
+
482
+ if(!segments[i])
483
+ segments[i] = { data:[] }
484
+
485
+ for(let j in datasets){
486
+ segments[i].data.push(datasets[j].data[i])
487
+ }
488
+
489
+ segments[i].max = Math.max(...segments[i].data)
490
+
491
+ if(segments[i].max > segmentMax)
492
+ segmentMax = segments[i].max
493
+ }
494
+
495
+ for(let i in datasets){
496
+
497
+ const data0 = []
498
+ for(let j in datasets[i].data){
499
+ const multipler = segmentMax / segments[j].max
500
+ data0.push(datasets[i].data[j])
501
+ datasets[i].data[j] = ((datasets[i].data[j] * multipler) / segmentMax * 100).toFixed(1)
502
+ }
503
+ datasets[i].data0 = data0
504
+ }
505
+ }
506
+
507
+ if(datasets.length > 500){
508
+ return { error:1, message:'Too many datasets' }
509
+ }
510
+ if(labels > 1000){
511
+ return { error:1, message:'Too many labels' }
512
+ }
513
+
514
+ return {
515
+ labels,
516
+ datasets,
517
+ yLegends
518
+ }
519
+ }
520
+
521
+ const loadMetric = async(params, opt) => {
522
+
523
+ const { column, datasource, columnModifier, column2Enabled, column2, column2Modifier } = params
524
+
525
+ const res = {
526
+ value: '',
527
+ comparedValue: '',
528
+ comparedPercent: ''
529
+ }
530
+
531
+ if(column){
532
+
533
+ let getValue
534
+ if('items' in params){
535
+ const { items } = params
536
+ getValue = async (column, modifier) => {
537
+ let value
538
+
539
+ switch(modifier){
540
+ case 'count':
541
+ value = items.length
542
+ break
543
+
544
+ case 'countDistinct':
545
+ value = items.reduce((acc, item) => {
546
+ if(item[column] && !acc.includes(item[column]))
547
+ acc.push(item[column])
548
+ return acc
549
+ }, []).length
550
+ break
551
+
552
+ case 'sum':
553
+ value = items.reduce((acc, item) => {
554
+ const val = parseFloat(item[column])
555
+ return acc + (isNaN(val) ? 0 : val)
556
+ }, 0)
557
+ break
558
+
559
+ case 'avg':
560
+ value = items.reduce((acc, item) => {
561
+ const val = parseFloat(item[column])
562
+ return acc + (isNaN(val) ? 0 : val)
563
+ }, 0) / items.length
564
+ break
565
+
566
+ case 'min':
567
+ value = items.reduce((acc, item) => {
568
+ const val = parseFloat(item[column])
569
+ return Math.min(acc, isNaN(val) ? Number.MAX_SAFE_INTEGER : val)
570
+ }, Number.MAX_SAFE_INTEGER)
571
+
572
+ if(value === Number.MAX_SAFE_INTEGER)
573
+ value = ''
574
+ break
575
+
576
+ case 'max':
577
+ value = items.reduce((acc, item) => {
578
+ const val = parseFloat(item[column])
579
+ return Math.max(acc, isNaN(val) ? Number.MIN_SAFE_INTEGER : val)
580
+ }, Number.MIN_SAFE_INTEGER)
581
+
582
+ if(value === Number.MIN_SAFE_INTEGER)
583
+ value = ''
584
+ break
585
+
586
+ case 'first':
587
+ value = (items[0] ?? {})[column]
588
+ break
589
+
590
+ case 'last':
591
+ value = (items[items.length - 1] ?? {})[column]
592
+ break
593
+ }
594
+
595
+ return value
596
+ }
597
+ }
598
+
599
+ else{
600
+ const Model = await getModelFromDatasource(datasource, opt)
601
+ const config = {
602
+ columns: datasource.columns
603
+ }
604
+ const findOpt = await presetToSequelizeList(datasource, {
605
+ config,
606
+ model: Model,
607
+ conn: opt.conn
608
+ })
609
+
610
+ getValue = async (column, modifier) => {
611
+ let value
612
+
613
+ switch(modifier){
614
+ case 'count':
615
+ value = await Model.count({
616
+ where: findOpt.where,
617
+ col: column
618
+ })
619
+ break
620
+
621
+ case 'countDistinct':
622
+ value = await Model.count({
623
+ where: findOpt.where,
624
+ distinct: true,
625
+ col: column
626
+ })
627
+ break
628
+
629
+ case 'sum':
630
+ value = await Model.sum(column, findOpt)
631
+ break
632
+
633
+ case 'avg':
634
+ const row = await Model.findOne({
635
+ ...findOpt,
636
+ attributes: [[fn('AVG', col(column)), 'avg']],
637
+ });
638
+ value = row?.dataValues.avg ?
639
+ round(row.dataValues.avg, 1) :
640
+ row?.dataValues.avg
641
+ break
642
+
643
+ case 'min':
644
+ value = await Model.min(column, findOpt)
645
+ break
646
+
647
+ case 'max':
648
+ value = await Model.max(column, findOpt)
649
+ break
650
+
651
+ case 'first':
652
+ const first = await Model.findOne({
653
+ ...findOpt,
654
+ attributes: [column],
655
+ order: [[column, 'ASC']]
656
+ })
657
+ value = first?.dataValues[column]
658
+ break
659
+
660
+ case 'last':
661
+ const last = await Model.findOne({
662
+ ...findOpt,
663
+ attributes: [column],
664
+ order: [[column, 'DESC']]
665
+ })
666
+ value = last?.dataValues[column]
667
+ break
668
+ }
669
+
670
+ return value
671
+ }
672
+ }
673
+
674
+ res.value = await getValue(column, columnModifier)
675
+ if(column2Enabled){
676
+ res.comparedValue = await getValue(column2, column2Modifier)
677
+ res.comparedPercent = round(res.value / res.comparedValue * 100, 1)
678
+ }
679
+ }
680
+
681
+ return res
682
+ }
683
+
684
+ const loadDoughnut = async(params, opt) => {
685
+
686
+ const { column, columnModifier, rowsModifier, datasource } = params
687
+
688
+ const { items } = params
689
+
690
+ let rowsEnum = {}
691
+ const datasourceColumn = datasource?.columns.find(_ => _.key === params.rows)
692
+ if(datasourceColumn?.type === 'enum'){
693
+ if(datasourceColumn?.typeParams){
694
+ rowsEnum = datasourceColumn.typeParams.reduce((res, cur) => {
695
+ res[cur.value] = cur.text
696
+ return res
697
+ }, {})
698
+ }
699
+ else if(datasourceColumn?.enumSrc) {
700
+ const [path, key, name] = datasourceColumn.enumSrc.split(':')
701
+ if (typeof opt.enumFn === 'function') {
702
+ const {items} = await opt.enumFn(path, {}, opt.socket) ?? {}
703
+
704
+ if (Array.isArray(items)) {
705
+ rowsEnum = items.reduce((res, cur) => {
706
+ res[cur[key]] = cur[name]
707
+ return res
708
+ }, {})
709
+ }
710
+ }
711
+ }
712
+ }
713
+
714
+ const labels = Object.values(items.reduce((res, cur) => {
715
+ let label = cur[params.rows]
716
+
717
+ let alias = rowsEnum[cur[params.rows]] ?? label
718
+ switch(rowsModifier){
719
+ case 'lowercase':
720
+ alias = label.toString().toLowerCase()
721
+ break
722
+
723
+ case 'uppercase':
724
+ alias = label.toString().toUpperCase()
725
+ break
726
+
727
+ case 'capitalize':
728
+ alias = capitalize(label)
729
+ break
730
+
731
+ case 'date':
732
+ label = alias = dayjs(label).format('DD/MM/YYYY')
733
+ break
734
+
735
+ case 'month':
736
+ label = alias = dayjs(label).format('MMM')
737
+ break
738
+
739
+ case 'year':
740
+ label = alias = dayjs(label).format('YYYY')
741
+ break
742
+
743
+ case 'hour':
744
+ label = alias = dayjs(label).format('HH')
745
+ break
746
+
747
+ case 'minute':
748
+ label = alias = dayjs(label).format('mm')
749
+ break
750
+ }
751
+
752
+ res[label] = {
753
+ label,
754
+ alias
755
+ }
756
+
757
+ return res
758
+ }, {}))
759
+
760
+ const datasets = []
761
+
762
+ const data = labels.map(({ label }) => {
763
+ const currentItems = items.filter(_ => {
764
+ switch(rowsModifier){
765
+
766
+ case 'date':
767
+ return dayjs(_[params.rows]).format('DD/MM/YYYY') === label
768
+
769
+ case 'month':
770
+ return dayjs(_[params.rows]).format('MMM') === label
771
+
772
+ case 'year':
773
+ return dayjs(_[params.rows]).format('YYYY') === label
774
+
775
+ case 'hour':
776
+ return dayjs(_[params.rows]).format('HH') === label
777
+
778
+ case 'minute':
779
+ return dayjs(_[params.rows]).format('mm') === label
780
+
781
+ default:
782
+ return _[params.rows] === label
783
+ }
784
+ })
785
+
786
+ let value
787
+ switch(columnModifier){
788
+
789
+ case 'count':
790
+ value = currentItems.length
791
+ break
792
+
793
+ case 'countDistinct':
794
+ value = currentItems.reduce((acc, item) => {
795
+ if(!acc.includes(item[column]))
796
+ acc.push(item[column])
797
+ return acc
798
+ }, []).length
799
+ break
800
+
801
+ case 'min':
802
+ value = currentItems.reduce((res, cur) => {
803
+ let val = parseFloat(cur[column])
804
+ return Math.min(res, isNaN(val) ? Number.MAX_SAFE_INTEGER : val)
805
+ })
806
+ break
807
+
808
+ case 'max':
809
+ value = currentItems.reduce((acc, item) => {
810
+ return Math.max(acc, item[column])
811
+ }, Number.MIN_SAFE_INTEGER)
812
+ break
813
+
814
+ case 'first':
815
+ value = (currentItems[0] ?? {})[column]
816
+ break
817
+
818
+ case 'last':
819
+ value = (currentItems[items.length - 1] ?? {})[column]
820
+ break
821
+
822
+ case 'sum':
823
+ default:
824
+ value = currentItems.reduce((res, cur) => {
825
+ let val = parseFloat(cur[column])
826
+ return isNaN(val) ? 0 : val
827
+ }, 0)
828
+ }
829
+
830
+ return value
831
+ })
832
+
833
+ const backgroundColor = labels.map((_, i) => backgroundColors[i % backgroundColors.length])
834
+
835
+ datasets.push({
836
+ label: column,
837
+ data: data,
838
+ backgroundColor,
839
+ hoverOffset: 4
840
+ })
841
+
842
+ return {
843
+ labels: labels.map(_ => _.alias),
844
+ datasets
845
+ }
846
+ }
847
+
848
+ const loadPie = async(params, opt) => {
849
+
850
+ const { rowsModifier, datasource } = params
851
+
852
+ const { items } = params
853
+
854
+ let rowsEnum = {}
855
+ const datasourceColumn = datasource?.columns.find(_ => _.key === params.rows)
856
+ if(datasourceColumn?.type === 'enum'){
857
+ if(datasourceColumn?.typeParams){
858
+ rowsEnum = datasourceColumn.typeParams.reduce((res, cur) => {
859
+ res[cur.value] = cur.text
860
+ return res
861
+ }, {})
862
+ }
863
+ else if(datasourceColumn?.enumSrc) {
864
+ const [path, key, name] = datasourceColumn.enumSrc.split(':')
865
+ if (typeof opt.enumFn === 'function') {
866
+ const {items} = await opt.enumFn(path, {}, opt.socket) ?? {}
867
+
868
+ if (Array.isArray(items)) {
869
+ rowsEnum = items.reduce((res, cur) => {
870
+ res[cur[key]] = cur[name]
871
+ return res
872
+ }, {})
873
+ }
874
+ }
875
+ }
876
+ }
877
+
878
+ const labels = Object.values(items.reduce((res, cur) => {
879
+ let label = cur[params.rows]
880
+
881
+ let alias = rowsEnum[cur[params.rows]] ?? label
882
+ switch(rowsModifier){
883
+ case 'lowercase':
884
+ alias = label.toString().toLowerCase()
885
+ break
886
+
887
+ case 'uppercase':
888
+ alias = label.toString().toUpperCase()
889
+ break
890
+
891
+ case 'capitalize':
892
+ alias = capitalize(label)
893
+ break
894
+
895
+ case 'date':
896
+ label = alias = dayjs(label).format('DD/MM/YYYY')
897
+ break
898
+
899
+ case 'month':
900
+ label = alias = dayjs(label).format('MMM')
901
+ break
902
+
903
+ case 'year':
904
+ label = alias = dayjs(label).format('YYYY')
905
+ break
906
+
907
+ case 'hour':
908
+ label = alias = dayjs(label).format('HH')
909
+ break
910
+
911
+ case 'minute':
912
+ label = alias = dayjs(label).format('mm')
913
+ break
914
+ }
915
+
916
+ res[label] = {
917
+ label,
918
+ alias
919
+ }
920
+
921
+ return res
922
+ }, {}))
923
+
924
+ const datasets = []
925
+
926
+ for(let column of params.column){
927
+
928
+ const data = labels.map(({ label }) => {
929
+
930
+ const currentItems = items.filter(_ => {
931
+
932
+ switch(rowsModifier){
933
+
934
+ case 'date':
935
+ return dayjs(_[params.rows]).format('DD/MM/YYYY') === label
936
+ break
937
+
938
+ case 'month':
939
+ return dayjs(_[params.rows]).format('MMM') === label
940
+ break
941
+
942
+ case 'year':
943
+ return dayjs(_[params.rows]).format('YYYY') === label
944
+ break
945
+
946
+ case 'hour':
947
+ return dayjs(_[params.rows]).format('HH') === label
948
+ break
949
+
950
+ case 'minute':
951
+ return dayjs(_[params.rows]).format('mm') === label
952
+ break
953
+
954
+ default:
955
+ return _[params.rows] === label
956
+ }
957
+ })
958
+
959
+ let value
960
+ switch(column.modifier){
961
+
962
+ case 'count':
963
+ value = currentItems.length
964
+ break
965
+
966
+ case 'countDistinct':
967
+ value = currentItems.reduce((acc, item) => {
968
+ if(!acc.includes(item[column.key]))
969
+ acc.push(item[column.key])
970
+ return acc
971
+ }, []).length
972
+ break
973
+
974
+ case 'min':
975
+ value = currentItems.reduce((res, cur) => {
976
+ let val = parseFloat(cur[column.key])
977
+ return Math.min(res, isNaN(val) ? Number.MAX_SAFE_INTEGER : val)
978
+ })
979
+ break
980
+
981
+ case 'max':
982
+ value = currentItems.reduce((acc, item) => {
983
+ return Math.max(acc, item[column.key])
984
+ }, Number.MIN_SAFE_INTEGER)
985
+ break
986
+
987
+ case 'first':
988
+ value = (currentItems[0] ?? {})[column.key]
989
+ break
990
+
991
+ case 'last':
992
+ value = (currentItems[items.length - 1] ?? {})[column.key]
993
+ break
994
+
995
+ case 'sum':
996
+ default:
997
+ value = currentItems.reduce((res, cur) => {
998
+ let val = parseFloat(cur[column.key])
999
+ return isNaN(val) ? 0 : val
1000
+ }, 0)
1001
+ }
1002
+
1003
+ return value
1004
+ })
1005
+
1006
+ const backgroundColor = labels.map((_, i) => backgroundColors[i % backgroundColors.length])
1007
+
1008
+ datasets.push({
1009
+ label: column.label ?? column.key,
1010
+ data: data,
1011
+ backgroundColor,
1012
+ hoverOffset: 4
1013
+ })
1014
+
1015
+ if(!params.multipleValues)
1016
+ break
1017
+ }
1018
+
1019
+ return {
1020
+ labels: labels.map(_ => _.alias),
1021
+ datasets
1022
+ }
1023
+ }
1024
+
1025
+ const loadPolarArea = async(params, opt) => {
1026
+ return loadDoughnut(params, opt)
1027
+ }
1028
+
1029
+ const loadVirtualTable = async(params, opt) => {
1030
+
1031
+ const res = await getDatasourceItems({
1032
+ ...params.datasource,
1033
+ sorts: [
1034
+ { key:'id', type:'asc' }
1035
+ ]
1036
+ }, opt)
1037
+
1038
+ let { items, hasNext } = res
1039
+ let { datasourceColumns } = params
1040
+
1041
+ items = items.map(item => {
1042
+ const obj = {}
1043
+
1044
+ for(let column of datasourceColumns){
1045
+ if(column.visible || [ 'id' ].includes(column.key)){
1046
+ obj[column.key] = item[column.key]
1047
+ }
1048
+ }
1049
+
1050
+ return obj
1051
+ })
1052
+
1053
+ return {
1054
+ items,
1055
+ hasNext
1056
+ }
1057
+ }
1058
+
1059
+ const loadGHeatMaps = async(params, opt) => {
1060
+ return {
1061
+ items: params.items
1062
+ }
1063
+ }
1064
+
1065
+
1066
+ module.exports = {
1067
+ getModelFromDatasource,
1068
+ getDatasourceItems,
1069
+ getSequelizeColumns,
1070
+
1071
+ backgroundColors,
1072
+
1073
+ loadBarChart,
1074
+ loadMetric,
1075
+ loadDoughnut,
1076
+ loadPie,
1077
+ loadPolarArea,
1078
+ loadVirtualTable,
1079
+ loadGHeatMaps
1080
+ }