@opengis/bi 1.2.2 → 1.2.4

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-D8jh74Dz.js";
1
+ import { _ as n, c, a as o, b as h, d as l } from "./import-file-MEpI7PGd.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-D8jh74Dz.js";
2
+ import { _ as p } from "./import-file-MEpI7PGd.js";
3
3
  function N(t) {
4
4
  return [
5
5
  {
@@ -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-DeHF_Oaf.js";
2
- import { _ as V, c as N } from "./import-file-D8jh74Dz.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-_1Ub562I.js";
2
+ import { _ as V, c as N } from "./import-file-MEpI7PGd.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-DeHF_Oaf.js";
2
- import { _ as x, V as K, c as W, e as j } from "./import-file-D8jh74Dz.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-_1Ub562I.js";
2
+ import { _ as x, V as K, c as W, e as j } from "./import-file-MEpI7PGd.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 },
@@ -1,4 +1,4 @@
1
- import { _ as c, c as o, f as n } from "./import-file-D8jh74Dz.js";
1
+ import { _ as c, c as o, f as n } from "./import-file-MEpI7PGd.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-D8jh74Dz.js";
1
+ import { _ as m, c as f, b as _, d as b } from "./import-file-MEpI7PGd.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-D8jh74Dz.js";
4
+ import { _ as de, c as xe } from "./import-file-MEpI7PGd.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.2",
3
+ "version": "1.2.4",
4
4
  "description": "BI data visualization module",
5
5
  "main": "dist/bi.js",
6
6
  "browser": "dist/bi.umd.cjs",
@@ -12,6 +12,7 @@
12
12
  "utils.js"
13
13
  ],
14
14
  "scripts": {
15
+ "patch": "npm version patch && git push && npm publish",
15
16
  "prepublishOnly": "npm run build",
16
17
  "debug": "node --watch-path=server server",
17
18
  "dev": "vite",
@@ -75,4 +76,4 @@
75
76
  "typescript-eslint": "~8.8.1",
76
77
  "vue-tsc": "^2.1.6"
77
78
  }
78
- }
79
+ }
@@ -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;
@@ -23,12 +23,12 @@ export default async function dataAPI(req, reply) {
23
23
 
24
24
  const { query = {}, user = {}, unittest } = req;
25
25
 
26
- query.metric = Array.isArray(query.metric) ? query.metric.pop() : query.metric;
27
-
28
26
  const { dashboard, widget, filter, search, samples } = query;
29
27
 
30
28
  const widgetData = await getWidget({ pg: req.pg, dashboard, widget });
31
29
 
30
+ query.metric = Array.isArray(query.metric) ? query.metric.pop() : query.metric || widgetData?.metric || widgetData?.data?.metrics?.[0]?.fx;
31
+
32
32
  if (widgetData.status) return widgetData;
33
33
 
34
34
  const { type, text, data = {}, controls, style, options } = widgetData;
@@ -26,7 +26,13 @@ export default async function route(fastify, opts) {
26
26
  method: 'GET',
27
27
  url: '/bi-data',
28
28
  schema: biSchema,
29
- config: { policy },
29
+ config: {
30
+ policy,
31
+ rateLimit: {
32
+ max: 3000,
33
+ timeWindow: '1 minute',
34
+ }
35
+ },
30
36
  handler: data,
31
37
  });
32
38
  }
@@ -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
+ }