@isoftdata/utility-dashboard-backend 2.4.0 → 2.4.1
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/README.md +177 -177
- package/dist/chart-helper.d.ts +30 -30
- package/dist/chart-helper.js +255 -255
- package/dist/formatting-templates.d.ts +3 -3
- package/dist/formatting-templates.js +16 -16
- package/dist/index.d.ts +82 -82
- package/dist/index.js +345 -345
- package/dist/style-templates.d.ts +4 -4
- package/dist/style-templates.js +37 -37
- package/dist/types/chart.d.ts +92 -92
- package/dist/types/chart.js +2 -2
- package/dist/types/parameter.d.ts +38 -38
- package/dist/types/parameter.js +2 -2
- package/dist/types/report.d.ts +25 -24
- package/dist/types/report.js +2 -2
- package/dist/types/session.d.ts +10 -10
- package/dist/types/session.js +2 -2
- package/package.json +46 -46
package/dist/index.js
CHANGED
|
@@ -1,346 +1,346 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.formatChartDataForReport = exports.formatReportMetadata = exports.parameterizeUrl = exports.parameterizeQuery = exports.parameterRegex = exports.loadOutputParameterValues = exports.loadDataTable = exports.loadDataTableAndProcessedFormatting = exports.handleFormattingTemplates = exports.formatterTemplates = void 0;
|
|
30
|
-
const db = require('@isoftdata/utility-db');
|
|
31
|
-
const chart_helper_js_1 = require("./chart-helper.js");
|
|
32
|
-
const utility_date_time_1 = require("@isoftdata/utility-date-time");
|
|
33
|
-
const formatDate = require('date-fns/format');
|
|
34
|
-
const camelcase_1 = __importDefault(require("camelcase"));
|
|
35
|
-
const mysql = require('mysql');
|
|
36
|
-
const deepmerge = require('deepmerge');
|
|
37
|
-
exports.formatterTemplates = __importStar(require("./formatting-templates"));
|
|
38
|
-
var chart_helper_js_2 = require("./chart-helper.js");
|
|
39
|
-
Object.defineProperty(exports, "handleFormattingTemplates", { enumerable: true, get: function () { return chart_helper_js_2.handleFormattingTemplates; } });
|
|
40
|
-
const handleChartTypeSpecificQuirks = (chartType, resultSet) => {
|
|
41
|
-
if (resultSet.length) {
|
|
42
|
-
switch (chartType) {
|
|
43
|
-
case 'PieChart':
|
|
44
|
-
resultSet = (0, chart_helper_js_1.makeNegativeNumericValuesZero)(resultSet);
|
|
45
|
-
break;
|
|
46
|
-
case 'TreeMap':
|
|
47
|
-
resultSet = (0, chart_helper_js_1.coerceNullColumn)(Object.keys(resultSet[0])[1], resultSet);
|
|
48
|
-
if (resultSet.length === 1) {
|
|
49
|
-
resultSet[0].id = 'No Data';
|
|
50
|
-
}
|
|
51
|
-
break;
|
|
52
|
-
default:
|
|
53
|
-
resultSet = (0, chart_helper_js_1.coerceNullColumn)(Object.keys(resultSet[0])[0], resultSet);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return resultSet;
|
|
57
|
-
};
|
|
58
|
-
async function loadChartData(mysqlConnection, chart, queryObject, customDataSource) {
|
|
59
|
-
let { formatting, chartWrapper, multiSeries, table, supertype, cumulative, ...theRest } = chart;
|
|
60
|
-
if (!chartWrapper) {
|
|
61
|
-
chartWrapper = {
|
|
62
|
-
chartType: supertype,
|
|
63
|
-
options: {
|
|
64
|
-
height: 450, width: 800,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
cumulative = cumulative || (chartWrapper?.chartType === 'AreaChart' && cumulative !== false);
|
|
69
|
-
let dataTable = {}, processedFormatting = {};
|
|
70
|
-
try {
|
|
71
|
-
const dataTableAndProcessedFormatting = await loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType: chartWrapper?.chartType, cumulative, table, customDataSource });
|
|
72
|
-
dataTable = dataTableAndProcessedFormatting.dataTable;
|
|
73
|
-
processedFormatting = dataTableAndProcessedFormatting.processedFormatting;
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
console.error('Error loading dataTable / processedFormatting');
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
if (chartWrapper?.chartType === 'Table') {
|
|
80
|
-
return {
|
|
81
|
-
...theRest,
|
|
82
|
-
supertype,
|
|
83
|
-
chartWrapper,
|
|
84
|
-
table: { ...table, ...dataTable },
|
|
85
|
-
formatting: processedFormatting,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
...theRest,
|
|
90
|
-
cumulative,
|
|
91
|
-
supertype,
|
|
92
|
-
chartWrapper: {
|
|
93
|
-
...chartWrapper,
|
|
94
|
-
dataTable: dataTable,
|
|
95
|
-
},
|
|
96
|
-
formatting: processedFormatting,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
async function loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType, cumulative = false, table = {}, customDataSource }) {
|
|
100
|
-
let results = [];
|
|
101
|
-
let fields = {};
|
|
102
|
-
if (typeof customDataSource === 'function') {
|
|
103
|
-
({ results, fields } = await customDataSource(queryObject.sql, queryObject.values));
|
|
104
|
-
}
|
|
105
|
-
else if (queryObject?.sql) {
|
|
106
|
-
({ results, fields } = await (0, chart_helper_js_1.queryWithFields)(mysqlConnection, queryObject));
|
|
107
|
-
}
|
|
108
|
-
let data = handleChartTypeSpecificQuirks(chartType, results);
|
|
109
|
-
if (multiSeries) {
|
|
110
|
-
({ data, formatting, fields } = (0, chart_helper_js_1.getMultiSeriesData)({
|
|
111
|
-
data,
|
|
112
|
-
seriesOptions: multiSeries,
|
|
113
|
-
formatting,
|
|
114
|
-
fields,
|
|
115
|
-
}));
|
|
116
|
-
}
|
|
117
|
-
const processedFormatting = (0, chart_helper_js_1.handleFormattingTemplates)(formatting);
|
|
118
|
-
if (chartType?.toLowerCase() === 'table') {
|
|
119
|
-
return { dataTable: (0, chart_helper_js_1.getRactiveTableDataFormat)(data, fields, processedFormatting, table?.style), processedFormatting, fields };
|
|
120
|
-
}
|
|
121
|
-
let dataTable = (0, chart_helper_js_1.getDataTableFormat)(data, fields, multiSeries);
|
|
122
|
-
if (cumulative || chartType === 'AreaChart') {
|
|
123
|
-
dataTable = (0, chart_helper_js_1.makeDataCumulative)(dataTable, cumulative);
|
|
124
|
-
}
|
|
125
|
-
return { dataTable, processedFormatting, fields };
|
|
126
|
-
}
|
|
127
|
-
exports.loadDataTableAndProcessedFormatting = loadDataTableAndProcessedFormatting;
|
|
128
|
-
async function loadDataTable(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType }) {
|
|
129
|
-
const { dataTable } = await loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType });
|
|
130
|
-
return dataTable;
|
|
131
|
-
}
|
|
132
|
-
exports.loadDataTable = loadDataTable;
|
|
133
|
-
const loadOptionList = async (mysqlConnection, optionListQuery, optionList = []) => {
|
|
134
|
-
const optionsFromQuery = (optionListQuery && await db.query(mysqlConnection, optionListQuery)) || [];
|
|
135
|
-
return [
|
|
136
|
-
...optionList,
|
|
137
|
-
...optionsFromQuery.map((option) => ({ ...option, fromQuery: true })),
|
|
138
|
-
];
|
|
139
|
-
};
|
|
140
|
-
async function loadOutputParameterValues(mysqlConnection, { definitionList = [], selectionList = [], forClient = false, session, site = 'store', siteIdColumn, siteNameColumn }) {
|
|
141
|
-
if (!(definitionList instanceof Array) || !definitionList.length) {
|
|
142
|
-
return [];
|
|
143
|
-
}
|
|
144
|
-
const parameterValues = [];
|
|
145
|
-
for (const definition of definitionList) {
|
|
146
|
-
const matchingParameterSelection = selectionList.find(param => param.name === definition.name);
|
|
147
|
-
let typedParameter;
|
|
148
|
-
const baseParameter = {
|
|
149
|
-
name: definition.name,
|
|
150
|
-
title: definition.title || definition.name,
|
|
151
|
-
default: definition.default,
|
|
152
|
-
value: (matchingParameterSelection?.value) ?? definition.default,
|
|
153
|
-
queryValues: {},
|
|
154
|
-
};
|
|
155
|
-
if (definition.type === 'dateRange') {
|
|
156
|
-
baseParameter.value = baseParameter.value || 'Last 30 Days';
|
|
157
|
-
typedParameter = {
|
|
158
|
-
...baseParameter,
|
|
159
|
-
type: 'dateRange',
|
|
160
|
-
dates: matchingParameterSelection?.dates ?? (0, utility_date_time_1.datesFromRange)(baseParameter.value),
|
|
161
|
-
};
|
|
162
|
-
if (typedParameter.value !== 'Custom') {
|
|
163
|
-
typedParameter.dates = (0, utility_date_time_1.datesFromRange)(typedParameter.value);
|
|
164
|
-
}
|
|
165
|
-
if (!forClient) {
|
|
166
|
-
typedParameter.queryValues = {
|
|
167
|
-
[`${typedParameter.name}_start_date`]: typedParameter.dates.from,
|
|
168
|
-
[`${typedParameter.name}_end_date`]: typedParameter.dates.to,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (definition.type === 'selection') {
|
|
173
|
-
typedParameter = {
|
|
174
|
-
...baseParameter,
|
|
175
|
-
type: 'selection',
|
|
176
|
-
optionListQuery: definition?.optionListQuery,
|
|
177
|
-
optionList: definition?.optionList,
|
|
178
|
-
};
|
|
179
|
-
typedParameter.optionList = await loadOptionList(mysqlConnection, typedParameter.optionListQuery, typedParameter.optionList);
|
|
180
|
-
if (!typedParameter.optionList.some(option => option.id?.toString() === typedParameter.value?.toString())) {
|
|
181
|
-
typedParameter.value = typedParameter.optionList?.[0]?.id?.toString();
|
|
182
|
-
}
|
|
183
|
-
const option = typedParameter.optionList.find(option => option.id.toString() === typedParameter.value);
|
|
184
|
-
if (!forClient) {
|
|
185
|
-
typedParameter.queryValues = {
|
|
186
|
-
[typedParameter.name]: option?.rawSql ? mysql.raw(typedParameter.value) : typedParameter.value,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else if (definition.type === 'siteSelection') {
|
|
191
|
-
typedParameter = {
|
|
192
|
-
...baseParameter,
|
|
193
|
-
type: 'siteSelection',
|
|
194
|
-
permission: definition?.permission,
|
|
195
|
-
optionList: [],
|
|
196
|
-
};
|
|
197
|
-
typedParameter.value = typedParameter.value ?? (session.siteId ?? session[`${site}Id`]);
|
|
198
|
-
siteIdColumn = siteIdColumn ? mysqlConnection.escapeId(siteIdColumn) : mysqlConnection.escapeId(`${site}id`);
|
|
199
|
-
siteNameColumn = siteNameColumn ? mysqlConnection.escapeId(siteNameColumn) : 'name';
|
|
200
|
-
const siteTableName = mysqlConnection.escapeId(site);
|
|
201
|
-
if (typedParameter.permission) {
|
|
202
|
-
typedParameter.optionList = await db.query(mysqlConnection, {
|
|
203
|
-
sql: `SELECT ${siteIdColumn} AS id, ${siteNameColumn} AS \`name\` FROM ${siteTableName} WHERE f_check_user_permission(?, ?, ${siteIdColumn})`,
|
|
204
|
-
values: [session.userAccountId, definition.permission],
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
typedParameter.optionList = await db.query(mysqlConnection, {
|
|
209
|
-
sql: `SELECT ${siteIdColumn} AS id, ${siteNameColumn} AS \`name\` FROM ${siteTableName} WHERE f_useraccount_has_site_access(?, ${siteIdColumn})`,
|
|
210
|
-
values: [session.userAccountId],
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
if (typedParameter.optionList.length > 1) {
|
|
214
|
-
typedParameter.optionList = typedParameter.optionList.map(option => ({ ...option, fromQuery: true }));
|
|
215
|
-
typedParameter.optionList.unshift({ id: 0, name: 'All', fromQuery: true });
|
|
216
|
-
}
|
|
217
|
-
if (!typedParameter.optionList.some(option => option.id?.toString() === typedParameter.value?.toString())) {
|
|
218
|
-
typedParameter.value = typedParameter.optionList?.[0]?.id.toString();
|
|
219
|
-
}
|
|
220
|
-
if (!forClient && typedParameter.value?.toString() === '0') {
|
|
221
|
-
typedParameter.queryValues = {
|
|
222
|
-
[typedParameter.name]: typedParameter.optionList
|
|
223
|
-
.map(({ id }) => id?.toString())
|
|
224
|
-
.filter(id => id !== '0'),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
else if (!forClient) {
|
|
228
|
-
typedParameter.queryValues = {
|
|
229
|
-
[typedParameter.name]: [typedParameter.value],
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
else if (definition.type === 'date') {
|
|
234
|
-
typedParameter = {
|
|
235
|
-
...baseParameter,
|
|
236
|
-
type: 'date',
|
|
237
|
-
};
|
|
238
|
-
typedParameter.value = typedParameter.value || formatDate(new Date(), 'yyyy-MM-dd');
|
|
239
|
-
if (!forClient) {
|
|
240
|
-
typedParameter.queryValues = {
|
|
241
|
-
[typedParameter.name]: typedParameter.value,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
else if (definition.type === 'session') {
|
|
246
|
-
typedParameter = {
|
|
247
|
-
...baseParameter,
|
|
248
|
-
type: 'session',
|
|
249
|
-
};
|
|
250
|
-
const parameterName = (0, camelcase_1.default)(definition.name);
|
|
251
|
-
typedParameter.value = (session[parameterName] ?? '').toString();
|
|
252
|
-
if (!forClient) {
|
|
253
|
-
typedParameter.queryValues = {
|
|
254
|
-
[typedParameter.name]: typedParameter.value,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
throw new Error(`Parameter type mismatch for ${baseParameter.name}`);
|
|
260
|
-
}
|
|
261
|
-
parameterValues.push(typedParameter);
|
|
262
|
-
}
|
|
263
|
-
return parameterValues;
|
|
264
|
-
}
|
|
265
|
-
exports.loadOutputParameterValues = loadOutputParameterValues;
|
|
266
|
-
exports.parameterRegex = /\${([^}]+)}/g;
|
|
267
|
-
function parameterizeQuery(queryString, parameterValues) {
|
|
268
|
-
if (!queryString) {
|
|
269
|
-
return {
|
|
270
|
-
sql: '',
|
|
271
|
-
values: [],
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
const queryParameterValues = parameterValues.reduce((acc, val) => {
|
|
275
|
-
return val.queryValues ? {
|
|
276
|
-
...acc,
|
|
277
|
-
...val.queryValues,
|
|
278
|
-
} : acc;
|
|
279
|
-
}, {});
|
|
280
|
-
const queryParameterKeys = [...queryString.matchAll(exports.parameterRegex)].map(([, insideToken]) => insideToken.trim());
|
|
281
|
-
const queryParameters = queryParameterKeys.map(key => queryParameterValues[key] || null);
|
|
282
|
-
return {
|
|
283
|
-
sql: queryString.replace(exports.parameterRegex, '?'),
|
|
284
|
-
values: queryParameters,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
exports.parameterizeQuery = parameterizeQuery;
|
|
288
|
-
function parameterizeUrl(url, parameterValues) {
|
|
289
|
-
if (!url) {
|
|
290
|
-
return '';
|
|
291
|
-
}
|
|
292
|
-
const queryParameterValues = parameterValues.reduce((acc, val) => {
|
|
293
|
-
return val.queryValues ? {
|
|
294
|
-
...acc,
|
|
295
|
-
...val.queryValues,
|
|
296
|
-
} : acc;
|
|
297
|
-
}, {});
|
|
298
|
-
const queryParameterKeys = [...url.matchAll(exports.parameterRegex)].map(([, insideToken]) => insideToken.trim());
|
|
299
|
-
const formattedUrl = queryParameterKeys.reduce((acc, key) => {
|
|
300
|
-
return acc.replace(`\${${key}}`, queryParameterValues[key] || '');
|
|
301
|
-
}, url);
|
|
302
|
-
return formattedUrl;
|
|
303
|
-
}
|
|
304
|
-
exports.parameterizeUrl = parameterizeUrl;
|
|
305
|
-
async function formatReportMetadata(mysqlConnection, { report, chartsInReport, session = {}, site, siteIdColumn, siteNameColumn }) {
|
|
306
|
-
try {
|
|
307
|
-
const parameterValues = await loadOutputParameterValues(mysqlConnection, { definitionList: report.parameters, forClient: true, session, site, siteIdColumn, siteNameColumn });
|
|
308
|
-
return {
|
|
309
|
-
dashboardReportId: report.dashboardReportId,
|
|
310
|
-
name: report.reportName,
|
|
311
|
-
title: report.reportTitle,
|
|
312
|
-
charts: chartsInReport,
|
|
313
|
-
parameterValues,
|
|
314
|
-
ownerId: report.ownerId,
|
|
315
|
-
autoRefreshInterval: report.autoRefreshInterval,
|
|
316
|
-
description: report.description ?? '',
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
catch (err) {
|
|
320
|
-
console.error(err);
|
|
321
|
-
throw err;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
exports.formatReportMetadata = formatReportMetadata;
|
|
325
|
-
async function formatChartDataForReport(mysqlConnection, { reportParameters, chart, reportChart, parameterSelectionList, session = {}, site, siteIdColumn, siteNameColumn, customDataSource }) {
|
|
326
|
-
try {
|
|
327
|
-
const parameterValues = await loadOutputParameterValues(mysqlConnection, { definitionList: reportParameters, selectionList: parameterSelectionList, session, site, siteIdColumn, siteNameColumn });
|
|
328
|
-
if (reportChart?.jsonOverride?.chartWrapper?.dataTable) {
|
|
329
|
-
const { dataTable, ...chartWrapper } = reportChart.jsonOverride.chartWrapper;
|
|
330
|
-
reportChart.jsonOverride.chartWrapper = chartWrapper;
|
|
331
|
-
}
|
|
332
|
-
const combinedChart = deepmerge(chart, reportChart.jsonOverride ?? {});
|
|
333
|
-
return await loadChartData(mysqlConnection, {
|
|
334
|
-
...combinedChart,
|
|
335
|
-
rank: reportChart.rank,
|
|
336
|
-
reportChartId: reportChart.reportChartId,
|
|
337
|
-
description: combinedChart.description ?? '',
|
|
338
|
-
}, parameterizeQuery(chart.query, parameterValues), customDataSource);
|
|
339
|
-
}
|
|
340
|
-
catch (err) {
|
|
341
|
-
console.error(err);
|
|
342
|
-
throw err;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
exports.formatChartDataForReport = formatChartDataForReport;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.formatChartDataForReport = exports.formatReportMetadata = exports.parameterizeUrl = exports.parameterizeQuery = exports.parameterRegex = exports.loadOutputParameterValues = exports.loadDataTable = exports.loadDataTableAndProcessedFormatting = exports.handleFormattingTemplates = exports.formatterTemplates = void 0;
|
|
30
|
+
const db = require('@isoftdata/utility-db');
|
|
31
|
+
const chart_helper_js_1 = require("./chart-helper.js");
|
|
32
|
+
const utility_date_time_1 = require("@isoftdata/utility-date-time");
|
|
33
|
+
const formatDate = require('date-fns/format');
|
|
34
|
+
const camelcase_1 = __importDefault(require("camelcase"));
|
|
35
|
+
const mysql = require('mysql');
|
|
36
|
+
const deepmerge = require('deepmerge');
|
|
37
|
+
exports.formatterTemplates = __importStar(require("./formatting-templates"));
|
|
38
|
+
var chart_helper_js_2 = require("./chart-helper.js");
|
|
39
|
+
Object.defineProperty(exports, "handleFormattingTemplates", { enumerable: true, get: function () { return chart_helper_js_2.handleFormattingTemplates; } });
|
|
40
|
+
const handleChartTypeSpecificQuirks = (chartType, resultSet) => {
|
|
41
|
+
if (resultSet.length) {
|
|
42
|
+
switch (chartType) {
|
|
43
|
+
case 'PieChart':
|
|
44
|
+
resultSet = (0, chart_helper_js_1.makeNegativeNumericValuesZero)(resultSet);
|
|
45
|
+
break;
|
|
46
|
+
case 'TreeMap':
|
|
47
|
+
resultSet = (0, chart_helper_js_1.coerceNullColumn)(Object.keys(resultSet[0])[1], resultSet);
|
|
48
|
+
if (resultSet.length === 1) {
|
|
49
|
+
resultSet[0].id = 'No Data';
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
resultSet = (0, chart_helper_js_1.coerceNullColumn)(Object.keys(resultSet[0])[0], resultSet);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return resultSet;
|
|
57
|
+
};
|
|
58
|
+
async function loadChartData(mysqlConnection, chart, queryObject, customDataSource) {
|
|
59
|
+
let { formatting, chartWrapper, multiSeries, table, supertype, cumulative, ...theRest } = chart;
|
|
60
|
+
if (!chartWrapper) {
|
|
61
|
+
chartWrapper = {
|
|
62
|
+
chartType: supertype,
|
|
63
|
+
options: {
|
|
64
|
+
height: 450, width: 800,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
cumulative = cumulative || (chartWrapper?.chartType === 'AreaChart' && cumulative !== false);
|
|
69
|
+
let dataTable = {}, processedFormatting = {};
|
|
70
|
+
try {
|
|
71
|
+
const dataTableAndProcessedFormatting = await loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType: chartWrapper?.chartType, cumulative, table, customDataSource });
|
|
72
|
+
dataTable = dataTableAndProcessedFormatting.dataTable;
|
|
73
|
+
processedFormatting = dataTableAndProcessedFormatting.processedFormatting;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error('Error loading dataTable / processedFormatting');
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
if (chartWrapper?.chartType === 'Table') {
|
|
80
|
+
return {
|
|
81
|
+
...theRest,
|
|
82
|
+
supertype,
|
|
83
|
+
chartWrapper,
|
|
84
|
+
table: { ...table, ...dataTable },
|
|
85
|
+
formatting: processedFormatting,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...theRest,
|
|
90
|
+
cumulative,
|
|
91
|
+
supertype,
|
|
92
|
+
chartWrapper: {
|
|
93
|
+
...chartWrapper,
|
|
94
|
+
dataTable: dataTable,
|
|
95
|
+
},
|
|
96
|
+
formatting: processedFormatting,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType, cumulative = false, table = {}, customDataSource }) {
|
|
100
|
+
let results = [];
|
|
101
|
+
let fields = {};
|
|
102
|
+
if (typeof customDataSource === 'function') {
|
|
103
|
+
({ results, fields } = await customDataSource(queryObject.sql, queryObject.values));
|
|
104
|
+
}
|
|
105
|
+
else if (queryObject?.sql) {
|
|
106
|
+
({ results, fields } = await (0, chart_helper_js_1.queryWithFields)(mysqlConnection, queryObject));
|
|
107
|
+
}
|
|
108
|
+
let data = handleChartTypeSpecificQuirks(chartType, results);
|
|
109
|
+
if (multiSeries) {
|
|
110
|
+
({ data, formatting, fields } = (0, chart_helper_js_1.getMultiSeriesData)({
|
|
111
|
+
data,
|
|
112
|
+
seriesOptions: multiSeries,
|
|
113
|
+
formatting,
|
|
114
|
+
fields,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
const processedFormatting = (0, chart_helper_js_1.handleFormattingTemplates)(formatting);
|
|
118
|
+
if (chartType?.toLowerCase() === 'table') {
|
|
119
|
+
return { dataTable: (0, chart_helper_js_1.getRactiveTableDataFormat)(data, fields, processedFormatting, table?.style), processedFormatting, fields };
|
|
120
|
+
}
|
|
121
|
+
let dataTable = (0, chart_helper_js_1.getDataTableFormat)(data, fields, multiSeries);
|
|
122
|
+
if (cumulative || chartType === 'AreaChart') {
|
|
123
|
+
dataTable = (0, chart_helper_js_1.makeDataCumulative)(dataTable, cumulative);
|
|
124
|
+
}
|
|
125
|
+
return { dataTable, processedFormatting, fields };
|
|
126
|
+
}
|
|
127
|
+
exports.loadDataTableAndProcessedFormatting = loadDataTableAndProcessedFormatting;
|
|
128
|
+
async function loadDataTable(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType }) {
|
|
129
|
+
const { dataTable } = await loadDataTableAndProcessedFormatting(mysqlConnection, { query: queryObject, multiSeries, formatting, chartType });
|
|
130
|
+
return dataTable;
|
|
131
|
+
}
|
|
132
|
+
exports.loadDataTable = loadDataTable;
|
|
133
|
+
const loadOptionList = async (mysqlConnection, optionListQuery, optionList = []) => {
|
|
134
|
+
const optionsFromQuery = (optionListQuery && await db.query(mysqlConnection, optionListQuery)) || [];
|
|
135
|
+
return [
|
|
136
|
+
...optionList,
|
|
137
|
+
...optionsFromQuery.map((option) => ({ ...option, fromQuery: true })),
|
|
138
|
+
];
|
|
139
|
+
};
|
|
140
|
+
async function loadOutputParameterValues(mysqlConnection, { definitionList = [], selectionList = [], forClient = false, session, site = 'store', siteIdColumn, siteNameColumn }) {
|
|
141
|
+
if (!(definitionList instanceof Array) || !definitionList.length) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const parameterValues = [];
|
|
145
|
+
for (const definition of definitionList) {
|
|
146
|
+
const matchingParameterSelection = selectionList.find(param => param.name === definition.name);
|
|
147
|
+
let typedParameter;
|
|
148
|
+
const baseParameter = {
|
|
149
|
+
name: definition.name,
|
|
150
|
+
title: definition.title || definition.name,
|
|
151
|
+
default: definition.default,
|
|
152
|
+
value: (matchingParameterSelection?.value) ?? definition.default,
|
|
153
|
+
queryValues: {},
|
|
154
|
+
};
|
|
155
|
+
if (definition.type === 'dateRange') {
|
|
156
|
+
baseParameter.value = baseParameter.value || 'Last 30 Days';
|
|
157
|
+
typedParameter = {
|
|
158
|
+
...baseParameter,
|
|
159
|
+
type: 'dateRange',
|
|
160
|
+
dates: matchingParameterSelection?.dates ?? (0, utility_date_time_1.datesFromRange)(baseParameter.value),
|
|
161
|
+
};
|
|
162
|
+
if (typedParameter.value !== 'Custom') {
|
|
163
|
+
typedParameter.dates = (0, utility_date_time_1.datesFromRange)(typedParameter.value);
|
|
164
|
+
}
|
|
165
|
+
if (!forClient) {
|
|
166
|
+
typedParameter.queryValues = {
|
|
167
|
+
[`${typedParameter.name}_start_date`]: typedParameter.dates.from,
|
|
168
|
+
[`${typedParameter.name}_end_date`]: typedParameter.dates.to,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (definition.type === 'selection') {
|
|
173
|
+
typedParameter = {
|
|
174
|
+
...baseParameter,
|
|
175
|
+
type: 'selection',
|
|
176
|
+
optionListQuery: definition?.optionListQuery,
|
|
177
|
+
optionList: definition?.optionList,
|
|
178
|
+
};
|
|
179
|
+
typedParameter.optionList = await loadOptionList(mysqlConnection, typedParameter.optionListQuery, typedParameter.optionList);
|
|
180
|
+
if (!typedParameter.optionList.some(option => option.id?.toString() === typedParameter.value?.toString())) {
|
|
181
|
+
typedParameter.value = typedParameter.optionList?.[0]?.id?.toString();
|
|
182
|
+
}
|
|
183
|
+
const option = typedParameter.optionList.find(option => option.id.toString() === typedParameter.value);
|
|
184
|
+
if (!forClient) {
|
|
185
|
+
typedParameter.queryValues = {
|
|
186
|
+
[typedParameter.name]: option?.rawSql ? mysql.raw(typedParameter.value) : typedParameter.value,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (definition.type === 'siteSelection') {
|
|
191
|
+
typedParameter = {
|
|
192
|
+
...baseParameter,
|
|
193
|
+
type: 'siteSelection',
|
|
194
|
+
permission: definition?.permission,
|
|
195
|
+
optionList: [],
|
|
196
|
+
};
|
|
197
|
+
typedParameter.value = typedParameter.value ?? (session.siteId ?? session[`${site}Id`]);
|
|
198
|
+
siteIdColumn = siteIdColumn ? mysqlConnection.escapeId(siteIdColumn) : mysqlConnection.escapeId(`${site}id`);
|
|
199
|
+
siteNameColumn = siteNameColumn ? mysqlConnection.escapeId(siteNameColumn) : 'name';
|
|
200
|
+
const siteTableName = mysqlConnection.escapeId(site);
|
|
201
|
+
if (typedParameter.permission) {
|
|
202
|
+
typedParameter.optionList = await db.query(mysqlConnection, {
|
|
203
|
+
sql: `SELECT ${siteIdColumn} AS id, ${siteNameColumn} AS \`name\` FROM ${siteTableName} WHERE f_check_user_permission(?, ?, ${siteIdColumn})`,
|
|
204
|
+
values: [session.userAccountId, definition.permission],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
typedParameter.optionList = await db.query(mysqlConnection, {
|
|
209
|
+
sql: `SELECT ${siteIdColumn} AS id, ${siteNameColumn} AS \`name\` FROM ${siteTableName} WHERE f_useraccount_has_site_access(?, ${siteIdColumn})`,
|
|
210
|
+
values: [session.userAccountId],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (typedParameter.optionList.length > 1) {
|
|
214
|
+
typedParameter.optionList = typedParameter.optionList.map(option => ({ ...option, fromQuery: true }));
|
|
215
|
+
typedParameter.optionList.unshift({ id: 0, name: 'All', fromQuery: true });
|
|
216
|
+
}
|
|
217
|
+
if (!typedParameter.optionList.some(option => option.id?.toString() === typedParameter.value?.toString())) {
|
|
218
|
+
typedParameter.value = typedParameter.optionList?.[0]?.id.toString();
|
|
219
|
+
}
|
|
220
|
+
if (!forClient && typedParameter.value?.toString() === '0') {
|
|
221
|
+
typedParameter.queryValues = {
|
|
222
|
+
[typedParameter.name]: typedParameter.optionList
|
|
223
|
+
.map(({ id }) => id?.toString())
|
|
224
|
+
.filter(id => id !== '0'),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
else if (!forClient) {
|
|
228
|
+
typedParameter.queryValues = {
|
|
229
|
+
[typedParameter.name]: [typedParameter.value],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (definition.type === 'date') {
|
|
234
|
+
typedParameter = {
|
|
235
|
+
...baseParameter,
|
|
236
|
+
type: 'date',
|
|
237
|
+
};
|
|
238
|
+
typedParameter.value = typedParameter.value || formatDate(new Date(), 'yyyy-MM-dd');
|
|
239
|
+
if (!forClient) {
|
|
240
|
+
typedParameter.queryValues = {
|
|
241
|
+
[typedParameter.name]: typedParameter.value,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (definition.type === 'session') {
|
|
246
|
+
typedParameter = {
|
|
247
|
+
...baseParameter,
|
|
248
|
+
type: 'session',
|
|
249
|
+
};
|
|
250
|
+
const parameterName = (0, camelcase_1.default)(definition.name);
|
|
251
|
+
typedParameter.value = (session[parameterName] ?? '').toString();
|
|
252
|
+
if (!forClient) {
|
|
253
|
+
typedParameter.queryValues = {
|
|
254
|
+
[typedParameter.name]: typedParameter.value,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
throw new Error(`Parameter type mismatch for ${baseParameter.name}`);
|
|
260
|
+
}
|
|
261
|
+
parameterValues.push(typedParameter);
|
|
262
|
+
}
|
|
263
|
+
return parameterValues;
|
|
264
|
+
}
|
|
265
|
+
exports.loadOutputParameterValues = loadOutputParameterValues;
|
|
266
|
+
exports.parameterRegex = /\${([^}]+)}/g;
|
|
267
|
+
function parameterizeQuery(queryString, parameterValues) {
|
|
268
|
+
if (!queryString) {
|
|
269
|
+
return {
|
|
270
|
+
sql: '',
|
|
271
|
+
values: [],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const queryParameterValues = parameterValues.reduce((acc, val) => {
|
|
275
|
+
return val.queryValues ? {
|
|
276
|
+
...acc,
|
|
277
|
+
...val.queryValues,
|
|
278
|
+
} : acc;
|
|
279
|
+
}, {});
|
|
280
|
+
const queryParameterKeys = [...queryString.matchAll(exports.parameterRegex)].map(([, insideToken]) => insideToken.trim());
|
|
281
|
+
const queryParameters = queryParameterKeys.map(key => queryParameterValues[key] || null);
|
|
282
|
+
return {
|
|
283
|
+
sql: queryString.replace(exports.parameterRegex, '?'),
|
|
284
|
+
values: queryParameters,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
exports.parameterizeQuery = parameterizeQuery;
|
|
288
|
+
function parameterizeUrl(url, parameterValues) {
|
|
289
|
+
if (!url) {
|
|
290
|
+
return '';
|
|
291
|
+
}
|
|
292
|
+
const queryParameterValues = parameterValues.reduce((acc, val) => {
|
|
293
|
+
return val.queryValues ? {
|
|
294
|
+
...acc,
|
|
295
|
+
...val.queryValues,
|
|
296
|
+
} : acc;
|
|
297
|
+
}, {});
|
|
298
|
+
const queryParameterKeys = [...url.matchAll(exports.parameterRegex)].map(([, insideToken]) => insideToken.trim());
|
|
299
|
+
const formattedUrl = queryParameterKeys.reduce((acc, key) => {
|
|
300
|
+
return acc.replace(`\${${key}}`, queryParameterValues[key] || '');
|
|
301
|
+
}, url);
|
|
302
|
+
return formattedUrl;
|
|
303
|
+
}
|
|
304
|
+
exports.parameterizeUrl = parameterizeUrl;
|
|
305
|
+
async function formatReportMetadata(mysqlConnection, { report, chartsInReport, session = {}, site, siteIdColumn, siteNameColumn }) {
|
|
306
|
+
try {
|
|
307
|
+
const parameterValues = await loadOutputParameterValues(mysqlConnection, { definitionList: report.parameters, forClient: true, session, site, siteIdColumn, siteNameColumn });
|
|
308
|
+
return {
|
|
309
|
+
dashboardReportId: report.dashboardReportId,
|
|
310
|
+
name: report.reportName,
|
|
311
|
+
title: report.reportTitle,
|
|
312
|
+
charts: chartsInReport,
|
|
313
|
+
parameterValues,
|
|
314
|
+
ownerId: report.ownerId,
|
|
315
|
+
autoRefreshInterval: report.autoRefreshInterval,
|
|
316
|
+
description: report.description ?? '',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
console.error(err);
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
exports.formatReportMetadata = formatReportMetadata;
|
|
325
|
+
async function formatChartDataForReport(mysqlConnection, { reportParameters, chart, reportChart, parameterSelectionList, session = {}, site, siteIdColumn, siteNameColumn, customDataSource }) {
|
|
326
|
+
try {
|
|
327
|
+
const parameterValues = await loadOutputParameterValues(mysqlConnection, { definitionList: reportParameters, selectionList: parameterSelectionList, session, site, siteIdColumn, siteNameColumn });
|
|
328
|
+
if (reportChart?.jsonOverride?.chartWrapper?.dataTable) {
|
|
329
|
+
const { dataTable, ...chartWrapper } = reportChart.jsonOverride.chartWrapper;
|
|
330
|
+
reportChart.jsonOverride.chartWrapper = chartWrapper;
|
|
331
|
+
}
|
|
332
|
+
const combinedChart = deepmerge(chart, reportChart.jsonOverride ?? {});
|
|
333
|
+
return await loadChartData(mysqlConnection, {
|
|
334
|
+
...combinedChart,
|
|
335
|
+
rank: reportChart.rank,
|
|
336
|
+
reportChartId: reportChart.reportChartId,
|
|
337
|
+
description: combinedChart.description ?? '',
|
|
338
|
+
}, parameterizeQuery(chart.query, parameterValues), customDataSource);
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
console.error(err);
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
exports.formatChartDataForReport = formatChartDataForReport;
|
|
346
346
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxNQUFNLEVBQUUsR0FBRyxPQUFPLENBQUMsdUJBQXVCLENBQUMsQ0FBQTtBQUMzQyx1REFTNkM7QUFDN0Msb0VBQTZEO0FBQzdELE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFBO0FBQzdDLDBEQUFpQztBQUNqQyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUE7QUFDOUIsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFBO0FBTXRDLDZFQUE0RDtBQUM1RCxxREFBNkQ7QUFBcEQsNEhBQUEseUJBQXlCLE9BQUE7QUFLbEMsTUFBTSw2QkFBNkIsR0FBRyxDQUFDLFNBQWlCLEVBQUUsU0FBMEIsRUFBRSxFQUFFO0lBQ3ZGLElBQUksU0FBUyxDQUFDLE1BQU0sRUFBRTtRQUNyQixRQUFRLFNBQVMsRUFBRTtZQUNsQixLQUFLLFVBQVU7Z0JBRWQsU0FBUyxHQUFHLElBQUEsK0NBQTZCLEVBQUMsU0FBUyxDQUFDLENBQUE7Z0JBQ3BELE1BQUs7WUFDTixLQUFLLFNBQVM7Z0JBRWIsU0FBUyxHQUFHLElBQUEsa0NBQWdCLEVBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQTtnQkFDckUsSUFBSSxTQUFTLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtvQkFDM0IsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxTQUFTLENBQUE7aUJBQzNCO2dCQUNELE1BQUs7WUFDTjtnQkFFQyxTQUFTLEdBQUcsSUFBQSxrQ0FBZ0IsRUFBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFD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
|