@opengis/bi 1.0.13 → 1.0.14
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 +2 -4
- package/config.js +5 -5
- package/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +116 -130
- package/dist/{import-file-1T7kpSzt.js → import-file-DUp3rsNI.js} +11132 -10748
- package/dist/{map-component-mixin-BLM9iEWA.js → map-component-mixin-CGM0P5ub.js} +1135 -1134
- package/dist/style.css +1 -1
- package/dist/{vs-calendar-WiK1hcHS.js → vs-calendar-cOoinEwc.js} +33 -30
- package/dist/vs-funnel-bar-kLkPoIhJ.js +105 -0
- package/dist/vs-heatmap-3XAVGTSo.js +98 -0
- package/dist/vs-map-B1tr6V5_.js +74 -0
- package/dist/{vs-map-cluster-Dfe9INqE.js → vs-map-cluster-BWJPx7wE.js} +28 -25
- package/dist/vs-number-CrU7LmkV.js +48 -0
- package/dist/{vs-text-DcrAdQ40.js → vs-text-DRPx3aID.js} +2 -1
- package/package.json +37 -12
- package/plugin.js +4 -4
- package/server/migrations/bi.sql +66 -0
- package/server/plugins/docs.js +36 -35
- package/server/plugins/hook.js +72 -69
- package/server/plugins/vite.js +22 -8
- package/server/routes/dashboard/controllers/dashboard.delete.js +5 -3
- package/server/routes/dashboard/controllers/dashboard.js +66 -32
- package/server/routes/dashboard/controllers/dashboard.list.js +2 -5
- package/server/routes/dashboard/controllers/utils/yaml.js +1 -2
- package/server/routes/dashboard/index.mjs +5 -4
- package/server/routes/data/controllers/data.js +94 -34
- package/server/routes/data/controllers/util/chartSQL.js +24 -10
- package/server/routes/data/controllers/util/normalizeData.js +51 -29
- package/server/routes/data/index.mjs +1 -3
- package/server/routes/db/controllers/dbTablePreview.js +63 -0
- package/server/routes/db/controllers/dbTables.js +36 -0
- package/server/routes/db/index.mjs +17 -0
- package/server/routes/edit/controllers/dashboard.add.js +6 -5
- package/server/routes/edit/controllers/dashboard.edit.js +16 -9
- package/server/routes/edit/controllers/widget.add.js +43 -19
- package/server/routes/edit/controllers/widget.del.js +13 -6
- package/server/routes/edit/controllers/widget.edit.js +34 -13
- package/server/routes/edit/index.mjs +14 -10
- package/server/routes/map/controllers/cluster.js +89 -60
- package/server/routes/map/controllers/clusterVtile.js +154 -84
- package/server/routes/map/controllers/geojson.js +48 -22
- package/server/routes/map/controllers/map.js +51 -51
- package/server/routes/map/controllers/vtile.js +61 -40
- package/server/routes/map/index.mjs +1 -1
- package/server/utils/getWidget.js +67 -40
- package/utils.js +5 -4
- package/dist/vs-funnel-bar-CpPbYZ0_.js +0 -92
- package/dist/vs-heatmap-BG4eIROH.js +0 -83
- package/dist/vs-map-BRk6Fmks.js +0 -66
- package/dist/vs-number-CJq-vi95.js +0 -39
|
@@ -3,23 +3,33 @@ import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { writeFile, mkdir, readFile, stat } from 'fs/promises';
|
|
6
|
-
import { existsSync, /*readdirSync, */ readFileSync } from 'fs';
|
|
6
|
+
import { existsSync, /* readdirSync, */ readFileSync } from 'fs';
|
|
7
7
|
|
|
8
8
|
import yaml from '../../dashboard/controllers/utils/yaml.js';
|
|
9
9
|
import normalizeData from '../../data/controllers/util/normalizeData.js';
|
|
10
10
|
|
|
11
11
|
import { getWidget } from '../../../../utils.js';
|
|
12
12
|
|
|
13
|
-
const types = {
|
|
14
|
-
|
|
13
|
+
const types = {
|
|
14
|
+
point: 'ST_Point' /* ,ST_MultiPoint */,
|
|
15
|
+
polygon: 'ST_Polygon,ST_MultiPolygon',
|
|
16
|
+
};
|
|
17
|
+
const hourMs = 3.6e6;
|
|
15
18
|
|
|
16
19
|
export default async function geojson(req, reply) {
|
|
20
|
+
const { pg, query = {}, funcs = {}, log } = req;
|
|
17
21
|
|
|
18
22
|
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
filter,
|
|
24
|
+
widget,
|
|
25
|
+
sql,
|
|
26
|
+
type,
|
|
27
|
+
nocache,
|
|
28
|
+
id,
|
|
29
|
+
dashboard,
|
|
30
|
+
geom = 'geom',
|
|
31
|
+
pointZoom = 0,
|
|
32
|
+
} = query;
|
|
23
33
|
|
|
24
34
|
if (!widget && !dashboard) {
|
|
25
35
|
return { message: 'not enough params: widget', status: 400 };
|
|
@@ -31,14 +41,17 @@ export default async function geojson(req, reply) {
|
|
|
31
41
|
const hash = [pointZoom, filter].filter((el) => el).join();
|
|
32
42
|
|
|
33
43
|
const root = funcs.getFolder(req);
|
|
34
|
-
const file = path.join(
|
|
44
|
+
const file = path.join(
|
|
45
|
+
root,
|
|
46
|
+
`/map/geojson/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}.geojson`
|
|
47
|
+
);
|
|
35
48
|
|
|
36
49
|
if (existsSync(file)) {
|
|
37
50
|
const timeNow = Date.now();
|
|
38
51
|
const stats = await stat(file);
|
|
39
52
|
const birthTime = new Date(stats.birthtime).getTime();
|
|
40
|
-
if (!(birthTime - timeNow >
|
|
41
|
-
const res = JSON.parse(await readFile(file, 'utf-8') || {});
|
|
53
|
+
if (!(birthTime - timeNow > hourMs * 24) && !nocache) {
|
|
54
|
+
const res = JSON.parse((await readFile(file, 'utf-8')) || {});
|
|
42
55
|
return res;
|
|
43
56
|
}
|
|
44
57
|
}
|
|
@@ -46,30 +59,44 @@ export default async function geojson(req, reply) {
|
|
|
46
59
|
try {
|
|
47
60
|
const pkey = pg.pk?.[data?.table];
|
|
48
61
|
if (!pkey) {
|
|
49
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
64
|
+
status: 400,
|
|
65
|
+
};
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
// data param
|
|
68
|
+
// data param
|
|
53
69
|
const { table, where = '1=1', xName, x } = normalizeData(data, query);
|
|
54
70
|
|
|
55
71
|
if (!xName && !x) {
|
|
56
|
-
return {
|
|
72
|
+
return {
|
|
73
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: x axis column not specified`,
|
|
74
|
+
status: 400,
|
|
75
|
+
};
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
// get sql
|
|
60
|
-
const filterQ = filter
|
|
79
|
+
const filterQ = filter
|
|
80
|
+
? await funcs.getFilterSQL({ pg, table, filter, query })
|
|
81
|
+
: undefined;
|
|
61
82
|
const q = `select "${pkey}", "${xName || x}", /* st_asgeojson(geom)::json as */ ${geom} as geom from ${filterQ ? `(${filterQ})` : table} q where ${where}`;
|
|
62
83
|
|
|
63
84
|
if (sql === '1') return q;
|
|
64
85
|
|
|
65
|
-
const { st_geometrytype: geomType = 'point' } = await pg
|
|
66
|
-
|
|
86
|
+
const { st_geometrytype: geomType = 'point' } = await pg
|
|
87
|
+
.query(
|
|
88
|
+
`select st_geometrytype(${geom}), count(*) from ${table}
|
|
89
|
+
where ${where} group by st_geometrytype(${geom})`
|
|
90
|
+
)
|
|
91
|
+
.then((res) => res.rows?.[0] || {});
|
|
67
92
|
|
|
68
93
|
const q1 = `SELECT 'FeatureCollection' As type, json_agg(f) As features FROM (
|
|
69
94
|
SELECT 'Feature' As type, row_number() over() as id,
|
|
70
|
-
st_asgeojson(st_force2d(${
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
st_asgeojson(st_force2d(${
|
|
96
|
+
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
|
+
}), 6, 0)::json as geometry,
|
|
73
100
|
(select row_to_json(tc) from (select ${'' ? `${''} as status, ` : ''}
|
|
74
101
|
${xName ? `${xName},` : ''}
|
|
75
102
|
${data.style?.colorAttr ? `${data.style.colorAttr},` : ''}
|
|
@@ -83,7 +110,7 @@ export default async function geojson(req, reply) {
|
|
|
83
110
|
limit ${geomType?.toLowerCase()?.includes('point') ? '15000' : '2500'})f`;
|
|
84
111
|
|
|
85
112
|
if (sql === '2') return q1;
|
|
86
|
-
|
|
113
|
+
|
|
87
114
|
// auto Index
|
|
88
115
|
funcs.autoIndex({ table, columns: [xName] });
|
|
89
116
|
|
|
@@ -93,8 +120,7 @@ export default async function geojson(req, reply) {
|
|
|
93
120
|
await writeFile(file, JSON.stringify(res));
|
|
94
121
|
|
|
95
122
|
return res;
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
123
|
+
} catch (err) {
|
|
98
124
|
log.error('bi/geojson', { error: err.toString(), query });
|
|
99
125
|
return { error: err.toString(), status: 500 };
|
|
100
126
|
}
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
import { getWidget } from '../../../../utils.js';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const q1 = `select PERCENTILE_CONT(0) WITHIN GROUP (ORDER BY "${metric}") as "0",
|
|
3
|
+
export default async function map({ pg, funcs = {}, query = {} }) {
|
|
4
|
+
const { dashboard, widget } = query;
|
|
5
|
+
|
|
6
|
+
const { data, type, layers } = await getWidget({ dashboard, widget });
|
|
7
|
+
|
|
8
|
+
if (!['map'].includes(type)) {
|
|
9
|
+
return { message: 'access restricted: invalid widget type', status: 403 };
|
|
10
|
+
}
|
|
11
|
+
if (!data?.table) {
|
|
12
|
+
return { message: 'invalid widget: param table is required', status: 400 };
|
|
13
|
+
}
|
|
14
|
+
if (!pg.pk[data?.table]) {
|
|
15
|
+
return { message: 'invalid widget: table pkey not found', status: 400 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { q = '' } = await funcs.getFilterSQL({
|
|
19
|
+
pg,
|
|
20
|
+
table: data?.table,
|
|
21
|
+
filter: query.filter,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const res = {};
|
|
25
|
+
if (data?.color) {
|
|
26
|
+
const { rows = [] } = await pg.query(
|
|
27
|
+
`select count(*), "${data.color}" as val from ${data.table} where ${data.query || '1=1'} group by "${data.color}"`
|
|
28
|
+
);
|
|
29
|
+
Object.assign(res, { colors: rows }); // кольори для легенди
|
|
30
|
+
}
|
|
31
|
+
if (data?.metrics?.length) {
|
|
32
|
+
const metric = data?.metrics[0];
|
|
33
|
+
const q1 = `select PERCENTILE_CONT(0) WITHIN GROUP (ORDER BY "${metric}") as "0",
|
|
37
34
|
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "${metric}") as "25",
|
|
38
35
|
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY "${metric}") as "50",
|
|
39
36
|
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY "${metric}") as "75",
|
|
40
37
|
PERCENTILE_CONT(1) WITHIN GROUP (ORDER BY "${metric}") as "100" from ${data.table} where ${data.query || '1=1'} and ${q || '1=1'}`;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
38
|
+
const sizes = await pg
|
|
39
|
+
.query(q1)
|
|
40
|
+
.then((res1) => Object.values(res1.rows?.[0] || {}));
|
|
41
|
+
Object.assign(res, { sizes }); // розміри для легенди
|
|
42
|
+
}
|
|
43
|
+
const { bbox } = await pg
|
|
44
|
+
.query(
|
|
45
|
+
`select st_asgeojson(box2d(geom))::json as bbox from ${data.table} where ${data.query || '1=1'}`
|
|
46
|
+
)
|
|
47
|
+
.then((res1) => res1.rows?.[0] || {});
|
|
48
|
+
Object.assign(res, {
|
|
49
|
+
layers,
|
|
50
|
+
columns: data.columns,
|
|
51
|
+
bbox, // Map bounds
|
|
52
|
+
|
|
53
|
+
top: [], // 10 найкращих
|
|
54
|
+
bottom: [], // 10 найгірших
|
|
55
|
+
});
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
@@ -3,7 +3,7 @@ import Sphericalmercator from '@mapbox/sphericalmercator';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { writeFile, mkdir } from 'fs/promises';
|
|
6
|
-
import { existsSync, /*readdirSync, */ readFileSync } from 'fs';
|
|
6
|
+
import { existsSync, /* readdirSync, */ readFileSync } from 'fs';
|
|
7
7
|
import { getWidget } from '../../../../utils.js';
|
|
8
8
|
|
|
9
9
|
import yaml from '../../dashboard/controllers/utils/yaml.js';
|
|
@@ -11,7 +11,10 @@ import normalizeData from '../../data/controllers/util/normalizeData.js';
|
|
|
11
11
|
|
|
12
12
|
const mercator = new Sphericalmercator({ size: 256 });
|
|
13
13
|
|
|
14
|
-
const types = {
|
|
14
|
+
const types = {
|
|
15
|
+
point: 'ST_Point' /* ,ST_MultiPoint */,
|
|
16
|
+
polygon: 'ST_Polygon,ST_MultiPolygon',
|
|
17
|
+
};
|
|
15
18
|
|
|
16
19
|
const area = {
|
|
17
20
|
1: 1000000,
|
|
@@ -30,14 +33,19 @@ const area = {
|
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
export default async function vtile(req, reply) {
|
|
36
|
+
const { pg, params = {}, query = {}, funcs = {}, log } = req;
|
|
33
37
|
|
|
34
38
|
const {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
filter,
|
|
40
|
+
widget,
|
|
41
|
+
dashboard,
|
|
42
|
+
sql,
|
|
43
|
+
cluster,
|
|
44
|
+
type,
|
|
45
|
+
nocache,
|
|
46
|
+
geom = 'geom',
|
|
47
|
+
pointZoom = 0,
|
|
48
|
+
} = query;
|
|
41
49
|
|
|
42
50
|
if (!widget) {
|
|
43
51
|
return { message: 'not enough params: widget', status: 400 };
|
|
@@ -50,35 +58,43 @@ export default async function vtile(req, reply) {
|
|
|
50
58
|
return { message: 'not enough params: xyz', status: 400 };
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
const { data } = await getWidget({ widget, dashboard })
|
|
61
|
+
const { data } = await getWidget({ widget, dashboard });
|
|
54
62
|
|
|
55
63
|
const headers = {
|
|
56
64
|
'Content-Type': 'application/x-protobuf',
|
|
57
|
-
'Cache-Control': nocache || sql
|
|
58
|
-
? 'no-cache'
|
|
59
|
-
: 'public, max-age=86400',
|
|
65
|
+
'Cache-Control': nocache || sql ? 'no-cache' : 'public, max-age=86400',
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
const hash = [pointZoom, filter].filter((el) => el).join();
|
|
63
69
|
|
|
64
70
|
const root = funcs.getFolder(req);
|
|
65
|
-
const file = path.join(
|
|
71
|
+
const file = path.join(
|
|
72
|
+
root,
|
|
73
|
+
`/map/vtile/${widget}/${hash ? `${createHash('sha1').update(hash).digest('base64')}/` : ''}${z}/${x}/${y}.mvt`
|
|
74
|
+
);
|
|
66
75
|
|
|
67
76
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
77
|
const pkey = pg.pk?.[data?.table];
|
|
71
78
|
if (!pkey) {
|
|
72
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: table pk not found (${data?.table})`,
|
|
81
|
+
status: 400,
|
|
82
|
+
};
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
// data param
|
|
76
|
-
const {
|
|
77
|
-
|
|
85
|
+
// data param
|
|
86
|
+
const {
|
|
87
|
+
table,
|
|
88
|
+
where = '1=1',
|
|
89
|
+
xName = {},
|
|
90
|
+
metric,
|
|
91
|
+
} = normalizeData(data, query);
|
|
78
92
|
|
|
79
93
|
// get sql
|
|
80
|
-
const columns = data.columns?.map(el => el.name || el)?.join(',') || '1';
|
|
81
|
-
const filterQ = filter
|
|
94
|
+
const columns = data.columns?.map((el) => el.name || el)?.join(',') || '1';
|
|
95
|
+
const filterQ = filter
|
|
96
|
+
? await funcs.getFilterSQL({ pg, table, filter })
|
|
97
|
+
: undefined;
|
|
82
98
|
const q = `select "${pkey}",
|
|
83
99
|
${data?.color ? `"${data?.color}"` : '0'} as x,
|
|
84
100
|
${data.metrics?.[0] ? `"${data.metrics[0]}"::float` : '0'} as metric,
|
|
@@ -88,22 +104,31 @@ export default async function vtile(req, reply) {
|
|
|
88
104
|
|
|
89
105
|
if (sql === '1') return q;
|
|
90
106
|
|
|
91
|
-
const koef =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
const koef =
|
|
108
|
+
{
|
|
109
|
+
10: 0.1,
|
|
110
|
+
11: 0.05,
|
|
111
|
+
12: 0.005,
|
|
112
|
+
13: 0.0002,
|
|
113
|
+
14: 0.00005,
|
|
114
|
+
}[z] || 0.000001;
|
|
115
|
+
|
|
116
|
+
const geomCol =
|
|
117
|
+
parseInt(z, 10) < parseInt(pointZoom, 10) || true
|
|
118
|
+
? `ST_Centroid(${geom})`
|
|
119
|
+
: geom;
|
|
120
|
+
|
|
121
|
+
const bbox = mercator.bbox(+y, +x, +z, false /* , '900913' */);
|
|
100
122
|
const bbox2d = `'BOX(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d`;
|
|
101
123
|
|
|
102
|
-
const areaZoom =
|
|
124
|
+
const areaZoom =
|
|
125
|
+
area[z] && false
|
|
126
|
+
? ` and (st_area(st_transform(${geom},3857)))>'${area[z]}'`
|
|
127
|
+
: '';
|
|
103
128
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
const q1 =
|
|
130
|
+
cluster > z
|
|
131
|
+
? `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
107
132
|
FROM (
|
|
108
133
|
SELECT floor(random() * 100000 + 1)::int + row_number() over() as row, count(*) as point_count,
|
|
109
134
|
ST_AsMVTGeom(st_transform(st_centroid(ST_Union(geom)),3857),ST_TileEnvelope(${z},${y},${x})::box2d,4096,256,false) as geom
|
|
@@ -114,8 +139,7 @@ export default async function vtile(req, reply) {
|
|
|
114
139
|
GROUP BY cluster
|
|
115
140
|
ORDER BY 1 DESC
|
|
116
141
|
)q`
|
|
117
|
-
|
|
118
|
-
: `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
142
|
+
: `SELECT ST_AsMVT(q, 'bi', 4096, 'geom','row') as tile
|
|
119
143
|
FROM (
|
|
120
144
|
SELECT
|
|
121
145
|
floor(random() * 100000 + 1)::int + row_number() over() as row,
|
|
@@ -139,8 +163,6 @@ export default async function vtile(req, reply) {
|
|
|
139
163
|
|
|
140
164
|
if (sql === '2') return q1;
|
|
141
165
|
|
|
142
|
-
|
|
143
|
-
|
|
144
166
|
const { rows = [] } = await pg.query(q1);
|
|
145
167
|
|
|
146
168
|
if (sql === '3') return rows.map((el) => el.tile);
|
|
@@ -153,8 +175,7 @@ export default async function vtile(req, reply) {
|
|
|
153
175
|
}
|
|
154
176
|
|
|
155
177
|
return reply.headers(headers).send(buffer);
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
178
|
+
} catch (err) {
|
|
158
179
|
log.error('bi/vtile', { error: err.toString(), query, params });
|
|
159
180
|
return { error: err.toString(), status: 500 };
|
|
160
181
|
}
|
|
@@ -9,7 +9,7 @@ const biSchema = {
|
|
|
9
9
|
querystring: {
|
|
10
10
|
widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
11
11
|
dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
12
|
-
sql: { type: 'string', pattern: '^([\\d])$' }
|
|
12
|
+
sql: { type: 'string', pattern: '^([\\d])$' },
|
|
13
13
|
},
|
|
14
14
|
params: {
|
|
15
15
|
id: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
@@ -1,56 +1,83 @@
|
|
|
1
1
|
import { getTemplate } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
import pgClients from '@opengis/fastify-table/pg/pgClients.js'
|
|
2
|
+
import pgClients from '@opengis/fastify-table/pg/pgClients.js';
|
|
3
|
+
|
|
3
4
|
const pg = pgClients.client;
|
|
4
5
|
|
|
5
6
|
async function getWidget({ dashboard, widget }) {
|
|
7
|
+
const dashboardData = dashboard
|
|
8
|
+
? await getTemplate('dashboard', dashboard)
|
|
9
|
+
: null;
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const { id, tableName } =
|
|
12
|
+
!dashboardData && pg.pk['bi.dashboard'] && dashboard
|
|
13
|
+
? await pg
|
|
14
|
+
.query(
|
|
15
|
+
`select dashboard_id as id, table_name as "tableName" from bi.dashboard where $1 in (dashboard_id, name)`,
|
|
16
|
+
[dashboard]
|
|
17
|
+
)
|
|
18
|
+
.then((res1) => res1.rows?.[0] || {})
|
|
19
|
+
: {};
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const dashboardIndex = dashboardData?.find(el => el[2] == 'index')?.[1];
|
|
21
|
+
if (!dashboardData && dashboard && !id) {
|
|
22
|
+
return { message: `dashboard not found: ${dashboard}`, status: 404 };
|
|
23
|
+
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!id && !dashboardData && !widgetData) {
|
|
26
|
-
return { message: `not found ${widget} ${dashboard}`, status: 404 };
|
|
27
|
-
}
|
|
25
|
+
dashboardData?.forEach((el) => {
|
|
26
|
+
el[2] = el[0].split('.')[0];
|
|
27
|
+
});
|
|
28
|
+
const dashboardIndex = dashboardData?.find((el) => el[2] == 'index')?.[1];
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
const widgetData = dashboard
|
|
31
|
+
? dashboardData?.find((el) => el[2] === (widget || 'index'))?.[1]
|
|
32
|
+
: await getTemplate('widget', widget);
|
|
33
|
+
if (typeof widgetData === 'string') {
|
|
34
|
+
return { source: widgetData, status: 200 };
|
|
35
|
+
}
|
|
36
|
+
if (!id && !dashboardData && !widgetData) {
|
|
37
|
+
return { message: `not found ${widget} ${dashboard}`, status: 404 };
|
|
38
|
+
}
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
const q = `select *, coalesce(data::jsonb, '{}'::jsonb) || jsonb_build_object('table', table_name) as data from bi.widget where dashboard_id=$1 and name=$2`;
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
const {
|
|
43
|
+
type,
|
|
44
|
+
text,
|
|
45
|
+
data = {},
|
|
46
|
+
controls,
|
|
47
|
+
style,
|
|
48
|
+
options,
|
|
49
|
+
yml
|
|
50
|
+
} = widgetData ||
|
|
51
|
+
(await pg
|
|
52
|
+
.query(q, [id || dashboard, widget])
|
|
53
|
+
.then((res1) => res1.rows?.[0] || {}));
|
|
36
54
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|| widgetData?.table_name
|
|
41
|
-
|| dashboardIndex?.table
|
|
42
|
-
|| dashboardIndex?.table_name,
|
|
43
|
-
db: dashboardIndex?.db || pgClients.client?.options?.database,
|
|
44
|
-
});
|
|
45
|
-
const main = { ...dashboardIndex || {}, ...widgetData, ...data };
|
|
55
|
+
if (!type) {
|
|
56
|
+
return { message: `widget not found: ${widget}`, status: 404 };
|
|
57
|
+
}
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
Object.assign(data, {
|
|
60
|
+
table:
|
|
61
|
+
data.table ||
|
|
62
|
+
tableName ||
|
|
63
|
+
widgetData?.table_name ||
|
|
64
|
+
dashboardIndex?.table ||
|
|
65
|
+
dashboardIndex?.table_name,
|
|
66
|
+
db: dashboardIndex?.db || pgClients.client?.options?.database,
|
|
67
|
+
});
|
|
68
|
+
const main = { ...(dashboardIndex || {}), ...widgetData, ...data };
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
if (!main?.table) {
|
|
71
|
+
return {
|
|
72
|
+
message: /* json.error || */ `invalid ${widget ? 'widget' : 'dashboard'}: 1`,
|
|
73
|
+
status: 404,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
const tableSQL = main?.sql?.map(
|
|
78
|
+
(el, i) => `left join lateral(${el})t${i + 1} on 1=1`
|
|
79
|
+
);
|
|
54
80
|
|
|
81
|
+
return { ...main, tableSQL, data, type, text, controls, style, options, yml };
|
|
55
82
|
}
|
|
56
|
-
export default getWidget;
|
|
83
|
+
export default getWidget;
|
package/utils.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
// import getTemplatePath from '@opengis/fastify-table/table/controllers/utils/getTemplatePath.js';
|
|
5
5
|
import getWidget from './server/utils/getWidget.js';
|
|
6
|
+
import yamlSafe from './server/routes/dashboard/controllers/utils/yaml.js';
|
|
6
7
|
|
|
7
|
-
export default null;
|
|
8
8
|
export {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
// getTemplatePath,
|
|
10
|
+
yamlSafe,
|
|
11
|
+
getWidget,
|
|
12
|
+
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { _ as l, c as h, g as u, a as d, b as p, d as n, i as m } from "./import-file-1T7kpSzt.js";
|
|
2
|
-
import { openBlock as f, createElementBlock as y } from "vue";
|
|
3
|
-
const D = {
|
|
4
|
-
mixins: [h],
|
|
5
|
-
data() {
|
|
6
|
-
return {
|
|
7
|
-
myChartvt: null,
|
|
8
|
-
uniqueID: null
|
|
9
|
-
};
|
|
10
|
-
},
|
|
11
|
-
async mounted() {
|
|
12
|
-
this.uniqueID = u(), await this.$nextTick(), await this.getData();
|
|
13
|
-
const { series: e } = this.prepareData();
|
|
14
|
-
e && this.initChart(e);
|
|
15
|
-
},
|
|
16
|
-
methods: {
|
|
17
|
-
buildTooltipForDonut(e, t) {
|
|
18
|
-
const { name: r, value: a, percent: s } = e;
|
|
19
|
-
return `
|
|
20
|
-
<div style="background-color:${t[0]};font-size: 12px; font-family: Helvetica, Arial, sans-serif;color:#ffff; padding:5px; border-radius:5px; ![box-shadow:none]">
|
|
21
|
-
${d(r)}: ${p(a)} (${s}%)
|
|
22
|
-
</div>`;
|
|
23
|
-
},
|
|
24
|
-
prepareData() {
|
|
25
|
-
var e;
|
|
26
|
-
try {
|
|
27
|
-
const t = Object.keys((e = this.sourceData) == null ? void 0 : e[0]), r = Array.from(new Set(this.sourceData.map((o) => o[t[0]]))), a = Array.from(new Set(this.sourceData.map((o) => o[t[1]]))), s = r.map((o, c) => ({
|
|
28
|
-
name: o,
|
|
29
|
-
value: a[c]
|
|
30
|
-
}));
|
|
31
|
-
return { series: [
|
|
32
|
-
{
|
|
33
|
-
name: this.titleCharts ? this.titleCharts : t[0],
|
|
34
|
-
type: "funnel",
|
|
35
|
-
...n(this.styleData),
|
|
36
|
-
data: s,
|
|
37
|
-
height: "100%",
|
|
38
|
-
emphasis: {
|
|
39
|
-
label: {
|
|
40
|
-
show: !1
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
] };
|
|
45
|
-
} catch (t) {
|
|
46
|
-
console.error(t);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
async initChart(e) {
|
|
50
|
-
try {
|
|
51
|
-
const t = document.getElementById(this.uniqueID), r = m(t), a = {
|
|
52
|
-
series: e,
|
|
53
|
-
...n(this.styleData),
|
|
54
|
-
tooltip: {
|
|
55
|
-
trigger: "item",
|
|
56
|
-
formatter: (s) => this.buildTooltipForDonut(s, [s.color]),
|
|
57
|
-
borderWidth: 0,
|
|
58
|
-
appendToBody: !0,
|
|
59
|
-
borderColor: "transparent",
|
|
60
|
-
textStyle: {
|
|
61
|
-
color: "#000"
|
|
62
|
-
},
|
|
63
|
-
padding: [10, 15],
|
|
64
|
-
shadowColor: "transparent",
|
|
65
|
-
backgroundColor: "transparent"
|
|
66
|
-
},
|
|
67
|
-
itemStyle: {
|
|
68
|
-
height: "15px"
|
|
69
|
-
},
|
|
70
|
-
labelLine: {
|
|
71
|
-
show: !1
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
r.setOption(a), r.resize(), window.addEventListener("resize", () => {
|
|
75
|
-
r.resize();
|
|
76
|
-
});
|
|
77
|
-
} catch (t) {
|
|
78
|
-
console.error(t);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}, b = ["id"];
|
|
83
|
-
function x(e, t, r, a, s, i) {
|
|
84
|
-
return f(), y("div", {
|
|
85
|
-
id: s.uniqueID,
|
|
86
|
-
class: "h-[90%] custom-scrollbar min-h-[200px]"
|
|
87
|
-
}, null, 8, b);
|
|
88
|
-
}
|
|
89
|
-
const v = /* @__PURE__ */ l(D, [["render", x]]);
|
|
90
|
-
export {
|
|
91
|
-
v as default
|
|
92
|
-
};
|