@opengis/bi 1.2.5 → 1.2.7

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.
@@ -1,4 +1,4 @@
1
- import { _ as n, c, a as o, b as h, d as l } from "./import-file-iZU4En2W.js";
1
+ import { _ as n, c, a as o, b as h, d as l } from "./import-file-DnbEvuUN.js";
2
2
  import { createElementBlock as d, openBlock as p } from "vue";
3
3
  const u = {
4
4
  name: "VsFunnelBar",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as l, openBlock as h, createStaticVNode as V, createElementVNode as a, withDirectives as B, toDisplayString as L, vShow as P, Fragment as z, renderList as S, normalizeClass as G, resolveComponent as j, createVNode as D } from "vue";
2
- import { _ as p } from "./import-file-iZU4En2W.js";
2
+ import { _ as p } from "./import-file-DnbEvuUN.js";
3
3
  function N(t) {
4
4
  return [
5
5
  {
@@ -180,9 +180,9 @@ const O = {
180
180
  class: "icon icon-tabler icons-tabler-outline icon-tabler-list-details"
181
181
  };
182
182
  function J(t, e) {
183
- return h(), l("svg", W, [...e[0] || (e[0] = [
183
+ return h(), l("svg", W, e[0] || (e[0] = [
184
184
  V('<path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M13 5h8"></path><path d="M13 9h5"></path><path d="M13 15h8"></path><path d="M13 19h5"></path><path d="M3 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path><path d="M3 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"></path>', 7)
185
- ])]);
185
+ ]));
186
186
  }
187
187
  const U = /* @__PURE__ */ p(R, [["render", J]]), Z = {}, q = {
188
188
  class: "shrink-0 size-3",
@@ -197,10 +197,10 @@ const U = /* @__PURE__ */ p(R, [["render", J]]), Z = {}, q = {
197
197
  "stroke-linejoin": "round"
198
198
  };
199
199
  function A(t, e) {
200
- return h(), l("svg", q, [...e[0] || (e[0] = [
200
+ return h(), l("svg", q, e[0] || (e[0] = [
201
201
  a("path", { d: "M18 6 6 18" }, null, -1),
202
202
  a("path", { d: "m6 6 12 12" }, null, -1)
203
- ])]);
203
+ ]));
204
204
  }
205
205
  const K = /* @__PURE__ */ p(Z, [["render", A]]), zt = {
206
206
  pink: ["#fde7e2", "#feafbe", "#fd58a4", "#d7038e", "#86007b"],
@@ -407,7 +407,7 @@ const ot = {}, nt = {
407
407
  class: "icon icon-tabler icons-tabler-outline icon-tabler-home"
408
408
  };
409
409
  function at(t, e) {
410
- return h(), l("svg", nt, [...e[0] || (e[0] = [
410
+ return h(), l("svg", nt, e[0] || (e[0] = [
411
411
  a("path", {
412
412
  stroke: "none",
413
413
  d: "M0 0h24v24H0z",
@@ -416,7 +416,7 @@ function at(t, e) {
416
416
  a("path", { d: "M5 12l-2 0l9 -9l9 9l-2 0" }, null, -1),
417
417
  a("path", { d: "M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" }, null, -1),
418
418
  a("path", { d: "M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" }, null, -1)
419
- ])]);
419
+ ]));
420
420
  }
421
421
  const ct = /* @__PURE__ */ p(ot, [["render", at]]), lt = {
422
422
  props: ["bbox", "map"],
@@ -450,7 +450,7 @@ const Mt = /* @__PURE__ */ p(lt, [["render", dt]]), ut = {}, pt = {
450
450
  "stroke-linejoin": "#9E9E9E"
451
451
  };
452
452
  function ft(t, e) {
453
- return h(), l("svg", pt, [...e[0] || (e[0] = [
453
+ return h(), l("svg", pt, e[0] || (e[0] = [
454
454
  a("circle", {
455
455
  cx: "12",
456
456
  cy: "12",
@@ -466,7 +466,7 @@ function ft(t, e) {
466
466
  cy: "19",
467
467
  r: "1"
468
468
  }, null, -1)
469
- ])]);
469
+ ]));
470
470
  }
471
471
  const mt = /* @__PURE__ */ p(ut, [["render", ft]]), yt = {
472
472
  components: { legendIcon: U, menuIcon: mt, closeIcon: K },
@@ -1,5 +1,5 @@
1
- import { c as E, l as L, p as $, V as T, a as B, b as H, d as O, e as F } from "./vs-list-BoMIMCT2.js";
2
- import { _ as V, c as N } from "./import-file-iZU4En2W.js";
1
+ import { c as E, l as L, p as $, V as T, a as B, b as H, d as O, e as F } from "./vs-list-_I6Z56hC.js";
2
+ import { _ as V, c as N } from "./import-file-DnbEvuUN.js";
3
3
  import { resolveComponent as u, createElementBlock as p, openBlock as i, Fragment as w, createElementVNode as l, createBlock as C, createCommentVNode as m, createVNode as x, Teleport as P, toDisplayString as f, renderList as S, normalizeStyle as k, normalizeClass as I } from "vue";
4
4
  const R = {
5
5
  components: { legendIcon: L, closeIcon: E },
@@ -1,5 +1,5 @@
1
- import { c as F, l as G, p as T, b as A, V as P, a as Z, d as q, e as D } from "./vs-list-BoMIMCT2.js";
2
- import { _ as x, V as K, c as W, e as j } from "./import-file-iZU4En2W.js";
1
+ import { c as F, l as G, p as T, b as A, V as P, a as Z, d as q, e as D } from "./vs-list-_I6Z56hC.js";
2
+ import { _ as x, V as K, c as W, e as j } from "./import-file-DnbEvuUN.js";
3
3
  import { createElementBlock as p, createCommentVNode as S, openBlock as c, createElementVNode as r, normalizeClass as I, Fragment as N, renderList as R, toDisplayString as O, normalizeStyle as J, createStaticVNode as Q, resolveComponent as g, withDirectives as M, createBlock as k, resolveDynamicComponent as U, createVNode as z, vShow as C } from "vue";
4
4
  const X = {
5
5
  components: { legendIcon: G, closeIcon: F },
@@ -88,7 +88,7 @@ const ot = /* @__PURE__ */ x(X, [["render", st]]), rt = {}, nt = {
88
88
  class: "icon icon-tabler icons-tabler-outline icon-tabler-map"
89
89
  };
90
90
  function at(e, s) {
91
- return c(), p("svg", nt, [...s[0] || (s[0] = [
91
+ return c(), p("svg", nt, s[0] || (s[0] = [
92
92
  r("path", {
93
93
  stroke: "none",
94
94
  d: "M0 0h24v24H0z",
@@ -97,7 +97,7 @@ function at(e, s) {
97
97
  r("path", { d: "M3 7l6 -3l6 3l6 -3v13l-6 3l-6 -3l-6 3v-13" }, null, -1),
98
98
  r("path", { d: "M9 4v13" }, null, -1),
99
99
  r("path", { d: "M15 7v13" }, null, -1)
100
- ])]);
100
+ ]));
101
101
  }
102
102
  const lt = /* @__PURE__ */ x(rt, [["render", at]]), it = {}, ct = {
103
103
  xmlns: "http://www.w3.org/2000/svg",
@@ -112,7 +112,7 @@ const lt = /* @__PURE__ */ x(rt, [["render", at]]), it = {}, ct = {
112
112
  class: "icon icon-tabler icons-tabler-outline icon-tabler-table"
113
113
  };
114
114
  function pt(e, s) {
115
- return c(), p("svg", ct, [...s[0] || (s[0] = [
115
+ return c(), p("svg", ct, s[0] || (s[0] = [
116
116
  r("path", {
117
117
  stroke: "none",
118
118
  d: "M0 0h24v24H0z",
@@ -121,7 +121,7 @@ function pt(e, s) {
121
121
  r("path", { d: "M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z" }, null, -1),
122
122
  r("path", { d: "M3 10h18" }, null, -1),
123
123
  r("path", { d: "M10 3v18" }, null, -1)
124
- ])]);
124
+ ]));
125
125
  }
126
126
  const dt = /* @__PURE__ */ x(it, [["render", pt]]), ht = {}, ut = {
127
127
  xmlns: "http://www.w3.org/2000/svg",
@@ -136,9 +136,9 @@ const dt = /* @__PURE__ */ x(it, [["render", pt]]), ht = {}, ut = {
136
136
  class: "icon icon-tabler icons-tabler-outline icon-tabler-align-box-left-stretch"
137
137
  };
138
138
  function mt(e, s) {
139
- return c(), p("svg", ut, [...s[0] || (s[0] = [
139
+ return c(), p("svg", ut, s[0] || (s[0] = [
140
140
  Q('<path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z"></path><path d="M9 17h-2"></path><path d="M13 12h-6"></path><path d="M11 7h-4"></path>', 5)
141
- ])]);
141
+ ]));
142
142
  }
143
143
  const gt = /* @__PURE__ */ x(ht, [["render", mt]]), ft = {
144
144
  mixins: [W, D],
@@ -1,4 +1,4 @@
1
- import { _ as c, c as o, f as n } from "./import-file-iZU4En2W.js";
1
+ import { _ as c, c as o, f as n } from "./import-file-DnbEvuUN.js";
2
2
  import { createElementBlock as i, openBlock as m, toDisplayString as s } from "vue";
3
3
  const u = {
4
4
  name: "VsNumber",
@@ -1,4 +1,4 @@
1
- import { _ as m, c as f, b as _, d as b } from "./import-file-iZU4En2W.js";
1
+ import { _ as m, c as f, b as _, d as b } from "./import-file-DnbEvuUN.js";
2
2
  import { createElementBlock as a, openBlock as r, createElementVNode as s, Fragment as n, renderList as c, toDisplayString as d } from "vue";
3
3
  const x = {
4
4
  name: "VsTable",
@@ -1,7 +1,7 @@
1
1
  var ge = Object.defineProperty;
2
2
  var ke = (c, e, t) => e in c ? ge(c, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : c[e] = t;
3
3
  var k = (c, e, t) => ke(c, typeof e != "symbol" ? e + "" : e, t);
4
- import { _ as de, c as xe } from "./import-file-iZU4En2W.js";
4
+ import { _ as de, c as xe } from "./import-file-DnbEvuUN.js";
5
5
  import { createElementBlock as V, openBlock as J, createCommentVNode as be } from "vue";
6
6
  function Q() {
7
7
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/bi",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "BI data visualization module",
5
5
  "main": "dist/bi.js",
6
6
  "browser": "dist/bi.umd.cjs",
@@ -42,7 +42,9 @@
42
42
  "author": "Softpro",
43
43
  "license": "ISC",
44
44
  "dependencies": {
45
- "@mapbox/sphericalmercator": "^1.2.0"
45
+ "@mapbox/sphericalmercator": "^1.2.0",
46
+ "@opengis/table": "^0.0.30",
47
+ "lucide-vue-next": "^0.546.0"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@highlightjs/vue-plugin": "github:highlightjs/vue-plugin",
@@ -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,125 +1,125 @@
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 && typeof query.metric === 'string') {
89
- const checkInvalid = query.metric.split(',').find(el => !columnList.includes(el) && el !== 'count');
90
- if (checkInvalid) {
91
- return reply.status(404).send(`invalid query metric value: ${checkInvalid}`);
92
- }
93
- }
94
-
95
- const multipleMetrics = query.metric ? query.metric.split(',').map(el => el === 'count' ? 'count(*)' : `sum(${el.replace(/'/g, "''")})::float as ${el}`).join(',') : null;
96
- const multipleMetricsOrder = query.metric ? query.metric.split(',').map(el => el === 'count' ? 'count(*)' : `sum(${el.replace(/'/g, "''")})::float`).join(',') : null;
97
- const metricFunc = multipleMetrics
98
- || `${clusterTable?.operator || 'sum'}("${metrics[0]}")::float`;
99
-
100
- const q = `select b.*, ${metricFunc} ${multipleMetrics ? '' : 'as metric'}
101
- from ${optimizedSQL ? `(${optimizedSQL})` : table} q
102
- 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
103
- where ${where} group by b.id, b.name, b.title order by ${multipleMetricsOrder || metricFunc} desc`;
104
-
105
- if (query.sql === '1') return q;
106
-
107
- // auto Index
108
- // autoIndex({ table, columns: (metrics || []).concat([cluster]) });
109
-
110
- const { rows = [] } = await pg.query(q);
111
- const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
112
- const len = vals.length;
113
- const sizes = [
114
- vals[0],
115
- vals[Math.floor(len / 4)],
116
- vals[Math.floor(len / 2)],
117
- vals[Math.floor(len * 0.75)],
118
- vals[len - 1],
119
- ];
120
- 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) };
121
- } catch (err) {
122
- logger.file('bi/cluster/error', { error: err.toString(), query });
123
- return { error: err.toString(), status: 500 };
124
- }
125
- }
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 && typeof query.metric === 'string') {
89
+ const checkInvalid = query.metric.split(',').find(el => !columnList.includes(el) && el !== 'count');
90
+ if (checkInvalid) {
91
+ return reply.status(404).send(`invalid query metric value: ${checkInvalid}`);
92
+ }
93
+ }
94
+
95
+ const multipleMetrics = query.metric ? query.metric.split(',').map(el => el === 'count' ? 'count(*)' : `sum(${el.replace(/'/g, "''")})::float as ${el}`).join(',') : null;
96
+ const multipleMetricsOrder = query.metric ? query.metric.split(',').map(el => el === 'count' ? 'count(*)' : `sum(${el.replace(/'/g, "''")})::float`).join(',') : null;
97
+ const metricFunc = multipleMetrics
98
+ || `${clusterTable?.operator || 'sum'}("${metrics[0]}")::float`;
99
+
100
+ const q = `select b.*, ${metricFunc} ${multipleMetrics ? '' : 'as metric'}
101
+ from ${optimizedSQL ? `(${optimizedSQL})` : table} q
102
+ 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
103
+ where ${where} group by b.id, b.name, b.title order by ${multipleMetricsOrder || metricFunc} desc`;
104
+
105
+ if (query.sql === '1') return q;
106
+
107
+ // auto Index
108
+ // autoIndex({ table, columns: (metrics || []).concat([cluster]) });
109
+
110
+ const { rows = [] } = await pg.query(q);
111
+ const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
112
+ const len = vals.length;
113
+ const sizes = [
114
+ vals[0],
115
+ vals[Math.floor(len / 4)],
116
+ vals[Math.floor(len / 2)],
117
+ vals[Math.floor(len * 0.75)],
118
+ vals[len - 1],
119
+ ];
120
+ 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) };
121
+ } catch (err) {
122
+ logger.file('bi/cluster/error', { error: err.toString(), query });
123
+ return { error: err.toString(), status: 500 };
124
+ }
125
+ }