@isoftdata/utility-dashboard-backend 1.2.2 → 1.3.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/chart-helper.js CHANGED
@@ -1,254 +1,254 @@
1
- const { formatterTemplates } = require('./formatter-templates.json')
2
- const toSnakeCase = require('to-snake-case')
3
- const mysqlDataTypes = require('mysql/lib/protocol/constants/types')
4
- const klona = require('klona')
5
-
6
- const mysqlToGoogleChartsDataTypeMap = mysqlDataType => {
7
- switch (mysqlDataType) {
8
- case 'VAR_STRING':
9
- case 'STRING':
10
- case 'ENUM':
11
- case 'VARCHAR':
12
- return 'string'
13
- case 'DECIMAL':
14
- case 'NEWDECIMAL':
15
- case 'BIT':
16
- case 'TINY':
17
- case 'SHORT':
18
- case 'LONG':
19
- case 'FLOAT':
20
- case 'DOUBLE':
21
- case 'LONGLONG':
22
- case 'INT24':
23
- case 'YEAR':
24
- return 'number'
25
- case 'TIMESTAMP':
26
- case 'DATETIME':
27
- case 'TIMESTAMP2':
28
- case 'DATETIME2':
29
- return 'datetime'
30
- case 'DATE':
31
- case 'NEWDATE':
32
- return 'date'
33
- case 'TIME':
34
- case 'TIME2':
35
- return 'timeofday'
36
- default:
37
- return 'string'
38
- }
39
- }
40
-
41
- const makeDataTableCol = (key, fields, role = null) => {
42
- return {
43
- id: toSnakeCase(key).toLowerCase(),
44
- label: key,
45
- type: mysqlToGoogleChartsDataTypeMap(fields[key]),
46
- role,
47
- }
48
- }
49
-
50
- module.exports = {
51
- queryWithFields: (connection, query) => { // the only function here that talks to the server at all
52
- return new Promise((resolve, reject) => {
53
- connection.query(query, (err, results, fields) => {
54
- if (err) {
55
- reject(err)
56
- } else {
57
- resolve({
58
- results,
59
- fields: fields.reduce((sum, field) => {
60
- return {
61
- ...sum,
62
- [field.name]: mysqlDataTypes[field.type] || field.type,
63
- }
64
- }, {}),
65
- })
66
- }
67
- })
68
- })
69
- },
70
- getMultiSeriesData: ({ data: queryResult, seriesOptions, formatting, fields }) => {
71
- const { groupXAxisBy, series, value, tooltip } = seriesOptions
72
- //Get an array of all of the keys for the series
73
- const allOfTheKeysInTheSeries = new Set(queryResult.map(row => row[series]))
74
-
75
- let objectOfAllKeysWithNullValues = {}
76
- for (const key of allOfTheKeysInTheSeries) {
77
- objectOfAllKeysWithNullValues[key] = null
78
- //add in a spot for the tooltips for each column to go
79
- if (tooltip) {
80
- objectOfAllKeysWithNullValues[`${tooltip} ${key}`] = null
81
- }
82
- }
83
-
84
- let grouped = {}
85
-
86
- //create objects for each thing they're grouping by
87
- // console.time(`create objects for each thing they're grouping by`)
88
- for (const row of queryResult) {
89
- //if we don't yet have a prop for what we're grouping by, then we need to initialize an object of all null values for it
90
- if (!grouped[row[groupXAxisBy]]) {
91
- grouped[row[groupXAxisBy]] = klona(objectOfAllKeysWithNullValues) //give nulls for all values by default
92
- }
93
-
94
- grouped[row[groupXAxisBy]][row[series]] = row[value]
95
- // add the tooltips for each series in each row
96
- if (tooltip) {
97
- grouped[row[groupXAxisBy]][`${tooltip} ${row[series]}`] = row[tooltip]
98
- }
99
- }
100
- // console.timeEnd(`create objects for each thing they're grouping by`)
101
-
102
- //The mysql field types need to map to our output data
103
- let seriesFields = { [groupXAxisBy]: fields[groupXAxisBy] }
104
- let seriesFormatting = {}
105
- for (const key of allOfTheKeysInTheSeries) {
106
- seriesFields[key] = fields[value]
107
- //add tooltips here, so a tooltip column is created for every series column
108
- if (tooltip) {
109
- seriesFields[`${tooltip} ${key}`] = fields[tooltip]
110
- }
111
- //If there is any formatting applied to the value, we need to apply it to all of the other keys
112
- if (formatting && value in formatting) {
113
- seriesFormatting[key] = formatting[value]
114
- }
115
- }
116
-
117
- //turn it back to an array and make sure the grouping column is first as required by a pie chart(at least)
118
- // console.time(`group it back together`)
119
- let multiSeriesData = []
120
- for (const key in grouped) {
121
- multiSeriesData.push({
122
- [groupXAxisBy]: key,
123
- ...grouped[key],
124
- })
125
- }
126
- // console.timeEnd(`group it back together`)
127
- // console.log(`${multiSeriesData.length} rows with ${Object.keys(multiSeriesData[0]).length} props each.`)
128
- return {
129
- data: multiSeriesData,
130
- formatting: {
131
- ...formatting,
132
- ...seriesFormatting,
133
- },
134
- fields: seriesFields,
135
- }
136
- },
137
- makeNegativeNumbericValuesZero: resultSet => {
138
- const numberCols = Object.keys(resultSet[0]).filter(key => typeof resultSet[0][key] === 'number')
139
-
140
- return resultSet.map(row => {
141
- numberCols.forEach(col => {
142
- if (row[col] < 0) {
143
- row[col] = 0
144
- }
145
- })
146
-
147
- return row
148
- })
149
- },
150
- coerceNullColumn: (columnProp, data, destinationType) => {
151
- const hasNullValue = data.find(row => row[columnProp] === null)
152
-
153
- if (hasNullValue) {
154
- const rowWithValidValue = data.find(row => row[columnProp] !== null)
155
- let nullReplacement = ''
156
-
157
- if ((destinationType && destinationType === 'number') || (typeof rowWithValidValue[columnProp] === 'number')) {
158
- nullReplacement = 0
159
- }
160
-
161
- return data.map(row => {
162
- return {
163
- ...row,
164
- [columnProp]: row[columnProp] ?? nullReplacement,
165
- }
166
- })
167
- } else {
168
- return data
169
- }
170
- },
171
- handleFormattingTemplates: formatting => {
172
- if (formatting && typeof formatting === 'object') {
173
- for (const key in formatting) {
174
- if (typeof formatting[key].format === 'string') {
175
- if (formatting[key].format in formatterTemplates) {
176
- formatting[key].format = formatterTemplates[formatting[key].format]
177
- } else {
178
- formatting[key].format = {}
179
- }
180
- }
181
- }
182
- }
183
-
184
- return formatting
185
- },
186
- getDataTableFormat: (data, fields, multiSeries) => {
187
- let cols = []
188
- // domain column for multiseries charts
189
- if (multiSeries) {
190
- cols = [ makeDataTableCol(multiSeries.groupXAxisBy, fields) ]
191
- }
192
- // All other columns (including domain for non multiseries charts)
193
- for (const key in fields) {
194
- if (!multiSeries || key !== multiSeries.groupXAxisBy) {
195
- if (multiSeries && multiSeries.tooltip && key.includes(multiSeries.tooltip)) {
196
- // If it's a tooltip, set the column's role exlicitly
197
- cols.push(makeDataTableCol(key, fields, 'tooltip'))
198
- } else {
199
- // Otherwise, role is implicitly assigned
200
- cols.push(makeDataTableCol(key, fields))
201
- }
202
- }
203
- }
204
-
205
- const rows = data.map(row => {
206
- let items = []
207
-
208
- for (const column in row) {
209
- items.push({ v: row[column] })
210
- }
211
-
212
- return {
213
- c: items,
214
- }
215
- })
216
-
217
- return { cols, rows }
218
- },
219
- makeDataCumulative: dataTable => {
220
- dataTable.rows.forEach((row, rowIndex) => {
221
- if (rowIndex > 0) {
222
- row.c.forEach((col, colIndex) => {
223
- if (colIndex > 0 && col.v == null) {
224
- col.v = dataTable.rows[rowIndex - 1].c[colIndex].v
225
- } else if (colIndex > 0) {
226
- col.v = col.v + dataTable.rows[rowIndex - 1].c[colIndex].v
227
- }
228
- })
229
- }
230
- })
231
- return dataTable
232
- },
233
- getRactiveTableDataFormat: (data, fields, formatting) => {
234
- const columns = Object.keys(fields).map(field => {
235
- // right align currency values
236
- if (formatting[field]?.type === 'NumberFormat' && formatting?.[field].format.prefix === '$') {
237
- return {
238
- property: field,
239
- name: field,
240
- class: 'text-right',
241
- }
242
- }
243
- return {
244
- property: field,
245
- name: field,
246
- }
247
- })
248
- return {
249
- columns,
250
- rows: data,
251
- sortedRows: [],
252
- }
253
- },
254
- }
1
+ const { formatterTemplates } = require('./formatter-templates.json')
2
+ const toSnakeCase = require('to-snake-case')
3
+ const mysqlDataTypes = require('mysql/lib/protocol/constants/types')
4
+ const klona = require('klona')
5
+
6
+ const mysqlToGoogleChartsDataTypeMap = mysqlDataType => {
7
+ switch (mysqlDataType) {
8
+ case 'VAR_STRING':
9
+ case 'STRING':
10
+ case 'ENUM':
11
+ case 'VARCHAR':
12
+ return 'string'
13
+ case 'DECIMAL':
14
+ case 'NEWDECIMAL':
15
+ case 'BIT':
16
+ case 'TINY':
17
+ case 'SHORT':
18
+ case 'LONG':
19
+ case 'FLOAT':
20
+ case 'DOUBLE':
21
+ case 'LONGLONG':
22
+ case 'INT24':
23
+ case 'YEAR':
24
+ return 'number'
25
+ case 'TIMESTAMP':
26
+ case 'DATETIME':
27
+ case 'TIMESTAMP2':
28
+ case 'DATETIME2':
29
+ return 'datetime'
30
+ case 'DATE':
31
+ case 'NEWDATE':
32
+ return 'date'
33
+ case 'TIME':
34
+ case 'TIME2':
35
+ return 'timeofday'
36
+ default:
37
+ return 'string'
38
+ }
39
+ }
40
+
41
+ const makeDataTableCol = (key, fields, role = null) => {
42
+ return {
43
+ id: toSnakeCase(key).toLowerCase(),
44
+ label: key,
45
+ type: mysqlToGoogleChartsDataTypeMap(fields[key]),
46
+ role,
47
+ }
48
+ }
49
+
50
+ module.exports = {
51
+ queryWithFields: (connection, query) => { // the only function here that talks to the server at all
52
+ return new Promise((resolve, reject) => {
53
+ connection.query(query, (err, results, fields) => {
54
+ if (err) {
55
+ reject(err)
56
+ } else {
57
+ resolve({
58
+ results,
59
+ fields: fields.reduce((sum, field) => {
60
+ return {
61
+ ...sum,
62
+ [field.name]: mysqlDataTypes[field.type] || field.type,
63
+ }
64
+ }, {}),
65
+ })
66
+ }
67
+ })
68
+ })
69
+ },
70
+ getMultiSeriesData: ({ data: queryResult, seriesOptions, formatting, fields }) => {
71
+ const { groupXAxisBy, series, value, tooltip } = seriesOptions
72
+ //Get an array of all of the keys for the series
73
+ const allOfTheKeysInTheSeries = new Set(queryResult.map(row => row[series]))
74
+
75
+ let objectOfAllKeysWithNullValues = {}
76
+ for (const key of allOfTheKeysInTheSeries) {
77
+ objectOfAllKeysWithNullValues[key] = null
78
+ //add in a spot for the tooltips for each column to go
79
+ if (tooltip) {
80
+ objectOfAllKeysWithNullValues[`${tooltip} ${key}`] = null
81
+ }
82
+ }
83
+
84
+ let grouped = {}
85
+
86
+ //create objects for each thing they're grouping by
87
+ // console.time(`create objects for each thing they're grouping by`)
88
+ for (const row of queryResult) {
89
+ //if we don't yet have a prop for what we're grouping by, then we need to initialize an object of all null values for it
90
+ if (!grouped[row[groupXAxisBy]]) {
91
+ grouped[row[groupXAxisBy]] = klona(objectOfAllKeysWithNullValues) //give nulls for all values by default
92
+ }
93
+
94
+ grouped[row[groupXAxisBy]][row[series]] = row[value]
95
+ // add the tooltips for each series in each row
96
+ if (tooltip) {
97
+ grouped[row[groupXAxisBy]][`${tooltip} ${row[series]}`] = row[tooltip]
98
+ }
99
+ }
100
+ // console.timeEnd(`create objects for each thing they're grouping by`)
101
+
102
+ //The mysql field types need to map to our output data
103
+ let seriesFields = { [groupXAxisBy]: fields[groupXAxisBy] }
104
+ let seriesFormatting = {}
105
+ for (const key of allOfTheKeysInTheSeries) {
106
+ seriesFields[key] = fields[value]
107
+ //add tooltips here, so a tooltip column is created for every series column
108
+ if (tooltip) {
109
+ seriesFields[`${tooltip} ${key}`] = fields[tooltip]
110
+ }
111
+ //If there is any formatting applied to the value, we need to apply it to all of the other keys
112
+ if (formatting && value in formatting) {
113
+ seriesFormatting[key] = formatting[value]
114
+ }
115
+ }
116
+
117
+ //turn it back to an array and make sure the grouping column is first as required by a pie chart(at least)
118
+ // console.time(`group it back together`)
119
+ let multiSeriesData = []
120
+ for (const key in grouped) {
121
+ multiSeriesData.push({
122
+ [groupXAxisBy]: key,
123
+ ...grouped[key],
124
+ })
125
+ }
126
+ // console.timeEnd(`group it back together`)
127
+ // console.log(`${multiSeriesData.length} rows with ${Object.keys(multiSeriesData[0]).length} props each.`)
128
+ return {
129
+ data: multiSeriesData,
130
+ formatting: {
131
+ ...formatting,
132
+ ...seriesFormatting,
133
+ },
134
+ fields: seriesFields,
135
+ }
136
+ },
137
+ makeNegativeNumbericValuesZero: resultSet => {
138
+ const numberCols = Object.keys(resultSet[0]).filter(key => typeof resultSet[0][key] === 'number')
139
+
140
+ return resultSet.map(row => {
141
+ numberCols.forEach(col => {
142
+ if (row[col] < 0) {
143
+ row[col] = 0
144
+ }
145
+ })
146
+
147
+ return row
148
+ })
149
+ },
150
+ coerceNullColumn: (columnProp, data, destinationType) => {
151
+ const hasNullValue = data.find(row => row[columnProp] === null)
152
+
153
+ if (hasNullValue) {
154
+ const rowWithValidValue = data.find(row => row[columnProp] !== null)
155
+ let nullReplacement = ''
156
+
157
+ if ((destinationType && destinationType === 'number') || (typeof rowWithValidValue[columnProp] === 'number')) {
158
+ nullReplacement = 0
159
+ }
160
+
161
+ return data.map(row => {
162
+ return {
163
+ ...row,
164
+ [columnProp]: row[columnProp] ?? nullReplacement,
165
+ }
166
+ })
167
+ } else {
168
+ return data
169
+ }
170
+ },
171
+ handleFormattingTemplates: formatting => {
172
+ if (formatting && typeof formatting === 'object') {
173
+ for (const key in formatting) {
174
+ if (typeof formatting[key].format === 'string') {
175
+ if (formatting[key].format in formatterTemplates) {
176
+ formatting[key].format = formatterTemplates[formatting[key].format]
177
+ } else {
178
+ formatting[key].format = {}
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ return formatting
185
+ },
186
+ getDataTableFormat: (data, fields, multiSeries) => {
187
+ let cols = []
188
+ // domain column for multiseries charts
189
+ if (multiSeries) {
190
+ cols = [ makeDataTableCol(multiSeries.groupXAxisBy, fields) ]
191
+ }
192
+ // All other columns (including domain for non multiseries charts)
193
+ for (const key in fields) {
194
+ if (!multiSeries || key !== multiSeries.groupXAxisBy) {
195
+ if (multiSeries && multiSeries.tooltip && key.includes(multiSeries.tooltip)) {
196
+ // If it's a tooltip, set the column's role exlicitly
197
+ cols.push(makeDataTableCol(key, fields, 'tooltip'))
198
+ } else {
199
+ // Otherwise, role is implicitly assigned
200
+ cols.push(makeDataTableCol(key, fields))
201
+ }
202
+ }
203
+ }
204
+
205
+ const rows = data.map(row => {
206
+ let items = []
207
+
208
+ for (const column in row) {
209
+ items.push({ v: row[column] })
210
+ }
211
+
212
+ return {
213
+ c: items,
214
+ }
215
+ })
216
+
217
+ return { cols, rows }
218
+ },
219
+ makeDataCumulative: dataTable => {
220
+ dataTable.rows.forEach((row, rowIndex) => {
221
+ if (rowIndex > 0) {
222
+ row.c.forEach((col, colIndex) => {
223
+ if (colIndex > 0 && col.v == null) {
224
+ col.v = dataTable.rows[rowIndex - 1].c[colIndex].v
225
+ } else if (colIndex > 0) {
226
+ col.v = col.v + dataTable.rows[rowIndex - 1].c[colIndex].v
227
+ }
228
+ })
229
+ }
230
+ })
231
+ return dataTable
232
+ },
233
+ getRactiveTableDataFormat: (data, fields, formatting) => {
234
+ const columns = Object.keys(fields).map(field => {
235
+ // right align currency values
236
+ if (formatting[field]?.type === 'NumberFormat' && formatting?.[field].format.prefix === '$') {
237
+ return {
238
+ property: field,
239
+ name: field,
240
+ class: 'text-right',
241
+ }
242
+ }
243
+ return {
244
+ property: field,
245
+ name: field,
246
+ }
247
+ })
248
+ return {
249
+ columns,
250
+ rows: data,
251
+ sortedRows: [],
252
+ }
253
+ },
254
+ }
@@ -1,9 +1,9 @@
1
- {
2
- "formatterTemplates": {
3
- "CURRENCY": {
4
- "prefix": "$",
5
- "fractionDigits": 2,
6
- "groupingSymbol": ","
7
- }
8
- }
1
+ {
2
+ "formatterTemplates": {
3
+ "CURRENCY": {
4
+ "prefix": "$",
5
+ "fractionDigits": 2,
6
+ "groupingSymbol": ","
7
+ }
8
+ }
9
9
  }