@opengis/bi 1.0.21 → 1.0.23
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/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +109 -106
- package/dist/{import-file-C8BY90-b.js → import-file-DgBd_UN1.js} +20484 -18794
- package/dist/{map-component-mixin-CFtShPun.js → map-component-mixin-NewmNy_M.js} +502 -483
- package/dist/style.css +1 -1
- package/dist/{vs-calendar-B9vXdsaG.js → vs-calendar-CPXj4hBh.js} +1 -1
- package/dist/vs-donut-CUmi2ir5.js +148 -0
- package/dist/{vs-funnel-bar-Cj0O8tIf.js → vs-funnel-bar-B3DpbtUl.js} +1 -1
- package/dist/{vs-heatmap-C9oFph_f.js → vs-heatmap-COwT3bHE.js} +1 -1
- package/dist/{vs-map-WOn0RAU7.js → vs-map-DwyQHLpN.js} +2 -2
- package/dist/{vs-map-cluster-RJa6sNfI.js → vs-map-cluster-CnZ9g6k-.js} +2 -2
- package/dist/{vs-number-BG0szZL-.js → vs-number-LwROg9Oe.js} +5 -5
- package/dist/vs-table-Dt_MSaCC.js +68 -0
- package/dist/{vs-text-Kwl3-0yy.js → vs-text-DgAf3Ids.js} +2 -2
- package/package.json +6 -5
- package/plugin.js +1 -1
- package/server/helpers/mdToHTML.js +17 -0
- package/server/migrations/bi.dataset.sql +13 -0
- package/server/migrations/bi.sql +2 -0
- package/server/routes/dashboard/controllers/dashboard.js +10 -11
- package/server/routes/dashboard/controllers/dashboard.list.js +6 -6
- package/server/routes/data/controllers/data.js +14 -6
- package/server/routes/data/controllers/util/chartSQL.js +6 -3
- package/server/routes/dataset/controllers/bi.dataset.list.js +3 -1
- package/server/routes/dataset/controllers/comment.js +55 -0
- package/server/routes/dataset/controllers/createDatasetPost.js +132 -0
- package/server/routes/dataset/controllers/data.js +145 -0
- package/server/routes/{db → dataset}/controllers/dbTablePreview.js +17 -24
- package/server/routes/{db → dataset}/controllers/dbTables.js +7 -11
- package/server/routes/dataset/controllers/delete.js +39 -0
- package/server/routes/dataset/controllers/{bi.dataset.edit.js → editDataset.js} +32 -26
- package/server/routes/dataset/controllers/export.js +213 -0
- package/server/routes/dataset/controllers/form.js +99 -0
- package/server/routes/dataset/controllers/format.js +44 -0
- package/server/routes/dataset/controllers/insert.js +46 -0
- package/server/routes/dataset/controllers/table.js +69 -0
- package/server/routes/dataset/controllers/update.js +42 -0
- package/server/routes/dataset/index.mjs +88 -43
- package/server/routes/dataset/utils/convertJSONToCSV.js +17 -0
- package/server/routes/dataset/utils/convertJSONToXls.js +49 -0
- package/server/routes/dataset/utils/createTableQuery.js +59 -0
- package/server/routes/dataset/utils/datasetForms.js +1 -0
- package/server/routes/dataset/utils/descriptionList.js +46 -0
- package/server/routes/dataset/utils/downloadRemoteFile.js +58 -0
- package/server/routes/dataset/utils/executeQuery.js +46 -0
- package/server/routes/dataset/utils/getLayersData.js +107 -0
- package/server/routes/dataset/utils/getTableData.js +47 -0
- package/server/routes/dataset/utils/insertDataQuery.js +12 -0
- package/server/routes/dataset/utils/metaFormat.js +24 -0
- package/server/routes/edit/controllers/dashboard.add.js +3 -3
- package/server/routes/edit/controllers/widget.add.js +8 -3
- package/server/routes/edit/controllers/widget.edit.js +23 -5
- package/server/routes/map/controllers/cluster.js +41 -41
- package/server/routes/map/controllers/clusterVtile.js +5 -5
- package/server/routes/map/controllers/geojson.js +6 -6
- package/server/routes/map/controllers/heatmap.js +118 -0
- package/server/routes/map/controllers/map.js +3 -3
- package/server/routes/map/controllers/utils/downloadClusterData.js +6 -4
- package/server/routes/map/controllers/vtile.js +3 -3
- package/server/routes/map/index.mjs +2 -0
- package/server/utils/getWidget.js +10 -6
- package/server/routes/dataset/controllers/bi.dataset.add.js +0 -86
- package/server/routes/dataset/controllers/bi.dataset.data.add.js +0 -49
- package/server/routes/dataset/controllers/bi.dataset.data.del.js +0 -54
- package/server/routes/dataset/controllers/bi.dataset.data.edit.js +0 -55
- package/server/routes/dataset/controllers/bi.dataset.data.list.js +0 -71
- package/server/routes/dataset/controllers/bi.dataset.del.js +0 -48
- package/server/routes/dataset/controllers/bi.dataset.demo.add.js +0 -97
- package/server/routes/dataset/controllers/util/create.table.js +0 -21
- package/server/routes/dataset/controllers/util/prepare.data.js +0 -49
- package/server/routes/db/index.mjs +0 -17
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
1
|
+
import { getFilterSQL, logger, pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
2
|
|
|
3
3
|
import { getWidget } from '../../../../utils.js';
|
|
4
4
|
|
|
@@ -6,58 +6,58 @@ import downloadClusterData from './utils/downloadClusterData.js';
|
|
|
6
6
|
|
|
7
7
|
const clusterExists = {};
|
|
8
8
|
|
|
9
|
-
export default async function cluster({
|
|
9
|
+
export default async function cluster({ query = {} }) {
|
|
10
10
|
const { widget, filter, dashboard, search } = query;
|
|
11
11
|
|
|
12
12
|
if (!widget) {
|
|
13
13
|
return { message: 'not enough params: widget', status: 400 };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const { data } = await getWidget({ dashboard, widget });
|
|
16
|
+
const { pg = pgClients.client, data } = await getWidget({ dashboard, widget });
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const pkey = pg.pk?.[data?.table];
|
|
20
|
-
|
|
21
|
-
if (!pkey) {
|
|
22
|
-
return {
|
|
23
|
-
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
24
|
-
status: 400,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
18
|
+
const pkey = pg.pk?.[data?.table];
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
table
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
clusterTable = {},
|
|
35
|
-
} = data;
|
|
20
|
+
if (!pkey) {
|
|
21
|
+
return {
|
|
22
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
23
|
+
status: 400,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
// data param
|
|
28
|
+
const {
|
|
29
|
+
table,
|
|
30
|
+
query: where = '1=1',
|
|
31
|
+
metrics = [],
|
|
32
|
+
cluster,
|
|
33
|
+
clusterTable = {},
|
|
34
|
+
} = data;
|
|
35
|
+
|
|
36
|
+
if (!cluster) {
|
|
37
|
+
return {
|
|
38
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
39
|
+
status: 400,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
if (!metrics.length) {
|
|
44
|
+
return {
|
|
45
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
46
|
+
status: 400,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
if (!clusterTable?.name) {
|
|
51
|
+
Object.assign(clusterTable, {
|
|
52
|
+
name: 'bi.cluster',
|
|
53
|
+
title: 'title',
|
|
54
|
+
query: `type='${cluster}'`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
58
57
|
|
|
58
|
+
try {
|
|
59
59
|
if (cluster && !clusterExists[cluster]) {
|
|
60
|
-
const res = await downloadClusterData({ pg,
|
|
60
|
+
const res = await downloadClusterData({ pg, cluster });
|
|
61
61
|
if (res) return res;
|
|
62
62
|
clusterExists[cluster] = 1;
|
|
63
63
|
}
|
|
@@ -103,7 +103,7 @@ export default async function cluster({ pg, query = {}, log }) {
|
|
|
103
103
|
];
|
|
104
104
|
return { sizes, rows, bounds, extent, count: rows.length, total: rows?.reduce((acc, curr) => (curr.metric || 0) + acc, 0) };
|
|
105
105
|
} catch (err) {
|
|
106
|
-
|
|
106
|
+
logger.file('bi/cluster/error', { error: err.toString(), query });
|
|
107
107
|
return { error: err.toString(), status: 500 };
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { writeFile, mkdir } from 'fs/promises';
|
|
6
6
|
|
|
7
|
-
import { getFolder, getFilterSQL, autoIndex } from '@opengis/fastify-table/utils.js';
|
|
7
|
+
import { getFolder, getFilterSQL, autoIndex, pgClients } from '@opengis/fastify-table/utils.js';
|
|
8
8
|
|
|
9
9
|
import { getWidget } from '../../../../utils.js';
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ const mercator = new Sphericalmercator({ size: 256 });
|
|
|
15
15
|
const clusterExists = {};
|
|
16
16
|
|
|
17
17
|
export default async function clusterVtile(req, reply) {
|
|
18
|
-
const {
|
|
18
|
+
const { params = {}, query = {} } = req;
|
|
19
19
|
const { z, y } = params;
|
|
20
20
|
const x = params.x?.split('.')[0] - 0;
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ export default async function clusterVtile(req, reply) {
|
|
|
30
30
|
return { message: 'not enough params: widget', status: 400 };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const { data } = await getWidget({ dashboard, widget });
|
|
33
|
+
const { pg = pgClients.client, data } = await getWidget({ dashboard, widget });
|
|
34
34
|
|
|
35
35
|
const headers = {
|
|
36
36
|
'Content-Type': 'application/x-protobuf',
|
|
@@ -80,7 +80,7 @@ export default async function clusterVtile(req, reply) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
if (cluster && !clusterExists[data.cluster]) {
|
|
83
|
-
const res = await downloadClusterData({ pg,
|
|
83
|
+
const res = await downloadClusterData({ pg, cluster });
|
|
84
84
|
if (res) return res;
|
|
85
85
|
clusterExists[cluster] = 1;
|
|
86
86
|
}
|
|
@@ -160,7 +160,7 @@ export default async function clusterVtile(req, reply) {
|
|
|
160
160
|
|
|
161
161
|
return reply.headers(headers).send(buffer);
|
|
162
162
|
} catch (err) {
|
|
163
|
-
|
|
163
|
+
logger.file('bi/clusterVtile/error', { error: err.toString(), query, params });
|
|
164
164
|
return { error: err.toString(), status: 500 };
|
|
165
165
|
}
|
|
166
166
|
}
|
|
@@ -4,7 +4,7 @@ import { writeFile, mkdir, readFile, stat } from 'fs/promises';
|
|
|
4
4
|
import { existsSync, /* readdirSync, */ readFileSync } from 'fs';
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
import { getFolder, getFilterSQL, autoIndex, logger } from '@opengis/fastify-table/utils.js';
|
|
7
|
+
import { getFolder, getFilterSQL, autoIndex, logger, pgClients } from '@opengis/fastify-table/utils.js';
|
|
8
8
|
|
|
9
9
|
import normalizeData from '../../data/controllers/util/normalizeData.js';
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ const types = {
|
|
|
17
17
|
const hourMs = 3.6e6;
|
|
18
18
|
|
|
19
19
|
export default async function geojson(req, reply) {
|
|
20
|
-
const {
|
|
20
|
+
const { query = {} } = req;
|
|
21
21
|
|
|
22
22
|
const {
|
|
23
23
|
filter,
|
|
@@ -38,6 +38,7 @@ export default async function geojson(req, reply) {
|
|
|
38
38
|
const data = await getWidget({ dashboard, widget });
|
|
39
39
|
if (data.status) return data;
|
|
40
40
|
|
|
41
|
+
const pg = data.pg || pgClients.client;
|
|
41
42
|
const hash = [pointZoom, filter].filter((el) => el).join();
|
|
42
43
|
|
|
43
44
|
const root = getFolder(req);
|
|
@@ -92,10 +93,9 @@ export default async function geojson(req, reply) {
|
|
|
92
93
|
|
|
93
94
|
const q1 = `SELECT 'FeatureCollection' As type, json_agg(f) As features FROM (
|
|
94
95
|
SELECT 'Feature' As type, row_number() over() as id,
|
|
95
|
-
st_asgeojson(st_force2d(${
|
|
96
|
-
query.srid
|
|
97
|
-
|
|
98
|
-
: `${type === 'centroid' || query.point || query.centroid ? `st_centroid(${geom})` : geom}`
|
|
96
|
+
st_asgeojson(st_force2d(${query.srid
|
|
97
|
+
? `st_transform(${type === 'centroid' ? `st_centroid(${geom})` : geom},${query.srid})`
|
|
98
|
+
: `${type === 'centroid' || query.point || query.centroid ? `st_centroid(${geom})` : geom}`
|
|
99
99
|
}), 6, 0)::json as geometry,
|
|
100
100
|
(select row_to_json(tc) from (select ${'' ? `${''} as status, ` : ''}
|
|
101
101
|
${xName ? `${xName},` : ''}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
import { pgClients, getFilterSQL, getMeta, getFolder } from '@opengis/fastify-table/utils.js';
|
|
7
|
+
|
|
8
|
+
import { getWidget } from '../../../../utils.js';
|
|
9
|
+
|
|
10
|
+
const hourMs = 3.6e6;
|
|
11
|
+
const maxLimit = 2500;
|
|
12
|
+
|
|
13
|
+
export default async function heatmap(req, reply) {
|
|
14
|
+
const { query = {}, user = {} } = req;
|
|
15
|
+
|
|
16
|
+
const { widget, dashboard, filter, search, size = 0.1 } = query;
|
|
17
|
+
|
|
18
|
+
if (query.size && (+query.size || 0) <= 0) {
|
|
19
|
+
return { message: 'param size is invalid', status: 400 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!dashboard || !widget) {
|
|
23
|
+
return { message: 'not enough params: dashboard / widget', status: 400 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { data } = await getWidget({ widget, dashboard });
|
|
27
|
+
|
|
28
|
+
if (!data?.table) {
|
|
29
|
+
return { message: 'widget not found: ' + widget, status: 400 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const limit = Math.min(+query.limit || maxLimit, maxLimit);
|
|
33
|
+
const hash = [search, filter, limit].filter((el) => el).join();
|
|
34
|
+
|
|
35
|
+
const root = getFolder(req, 'local');
|
|
36
|
+
const file = path.join(
|
|
37
|
+
root,
|
|
38
|
+
`/map/geojson/heatmap/${data.table}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}.geojson`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (existsSync(file) && !query.nocache && !query.sql) {
|
|
42
|
+
const timeNow = Date.now();
|
|
43
|
+
const stats = await stat(file);
|
|
44
|
+
const birthTime = new Date(stats.birthtime).getTime();
|
|
45
|
+
if (!(birthTime - timeNow > hourMs * 24)) {
|
|
46
|
+
const geojson = JSON.parse((await readFile(file, 'utf-8')) || {});
|
|
47
|
+
return geojson;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pg = data.pg || pgClients.client;
|
|
52
|
+
|
|
53
|
+
if (!pg.pk?.[data.table]) {
|
|
54
|
+
return { message: `table not found: ${data.table}`, status: 404 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const metric = query.metric || data.metrics?.[0]?.name || (Array.isArray(data.metrics) ? data.metrics?.[0] : data.metrics);
|
|
58
|
+
|
|
59
|
+
const operator = metric
|
|
60
|
+
? (['sum', 'min', 'max', 'avg'].find(el => el === query.operator) || 'sum')
|
|
61
|
+
: undefined;
|
|
62
|
+
|
|
63
|
+
const aggregator = metric
|
|
64
|
+
? `${operator}(${metric})`
|
|
65
|
+
: 'count(*)';
|
|
66
|
+
|
|
67
|
+
const { geom, columns } = await getMeta({ pg, table: data.table });
|
|
68
|
+
|
|
69
|
+
const { dataTypeID } = columns.find(col => col.name === metric) || {};
|
|
70
|
+
|
|
71
|
+
if (metric && !dataTypeID) {
|
|
72
|
+
return { message: `metric column not found: ${metric}`, status: 404 };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!['integer', 'numeric', 'double precision'].includes(pg.pgType[dataTypeID])) {
|
|
76
|
+
return { message: `metric column invalid type: ${metric} (${pg.pgType[dataTypeID]})`, status: 404 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!geom) {
|
|
80
|
+
return { message: `geometry column not found: ${data.table}`, status: 404 };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { optimizedSQL = `select * from ${data.table} where 1=1` } = hash ? await getFilterSQL({ pg, table: data.table, filter, search }) : {};
|
|
84
|
+
|
|
85
|
+
const subQuery = `SELECT ${aggregator} AS metric, hex.geom FROM (
|
|
86
|
+
SELECT ST_SetSRID( (ST_HexagonGrid(${size}, ST_Extent(q.${geom})) ).geom, 4326 ) as geom FROM ( ${optimizedSQL})q
|
|
87
|
+
)hex
|
|
88
|
+
LEFT JOIN ( ${optimizedSQL} )pts
|
|
89
|
+
ON ST_Within(pts.${geom}, hex.geom)
|
|
90
|
+
|
|
91
|
+
JOIN ( SELECT ST_ConvexHull(ST_Collect(${geom})) AS mask FROM ( ${optimizedSQL} )q )point_mask
|
|
92
|
+
ON ST_Intersects(hex.geom, point_mask.mask)
|
|
93
|
+
|
|
94
|
+
WHERE 1=1 /*and pts.${geom} is not null AND st_srid(pts.${geom}) > 0*/
|
|
95
|
+
GROUP BY hex.geom
|
|
96
|
+
limit ${limit}`;
|
|
97
|
+
|
|
98
|
+
if (query.sql === '1' && user?.user_type?.includes('admin')) return subQuery;
|
|
99
|
+
|
|
100
|
+
const q = `SELECT 'FeatureCollection' As type, json_agg(f) As features FROM (
|
|
101
|
+
SELECT
|
|
102
|
+
'Feature' As type,
|
|
103
|
+
row_number() over() as id,
|
|
104
|
+
st_asgeojson(geom, 6, 0)::json as geometry,
|
|
105
|
+
json_build_object( 'metric', metric ) as properties
|
|
106
|
+
from (${subQuery})sq
|
|
107
|
+
)f`;
|
|
108
|
+
|
|
109
|
+
if (query.sql === '2' && user?.user_type?.includes('admin')) return q;
|
|
110
|
+
|
|
111
|
+
const geojson = await pg.query(q)
|
|
112
|
+
.then(el => el.rows?.[0] || {});
|
|
113
|
+
|
|
114
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
115
|
+
await writeFile(file, JSON.stringify(geojson));
|
|
116
|
+
|
|
117
|
+
return geojson;
|
|
118
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
1
|
+
import { pgClients, getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
2
2
|
|
|
3
3
|
import { getWidget } from '../../../../utils.js';
|
|
4
4
|
|
|
5
|
-
export default async function map({
|
|
5
|
+
export default async function map({ query = {} }) {
|
|
6
6
|
const { dashboard, widget } = query;
|
|
7
7
|
|
|
8
|
-
const { data, type, layers } = await getWidget({ dashboard, widget });
|
|
8
|
+
const { pg = pgClients.client, data, type, layers } = await getWidget({ dashboard, widget });
|
|
9
9
|
|
|
10
10
|
if (!['map'].includes(type)) {
|
|
11
11
|
return { message: 'access restricted: invalid widget type', status: 403 };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { logger, pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function downloadClusterData({ pg = pgClients.client, cluster }) {
|
|
4
|
+
if (!pg || !cluster) return null;
|
|
3
5
|
const res = await fetch(`https://cdn.softpro.ua/data/bi/${cluster}-ua.geojson`);
|
|
4
6
|
if (res?.status !== 200) {
|
|
5
7
|
return {
|
|
@@ -28,10 +30,10 @@ export default async function downloadClusterData({ pg, log, cluster }) {
|
|
|
28
30
|
|
|
29
31
|
const { rowCount } = await pg.query(`insert into bi.cluster (title,type,geom)
|
|
30
32
|
values ${values} on conflict(title,type) do update set geom=excluded.geom`);
|
|
31
|
-
|
|
33
|
+
logger.file('bi/clusterVtile', { cluster, rowCount });
|
|
32
34
|
}
|
|
33
35
|
} catch (err) {
|
|
34
|
-
|
|
36
|
+
logger.file('bi/clusterVtile/error', {
|
|
35
37
|
error: err.toString(),
|
|
36
38
|
filename: `${cluster}-ua.json`,
|
|
37
39
|
});
|
|
@@ -6,7 +6,7 @@ import { writeFile, mkdir } from 'fs/promises';
|
|
|
6
6
|
import { existsSync, /* readdirSync, */ readFileSync } from 'fs';
|
|
7
7
|
import { getWidget } from '../../../../utils.js';
|
|
8
8
|
|
|
9
|
-
import { getFolder, getFilterSQL, logger } from '@opengis/fastify-table/utils.js';
|
|
9
|
+
import { getFolder, getFilterSQL, logger, pgClients } from '@opengis/fastify-table/utils.js';
|
|
10
10
|
|
|
11
11
|
import normalizeData from '../../data/controllers/util/normalizeData.js';
|
|
12
12
|
|
|
@@ -34,7 +34,7 @@ const area = {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
export default async function vtile(req, reply) {
|
|
37
|
-
const {
|
|
37
|
+
const { params = {}, query = {} } = req;
|
|
38
38
|
|
|
39
39
|
const {
|
|
40
40
|
filter,
|
|
@@ -59,7 +59,7 @@ export default async function vtile(req, reply) {
|
|
|
59
59
|
return { message: 'not enough params: xyz', status: 400 };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const { data } = await getWidget({ widget, dashboard });
|
|
62
|
+
const { pg = pgClients.client, data } = await getWidget({ widget, dashboard });
|
|
63
63
|
|
|
64
64
|
const headers = {
|
|
65
65
|
'Content-Type': 'application/x-protobuf',
|
|
@@ -4,6 +4,7 @@ import vtile from './controllers/vtile.js';
|
|
|
4
4
|
|
|
5
5
|
import cluster from './controllers/cluster.js';
|
|
6
6
|
import clusterVtile from './controllers/clusterVtile.js';
|
|
7
|
+
import heatmap from './controllers/heatmap.js';
|
|
7
8
|
|
|
8
9
|
const biSchema = {
|
|
9
10
|
querystring: {
|
|
@@ -22,4 +23,5 @@ export default async function route(fastify, opts) {
|
|
|
22
23
|
fastify.get('/bi-vtile/:z/:y/:x', { schema: biSchema }, vtile);
|
|
23
24
|
fastify.get('/bi-cluster', { schema: biSchema }, cluster);
|
|
24
25
|
fastify.get('/bi-cluster-vtile/:z/:y/:x', { schema: biSchema }, clusterVtile);
|
|
26
|
+
fastify.get('/bi-heatmap', {}, heatmap);
|
|
25
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getTemplate, pgClients } from '@opengis/fastify-table/utils.js';
|
|
1
|
+
import { getTemplate, getPGAsync, pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import mdToHTML from '../helpers/mdToHTML.js';
|
|
4
4
|
|
|
5
5
|
async function getWidget({ dashboard, widget }) {
|
|
6
6
|
if (!dashboard && !widget) {
|
|
@@ -10,6 +10,9 @@ async function getWidget({ dashboard, widget }) {
|
|
|
10
10
|
? await getTemplate('dashboard', dashboard)
|
|
11
11
|
: null;
|
|
12
12
|
|
|
13
|
+
const dashboardIndex = dashboardData?.find((el) => el[0] == 'index.yml')?.[1]; // dashboardData?.find((el) => el[2] == 'index')?.[1]
|
|
14
|
+
const pg = dashboardIndex?.db ? await getPGAsync(dashboardIndex?.db) : pgClients.client;
|
|
15
|
+
|
|
13
16
|
const { id, tableName } =
|
|
14
17
|
!dashboardData && pg.pk['bi.dashboard'] && dashboard
|
|
15
18
|
? await pg
|
|
@@ -27,13 +30,13 @@ async function getWidget({ dashboard, widget }) {
|
|
|
27
30
|
dashboardData?.forEach((el) => {
|
|
28
31
|
el[2] = el[0].split('.')[0];
|
|
29
32
|
});
|
|
30
|
-
const dashboardIndex = dashboardData?.find((el) => el[2] == 'index')?.[1];
|
|
31
33
|
|
|
32
34
|
const widgetData = dashboard
|
|
33
35
|
? dashboardData?.find((el) => el[2] === (widget || 'index'))?.[1]
|
|
34
36
|
: await getTemplate('widget', widget);
|
|
35
37
|
if (typeof widgetData === 'string') {
|
|
36
|
-
|
|
38
|
+
const html = mdToHTML(widgetData);
|
|
39
|
+
return { source: html, status: 200 };
|
|
37
40
|
}
|
|
38
41
|
if (!id && !dashboardData && !widgetData) {
|
|
39
42
|
return { message: `not found ${widget} ${dashboard}`, status: 404 };
|
|
@@ -65,9 +68,10 @@ async function getWidget({ dashboard, widget }) {
|
|
|
65
68
|
widgetData?.table_name ||
|
|
66
69
|
dashboardIndex?.table ||
|
|
67
70
|
dashboardIndex?.table_name,
|
|
68
|
-
db: dashboardIndex?.db || pgClients.client?.options?.database,
|
|
71
|
+
db: dashboardIndex?.db || widgetData?.db || pgClients.client?.options?.database,
|
|
69
72
|
});
|
|
70
73
|
const main = { ...(dashboardIndex || {}), ...widgetData, ...data, ...data?.data || {} };
|
|
74
|
+
const pg1 = widgetData?.db ? await getPGAsync(widgetData?.db) : pg;
|
|
71
75
|
|
|
72
76
|
if (!main?.table) {
|
|
73
77
|
return {
|
|
@@ -80,6 +84,6 @@ async function getWidget({ dashboard, widget }) {
|
|
|
80
84
|
(el, i) => `left join lateral(${el})t${i + 1} on 1=1`
|
|
81
85
|
);
|
|
82
86
|
|
|
83
|
-
return { ...main, tableSQL, data, type, text, controls, style, options, yml };
|
|
87
|
+
return { ...main, pg: pg1, tableSQL, data, type, text, controls, style, options, yml };
|
|
84
88
|
}
|
|
85
89
|
export default getWidget;
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
import { dataInsert, getPG, pgClients, initPG } from '@opengis/fastify-table/utils.js';
|
|
4
|
-
|
|
5
|
-
import createTableFunc from './util/create.table.js';
|
|
6
|
-
import prepareData from './util/prepare.data.js';
|
|
7
|
-
|
|
8
|
-
async function updatePGMeta(pg, table) {
|
|
9
|
-
if (!pg.tlist) await initPG(pg);
|
|
10
|
-
if (!pg.tlist.includes(table)) {
|
|
11
|
-
const { pks } = await pg.query(`SELECT json_object_agg(c.conrelid::regclass, a.attname) as pks FROM pg_constraint c
|
|
12
|
-
left join pg_attribute a on c.conrelid=a.attrelid and a.attnum = c.conkey[1] WHERE c.contype='p'::"char"`).then(el => el.rows[0]) || {};
|
|
13
|
-
pg.pk[table] = pks[table];
|
|
14
|
-
pg.tlist.push(table);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Додавання нового набору даних BI
|
|
20
|
-
*
|
|
21
|
-
* @method POST
|
|
22
|
-
* @summary Додавання нового набору даних BI
|
|
23
|
-
* @priority 4
|
|
24
|
-
* @alias biDatasetAdd
|
|
25
|
-
* @type api
|
|
26
|
-
* @tag bi
|
|
27
|
-
* @param {Object} params.id Dashboard ID
|
|
28
|
-
* @errors 400,500
|
|
29
|
-
* @returns {Number} status Номер помилки
|
|
30
|
-
* @returns {String} error Опис помилки
|
|
31
|
-
* @returns {Object} rows Масив з колонками таблиці
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
export default async function biDatasetAdd(req) {
|
|
35
|
-
const { body = {}, session = {} } = req;
|
|
36
|
-
const { uid } = session?.passport?.user || {};
|
|
37
|
-
|
|
38
|
-
if (!uid) {
|
|
39
|
-
return { message: 'access restricted', status: 403 };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (body.table_name) {
|
|
43
|
-
const result = await dataInsert({
|
|
44
|
-
table: 'bi.dataset',
|
|
45
|
-
data: { ...body, source_type: 'db' },
|
|
46
|
-
uid,
|
|
47
|
-
}).then(el => el.rows?.[0]);
|
|
48
|
-
|
|
49
|
-
const pg = body.db ? getPG({ db: body.db }) : pgClients.client;
|
|
50
|
-
await updatePGMeta(pg,body.table_name);
|
|
51
|
-
|
|
52
|
-
return { message: { id: result?.dataset_id, action: 'assign', table: body.table_name }, status: 200 };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!Array.isArray(body.data) && body.data?.features?.[0]?.type !== 'Feature' && !body.file_path) {
|
|
56
|
-
return { message: 'body data param is invalid', status: 400 };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const tableName = randomBytes(64).toString('hex').substring(0, 24).replace(/^\d+/, '');
|
|
60
|
-
const pkey = tableName.concat('_id');
|
|
61
|
-
const table = `bi_data.${tableName}`;
|
|
62
|
-
const { columns, insertData } = await prepareData({ table, file_path: body.file_path, data: body?.data });
|
|
63
|
-
const createTable = createTableFunc({ table, pkey, columns, name: body.name });
|
|
64
|
-
|
|
65
|
-
const q = ['create extension if not exists postgis;create schema if not exists bi_data', createTable, insertData].join(';');
|
|
66
|
-
|
|
67
|
-
const pg = body.db ? getPG({ db: body.db }) : pgClients.client;
|
|
68
|
-
if (!pg.pk) await initPG(pg);
|
|
69
|
-
|
|
70
|
-
const results = await pg.query(q);
|
|
71
|
-
|
|
72
|
-
const result = await dataInsert({
|
|
73
|
-
table: 'bi.dataset',
|
|
74
|
-
data: { ...body, table_name: table, source_type: body.data ? 'user' : 'file' },
|
|
75
|
-
uid,
|
|
76
|
-
}).then(el => el.rows?.[0]);
|
|
77
|
-
|
|
78
|
-
await updatePGMeta(pg,table);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
message: {
|
|
82
|
-
id: result?.dataset_id, action: 'import', total: results?.find((el) => el.command === 'INSERT')?.rowCount, table,
|
|
83
|
-
},
|
|
84
|
-
status: 200,
|
|
85
|
-
};
|
|
86
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { dataInsert, getPG, initPG, pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Внесення даних до набору BI
|
|
5
|
-
*
|
|
6
|
-
* @method POST
|
|
7
|
-
* @summary Внесення даних до набору BI
|
|
8
|
-
* @priority 4
|
|
9
|
-
* @alias biDatasetDataAdd
|
|
10
|
-
* @type api
|
|
11
|
-
* @tag bi
|
|
12
|
-
* @param {Object} params.id Dataset ID
|
|
13
|
-
* @errors 400,500
|
|
14
|
-
* @returns {Number} status Номер помилки
|
|
15
|
-
* @returns {String} error Опис помилки
|
|
16
|
-
* @returns {Object} rows Масив з колонками таблиці
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export default async function biDatasetDataAdd(req) {
|
|
20
|
-
const { params = {}, session = {}, body = {} } = req;
|
|
21
|
-
const { uid } = session?.passport?.user || {};
|
|
22
|
-
|
|
23
|
-
if (!uid) {
|
|
24
|
-
return { message: 'access restricted', status: 403 };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!params?.id) {
|
|
28
|
-
return { message: 'not enough params: id', status: 400 };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const { id, table, db } = await pgClients.client.query('select db, dataset_id as id, table_name as table from bi.dataset where dataset_id=$1', [params.id])
|
|
32
|
-
.then(el => el.rows[0]);
|
|
33
|
-
|
|
34
|
-
if (!id) {
|
|
35
|
-
return { message: 'dataset not found', status: 404 };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const pg = db ? getPG({ db }) : pgClients.client;
|
|
39
|
-
if (!pg.pk) await initPG(pg);
|
|
40
|
-
|
|
41
|
-
const result = await dataInsert({
|
|
42
|
-
pg,
|
|
43
|
-
table,
|
|
44
|
-
data: body,
|
|
45
|
-
uid,
|
|
46
|
-
}).then(el => el.rows[0]);
|
|
47
|
-
|
|
48
|
-
return { id, action: 'insert', result };
|
|
49
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { dataDelete, getPG, initPG, pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Видалення змін до даних набору BI
|
|
5
|
-
*
|
|
6
|
-
* @method DELETE
|
|
7
|
-
* @summary Внесення змін до даних набору BI
|
|
8
|
-
* @priority 4
|
|
9
|
-
* @alias biDatasetDataDel
|
|
10
|
-
* @type api
|
|
11
|
-
* @tag bi
|
|
12
|
-
* @param {Object} params.id Dataset ID
|
|
13
|
-
* @param {Object} params.objectId Dataset object ID
|
|
14
|
-
* @errors 400,500
|
|
15
|
-
* @returns {Number} status Номер помилки
|
|
16
|
-
* @returns {String} error Опис помилки
|
|
17
|
-
* @returns {Object} rows Масив з колонками таблиці
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export default async function biDatasetDataDel(req) {
|
|
21
|
-
const { params = {}, session = {} } = req;
|
|
22
|
-
const { uid } = session?.passport?.user || {};
|
|
23
|
-
|
|
24
|
-
if (!uid) {
|
|
25
|
-
return { message: 'access restricted', status: 403 };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!params?.id) {
|
|
29
|
-
return { message: 'not enough params: id', status: 400 };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!params?.objectId) {
|
|
33
|
-
return { message: 'not enough params: objectId', status: 400 };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const { id, table, db } = await pgClients.client.query('select db, dataset_id as id, table_name as table from bi.dataset where dataset_id=$1', [params.id])
|
|
37
|
-
.then(el => el.rows[0]);
|
|
38
|
-
|
|
39
|
-
if (!id) {
|
|
40
|
-
return { message: 'dataset not found', status: 404 };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const pg = db ? getPG({ db }) : pgClients.client;
|
|
44
|
-
if (!pg.pk) await initPG(pg);
|
|
45
|
-
|
|
46
|
-
const result = await dataDelete({
|
|
47
|
-
pg,
|
|
48
|
-
table,
|
|
49
|
-
id: params.objectId,
|
|
50
|
-
uid,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return { id, action: 'delete', result };
|
|
54
|
-
};
|