@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
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//import autoIndex from '@opengis/fastify-table/pg/funcs/autoIndex.js';
|
|
1
|
+
// import autoIndex from '@opengis/fastify-table/pg/funcs/autoIndex.js';
|
|
4
2
|
// import pgClients from '@opengis/fastify-table/pg/pgClients.js';
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getPGAsync,
|
|
7
|
+
autoIndex,
|
|
8
|
+
pgClients,
|
|
9
|
+
getSelectVal,
|
|
10
|
+
} from '@opengis/fastify-table/utils.js';
|
|
7
11
|
import chartSQL from './util/chartSQL.js';
|
|
8
12
|
import normalizeData from './util/normalizeData.js';
|
|
9
13
|
|
|
10
14
|
import { getWidget } from '../../../../utils.js';
|
|
11
15
|
|
|
12
|
-
export default async function
|
|
13
|
-
funcs = {}, query = {},
|
|
14
|
-
}) {
|
|
16
|
+
export default async function dataAPI({ funcs = {}, query = {} }) {
|
|
15
17
|
const time = Date.now();
|
|
16
18
|
const { dashboard, widget, filter, search, samples } = query;
|
|
17
19
|
|
|
@@ -21,63 +23,119 @@ export default async function data({
|
|
|
21
23
|
|
|
22
24
|
const { type, text, data = {}, controls, style, options } = widgetData;
|
|
23
25
|
|
|
24
|
-
const pg = data.db ? await getPGAsync(data.db) : pgClients.client
|
|
26
|
+
const pg = data.db ? await getPGAsync(data.db) : pgClients.client;
|
|
25
27
|
|
|
26
|
-
const { fields: cols } = await pg.query(
|
|
27
|
-
|
|
28
|
+
const { fields: cols } = await pg.query(
|
|
29
|
+
`select * from ${data.table} limit 0`
|
|
30
|
+
);
|
|
31
|
+
const columnTypes = cols.map((el) => ({
|
|
32
|
+
name: el.name,
|
|
33
|
+
type: pg.pgType?.[el.dataTypeID],
|
|
34
|
+
}));
|
|
28
35
|
|
|
29
|
-
// data param
|
|
30
|
-
const { x, cls, metric, table, where, tableSQL, groupby, xName } =
|
|
36
|
+
// data param
|
|
37
|
+
const { x, cls, metric, table, where, tableSQL, groupby, xName } =
|
|
38
|
+
normalizeData(widgetData, query, columnTypes);
|
|
31
39
|
|
|
32
40
|
// auto Index
|
|
33
41
|
if (pg.pk?.[data.table]) {
|
|
34
|
-
autoIndex({
|
|
42
|
+
autoIndex({
|
|
43
|
+
table: data.table,
|
|
44
|
+
pg,
|
|
45
|
+
columns: [data?.time]
|
|
46
|
+
.concat([xName])
|
|
47
|
+
.concat([groupby])
|
|
48
|
+
.filter((el) => el),
|
|
49
|
+
}).catch((err) => console.log(err));
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
// get group
|
|
38
|
-
const groupData = groupby
|
|
52
|
+
// get group
|
|
53
|
+
const groupData = groupby
|
|
54
|
+
? await pg
|
|
55
|
+
.query(
|
|
56
|
+
`select ${groupby} as name ,count(*) from ${tableSQL || table} group by ${groupby} order by count(*) desc limit 20`
|
|
57
|
+
)
|
|
58
|
+
.then((el) => el.rows)
|
|
59
|
+
: null;
|
|
39
60
|
|
|
40
61
|
if (query.sql === '2') return { x, metric, table, tableSQL, data, groupData };
|
|
41
62
|
|
|
42
63
|
const order = data.order || (type === 'listbar' ? 'metric desc' : null);
|
|
43
64
|
|
|
44
|
-
const { optimizedSQL = `select * from ${tableSQL || table}` } =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
const { optimizedSQL = `select * from ${tableSQL || table}` } =
|
|
66
|
+
filter || search
|
|
67
|
+
? await funcs.getFilterSQL({
|
|
68
|
+
pg,
|
|
69
|
+
table,
|
|
70
|
+
filter,
|
|
71
|
+
search,
|
|
72
|
+
})
|
|
73
|
+
: {};
|
|
74
|
+
|
|
75
|
+
const sql = (chartSQL[type] || chartSQL.chart)({
|
|
76
|
+
where,
|
|
77
|
+
metric,
|
|
78
|
+
table: `(${optimizedSQL})q`,
|
|
79
|
+
x,
|
|
80
|
+
groupData,
|
|
81
|
+
groupby,
|
|
82
|
+
order,
|
|
83
|
+
samples,
|
|
84
|
+
});
|
|
49
85
|
|
|
50
86
|
if (query.sql) return sql;
|
|
51
87
|
|
|
52
88
|
if (!sql || sql?.includes('undefined')) {
|
|
53
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
message: {
|
|
91
|
+
error: 'invalid sql',
|
|
92
|
+
type,
|
|
93
|
+
sql,
|
|
94
|
+
where,
|
|
95
|
+
metric,
|
|
96
|
+
table: `(${optimizedSQL})q`,
|
|
97
|
+
x,
|
|
98
|
+
groupData,
|
|
99
|
+
groupby,
|
|
100
|
+
},
|
|
101
|
+
status: 500,
|
|
102
|
+
};
|
|
54
103
|
}
|
|
55
104
|
|
|
56
105
|
const { rows, fields } = await pg.query(sql); // test with limit
|
|
57
106
|
|
|
58
107
|
if (cls) {
|
|
59
|
-
const values = rows
|
|
108
|
+
const values = rows
|
|
109
|
+
.map((row) => row[x])
|
|
110
|
+
?.filter((el, idx, arr) => el && arr.indexOf(el) === idx);
|
|
60
111
|
const vals = await getSelectVal({ pg, name: cls, values });
|
|
61
|
-
rows
|
|
62
|
-
|
|
63
|
-
|
|
112
|
+
rows
|
|
113
|
+
.filter((row) => row[x])
|
|
114
|
+
.forEach((row) => {
|
|
115
|
+
Object.assign(row, { [x]: vals?.[row[x]] || row[x] });
|
|
116
|
+
});
|
|
64
117
|
}
|
|
65
118
|
|
|
66
|
-
const
|
|
119
|
+
const yml = widgetData.yml || yaml.dump(extractYml(widgetData));
|
|
120
|
+
const dimensions = fields.map((el) => el.name);
|
|
67
121
|
|
|
68
122
|
const res = {
|
|
69
123
|
time: Date.now() - time,
|
|
70
124
|
dimensions,
|
|
71
125
|
|
|
72
|
-
dimensionsType: fields.map(el => pg.pgType?.[el.dataTypeID]),
|
|
126
|
+
dimensionsType: fields.map((el) => pg.pgType?.[el.dataTypeID]),
|
|
73
127
|
type,
|
|
74
128
|
|
|
75
|
-
text: text
|
|
76
|
-
//data: query.format === 'data' ? dimensions.map(el => rows.map(r => r[el])) : undefined,
|
|
77
|
-
source:
|
|
129
|
+
text: text || widgetData?.title || data.text,
|
|
130
|
+
// data: query.format === 'data' ? dimensions.map(el => rows.map(r => r[el])) : undefined,
|
|
131
|
+
source:
|
|
132
|
+
query.format === 'array'
|
|
133
|
+
? dimensions.map((el) => rows.map((r) => r[el]))
|
|
134
|
+
: rows,
|
|
78
135
|
style,
|
|
79
136
|
options,
|
|
80
137
|
controls,
|
|
138
|
+
yml,
|
|
81
139
|
params: {
|
|
82
140
|
x,
|
|
83
141
|
cls,
|
|
@@ -91,7 +149,9 @@ export default async function data({
|
|
|
91
149
|
columnTypes,
|
|
92
150
|
};
|
|
93
151
|
return res;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
152
|
}
|
|
153
|
+
|
|
154
|
+
function extractYml(sourceData) {
|
|
155
|
+
const { title, description, type, data, style, controls } = sourceData;
|
|
156
|
+
return { title, description, type, data, style, controls };
|
|
157
|
+
}
|
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
function number({ metric, where, table, samples }) {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const sql = `select ${metric} from ${table} where ${where} ${samples ? 'limit 10' : ''}`;
|
|
3
|
+
return sql;
|
|
4
4
|
}
|
|
5
5
|
function table({ columns, table, where, samples }) {
|
|
6
|
-
|
|
6
|
+
return `select ${columns.map((el) => el.name || el)}::text from ${table} where ${where} ${samples ? 'limit 10' : 'limit 20'} `;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function chart({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
function chart({
|
|
10
|
+
metric,
|
|
11
|
+
where,
|
|
12
|
+
table,
|
|
13
|
+
x,
|
|
14
|
+
groupby,
|
|
15
|
+
groupData,
|
|
16
|
+
order,
|
|
17
|
+
samples,
|
|
18
|
+
}) {
|
|
19
|
+
const metricData =
|
|
20
|
+
groupData
|
|
21
|
+
?.map(
|
|
22
|
+
(el) =>
|
|
23
|
+
`${metric} filter (where ${groupby}='${el.name}') as "${el.name}"`
|
|
24
|
+
)
|
|
25
|
+
.join(',') || `${metric} as metric`;
|
|
26
|
+
const sql = `select ${x}, ${metricData}
|
|
13
27
|
from ${table}
|
|
14
28
|
where ${where}
|
|
15
29
|
group by ${x}
|
|
16
30
|
order by ${order || x}
|
|
17
31
|
${samples ? 'limit 10' : 'limit 100'}`;
|
|
18
|
-
|
|
32
|
+
return sql;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
function text() {
|
|
22
|
-
|
|
36
|
+
return undefined;
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
export default { number, chart
|
|
39
|
+
export default { number, chart };
|
|
@@ -1,34 +1,56 @@
|
|
|
1
1
|
function normalizeData(data, query = {}, columnTypes = []) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
['x', 'groupby', 'granularity'].forEach((el) => {
|
|
3
|
+
// console.log(el, query[el], columnTypes.find(col => col.name == query[el]))
|
|
4
|
+
if (!columnTypes.find((col) => col.name == query[el])) {
|
|
5
|
+
delete query[el];
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
if (
|
|
10
|
+
!columnTypes.find(
|
|
11
|
+
(col) => col.type === 'numeric' && col.name == query.metric
|
|
12
|
+
)
|
|
13
|
+
) {
|
|
14
|
+
delete query.metric;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const xName = query.x || (Array.isArray(data.x) ? data.x[0] : data.x);
|
|
18
|
+
const xTYpe = columnTypes.find((el) => el.name == xName)?.type;
|
|
19
|
+
|
|
20
|
+
const granularity =
|
|
21
|
+
xTYpe === 'date' || xTYpe?.includes('timestamp')
|
|
22
|
+
? query.granularity || data.granularity || 'year'
|
|
23
|
+
: null;
|
|
24
|
+
|
|
25
|
+
const x =
|
|
26
|
+
(granularity
|
|
27
|
+
? `date_trunc('${granularity}',${xName})::date::text`
|
|
28
|
+
: null) || xName;
|
|
29
|
+
|
|
30
|
+
const metrics = Array.isArray(data.metrics) ? data.metrics : [data.metrics];
|
|
31
|
+
const metric =
|
|
32
|
+
(query.metric ? `sum(${query.metric})` : null) ||
|
|
33
|
+
(metrics.length
|
|
34
|
+
? metrics
|
|
35
|
+
?.filter((el) => el)
|
|
36
|
+
.map((el) => el.fx || `${el.operator || 'sum'}(${el.name || el})`)
|
|
37
|
+
: 'count(*)');
|
|
38
|
+
|
|
39
|
+
const { cls, table, filterCustom } = data;
|
|
40
|
+
const groupby = query.groupby || data.groupby;
|
|
41
|
+
// const orderby = query.orderby || data.orderby || 'count(*)';
|
|
42
|
+
|
|
43
|
+
const custom = query?.filterCustom
|
|
44
|
+
?.split(',')
|
|
24
45
|
?.map((el) => filterCustom?.find((item) => item?.name === el)?.sql)
|
|
25
|
-
?.filter((el) => el)
|
|
26
|
-
|
|
46
|
+
?.filter((el) => el)
|
|
47
|
+
?.join(' and ');
|
|
48
|
+
const where = `${data.query || '1=1'} and ${custom || 'true'}`;
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
const tableSQL = data.tableSQL?.length
|
|
51
|
+
? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
|
|
52
|
+
: undefined;
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
return { x, cls, metric, table, where, tableSQL, groupby, xName };
|
|
33
55
|
}
|
|
34
|
-
export default normalizeData;
|
|
56
|
+
export default normalizeData;
|
|
@@ -2,19 +2,17 @@ import config from '../../../config.js';
|
|
|
2
2
|
|
|
3
3
|
import data from './controllers/data.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
const biSchema = {
|
|
7
6
|
querystring: {
|
|
8
7
|
widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
9
8
|
dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
10
|
-
sql: { type: 'string', pattern: '^([\\d])$' }
|
|
9
|
+
sql: { type: 'string', pattern: '^([\\d])$' },
|
|
11
10
|
},
|
|
12
11
|
params: {
|
|
13
12
|
id: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
14
13
|
},
|
|
15
14
|
};
|
|
16
15
|
|
|
17
|
-
|
|
18
16
|
export default async function route(fastify, opts) {
|
|
19
17
|
const prefix = opts?.prefix || config.prefix || '/api';
|
|
20
18
|
fastify.route({
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const q = `select nspname||'.'||relname as table, json_agg(json_build_object('name',attname, 'type', a.atttypid::regtype, 'description', coalesce(col_description(attrelid, attnum),attname))) as columns
|
|
2
|
+
from pg_attribute a
|
|
3
|
+
left join pg_catalog.pg_attrdef d ON (a.attrelid, a.attnum) = (d.adrelid, d.adnum)
|
|
4
|
+
JOIN pg_class AS i
|
|
5
|
+
ON i.oid = a.attrelid
|
|
6
|
+
JOIN pg_namespace AS NS ON i.relnamespace = NS.OID
|
|
7
|
+
where a.attnum > 0 and nspname||'.'||relname = $1
|
|
8
|
+
and not a.attisdropped
|
|
9
|
+
group by nspname||'.'||relname limit 1`;
|
|
10
|
+
|
|
11
|
+
export default async function dbTablePreview({ pg, params = {}, query = {} }) {
|
|
12
|
+
if (!params?.name) {
|
|
13
|
+
return { message: 'not enough params: name', status: 400 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (query.sql) return q;
|
|
17
|
+
try {
|
|
18
|
+
const { table, columns } = await pg
|
|
19
|
+
.query(q, [params.name])
|
|
20
|
+
.then((res) => res.rows?.[0] || {});
|
|
21
|
+
if (!table) {
|
|
22
|
+
return { message: 'table not found', status: 404 };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { count = 0 } = await pg
|
|
26
|
+
.query(
|
|
27
|
+
`select reltuples as count from pg_class where oid = to_regclass($1)`,
|
|
28
|
+
[params.name]
|
|
29
|
+
)
|
|
30
|
+
.then((res) => res.rows?.[0] || {});
|
|
31
|
+
const geom = columns.find((el) => el.type === 'geometry')?.name;
|
|
32
|
+
const { bounds, extentStr } = geom
|
|
33
|
+
? await pg
|
|
34
|
+
.query(
|
|
35
|
+
`select count(*),
|
|
36
|
+
st_asgeojson(st_extent(${geom}))::json as bounds,
|
|
37
|
+
replace(regexp_replace(st_extent(${geom})::box2d::text,'BOX\\(|\\)','','g'),' ',',') as "extentStr"
|
|
38
|
+
from ${params.name}`
|
|
39
|
+
)
|
|
40
|
+
.then((res) => res.rows?.[0] || {})
|
|
41
|
+
: {};
|
|
42
|
+
const extent = extentStr ? extentStr.split(',') : undefined;
|
|
43
|
+
|
|
44
|
+
const systemColumns = [
|
|
45
|
+
'uid',
|
|
46
|
+
'files',
|
|
47
|
+
'editor_date',
|
|
48
|
+
'cdate',
|
|
49
|
+
'editor_id',
|
|
50
|
+
geom,
|
|
51
|
+
];
|
|
52
|
+
const columnList = columns
|
|
53
|
+
.map((el) => el?.name)
|
|
54
|
+
.filter((el) => !systemColumns.includes(el));
|
|
55
|
+
const { rows = [] } = await pg.query(
|
|
56
|
+
`select ${columnList.join(',')} ${geom ? `, st_asgeojson(geom)::json as geom` : ''} from ${table} limit 10`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return { count, geom: !!geom, bounds, extent, columns, rows };
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { error: err.toString(), status: 500 };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default async function dbTables({ pg, query = {} }) {
|
|
2
|
+
const q = `select
|
|
3
|
+
t.table_schema ||'.'|| t.table_name as table,
|
|
4
|
+
obj_description(to_regclass(t.table_schema ||'."'|| t.table_name||'"')) as description,
|
|
5
|
+
t.table_schema as schema,
|
|
6
|
+
(select reltuples from pg_class where oid = to_regclass(t.table_schema ||'."'|| t.table_name||'"') ) as total,
|
|
7
|
+
coalesce(isgeom,false) as isgeom
|
|
8
|
+
|
|
9
|
+
from information_schema.tables t
|
|
10
|
+
left join lateral(
|
|
11
|
+
select true as isgeom from information_schema.columns c
|
|
12
|
+
where c.table_name = t.table_name
|
|
13
|
+
and c.table_schema = t.table_schema and 'geometry'=c.udt_name limit 1
|
|
14
|
+
)c on 1=1
|
|
15
|
+
|
|
16
|
+
where t.table_type = 'BASE TABLE'
|
|
17
|
+
and t.table_schema not in ('public','log','admin','feature_ir','gis', 'setting')
|
|
18
|
+
and t.table_name not like '%.%'
|
|
19
|
+
and regexp_replace(t.table_name, '^[[:digit:]]', '', 'g') = t.table_name
|
|
20
|
+
and 1=(SELECT count(*) FROM pg_catalog.pg_constraint con
|
|
21
|
+
INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
|
|
22
|
+
INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
|
|
23
|
+
WHERE nsp.nspname = t.table_schema AND rel.relname = t.table_name and contype='p'
|
|
24
|
+
)
|
|
25
|
+
and ${query.schema ? `t.table_schema=$1` : '1=1'} order by total desc`;
|
|
26
|
+
if (query.sql) return q;
|
|
27
|
+
try {
|
|
28
|
+
const { rows = [] } = await pg.query(
|
|
29
|
+
q,
|
|
30
|
+
[query.schema].filter((el) => el)
|
|
31
|
+
);
|
|
32
|
+
return { rows };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return { error: err.toString(), status: 500 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import dbTables from './controllers/dbTables.js';
|
|
2
|
+
import dbTablePreview from './controllers/dbTablePreview.js';
|
|
3
|
+
|
|
4
|
+
export default async function route(fastify, opts) {
|
|
5
|
+
fastify.route({
|
|
6
|
+
method: 'GET',
|
|
7
|
+
url: '/db-tables',
|
|
8
|
+
schema: {},
|
|
9
|
+
handler: dbTables,
|
|
10
|
+
});
|
|
11
|
+
fastify.route({
|
|
12
|
+
method: 'GET',
|
|
13
|
+
url: '/db-tables/:name',
|
|
14
|
+
schema: {},
|
|
15
|
+
handler: dbTablePreview,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -2,20 +2,21 @@ export default async function widgetAdd({ pg, funcs, params = {}, body }) {
|
|
|
2
2
|
try {
|
|
3
3
|
const time = Date.now();
|
|
4
4
|
const tableName = body.table_name;
|
|
5
|
-
const checkTable = await pg.query(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const checkTable = await pg.query(
|
|
6
|
+
`select * from bi.dashboard where $1 in (table_name)`,
|
|
7
|
+
[tableName]
|
|
8
|
+
);
|
|
8
9
|
if (!checkTable.rows.length) return { message: 'bad params', status: 401 };
|
|
9
10
|
const res = await funcs.dataInsert({
|
|
10
11
|
table: 'bi.dashboard',
|
|
11
|
-
data: body
|
|
12
|
+
data: body,
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
return {
|
|
15
16
|
time: Date.now() - time,
|
|
16
17
|
message: `Added new dashboard, ID: '${res.rows[0].title}'`,
|
|
17
18
|
status: 200,
|
|
18
|
-
rows: res.rows
|
|
19
|
+
rows: res.rows,
|
|
19
20
|
};
|
|
20
21
|
} catch (err) {
|
|
21
22
|
return { error: err.toString(), status: 500 };
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
export default async function dashboardEdit(
|
|
2
|
-
pg, funcs, params = {}, body = {},
|
|
3
|
-
|
|
1
|
+
export default async function dashboardEdit(
|
|
2
|
+
{ pg, funcs, params = {}, body = {} },
|
|
3
|
+
reply
|
|
4
|
+
) {
|
|
4
5
|
try {
|
|
5
6
|
if (!params.name) {
|
|
6
7
|
return {
|
|
7
|
-
message:
|
|
8
|
+
message: 'not enough params: dashboard name required',
|
|
8
9
|
status: 400,
|
|
9
|
-
}
|
|
10
|
+
};
|
|
10
11
|
}
|
|
11
12
|
const tableName = body.table_name;
|
|
12
|
-
const checkTable = await pg.query(
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const checkTable = await pg.query(
|
|
14
|
+
`select * from bi.dashboard where $1 in (table_name)`,
|
|
15
|
+
[tableName]
|
|
16
|
+
);
|
|
15
17
|
if (!checkTable.rows.length) return { message: 'bad params', status: 401 };
|
|
16
18
|
const { name: dashboardName } = params;
|
|
17
|
-
const row = await pg
|
|
19
|
+
const row = await pg
|
|
20
|
+
.query(
|
|
21
|
+
`select dashboard_id from bi.dashboard where $1 in (dashboard_id, name)`,
|
|
22
|
+
[dashboardName]
|
|
23
|
+
)
|
|
24
|
+
.then((res1) => res1.rows?.[0] || {});
|
|
18
25
|
const { dashboard_id: dashboardId } = row;
|
|
19
26
|
|
|
20
27
|
const res = await funcs.dataUpdate({
|
|
@@ -1,49 +1,73 @@
|
|
|
1
|
+
/* eslint-disable import/extensions */
|
|
2
|
+
import { yamlSafe } from '../../../../utils.js';
|
|
3
|
+
|
|
4
|
+
function generateUniqueName(prefix = 'bar') {
|
|
5
|
+
const randomPart = Math.floor(Math.random() * 10000);
|
|
6
|
+
const timestamp = Date.now();
|
|
7
|
+
return `${prefix}_${randomPart}_${timestamp}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
export default async function widgetAdd({ pg, funcs, params = {}, body = {} }) {
|
|
2
11
|
const { name: dashboardName } = params;
|
|
3
12
|
if (!dashboardName) {
|
|
4
13
|
return { message: 'not enough params: id', status: 400 };
|
|
5
14
|
}
|
|
6
|
-
const
|
|
15
|
+
const data = body.yml ? yamlSafe.load(body.yml) : body;
|
|
7
16
|
try {
|
|
8
17
|
const row = await pg
|
|
9
18
|
.query(
|
|
10
|
-
|
|
19
|
+
'select dashboard_id, widgets, panels, table_name from bi.dashboard where $1 in (dashboard_id,name)',
|
|
11
20
|
[dashboardName]
|
|
12
21
|
)
|
|
13
22
|
.then((res) => res.rows?.[0] || {});
|
|
14
23
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
const tableName =
|
|
25
|
+
data.data?.table || data?.table || data.table_name || row.table_name;
|
|
26
|
+
|
|
27
|
+
// const checkTable = await pg.query(
|
|
28
|
+
// 'select * from bi.widget where $1 in (table_name)',
|
|
29
|
+
// [tableName]
|
|
30
|
+
// );
|
|
31
|
+
// if (!checkTable.rows.length) {
|
|
32
|
+
// return { message: 'bad params', status: 400 };
|
|
33
|
+
// }
|
|
34
|
+
if (!tableName || !pg.pk?.[tableName]) {
|
|
35
|
+
return { message: 'bad params: table', status: 400 };
|
|
36
|
+
}
|
|
19
37
|
|
|
20
38
|
const { dashboard_id: dashboardId } = row;
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
Object.assign(data, {
|
|
41
|
+
name: generateUniqueName(data.type),
|
|
42
|
+
table_name: tableName,
|
|
43
|
+
metrics:
|
|
44
|
+
(data.data?.metrics || data?.metrics)?.[0] ||
|
|
45
|
+
data.data?.metrics ||
|
|
46
|
+
data?.metrics,
|
|
47
|
+
});
|
|
48
|
+
|
|
24
49
|
const res = await funcs.dataUpdate({
|
|
25
50
|
table: 'bi.dashboard',
|
|
26
51
|
id: dashboardId,
|
|
27
52
|
data: {
|
|
28
|
-
widgets: [
|
|
29
|
-
panels: [{ widget:
|
|
30
|
-
|
|
53
|
+
widgets: [data].concat(row.widgets || []),
|
|
54
|
+
panels: [{ widget: data.name, col: data.col || 3 }].concat(
|
|
55
|
+
row.panels || []
|
|
56
|
+
),
|
|
57
|
+
},
|
|
31
58
|
});
|
|
32
|
-
const
|
|
59
|
+
const widgetData = { ...data, data, dashboard_id: dashboardId };
|
|
60
|
+
if (body?.yml) Object.assign(widgetData, { yml: body.yml });
|
|
61
|
+
await funcs.dataInsert({
|
|
33
62
|
table: 'bi.widget',
|
|
34
|
-
data:
|
|
63
|
+
data: widgetData,
|
|
35
64
|
});
|
|
36
65
|
return {
|
|
37
66
|
message: `Added widget to ${dashboardName}`,
|
|
38
67
|
status: 200,
|
|
39
|
-
rows: res
|
|
68
|
+
rows: res,
|
|
40
69
|
};
|
|
41
70
|
} catch (err) {
|
|
42
71
|
return { error: err.toString(), status: 500 };
|
|
43
72
|
}
|
|
44
73
|
}
|
|
45
|
-
function generateUniqueName(prefix = 'bar') {
|
|
46
|
-
const randomPart = Math.floor(Math.random() * 10000);
|
|
47
|
-
const timestamp = Date.now();
|
|
48
|
-
return `${prefix}_${randomPart}_${timestamp}`;
|
|
49
|
-
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import dataDelete from '@opengis/fastify-table/crud/funcs/dataDelete.js';
|
|
2
2
|
import dataUpdate from '@opengis/fastify-table/crud/funcs/dataUpdate.js';
|
|
3
|
+
|
|
3
4
|
export default async function widgetDel({ pg = {}, params = {} }) {
|
|
4
5
|
const { widget: widgetName, name: dashboardName } = params;
|
|
5
6
|
|
|
6
7
|
if (!widgetName || !dashboardName) {
|
|
7
8
|
return {
|
|
8
9
|
message: 'not enough params: dashboard and widget name',
|
|
9
|
-
status: 400
|
|
10
|
+
status: 400,
|
|
10
11
|
};
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -27,7 +28,7 @@ export default async function widgetDel({ pg = {}, params = {} }) {
|
|
|
27
28
|
|
|
28
29
|
const res = await dataDelete({
|
|
29
30
|
table: 'bi.widget',
|
|
30
|
-
id: widgetId
|
|
31
|
+
id: widgetId,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
const currentDashboard = await pg
|
|
@@ -43,19 +44,25 @@ export default async function widgetDel({ pg = {}, params = {} }) {
|
|
|
43
44
|
if (!currentDashboard) {
|
|
44
45
|
return { message: `dashboard not found ${dashboardName}`, status: 404 };
|
|
45
46
|
}
|
|
46
|
-
body.panels =
|
|
47
|
-
|
|
47
|
+
body.panels =
|
|
48
|
+
Array.isArray(body.panels) && body.panels?.length
|
|
49
|
+
? body.panels?.filter((panel) => panel.widget !== widgetName)
|
|
50
|
+
: undefined;
|
|
51
|
+
body.widgets =
|
|
52
|
+
Array.isArray(body.widgets) && body?.widgets?.length
|
|
53
|
+
? body.widgets?.filter((widget) => widget.name !== widgetName)
|
|
54
|
+
: undefined;
|
|
48
55
|
|
|
49
56
|
const res1 = await dataUpdate({
|
|
50
57
|
table: 'bi.dashboard',
|
|
51
58
|
id: dashboardId,
|
|
52
|
-
data: body
|
|
59
|
+
data: body,
|
|
53
60
|
});
|
|
54
61
|
|
|
55
62
|
return {
|
|
56
63
|
message: `Deleted widget ${widgetName}`,
|
|
57
64
|
status: 200,
|
|
58
|
-
rows: res1
|
|
65
|
+
rows: res1,
|
|
59
66
|
};
|
|
60
67
|
} catch (err) {
|
|
61
68
|
return { error: err.toString(), status: 500 };
|