@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.
Files changed (50) hide show
  1. package/README.md +2 -4
  2. package/config.js +5 -5
  3. package/dist/bi.js +1 -1
  4. package/dist/bi.umd.cjs +116 -130
  5. package/dist/{import-file-1T7kpSzt.js → import-file-DUp3rsNI.js} +11132 -10748
  6. package/dist/{map-component-mixin-BLM9iEWA.js → map-component-mixin-CGM0P5ub.js} +1135 -1134
  7. package/dist/style.css +1 -1
  8. package/dist/{vs-calendar-WiK1hcHS.js → vs-calendar-cOoinEwc.js} +33 -30
  9. package/dist/vs-funnel-bar-kLkPoIhJ.js +105 -0
  10. package/dist/vs-heatmap-3XAVGTSo.js +98 -0
  11. package/dist/vs-map-B1tr6V5_.js +74 -0
  12. package/dist/{vs-map-cluster-Dfe9INqE.js → vs-map-cluster-BWJPx7wE.js} +28 -25
  13. package/dist/vs-number-CrU7LmkV.js +48 -0
  14. package/dist/{vs-text-DcrAdQ40.js → vs-text-DRPx3aID.js} +2 -1
  15. package/package.json +37 -12
  16. package/plugin.js +4 -4
  17. package/server/migrations/bi.sql +66 -0
  18. package/server/plugins/docs.js +36 -35
  19. package/server/plugins/hook.js +72 -69
  20. package/server/plugins/vite.js +22 -8
  21. package/server/routes/dashboard/controllers/dashboard.delete.js +5 -3
  22. package/server/routes/dashboard/controllers/dashboard.js +66 -32
  23. package/server/routes/dashboard/controllers/dashboard.list.js +2 -5
  24. package/server/routes/dashboard/controllers/utils/yaml.js +1 -2
  25. package/server/routes/dashboard/index.mjs +5 -4
  26. package/server/routes/data/controllers/data.js +94 -34
  27. package/server/routes/data/controllers/util/chartSQL.js +24 -10
  28. package/server/routes/data/controllers/util/normalizeData.js +51 -29
  29. package/server/routes/data/index.mjs +1 -3
  30. package/server/routes/db/controllers/dbTablePreview.js +63 -0
  31. package/server/routes/db/controllers/dbTables.js +36 -0
  32. package/server/routes/db/index.mjs +17 -0
  33. package/server/routes/edit/controllers/dashboard.add.js +6 -5
  34. package/server/routes/edit/controllers/dashboard.edit.js +16 -9
  35. package/server/routes/edit/controllers/widget.add.js +43 -19
  36. package/server/routes/edit/controllers/widget.del.js +13 -6
  37. package/server/routes/edit/controllers/widget.edit.js +34 -13
  38. package/server/routes/edit/index.mjs +14 -10
  39. package/server/routes/map/controllers/cluster.js +89 -60
  40. package/server/routes/map/controllers/clusterVtile.js +154 -84
  41. package/server/routes/map/controllers/geojson.js +48 -22
  42. package/server/routes/map/controllers/map.js +51 -51
  43. package/server/routes/map/controllers/vtile.js +61 -40
  44. package/server/routes/map/index.mjs +1 -1
  45. package/server/utils/getWidget.js +67 -40
  46. package/utils.js +5 -4
  47. package/dist/vs-funnel-bar-CpPbYZ0_.js +0 -92
  48. package/dist/vs-heatmap-BG4eIROH.js +0 -83
  49. package/dist/vs-map-BRk6Fmks.js +0 -66
  50. 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
- import { getPGAsync, autoIndex, pgClients, getSelectVal } from '@opengis/fastify-table/utils.js';
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 data({
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(`select * from ${data.table} limit 0`);
27
- const columnTypes = cols.map(el => ({ name: el.name, type: pg.pgType?.[el.dataTypeID] }))
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 } = normalizeData(widgetData, query, columnTypes);
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({ table: data.table, pg, columns: [data?.time].concat([xName]).concat([groupby]).filter(el => el) }).catch(err => console.log(err))
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 ? await pg.query(`select ${groupby} as name ,count(*) from ${tableSQL || table} group by ${groupby} order by count(*) desc limit 20`).then(el => el.rows) : null;
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}` } = filter || search ? await funcs.getFilterSQL({
45
- pg, table, filter, search,
46
- }) : {};
47
-
48
- const sql = (chartSQL[type] || chartSQL['chart'])({ where, metric, table: `(${optimizedSQL})q`, x, groupData, groupby, order, samples });
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 { message: { error: 'invalid sql', type, sql, where, metric, table: `(${optimizedSQL})q`, x, groupData, groupby }, status: 500 };
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.map((row) => row[x])?.filter((el, idx, arr) => el && arr.indexOf(el) === idx);
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.filter((row) => row[x]).forEach((row) => {
62
- Object.assign(row, { [x]: vals?.[row[x]] || row[x] });
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 dimensions = fields.map(el => el.name);
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 ? text : (widgetData?.title || data.text),
76
- //data: query.format === 'data' ? dimensions.map(el => rows.map(r => r[el])) : undefined,
77
- source: query.format === 'array' ? dimensions.map(el => rows.map(r => r[el])) : rows,
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
- const sql = `select ${metric} from ${table} where ${where} ${samples ? 'limit 10' : ''}`
3
- return sql;
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
- return `select ${columns.map(el => el.name || el)}::text from ${table} where ${where} ${samples ? 'limit 10' : 'limit 20'} `
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({ metric, where, table, x, groupby, groupData, order, samples }) {
10
-
11
- const metricData = groupData?.map(el => `${metric} filter (where ${groupby}='${el.name}') as "${el.name}"`).join(',') || `${metric} as metric`
12
- const sql = `select ${x}, ${metricData}
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
- return sql;
32
+ return sql;
19
33
  }
20
34
 
21
35
  function text() {
22
- return undefined;
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
- ['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])) { delete query[el]; }
5
- });
6
-
7
- if (!columnTypes.find(col => col.type === 'numeric' && col.name == query.metric)) { delete query.metric; }
8
-
9
- const xName = query.x || (Array.isArray(data.x) ? data.x[0] : data.x);
10
- const xTYpe = columnTypes.find(el => el.name == xName)?.type;
11
-
12
- const granularity = xTYpe === 'date' || xTYpe?.includes('timestamp') ? (query.granularity || data.granularity || 'year') : null;
13
-
14
- const x = (granularity ? `date_trunc('${granularity}',${xName})::date::text` : null) || xName;
15
-
16
- const metrics = Array.isArray(data.metrics) ? data.metrics : [data.metrics];
17
- const metric = (query.metric ? `sum(${query.metric})` : null) || (metrics.length ? metrics?.filter(el => el).map(el => el.fx || `${el.operator || 'sum'}(${el.name || el})`) : 'count(*)');
18
-
19
- const { cls, table, filterCustom } = data;
20
- const groupby = query.groupby || data.groupby;
21
- //const orderby = query.orderby || data.orderby || 'count(*)';
22
-
23
- const custom = query?.filterCustom?.split(',')
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)?.join(' and ');
26
- const where = `${data.query || '1=1'} and ${custom || 'true'}`;
46
+ ?.filter((el) => el)
47
+ ?.join(' and ');
48
+ const where = `${data.query || '1=1'} and ${custom || 'true'}`;
27
49
 
28
- const tableSQL = data.tableSQL?.length
29
- ? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
30
- : undefined;
50
+ const tableSQL = data.tableSQL?.length
51
+ ? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
52
+ : undefined;
31
53
 
32
- return { x, cls, metric, table, where, tableSQL, groupby, xName }
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(`select * from bi.dashboard where $1 in (table_name)`, [
6
- tableName
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
- }, reply) {
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: "not enough params: dashboard name required",
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(`select * from bi.dashboard where $1 in (table_name)`, [
13
- tableName
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.query(`select dashboard_id from bi.dashboard where $1 in (dashboard_id, name)`, [dashboardName]).then((res1) => res1.rows?.[0] || {});
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 tableName = body.table_name;
15
+ const data = body.yml ? yamlSafe.load(body.yml) : body;
7
16
  try {
8
17
  const row = await pg
9
18
  .query(
10
- `select dashboard_id, widgets, panels from bi.dashboard where $1 in (dashboard_id,name)`,
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 checkTable = await pg.query(`select * from bi.widget where $1 in (table_name)`, [
16
- tableName
17
- ]);
18
- if (!checkTable.rows.length) return { message: 'bad params', status: 401 };
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
- const generatedName = generateUniqueName(body.type);
23
- body.name = generatedName;
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: [body].concat(row.widgets || []),
29
- panels: [{ widget: body.name, col: body.col }].concat(row.panels || [])
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 res2 = await funcs.dataInsert({
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: { ...body, data: body, dashboard_id: dashboardId }
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 = Array.isArray(body.panels) && body.panels?.length ? body.panels?.filter((panel) => panel.widget !== widgetName) : undefined;
47
- body.widgets = Array.isArray(body.widgets) && body?.widgets?.length ? body.widgets?.filter((widget) => widget.name !== widgetName) : undefined;
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 };