@opengis/bi 1.0.44 → 1.0.45
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/config.js +12 -12
- package/dist/bi.js +1 -1
- package/dist/bi.umd.cjs +122 -109
- package/dist/{import-file-XtDaxS3X.js → import-file-n7AnE4rc.js} +10097 -10044
- package/dist/style.css +1 -1
- package/dist/{vs-donut-DsuDQZ3C.js → vs-donut-Bc9gBhHN.js} +7 -7
- package/dist/{vs-funnel-bar-CQAgEcLe.js → vs-funnel-bar-BQLFsh4u.js} +5 -5
- package/dist/{vs-list-C-gFvDvr.js → vs-list-D1NHJa-8.js} +1535 -1532
- package/dist/{vs-map-uUiqdNLp.js → vs-map-CUF5ZJo4.js} +78 -76
- package/dist/{vs-map-cluster-B9kG91Ge.js → vs-map-cluster-ZPcu4qhm.js} +51 -49
- package/dist/{vs-number-BuOWTFVp.js → vs-number-Z4ee8Kwo.js} +3 -3
- package/dist/{vs-table-BG9nb7R0.js → vs-table-BrnSztWP.js} +6 -6
- package/dist/{vs-text-BEb3W2ua.js → vs-text-Bj9iBISe.js} +4 -4
- package/package.json +1 -1
- package/server/plugins/docs.js +48 -48
- package/server/plugins/vite.js +69 -69
- package/server/routes/dashboard/controllers/utils/yaml.js +11 -11
- package/server/routes/map/controllers/cluster.js +121 -121
- package/server/routes/map/controllers/clusterVtile.js +166 -166
- package/server/routes/map/controllers/geojson.js +127 -127
- package/server/routes/map/controllers/map.js +67 -67
- package/server/routes/map/controllers/utils/downloadClusterData.js +44 -44
- package/server/routes/map/controllers/vtile.js +183 -183
- package/utils.js +12 -12
package/server/plugins/docs.js
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import path, { dirname } from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
|
|
5
|
-
const dir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const root = `${dir}/../../`;
|
|
7
|
-
|
|
8
|
-
async function plugin(fastify, opts) {
|
|
9
|
-
fastify.get('/docs-dev*', async (req, reply) => {
|
|
10
|
-
if (!fs.existsSync(path.join(root, 'docs-dev/.vitepress/dist/'))) {
|
|
11
|
-
return reply.status(404).send('docs not exists');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const { params } = req;
|
|
15
|
-
const url = params['*'];
|
|
16
|
-
|
|
17
|
-
const filePath =
|
|
18
|
-
url && url[url.length - 1] !== '/'
|
|
19
|
-
? path.join(root, 'docs-dev/.vitepress/dist/', url)
|
|
20
|
-
: path.join(root, 'docs-dev/.vitepress/dist/', url, 'index.html');
|
|
21
|
-
|
|
22
|
-
if (!fs.existsSync(filePath)) {
|
|
23
|
-
return reply.status(404).send('File not found');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const ext = path.extname(filePath);
|
|
27
|
-
const mime = {
|
|
28
|
-
'.js': 'text/javascript',
|
|
29
|
-
'.css': 'text/css',
|
|
30
|
-
'.woff2': 'application/font-woff',
|
|
31
|
-
'.png': 'image/png',
|
|
32
|
-
'.svg': 'image/svg+xml',
|
|
33
|
-
'.jpg': 'image/jpg',
|
|
34
|
-
'.html': 'text/html',
|
|
35
|
-
'.json': 'application/json',
|
|
36
|
-
'.pdf': 'application/pdf',
|
|
37
|
-
}[ext];
|
|
38
|
-
|
|
39
|
-
const stream = fs.createReadStream(filePath);
|
|
40
|
-
stream.on('error', (err) => {
|
|
41
|
-
reply.status(500).send('Error reading file');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return mime ? reply.type(mime).send(stream) : reply.send(stream);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export default plugin;
|
|
1
|
+
import path, { dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const root = `${dir}/../../`;
|
|
7
|
+
|
|
8
|
+
async function plugin(fastify, opts) {
|
|
9
|
+
fastify.get('/docs-dev*', async (req, reply) => {
|
|
10
|
+
if (!fs.existsSync(path.join(root, 'docs-dev/.vitepress/dist/'))) {
|
|
11
|
+
return reply.status(404).send('docs not exists');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { params } = req;
|
|
15
|
+
const url = params['*'];
|
|
16
|
+
|
|
17
|
+
const filePath =
|
|
18
|
+
url && url[url.length - 1] !== '/'
|
|
19
|
+
? path.join(root, 'docs-dev/.vitepress/dist/', url)
|
|
20
|
+
: path.join(root, 'docs-dev/.vitepress/dist/', url, 'index.html');
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(filePath)) {
|
|
23
|
+
return reply.status(404).send('File not found');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ext = path.extname(filePath);
|
|
27
|
+
const mime = {
|
|
28
|
+
'.js': 'text/javascript',
|
|
29
|
+
'.css': 'text/css',
|
|
30
|
+
'.woff2': 'application/font-woff',
|
|
31
|
+
'.png': 'image/png',
|
|
32
|
+
'.svg': 'image/svg+xml',
|
|
33
|
+
'.jpg': 'image/jpg',
|
|
34
|
+
'.html': 'text/html',
|
|
35
|
+
'.json': 'application/json',
|
|
36
|
+
'.pdf': 'application/pdf',
|
|
37
|
+
}[ext];
|
|
38
|
+
|
|
39
|
+
const stream = fs.createReadStream(filePath);
|
|
40
|
+
stream.on('error', (err) => {
|
|
41
|
+
reply.status(500).send('Error reading file');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return mime ? reply.type(mime).send(stream) : reply.send(stream);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default plugin;
|
package/server/plugins/vite.js
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import config from '../../config.js';
|
|
4
|
-
|
|
5
|
-
const { disableAuth } = config;
|
|
6
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
7
|
-
|
|
8
|
-
async function plugin(fastify) {
|
|
9
|
-
// vite server
|
|
10
|
-
if (!isProduction) {
|
|
11
|
-
const vite = await import('vite');
|
|
12
|
-
|
|
13
|
-
const viteServer = await vite.createServer({
|
|
14
|
-
server: {
|
|
15
|
-
middlewareMode: true,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
// hot reload
|
|
19
|
-
viteServer.watcher.on('all', (d, t) => {
|
|
20
|
-
if (!t.includes('module') && !t.includes('templates')) return;
|
|
21
|
-
// console.log(d, t);
|
|
22
|
-
viteServer.ws.send({ type: 'full-reload' });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// this is middleware for vite's dev servert
|
|
26
|
-
fastify.addHook('onRequest', async (req, reply) => {
|
|
27
|
-
// const { user } = req.session?.passport || {};
|
|
28
|
-
const next = () => new Promise((resolve) => {
|
|
29
|
-
viteServer.middlewares(req.raw, reply.raw, () => resolve());
|
|
30
|
-
});
|
|
31
|
-
await next();
|
|
32
|
-
});
|
|
33
|
-
fastify.get('*', async () => {});
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// From Build
|
|
38
|
-
fastify.get('*', async (req, reply) => {
|
|
39
|
-
// console.log(disableAuth)
|
|
40
|
-
if (!req.user && !disableAuth) return reply.redirect('/login');
|
|
41
|
-
const stream = fs.createReadStream('dist/index.html');
|
|
42
|
-
return reply
|
|
43
|
-
.headers({ 'Cache-Control': 'public, no-cache' })
|
|
44
|
-
.type('text/html')
|
|
45
|
-
.send(stream);
|
|
46
|
-
});
|
|
47
|
-
fastify.get('/assets/:file', async (req, reply) => {
|
|
48
|
-
const stream = fs.createReadStream(`dist/assets/${req.params.file}`);
|
|
49
|
-
const ext = path.extname(req.params.file);
|
|
50
|
-
const mime = {
|
|
51
|
-
'.js': 'text/javascript',
|
|
52
|
-
'.css': 'text/css',
|
|
53
|
-
'.woff2': 'application/font-woff',
|
|
54
|
-
'.png': 'image/png',
|
|
55
|
-
}[ext];
|
|
56
|
-
// reply.cacheControl('max-age', '1d');
|
|
57
|
-
return mime
|
|
58
|
-
? reply
|
|
59
|
-
.headers({
|
|
60
|
-
'Cache-Control': 'public, max-age=3600',
|
|
61
|
-
'Content-Encoding': 'identity',
|
|
62
|
-
})
|
|
63
|
-
.type(mime)
|
|
64
|
-
.send(stream)
|
|
65
|
-
: stream;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default plugin;
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import config from '../../config.js';
|
|
4
|
+
|
|
5
|
+
const { disableAuth } = config;
|
|
6
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
7
|
+
|
|
8
|
+
async function plugin(fastify) {
|
|
9
|
+
// vite server
|
|
10
|
+
if (!isProduction) {
|
|
11
|
+
const vite = await import('vite');
|
|
12
|
+
|
|
13
|
+
const viteServer = await vite.createServer({
|
|
14
|
+
server: {
|
|
15
|
+
middlewareMode: true,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
// hot reload
|
|
19
|
+
viteServer.watcher.on('all', (d, t) => {
|
|
20
|
+
if (!t.includes('module') && !t.includes('templates')) return;
|
|
21
|
+
// console.log(d, t);
|
|
22
|
+
viteServer.ws.send({ type: 'full-reload' });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// this is middleware for vite's dev servert
|
|
26
|
+
fastify.addHook('onRequest', async (req, reply) => {
|
|
27
|
+
// const { user } = req.session?.passport || {};
|
|
28
|
+
const next = () => new Promise((resolve) => {
|
|
29
|
+
viteServer.middlewares(req.raw, reply.raw, () => resolve());
|
|
30
|
+
});
|
|
31
|
+
await next();
|
|
32
|
+
});
|
|
33
|
+
fastify.get('*', async () => {});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// From Build
|
|
38
|
+
fastify.get('*', async (req, reply) => {
|
|
39
|
+
// console.log(disableAuth)
|
|
40
|
+
if (!req.user && !disableAuth) return reply.redirect('/login');
|
|
41
|
+
const stream = fs.createReadStream('dist/index.html');
|
|
42
|
+
return reply
|
|
43
|
+
.headers({ 'Cache-Control': 'public, no-cache' })
|
|
44
|
+
.type('text/html')
|
|
45
|
+
.send(stream);
|
|
46
|
+
});
|
|
47
|
+
fastify.get('/assets/:file', async (req, reply) => {
|
|
48
|
+
const stream = fs.createReadStream(`dist/assets/${req.params.file}`);
|
|
49
|
+
const ext = path.extname(req.params.file);
|
|
50
|
+
const mime = {
|
|
51
|
+
'.js': 'text/javascript',
|
|
52
|
+
'.css': 'text/css',
|
|
53
|
+
'.woff2': 'application/font-woff',
|
|
54
|
+
'.png': 'image/png',
|
|
55
|
+
}[ext];
|
|
56
|
+
// reply.cacheControl('max-age', '1d');
|
|
57
|
+
return mime
|
|
58
|
+
? reply
|
|
59
|
+
.headers({
|
|
60
|
+
'Cache-Control': 'public, max-age=3600',
|
|
61
|
+
'Content-Encoding': 'identity',
|
|
62
|
+
})
|
|
63
|
+
.type(mime)
|
|
64
|
+
.send(stream)
|
|
65
|
+
: stream;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default plugin;
|
|
@@ -1,11 +1,11 @@
|
|
|
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
|
+
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,121 +1,121 @@
|
|
|
1
|
-
import { getFilterSQL, logger, pgClients, getMeta } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
import { getWidget } from '../../../../utils.js';
|
|
4
|
-
|
|
5
|
-
import downloadClusterData from './utils/downloadClusterData.js';
|
|
6
|
-
|
|
7
|
-
const clusterExists = {};
|
|
8
|
-
|
|
9
|
-
export default async function cluster(req, reply) {
|
|
10
|
-
const { query = {} } = req;
|
|
11
|
-
const { widget, filter, dashboard, search } = query;
|
|
12
|
-
|
|
13
|
-
if (!widget) {
|
|
14
|
-
return { message: 'not enough params: widget', status: 400 };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const { pg = req.pg || pgClients.client, data, style, controls } = await getWidget({ pg: req.pg, dashboard, widget });
|
|
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
|
-
}
|
|
27
|
-
|
|
28
|
-
// data param
|
|
29
|
-
const {
|
|
30
|
-
table,
|
|
31
|
-
query: where = '1=1',
|
|
32
|
-
metrics = [],
|
|
33
|
-
cluster,
|
|
34
|
-
clusterTable = {},
|
|
35
|
-
} = data;
|
|
36
|
-
|
|
37
|
-
if (!cluster) {
|
|
38
|
-
return {
|
|
39
|
-
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
40
|
-
status: 400,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!metrics.length) {
|
|
45
|
-
return {
|
|
46
|
-
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
47
|
-
status: 400,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!clusterTable?.name) {
|
|
52
|
-
Object.assign(clusterTable, {
|
|
53
|
-
name: 'bi.cluster',
|
|
54
|
-
title: 'title',
|
|
55
|
-
query: `type='${cluster}'`,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
if (cluster && !clusterExists[cluster]) {
|
|
61
|
-
const res = await downloadClusterData({ pg, cluster });
|
|
62
|
-
if (res) return res;
|
|
63
|
-
clusterExists[cluster] = 1;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (clusterTable?.name && !pg.pk?.[clusterTable?.name]) {
|
|
67
|
-
return {
|
|
68
|
-
message: 'invalid widget params: clusterTable pkey not found',
|
|
69
|
-
status: 404,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { bounds, extentStr } = await pg.query(`select count(*),
|
|
74
|
-
st_asgeojson(st_extent(geom))::json as bounds,
|
|
75
|
-
replace(regexp_replace(st_extent(geom)::box2d::text,'BOX\\(|\\)','','g'),' ',',') as "extentStr"
|
|
76
|
-
from ${table} where ${where || '1=1'}`).then((res) => res.rows?.[0] || {});
|
|
77
|
-
const extent = extentStr ? extentStr.split(',') : undefined;
|
|
78
|
-
|
|
79
|
-
// get sql
|
|
80
|
-
const { optimizedSQL } =
|
|
81
|
-
filter || search
|
|
82
|
-
? await getFilterSQL({ pg, table, filter, search })
|
|
83
|
-
: {};
|
|
84
|
-
|
|
85
|
-
const { columns = [] } = await getMeta({ pg, table });
|
|
86
|
-
const columnList = columns.map(el => el.name);
|
|
87
|
-
|
|
88
|
-
if (query.metric && query.metric !== 'count' && !columnList.includes(query.metric)) {
|
|
89
|
-
return reply.status(404).send(`metric column not found: ${query.metric}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const metricFunc = (columnList.includes(query.metric) ? `sum(${query.metric.replace(/'/g, "''")})::float` : null)
|
|
93
|
-
|| ({ count: 'count(*)' }[query.metric])
|
|
94
|
-
|| `${clusterTable?.operator || 'sum'}("${metrics[0]}")::float`;
|
|
95
|
-
|
|
96
|
-
const q = `select b.*, ${metricFunc} as metric
|
|
97
|
-
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
98
|
-
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id, ${clusterTable?.column || cluster} as name, ${clusterTable?.title} as title from ${clusterTable?.name} where ${clusterTable?.codifierColumn || 'codifier'}=q."${clusterTable?.column || cluster}" limit 1)b on 1=1
|
|
99
|
-
where ${where} group by b.id, b.name, b.title order by ${metricFunc} desc`;
|
|
100
|
-
|
|
101
|
-
if (query.sql === '1') return q;
|
|
102
|
-
|
|
103
|
-
// auto Index
|
|
104
|
-
// autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
105
|
-
|
|
106
|
-
const { rows = [] } = await pg.query(q);
|
|
107
|
-
const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
|
|
108
|
-
const len = vals.length;
|
|
109
|
-
const sizes = [
|
|
110
|
-
vals[0],
|
|
111
|
-
vals[Math.floor(len / 4)],
|
|
112
|
-
vals[Math.floor(len / 2)],
|
|
113
|
-
vals[Math.floor(len * 0.75)],
|
|
114
|
-
vals[len - 1],
|
|
115
|
-
];
|
|
116
|
-
return { sizes, style, controls, rows, bounds, extent, count: rows.length, total: rows?.reduce((acc, curr) => (curr.metric || 0) + acc, 0) };
|
|
117
|
-
} catch (err) {
|
|
118
|
-
logger.file('bi/cluster/error', { error: err.toString(), query });
|
|
119
|
-
return { error: err.toString(), status: 500 };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
1
|
+
import { getFilterSQL, logger, pgClients, getMeta } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import { getWidget } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
import downloadClusterData from './utils/downloadClusterData.js';
|
|
6
|
+
|
|
7
|
+
const clusterExists = {};
|
|
8
|
+
|
|
9
|
+
export default async function cluster(req, reply) {
|
|
10
|
+
const { query = {} } = req;
|
|
11
|
+
const { widget, filter, dashboard, search } = query;
|
|
12
|
+
|
|
13
|
+
if (!widget) {
|
|
14
|
+
return { message: 'not enough params: widget', status: 400 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { pg = req.pg || pgClients.client, data, style, controls } = await getWidget({ pg: req.pg, dashboard, widget });
|
|
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
|
+
}
|
|
27
|
+
|
|
28
|
+
// data param
|
|
29
|
+
const {
|
|
30
|
+
table,
|
|
31
|
+
query: where = '1=1',
|
|
32
|
+
metrics = [],
|
|
33
|
+
cluster,
|
|
34
|
+
clusterTable = {},
|
|
35
|
+
} = data;
|
|
36
|
+
|
|
37
|
+
if (!cluster) {
|
|
38
|
+
return {
|
|
39
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: cluster column not specified`,
|
|
40
|
+
status: 400,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!metrics.length) {
|
|
45
|
+
return {
|
|
46
|
+
message: `invalid ${widget ? 'widget' : 'dashboard'}: metric columns not found`,
|
|
47
|
+
status: 400,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!clusterTable?.name) {
|
|
52
|
+
Object.assign(clusterTable, {
|
|
53
|
+
name: 'bi.cluster',
|
|
54
|
+
title: 'title',
|
|
55
|
+
query: `type='${cluster}'`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (cluster && !clusterExists[cluster]) {
|
|
61
|
+
const res = await downloadClusterData({ pg, cluster });
|
|
62
|
+
if (res) return res;
|
|
63
|
+
clusterExists[cluster] = 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (clusterTable?.name && !pg.pk?.[clusterTable?.name]) {
|
|
67
|
+
return {
|
|
68
|
+
message: 'invalid widget params: clusterTable pkey not found',
|
|
69
|
+
status: 404,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { bounds, extentStr } = await pg.query(`select count(*),
|
|
74
|
+
st_asgeojson(st_extent(geom))::json as bounds,
|
|
75
|
+
replace(regexp_replace(st_extent(geom)::box2d::text,'BOX\\(|\\)','','g'),' ',',') as "extentStr"
|
|
76
|
+
from ${table} where ${where || '1=1'}`).then((res) => res.rows?.[0] || {});
|
|
77
|
+
const extent = extentStr ? extentStr.split(',') : undefined;
|
|
78
|
+
|
|
79
|
+
// get sql
|
|
80
|
+
const { optimizedSQL } =
|
|
81
|
+
filter || search
|
|
82
|
+
? await getFilterSQL({ pg, table, filter, search })
|
|
83
|
+
: {};
|
|
84
|
+
|
|
85
|
+
const { columns = [] } = await getMeta({ pg, table });
|
|
86
|
+
const columnList = columns.map(el => el.name);
|
|
87
|
+
|
|
88
|
+
if (query.metric && query.metric !== 'count' && !columnList.includes(query.metric)) {
|
|
89
|
+
return reply.status(404).send(`metric column not found: ${query.metric}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const metricFunc = (columnList.includes(query.metric) ? `sum(${query.metric.replace(/'/g, "''")})::float` : null)
|
|
93
|
+
|| ({ count: 'count(*)' }[query.metric])
|
|
94
|
+
|| `${clusterTable?.operator || 'sum'}("${metrics[0]}")::float`;
|
|
95
|
+
|
|
96
|
+
const q = `select b.*, ${metricFunc} as metric
|
|
97
|
+
from ${optimizedSQL ? `(${optimizedSQL})` : table} q
|
|
98
|
+
left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id, ${clusterTable?.column || cluster} as name, ${clusterTable?.title} as title from ${clusterTable?.name} where ${clusterTable?.codifierColumn || 'codifier'}=q."${clusterTable?.column || cluster}" limit 1)b on 1=1
|
|
99
|
+
where ${where} group by b.id, b.name, b.title order by ${metricFunc} desc`;
|
|
100
|
+
|
|
101
|
+
if (query.sql === '1') return q;
|
|
102
|
+
|
|
103
|
+
// auto Index
|
|
104
|
+
// autoIndex({ table, columns: (metrics || []).concat([cluster]) });
|
|
105
|
+
|
|
106
|
+
const { rows = [] } = await pg.query(q);
|
|
107
|
+
const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
|
|
108
|
+
const len = vals.length;
|
|
109
|
+
const sizes = [
|
|
110
|
+
vals[0],
|
|
111
|
+
vals[Math.floor(len / 4)],
|
|
112
|
+
vals[Math.floor(len / 2)],
|
|
113
|
+
vals[Math.floor(len * 0.75)],
|
|
114
|
+
vals[len - 1],
|
|
115
|
+
];
|
|
116
|
+
return { sizes, style, controls, metrics, rows, columns: columns.map(({ name, title, dataTypeID }) => ({ name, title, type: pg.pgType[dataTypeID] })), bounds, extent, count: rows.length, total: rows?.reduce((acc, curr) => (curr.metric || 0) + acc, 0) };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
logger.file('bi/cluster/error', { error: err.toString(), query });
|
|
119
|
+
return { error: err.toString(), status: 500 };
|
|
120
|
+
}
|
|
121
|
+
}
|