@opengis/bi 1.0.13 → 1.0.15

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 (58) hide show
  1. package/README.md +50 -52
  2. package/config.js +12 -12
  3. package/dist/bi.js +1 -1
  4. package/dist/bi.umd.cjs +120 -134
  5. package/dist/{import-file-1T7kpSzt.js → import-file-CRC0sYYT.js} +11974 -11522
  6. package/dist/{map-component-mixin-BLM9iEWA.js → map-component-mixin-BCtWEvzv.js} +4830 -3150
  7. package/dist/style.css +1 -1
  8. package/dist/vs-calendar-5ot79n0N.js +110 -0
  9. package/dist/vs-funnel-bar-CLo6gXI_.js +105 -0
  10. package/dist/vs-heatmap-DHGA8dRk.js +97 -0
  11. package/dist/{vs-map-cluster-Dfe9INqE.js → vs-map-cluster-CNgX6JVF.js} +28 -25
  12. package/dist/vs-map-pIn5wS4G.js +74 -0
  13. package/dist/vs-number-DYfok8VU.js +55 -0
  14. package/dist/{vs-text-DcrAdQ40.js → vs-text-Dckykz09.js} +19 -13
  15. package/package.json +107 -72
  16. package/plugin.js +14 -13
  17. package/server/migrations/bi.dataset.sql +26 -0
  18. package/server/migrations/bi.sql +93 -27
  19. package/server/plugins/docs.js +48 -47
  20. package/server/plugins/hook.js +89 -86
  21. package/server/plugins/vite.js +69 -55
  22. package/server/routes/dashboard/controllers/dashboard.delete.js +38 -35
  23. package/server/routes/dashboard/controllers/dashboard.js +118 -80
  24. package/server/routes/dashboard/controllers/dashboard.list.js +30 -39
  25. package/server/routes/dashboard/controllers/utils/yaml.js +11 -12
  26. package/server/routes/dashboard/index.mjs +25 -24
  27. package/server/routes/data/controllers/data.js +168 -97
  28. package/server/routes/data/controllers/util/chartSQL.js +42 -25
  29. package/server/routes/data/controllers/util/normalizeData.js +59 -34
  30. package/server/routes/data/index.mjs +29 -26
  31. package/server/routes/dataset/controllers/bi.dataset.demo.add.js +97 -0
  32. package/server/routes/dataset/controllers/bi.dataset.import.js +67 -0
  33. package/server/routes/dataset/controllers/util/create.table.js +22 -0
  34. package/server/routes/dataset/controllers/util/prepare.data.js +49 -0
  35. package/server/routes/dataset/index.mjs +19 -0
  36. package/server/routes/db/controllers/dbTablePreview.js +63 -0
  37. package/server/routes/db/controllers/dbTables.js +36 -0
  38. package/server/routes/db/index.mjs +17 -0
  39. package/server/routes/edit/controllers/dashboard.add.js +26 -23
  40. package/server/routes/edit/controllers/dashboard.edit.js +46 -37
  41. package/server/routes/edit/controllers/widget.add.js +75 -49
  42. package/server/routes/edit/controllers/widget.del.js +69 -63
  43. package/server/routes/edit/controllers/widget.edit.js +52 -82
  44. package/server/routes/edit/index.mjs +31 -27
  45. package/server/routes/map/controllers/cluster.js +109 -75
  46. package/server/routes/map/controllers/clusterVtile.js +166 -143
  47. package/server/routes/map/controllers/geojson.js +127 -101
  48. package/server/routes/map/controllers/map.js +60 -57
  49. package/server/routes/map/controllers/utils/downloadClusterData.js +43 -0
  50. package/server/routes/map/controllers/vtile.js +183 -161
  51. package/server/routes/map/index.mjs +25 -25
  52. package/server/utils/getWidget.js +85 -56
  53. package/utils.js +12 -11
  54. package/dist/vs-calendar-WiK1hcHS.js +0 -96
  55. package/dist/vs-funnel-bar-CpPbYZ0_.js +0 -92
  56. package/dist/vs-heatmap-BG4eIROH.js +0 -83
  57. package/dist/vs-map-BRk6Fmks.js +0 -66
  58. package/dist/vs-number-CJq-vi95.js +0 -39
@@ -1,12 +1,11 @@
1
- import yaml from 'js-yaml';
2
-
3
- yaml.loadSafe = (yml) => {
4
- try {
5
- return yaml.load(yml);
6
- }
7
- catch (err) {
8
- return { error: err.toString() };
9
- }
10
- };
11
-
12
- export default yaml;
1
+ import yaml from 'js-yaml';
2
+
3
+ yaml.loadSafe = (yml) => {
4
+ try {
5
+ return yaml.load(yml);
6
+ } catch (err) {
7
+ return { error: err.toString() };
8
+ }
9
+ };
10
+
11
+ export default yaml;
@@ -1,24 +1,25 @@
1
- import config from '../../../config.js';
2
-
3
- import dashboard from './controllers/dashboard.js';
4
- import dashboardList from './controllers/dashboard.list.js';
5
- import dashboardDelete from './controllers/dashboard.delete.js';
6
- const biSchema = {
7
- querystring: {
8
- widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
9
- dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
10
- list: { type: 'string', pattern: '^([\\d])$' },
11
- sql: { type: 'string', pattern: '^([\\d])$' }
12
- },
13
- params: {
14
- id: { type: 'string', pattern: '^([\\d\\w]+)$' },
15
- },
16
- };
17
-
18
-
19
- export default async function route(fastify) {
20
- fastify.get(`/bi-dashboard/:id`, { schema: biSchema }, dashboard);
21
- fastify.get(`/bi-dashboard`, dashboardList);
22
- fastify.delete(`/bi-dashboard/:id`, dashboardDelete);
23
- fastify.get(`/bi-test`, () => { return { test: '2' }});
24
- }
1
+ import config from '../../../config.js';
2
+
3
+ import dashboard from './controllers/dashboard.js';
4
+ import dashboardList from './controllers/dashboard.list.js';
5
+ import dashboardDelete from './controllers/dashboard.delete.js';
6
+ const biSchema = {
7
+ querystring: {
8
+ widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
9
+ dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
10
+ list: { type: 'string', pattern: '^([\\d])$' },
11
+ sql: { type: 'string', pattern: '^([\\d])$' },
12
+ },
13
+ params: {
14
+ id: { type: 'string', pattern: '^([\\d\\w]+)$' },
15
+ },
16
+ };
17
+
18
+ export default async function route(fastify) {
19
+ fastify.get(`/bi-dashboard/:id`, { schema: biSchema }, dashboard);
20
+ fastify.get(`/bi-dashboard`, dashboardList);
21
+ fastify.delete(`/bi-dashboard/:id`, dashboardDelete);
22
+ fastify.get(`/bi-test`, () => {
23
+ return { test: '2' };
24
+ });
25
+ }
@@ -1,97 +1,168 @@
1
-
2
-
3
- //import autoIndex from '@opengis/fastify-table/pg/funcs/autoIndex.js';
4
- // import pgClients from '@opengis/fastify-table/pg/pgClients.js';
5
-
6
- import { getPGAsync, autoIndex, pgClients, getSelectVal } from '@opengis/fastify-table/utils.js';
7
- import chartSQL from './util/chartSQL.js';
8
- import normalizeData from './util/normalizeData.js';
9
-
10
- import { getWidget } from '../../../../utils.js';
11
-
12
- export default async function data({
13
- funcs = {}, query = {},
14
- }) {
15
- const time = Date.now();
16
- const { dashboard, widget, filter, search, samples } = query;
17
-
18
- const widgetData = await getWidget({ dashboard, widget });
19
-
20
- if (widgetData.status) return widgetData;
21
-
22
- const { type, text, data = {}, controls, style, options } = widgetData;
23
-
24
- const pg = data.db ? await getPGAsync(data.db) : pgClients.client
25
-
26
- const { fields: cols } = await pg.query(`select * from ${data.table} limit 0`);
27
- const columnTypes = cols.map(el => ({ name: el.name, type: pg.pgType?.[el.dataTypeID] }))
28
-
29
- // data param
30
- const { x, cls, metric, table, where, tableSQL, groupby, xName } = normalizeData(widgetData, query, columnTypes);
31
-
32
- // auto Index
33
- if (pg.pk?.[data.table]) {
34
- autoIndex({ table: data.table, pg, columns: [data?.time].concat([xName]).concat([groupby]).filter(el => el) }).catch(err => console.log(err))
35
- }
36
-
37
- // get group
38
- const groupData = groupby ? await pg.query(`select ${groupby} as name ,count(*) from ${tableSQL || table} group by ${groupby} order by count(*) desc limit 20`).then(el => el.rows) : null;
39
-
40
- if (query.sql === '2') return { x, metric, table, tableSQL, data, groupData };
41
-
42
- const order = data.order || (type === 'listbar' ? 'metric desc' : null);
43
-
44
- const { optimizedSQL = `select * from ${tableSQL || table}` } = filter || search ? await funcs.getFilterSQL({
45
- pg, table, filter, search,
46
- }) : {};
47
-
48
- const sql = (chartSQL[type] || chartSQL['chart'])({ where, metric, table: `(${optimizedSQL})q`, x, groupData, groupby, order, samples });
49
-
50
- if (query.sql) return sql;
51
-
52
- if (!sql || sql?.includes('undefined')) {
53
- return { message: { error: 'invalid sql', type, sql, where, metric, table: `(${optimizedSQL})q`, x, groupData, groupby }, status: 500 };
54
- }
55
-
56
- const { rows, fields } = await pg.query(sql); // test with limit
57
-
58
- if (cls) {
59
- const values = rows.map((row) => row[x])?.filter((el, idx, arr) => el && arr.indexOf(el) === idx);
60
- const vals = await getSelectVal({ pg, name: cls, values });
61
- rows.filter((row) => row[x]).forEach((row) => {
62
- Object.assign(row, { [x]: vals?.[row[x]] || row[x] });
63
- });
64
- }
65
-
66
- const dimensions = fields.map(el => el.name);
67
-
68
- const res = {
69
- time: Date.now() - time,
70
- dimensions,
71
-
72
- dimensionsType: fields.map(el => pg.pgType?.[el.dataTypeID]),
73
- type,
74
-
75
- text: text ? text : (widgetData?.title || data.text),
76
- //data: query.format === 'data' ? dimensions.map(el => rows.map(r => r[el])) : undefined,
77
- source: query.format === 'array' ? dimensions.map(el => rows.map(r => r[el])) : rows,
78
- style,
79
- options,
80
- controls,
81
- params: {
82
- x,
83
- cls,
84
- metric,
85
- table,
86
- tableSQL,
87
- where,
88
- groupby,
89
- sql,
90
- },
91
- columnTypes,
92
- };
93
- return res;
94
-
95
-
96
-
97
- }
1
+ import yaml from 'js-yaml';
2
+
3
+ import {
4
+ config,
5
+ getPGAsync,
6
+ autoIndex,
7
+ pgClients,
8
+ getSelectVal,
9
+ getFilterSQL,
10
+ } from '@opengis/fastify-table/utils.js';
11
+
12
+ import chartSQL from './util/chartSQL.js';
13
+ import normalizeData from './util/normalizeData.js';
14
+
15
+ import { getWidget } from '../../../../utils.js';
16
+
17
+ export default async function dataAPI({ funcs = {}, query = {} }) {
18
+ const time = Date.now();
19
+
20
+ query.metric = Array.isArray(query.metric) ? query.metric.pop() : query.metric;
21
+
22
+ const { dashboard, widget, filter, search, samples } = query;
23
+
24
+ const widgetData = await getWidget({ dashboard, widget });
25
+
26
+ if (widgetData.status) return widgetData;
27
+
28
+ const { type, text, data = {}, controls, style, options } = widgetData;
29
+
30
+ const pg = data.db ? await getPGAsync(data.db) : pgClients.client;
31
+
32
+ const { fields: cols } = await pg.query(
33
+ `select * from ${data.table} limit 0`
34
+ );
35
+ const columnTypes = cols.map((el) => ({
36
+ name: el.name,
37
+ type: pg.pgType?.[el.dataTypeID],
38
+ }));
39
+
40
+ // data param
41
+ const { x, cls, metric, table, where, tableSQL, groupby, xName, yName, xType, yType } =
42
+ normalizeData(widgetData, query, columnTypes);
43
+
44
+ // auto Index
45
+ if (pg.pk?.[data.table]) {
46
+ autoIndex({
47
+ table: data.table,
48
+ pg,
49
+ columns: [data?.time]
50
+ .concat([xName])
51
+ .concat([groupby])
52
+ .filter((el) => el),
53
+ }).catch((err) => console.log(err));
54
+ }
55
+
56
+ // get group
57
+ const groupData = groupby
58
+ ? await pg
59
+ .query(
60
+ `select ${groupby} as name ,count(*) from ${tableSQL || table} group by ${groupby} order by count(*) desc limit 20`
61
+ )
62
+ .then((el) => el.rows)
63
+ : null;
64
+
65
+ if (query.sql === '2') return { x, metric, table, tableSQL, data, groupData };
66
+
67
+ const order = data.order || (type === 'listbar' ? 'metric desc' : null);
68
+
69
+ const { optimizedSQL = `select * from ${tableSQL || table}` } =
70
+ filter || search
71
+ ? await getFilterSQL({
72
+ pg,
73
+ table,
74
+ filter,
75
+ search,
76
+ })
77
+ : {};
78
+
79
+ if (type?.includes('bar') && !metric?.length) {
80
+ return { message: 'empty widget params: metrics', status: 400 };
81
+ }
82
+
83
+ const sql = (chartSQL[type] || chartSQL.chart)({
84
+ where,
85
+ metric,
86
+ table: `(${optimizedSQL} ${samples ? 'limit 10' : ''})q`,
87
+ x,
88
+ groupData,
89
+ groupby,
90
+ order,
91
+ samples,
92
+ xType,
93
+ });
94
+
95
+ if (query.sql) return sql;
96
+
97
+ if (!sql || sql?.includes('undefined')) {
98
+ return {
99
+ message: {
100
+ error: 'invalid sql',
101
+ type,
102
+ sql,
103
+ where,
104
+ metric,
105
+ table: `(${optimizedSQL})q`,
106
+ x,
107
+ groupData,
108
+ groupby,
109
+ },
110
+ status: 500,
111
+ };
112
+ }
113
+
114
+ const { rows, fields } = await pg.query(sql); // test with limit
115
+
116
+ if (cls) {
117
+ const values = rows
118
+ .map((row) => row[x])
119
+ ?.filter((el, idx, arr) => el && arr.indexOf(el) === idx);
120
+ const vals = await getSelectVal({ pg, name: cls, values });
121
+ rows
122
+ .filter((row) => row[x])
123
+ .forEach((row) => {
124
+ Object.assign(row, { [x]: vals?.[row[x]] || row[x] });
125
+ });
126
+ }
127
+
128
+ const yml = widgetData.yml || yaml.dump(extractYml(widgetData));
129
+ const dimensions = fields.map((el) => el.name);
130
+
131
+ const res = {
132
+ time: Date.now() - time,
133
+ dimensions,
134
+ filter: xName,
135
+ dimensionsType: [xType, yType].filter((el) => el)?.length
136
+ ? [xType, yType].filter((el) => el)
137
+ : fields.map((el) => pg.pgType?.[el.dataTypeID]),
138
+ type,
139
+
140
+ text: text || widgetData?.title || data.text,
141
+ // data: query.format === 'data' ? dimensions.map(el => rows.map(r => r[el])) : undefined,
142
+ source:
143
+ query.format === 'array'
144
+ ? dimensions.map((el) => rows.map((r) => r[el]))
145
+ : rows,
146
+ style,
147
+ options,
148
+ controls,
149
+ yml,
150
+ params: config?.local ? {
151
+ x,
152
+ cls,
153
+ metric,
154
+ table,
155
+ tableSQL,
156
+ where,
157
+ groupby,
158
+ sql,
159
+ } : undefined,
160
+ columnTypes,
161
+ };
162
+ return res;
163
+ }
164
+
165
+ function extractYml(sourceData) {
166
+ const { title, description, type, data, style, controls } = sourceData;
167
+ return { title, description, type, data, style, controls };
168
+ }
@@ -1,25 +1,42 @@
1
- function number({ metric, where, table, samples }) {
2
- const sql = `select ${metric} from ${table} where ${where} ${samples ? 'limit 10' : ''}`
3
- return sql;
4
- }
5
- function table({ columns, table, where, samples }) {
6
- return `select ${columns.map(el => el.name || el)}::text from ${table} where ${where} ${samples ? 'limit 10' : 'limit 20'} `
7
- }
8
-
9
- function chart({ metric, where, table, x, groupby, groupData, order, samples }) {
10
-
11
- const metricData = groupData?.map(el => `${metric} filter (where ${groupby}='${el.name}') as "${el.name}"`).join(',') || `${metric} as metric`
12
- const sql = `select ${x}, ${metricData}
13
- from ${table}
14
- where ${where}
15
- group by ${x}
16
- order by ${order || x}
17
- ${samples ? 'limit 10' : 'limit 100'}`;
18
- return sql;
19
- }
20
-
21
- function text() {
22
- return undefined;
23
- }
24
-
25
- export default { number, chart, }
1
+ function number({ metric, where, table, samples }) {
2
+ const sql = `select ${metric} from ${table} where ${where} ${samples ? 'limit 10' : ''}`;
3
+ return sql;
4
+ }
5
+ function table({ columns, table, where, samples }) {
6
+ return `select ${columns.map((el) => el.name || el)}::text from ${table} where ${where} ${samples ? 'limit 10' : 'limit 20'} `;
7
+ }
8
+
9
+ function chart({
10
+ metric,
11
+ where,
12
+ table,
13
+ x,
14
+ groupby,
15
+ groupData,
16
+ order,
17
+ samples,
18
+ xType,
19
+ }) {
20
+ const xCol = x && xType?.includes('[]') ? `unnest(${x})` : x;
21
+
22
+ const metricData =
23
+ groupData
24
+ ?.map(
25
+ (el) =>
26
+ `${metric} filter (where ${groupby}='${el.name}') as "${el.name}"`
27
+ )
28
+ .join(',') || `${metric} as metric`;
29
+ const sql = `select ${xCol}, ${metricData}
30
+ from ${table}
31
+ where ${where}
32
+ ${xCol ? `group by ${xCol}` : ''}
33
+ ${order || xCol ? `order by ${order || xCol}` : ''}
34
+ ${samples ? 'limit 10' : 'limit 100'}`;
35
+ return sql;
36
+ }
37
+
38
+ function text() {
39
+ return undefined;
40
+ }
41
+
42
+ export default { number, chart };
@@ -1,34 +1,59 @@
1
- function normalizeData(data, query = {}, columnTypes = []) {
2
- ['x', 'groupby', 'granularity'].forEach(el => {
3
- //console.log(el, query[el], columnTypes.find(col => col.name == query[el]))
4
- if (!columnTypes.find(col => col.name == query[el])) { delete query[el]; }
5
- });
6
-
7
- if (!columnTypes.find(col => col.type === 'numeric' && col.name == query.metric)) { delete query.metric; }
8
-
9
- const xName = query.x || (Array.isArray(data.x) ? data.x[0] : data.x);
10
- const xTYpe = columnTypes.find(el => el.name == xName)?.type;
11
-
12
- const granularity = xTYpe === 'date' || xTYpe?.includes('timestamp') ? (query.granularity || data.granularity || 'year') : null;
13
-
14
- const x = (granularity ? `date_trunc('${granularity}',${xName})::date::text` : null) || xName;
15
-
16
- const metrics = Array.isArray(data.metrics) ? data.metrics : [data.metrics];
17
- const metric = (query.metric ? `sum(${query.metric})` : null) || (metrics.length ? metrics?.filter(el => el).map(el => el.fx || `${el.operator || 'sum'}(${el.name || el})`) : 'count(*)');
18
-
19
- const { cls, table, filterCustom } = data;
20
- const groupby = query.groupby || data.groupby;
21
- //const orderby = query.orderby || data.orderby || 'count(*)';
22
-
23
- const custom = query?.filterCustom?.split(',')
24
- ?.map((el) => filterCustom?.find((item) => item?.name === el)?.sql)
25
- ?.filter((el) => el)?.join(' and ');
26
- const where = `${data.query || '1=1'} and ${custom || 'true'}`;
27
-
28
- const tableSQL = data.tableSQL?.length
29
- ? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
30
- : undefined;
31
-
32
- return { x, cls, metric, table, where, tableSQL, groupby, xName }
33
- }
34
- export default normalizeData;
1
+ function normalizeData(data, query = {}, columnTypes = []) {
2
+ ['x', 'groupby', 'granularity'].forEach((el) => {
3
+ // console.log(el, query[el], columnTypes.find(col => col.name == query[el]))
4
+ if (!columnTypes.find((col) => col.name == query[el])) {
5
+ delete query[el];
6
+ }
7
+ });
8
+
9
+ if (
10
+ !columnTypes.find(
11
+ (col) => col.type === 'numeric' && col.name == query.metric
12
+ )
13
+ ) {
14
+ delete query.metric;
15
+ }
16
+
17
+ const xName = query.x || (Array.isArray(data.x) ? data.x[0] : data.x);
18
+ const xType = columnTypes.find((el) => el.name == xName)?.type;
19
+
20
+ const granularity =
21
+ xType === 'date' || xType?.includes('timestamp')
22
+ ? query.granularity || data.granularity || 'year'
23
+ : null;
24
+
25
+ const x =
26
+ (granularity
27
+ ? `date_trunc('${granularity}',${xName})::date::text`
28
+ : null) || xName;
29
+
30
+ const metrics = Array.isArray(data.metrics) ? data.metrics : [data.metrics];
31
+ const metric =
32
+ (query.metric ? `sum(${query.metric})` : null) ||
33
+ (metrics.length
34
+ ? (metrics
35
+ ?.filter((el) => el && columnTypes.find((col) => col.name == (el?.name || el)))
36
+ ?.map((el) => el.fx || `${el.operator || 'sum'}(${el.name || el})`)?.join(',') || 'count(*)')
37
+ : 'count(*)');
38
+
39
+ const yName = metrics?.[0]?.name || metrics?.[0];
40
+ const yType = columnTypes.find((el) => el.name == yName)?.type;
41
+
42
+ const { cls, table, filterCustom } = data;
43
+ const groupby = query.groupby || data.groupby;
44
+ // const orderby = query.orderby || data.orderby || 'count(*)';
45
+
46
+ const custom = query?.filterCustom
47
+ ?.split(',')
48
+ ?.map((el) => filterCustom?.find((item) => item?.name === el)?.sql)
49
+ ?.filter((el) => el)
50
+ ?.join(' and ');
51
+ const where = `${data.query || '1=1'} and ${custom || 'true'}`;
52
+
53
+ const tableSQL = data.tableSQL?.length
54
+ ? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
55
+ : undefined;
56
+
57
+ return { x, cls, metric, table, where, tableSQL, groupby, xName, xType, yName, yType };
58
+ }
59
+ export default normalizeData;
@@ -1,26 +1,29 @@
1
- import config from '../../../config.js';
2
-
3
- import data from './controllers/data.js';
4
-
5
-
6
- const biSchema = {
7
- querystring: {
8
- widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
9
- dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
10
- sql: { type: 'string', pattern: '^([\\d])$' }
11
- },
12
- params: {
13
- id: { type: 'string', pattern: '^([\\d\\w]+)$' },
14
- },
15
- };
16
-
17
-
18
- export default async function route(fastify, opts) {
19
- const prefix = opts?.prefix || config.prefix || '/api';
20
- fastify.route({
21
- method: 'GET',
22
- url: '/bi-data',
23
- schema: biSchema,
24
- handler: data,
25
- });
26
- }
1
+ import config from '../../../config.js';
2
+
3
+ import data from './controllers/data.js';
4
+
5
+ const biSchema = {
6
+ querystring: {
7
+ widget: { type: 'string', pattern: '^([\\d\\w_]+)$' },
8
+ dashboard: { type: 'string', pattern: '^([\\d\\w_]+)$' },
9
+ sql: { type: 'string', pattern: '^([\\d])$' },
10
+ // metric: { type: 'string', pattern: '^([\\d\\w_]+)$' },
11
+ x: { type: 'string', pattern: '^([\\d\\w_]+)$' },
12
+ granularity: { type: 'string', pattern: '^(week|month|quarter|year)$' },
13
+ groupby: { type: 'string', pattern: '^([\\d\\w_]+)$' },
14
+ filterCustom: { type: 'string', pattern: '^([\\d\\w_,]+)$' },
15
+ },
16
+ params: {
17
+ id: { type: 'string', pattern: '^([\\d\\w]+)$' },
18
+ },
19
+ };
20
+
21
+ export default async function route(fastify, opts) {
22
+ const prefix = opts?.prefix || config.prefix || '/api';
23
+ fastify.route({
24
+ method: 'GET',
25
+ url: '/bi-data',
26
+ schema: biSchema,
27
+ handler: data,
28
+ });
29
+ }
@@ -0,0 +1,97 @@
1
+ import path from 'node:path';
2
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
3
+
4
+ import { getPG } from '@opengis/fastify-table/utils.js';
5
+
6
+ const host = 'https://cdn.softpro.ua/demo/bi';
7
+
8
+
9
+ const root = process.cwd();
10
+ const send = (msg) => console.log(msg)
11
+ async function downloadFile(fileName) {
12
+ const pg = getPG();
13
+ send(`${fileName} - checking if already downloaded...`);
14
+
15
+ const demoPath = path.join(root, '/log/temp/', fileName);
16
+ // const fileExists = await isFileExists(demoPath);
17
+
18
+ // send(`${host}/${fileName}` + demoPath)
19
+ const response = await fetch(`${host}/${fileName}`);
20
+ const body = await response.text();
21
+ //send(body)
22
+ if (response?.status !== 200) {
23
+ return { [fileName]: { result: 'file not found', status: 404 } };
24
+ }
25
+
26
+ await mkdir(path.dirname(demoPath), { recursive: true });
27
+
28
+ await writeFile(demoPath, body, 'utf8');
29
+
30
+ await pg.query(`drop table if exists ${path.parse(fileName).name}`);
31
+ const { exists } = await pg.query(`select to_regclass($1) is not null as exists`, [path.parse(fileName).name])
32
+ .then((res1) => res1.rows?.[0] || {});
33
+
34
+
35
+ if (exists) {
36
+ send(`${fileName} - dataset table already exists!`);
37
+ return { [fileName]: { result: 'dataset table already exists', status: 200 } };
38
+ }
39
+
40
+ const sql = await readFile(demoPath, 'utf8');
41
+ await pg.query(sql);
42
+ return { [fileName]: { result: 'success', status: 200 } };
43
+ }
44
+
45
+ /**
46
+ * Імпорт demo даних dashboard
47
+ *
48
+ * @method GET
49
+ * @summary Імпорт demo даних dashboard
50
+ * @priority 4
51
+ * @type api
52
+ * @tag bi
53
+ * @errors 400,500
54
+ * @returns {Number} status Номер помилки
55
+ * @returns {String} error Опис помилки
56
+ * @returns {Object} rows Масив з колонками таблиці
57
+ */
58
+
59
+ const demoList = {
60
+ 'demo.orders.sql': 'Статистика продажів (demo)',
61
+ 'demo.cleaned_sales_data.sql': 'Статистика продажів (demo 2)',
62
+ 'demo.video_game_sales.sql': 'Статистика продажів відеоігор (demo)',
63
+ };
64
+
65
+ export default async function biDatasetDemoAdd(req) {
66
+ const { pg, query = {} } = req;
67
+ // const send = () => { }; // eventStream?
68
+
69
+
70
+
71
+ const sqlList = [];
72
+
73
+ const tables = Object.keys(demoList)?.map((el) => {
74
+ const table = path.parse(el).name;
75
+ sqlList.push(`insert into bi.dataset(dataset_id,name,table_name)
76
+ select '${el}', '${demoList?.[el]}', '${table}' on conflict(dataset_id) do update set
77
+ name=excluded.name, table_name=excluded.table_name
78
+ returning dataset_id as id`);
79
+ return table;
80
+ });
81
+
82
+ const { count } = await pg.query(`select count(*) from pg_catalog.pg_tables where schemaname||'.'||tablename = any($1)`, [tables]).then((res1) => res1.rows?.[0] || {});
83
+
84
+ if (count === '3' && !query.nocache) {
85
+ const result = await pg.query(sqlList.join(';\n'));
86
+ const ids = (Array.isArray(result) ? result : [result]).filter((el) => el).map((el) => el?.rows?.[0]?.id);
87
+ return { message: { ids, message: 'all datasets already downloaded' }, status: 200 };
88
+ }
89
+
90
+ const res = {};
91
+ const resultAll = await Promise.all(Object.keys(demoList).map(async (fileName) => await downloadFile(fileName))).catch(err => console.log(err));
92
+
93
+ const result = await pg.query(sqlList.join(';\n'));
94
+ const ids = (Array.isArray(result) ? result : [result]).filter((el) => el).map((el) => el?.rows?.[0]?.id);
95
+ return { message: { ids, message: res, resultAll }, status: 200 };
96
+
97
+ };