@opengis/bi 1.0.14 → 1.0.16

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 (38) hide show
  1. package/dist/bi.js +1 -1
  2. package/dist/bi.umd.cjs +58 -58
  3. package/dist/{import-file-DUp3rsNI.js → import-file-BqdbrKVj.js} +6310 -6238
  4. package/dist/{map-component-mixin-CGM0P5ub.js → map-component-mixin-BeW3TYyl.js} +2 -2
  5. package/dist/{vs-calendar-cOoinEwc.js → vs-calendar-CnosX1Ss.js} +20 -9
  6. package/dist/{vs-funnel-bar-kLkPoIhJ.js → vs-funnel-bar-CcXr5oIQ.js} +2 -2
  7. package/dist/{vs-heatmap-3XAVGTSo.js → vs-heatmap-DvOx7wot.js} +3 -4
  8. package/dist/{vs-map-B1tr6V5_.js → vs-map-C2KEv_W6.js} +2 -2
  9. package/dist/{vs-map-cluster-BWJPx7wE.js → vs-map-cluster-DJpEG5n8.js} +2 -2
  10. package/dist/{vs-number-CrU7LmkV.js → vs-number-C23hqXxA.js} +19 -12
  11. package/dist/{vs-text-DRPx3aID.js → vs-text-COR-T0yn.js} +18 -13
  12. package/package.json +18 -8
  13. package/plugin.js +1 -0
  14. package/server/migrations/bi.dataset.sql +26 -0
  15. package/server/routes/dashboard/controllers/dashboard.delete.js +2 -1
  16. package/server/routes/dashboard/controllers/dashboard.js +29 -25
  17. package/server/routes/dashboard/controllers/dashboard.list.js +3 -9
  18. package/server/routes/data/controllers/data.js +21 -10
  19. package/server/routes/data/controllers/util/chartSQL.js +6 -3
  20. package/server/routes/data/controllers/util/normalizeData.js +9 -6
  21. package/server/routes/data/index.mjs +7 -2
  22. package/server/routes/dataset/controllers/bi.dataset.demo.add.js +97 -0
  23. package/server/routes/dataset/controllers/bi.dataset.import.js +67 -0
  24. package/server/routes/dataset/controllers/util/create.table.js +22 -0
  25. package/server/routes/dataset/controllers/util/prepare.data.js +49 -0
  26. package/server/routes/dataset/index.mjs +19 -0
  27. package/server/routes/edit/controllers/dashboard.add.js +4 -2
  28. package/server/routes/edit/controllers/dashboard.edit.js +4 -2
  29. package/server/routes/edit/controllers/widget.add.js +5 -3
  30. package/server/routes/edit/controllers/widget.del.js +1 -2
  31. package/server/routes/edit/controllers/widget.edit.js +27 -78
  32. package/server/routes/map/controllers/cluster.js +27 -22
  33. package/server/routes/map/controllers/clusterVtile.js +11 -58
  34. package/server/routes/map/controllers/geojson.js +8 -8
  35. package/server/routes/map/controllers/map.js +12 -9
  36. package/server/routes/map/controllers/utils/downloadClusterData.js +43 -0
  37. package/server/routes/map/controllers/vtile.js +6 -5
  38. package/server/utils/getWidget.js +5 -3
@@ -15,7 +15,10 @@ function chart({
15
15
  groupData,
16
16
  order,
17
17
  samples,
18
+ xType,
18
19
  }) {
20
+ const xCol = x && xType?.includes('[]') ? `unnest(${x})` : x;
21
+
19
22
  const metricData =
20
23
  groupData
21
24
  ?.map(
@@ -23,11 +26,11 @@ function chart({
23
26
  `${metric} filter (where ${groupby}='${el.name}') as "${el.name}"`
24
27
  )
25
28
  .join(',') || `${metric} as metric`;
26
- const sql = `select ${x}, ${metricData}
29
+ const sql = `select ${xCol}, ${metricData}
27
30
  from ${table}
28
31
  where ${where}
29
- group by ${x}
30
- order by ${order || x}
32
+ ${xCol ? `group by ${xCol}` : ''}
33
+ ${order || xCol ? `order by ${order || xCol}` : ''}
31
34
  ${samples ? 'limit 10' : 'limit 100'}`;
32
35
  return sql;
33
36
  }
@@ -15,10 +15,10 @@ function normalizeData(data, query = {}, columnTypes = []) {
15
15
  }
16
16
 
17
17
  const xName = query.x || (Array.isArray(data.x) ? data.x[0] : data.x);
18
- const xTYpe = columnTypes.find((el) => el.name == xName)?.type;
18
+ const xType = columnTypes.find((el) => el.name == xName)?.type;
19
19
 
20
20
  const granularity =
21
- xTYpe === 'date' || xTYpe?.includes('timestamp')
21
+ xType === 'date' || xType?.includes('timestamp')
22
22
  ? query.granularity || data.granularity || 'year'
23
23
  : null;
24
24
 
@@ -31,10 +31,13 @@ function normalizeData(data, query = {}, columnTypes = []) {
31
31
  const metric =
32
32
  (query.metric ? `sum(${query.metric})` : null) ||
33
33
  (metrics.length
34
- ? metrics
35
- ?.filter((el) => el)
36
- .map((el) => el.fx || `${el.operator || 'sum'}(${el.name || el})`)
34
+ ? (metrics
35
+ ?.filter((el) => el && columnTypes.find((col) => col.name == (el?.name || el)))
36
+ ?.map((el) => el.fx || `${el.operator || 'sum'}(${el.name || el})`)?.join(',') || 'count(*)')
37
37
  : 'count(*)');
38
+
39
+ const yName = metrics?.[0]?.name || metrics?.[0];
40
+ const yType = columnTypes.find((el) => el.name == yName)?.type;
38
41
 
39
42
  const { cls, table, filterCustom } = data;
40
43
  const groupby = query.groupby || data.groupby;
@@ -51,6 +54,6 @@ function normalizeData(data, query = {}, columnTypes = []) {
51
54
  ? `(select * from ${data?.table} t ${data.tableSQL.join(' \n ')} where ${where})q`
52
55
  : undefined;
53
56
 
54
- return { x, cls, metric, table, where, tableSQL, groupby, xName };
57
+ return { x, cls, metric, table, where, tableSQL, groupby, xName, xType, yName, yType };
55
58
  }
56
59
  export default normalizeData;
@@ -4,9 +4,14 @@ import data from './controllers/data.js';
4
4
 
5
5
  const biSchema = {
6
6
  querystring: {
7
- widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
8
- dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
7
+ widget: { type: 'string', pattern: '^([\\d\\w_]+)$' },
8
+ dashboard: { type: 'string', pattern: '^([\\d\\w_]+)$' },
9
9
  sql: { type: 'string', pattern: '^([\\d])$' },
10
+ // metric: { type: 'string', pattern: '^([\\d\\w_]+)$' },
11
+ x: { type: 'string', pattern: '^([\\d\\w_]+)$' },
12
+ granularity: { type: 'string', pattern: '^(week|month|quarter|year)$' },
13
+ groupby: { type: 'string', pattern: '^([\\d\\w_]+)$' },
14
+ filterCustom: { type: 'string', pattern: '^([\\d\\w_,]+)$' },
10
15
  },
11
16
  params: {
12
17
  id: { type: 'string', pattern: '^([\\d\\w]+)$' },
@@ -0,0 +1,97 @@
1
+ import path from 'node:path';
2
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
3
+
4
+ import { getPG } from '@opengis/fastify-table/utils.js';
5
+
6
+ const host = 'https://cdn.softpro.ua/demo/bi';
7
+
8
+
9
+ const root = process.cwd();
10
+ const send = (msg) => console.log(msg)
11
+ async function downloadFile(fileName) {
12
+ const pg = getPG();
13
+ send(`${fileName} - checking if already downloaded...`);
14
+
15
+ const demoPath = path.join(root, '/log/temp/', fileName);
16
+ // const fileExists = await isFileExists(demoPath);
17
+
18
+ // send(`${host}/${fileName}` + demoPath)
19
+ const response = await fetch(`${host}/${fileName}`);
20
+ const body = await response.text();
21
+ //send(body)
22
+ if (response?.status !== 200) {
23
+ return { [fileName]: { result: 'file not found', status: 404 } };
24
+ }
25
+
26
+ await mkdir(path.dirname(demoPath), { recursive: true });
27
+
28
+ await writeFile(demoPath, body, 'utf8');
29
+
30
+ await pg.query(`drop table if exists ${path.parse(fileName).name}`);
31
+ const { exists } = await pg.query(`select to_regclass($1) is not null as exists`, [path.parse(fileName).name])
32
+ .then((res1) => res1.rows?.[0] || {});
33
+
34
+
35
+ if (exists) {
36
+ send(`${fileName} - dataset table already exists!`);
37
+ return { [fileName]: { result: 'dataset table already exists', status: 200 } };
38
+ }
39
+
40
+ const sql = await readFile(demoPath, 'utf8');
41
+ await pg.query(sql);
42
+ return { [fileName]: { result: 'success', status: 200 } };
43
+ }
44
+
45
+ /**
46
+ * Імпорт demo даних dashboard
47
+ *
48
+ * @method GET
49
+ * @summary Імпорт demo даних dashboard
50
+ * @priority 4
51
+ * @type api
52
+ * @tag bi
53
+ * @errors 400,500
54
+ * @returns {Number} status Номер помилки
55
+ * @returns {String} error Опис помилки
56
+ * @returns {Object} rows Масив з колонками таблиці
57
+ */
58
+
59
+ const demoList = {
60
+ 'demo.orders.sql': 'Статистика продажів (demo)',
61
+ 'demo.cleaned_sales_data.sql': 'Статистика продажів (demo 2)',
62
+ 'demo.video_game_sales.sql': 'Статистика продажів відеоігор (demo)',
63
+ };
64
+
65
+ export default async function biDatasetDemoAdd(req) {
66
+ const { pg, query = {} } = req;
67
+ // const send = () => { }; // eventStream?
68
+
69
+
70
+
71
+ const sqlList = [];
72
+
73
+ const tables = Object.keys(demoList)?.map((el) => {
74
+ const table = path.parse(el).name;
75
+ sqlList.push(`insert into bi.dataset(dataset_id,name,table_name)
76
+ select '${el}', '${demoList?.[el]}', '${table}' on conflict(dataset_id) do update set
77
+ name=excluded.name, table_name=excluded.table_name
78
+ returning dataset_id as id`);
79
+ return table;
80
+ });
81
+
82
+ const { count } = await pg.query(`select count(*) from pg_catalog.pg_tables where schemaname||'.'||tablename = any($1)`, [tables]).then((res1) => res1.rows?.[0] || {});
83
+
84
+ if (count === '3' && !query.nocache) {
85
+ const result = await pg.query(sqlList.join(';\n'));
86
+ const ids = (Array.isArray(result) ? result : [result]).filter((el) => el).map((el) => el?.rows?.[0]?.id);
87
+ return { message: { ids, message: 'all datasets already downloaded' }, status: 200 };
88
+ }
89
+
90
+ const res = {};
91
+ const resultAll = await Promise.all(Object.keys(demoList).map(async (fileName) => await downloadFile(fileName))).catch(err => console.log(err));
92
+
93
+ const result = await pg.query(sqlList.join(';\n'));
94
+ const ids = (Array.isArray(result) ? result : [result]).filter((el) => el).map((el) => el?.rows?.[0]?.id);
95
+ return { message: { ids, message: res, resultAll }, status: 200 };
96
+
97
+ };
@@ -0,0 +1,67 @@
1
+ import { randomBytes } from 'node:crypto';
2
+
3
+ import createTableFunc from './util/create.table.js';
4
+ import prepareData from './util/prepare.data.js';
5
+
6
+ /**
7
+ * Імпорт даних до BI набору
8
+ *
9
+ * @method POST
10
+ * @summary Імпорт даних до BI набору
11
+ * @priority 4
12
+ * @alias biDatasetPost
13
+ * @type api
14
+ * @tag bi
15
+ * @param {Object} params.id Dashboard ID
16
+ * @errors 400,500
17
+ * @returns {Number} status Номер помилки
18
+ * @returns {String} error Опис помилки
19
+ * @returns {Object} rows Масив з колонками таблиці
20
+ */
21
+
22
+ export default async function biDatasetPost(req) {
23
+ const {
24
+ pg, params = {}, body = {}, session = {},
25
+ } = req;
26
+ const { uid } = session?.passport?.user || {};
27
+
28
+ if (!uid) {
29
+ return { message: 'access restricted', status: 403 };
30
+ }
31
+
32
+ if (!Array.isArray(body?.data) && body?.data?.features?.[0]?.type !== 'Feature') {
33
+ return { message: 'body data param is invalid', status: 400 };
34
+ }
35
+
36
+ // await pg.query(`insert into bi.dataset(dataset_id,name, uid) values($1,$1,$2) on conflict (dataset_id) do update set table_name=null`, [params?.id, uid]);
37
+ const dataset = await pg.query('select dataset_id as id, name, table_name as table from bi.dataset where dataset_id=$1', [params?.id]).then((res) => res.rows?.[0] || {});
38
+ if (!dataset.id) {
39
+ return { message: `dataset not found: ${params?.id}`, status: 404 };
40
+ }
41
+
42
+ const tableName = randomBytes(64).toString('hex').substring(0, 24).replace(/^\d+/, '');
43
+ const pkey = tableName.concat('_id');
44
+ const table = `bi_data.${tableName}`;
45
+ const { columns, insertData } = await prepareData({ req, table, data: body?.data });
46
+ const createTable = createTableFunc({ table, pkey, columns });
47
+ const updateDataset = `update bi.dataset set table_name='${table}', editor_id='${uid}' where dataset_id='${params?.id}'`;
48
+ const sqlList = [createTable, insertData, updateDataset];
49
+ if (dataset.name) {
50
+ sqlList.push(`comment on table ${table} is '${dataset.name}'`);
51
+ }
52
+ const q = sqlList.filter((el) => el).join(';');
53
+ await pg.query('create extension if not exists postgis;create schema if not exists bi_data;');
54
+
55
+ // await pg.query(createTable);
56
+
57
+ //const result = {}
58
+ const result = await pg.query(q);
59
+
60
+ return {
61
+ message: {
62
+ id: params?.id, action: 'import', result: 'success', total: result?.find((el) => el.command === 'INSERT')?.rowCount,
63
+ },
64
+ status: 200,
65
+ };
66
+
67
+ };
@@ -0,0 +1,22 @@
1
+ export default function createTableFunc({
2
+ table, pkey, columns,
3
+ }) {
4
+ const columnType1 = {
5
+ text: 'text', select: 'text', date: 'date', 'yes/no': 'boolean', badge: 'text', number: 'numeric', tags: 'text[]', geom: 'geom',
6
+ };
7
+
8
+ const createQuery = `create table if not exists ${table} (
9
+ ${pkey} text not null default public.uuid_generate_v4(),
10
+ geom public.geometry, ${columns ? `
11
+ ${columns?.map((el) => `${el.name} ${columnType1[el.format] || 'text'}`).join(', ')},` : ''}
12
+ cdate timestamp without time zone not null default now(),
13
+ editor_date timestamp without time zone,
14
+ uid text,
15
+ editor_id text,
16
+ files json,
17
+ constraint ${table.replace(/\./g, '_')}_constraint_pkey PRIMARY KEY (${pkey}) )`;
18
+
19
+ const commentQuery = columns?.filter((el) => el.ua)?.map((el) => `comment on column ${table}.${el.name} is '${el.ua}'`).join(';');
20
+
21
+ return [createQuery, commentQuery].join(';');
22
+ };
@@ -0,0 +1,49 @@
1
+ import path from 'node:path';
2
+
3
+ import { getFolder, isFileExists } from '@opengis/fastify-table/utils.js';
4
+
5
+ import { file2json } from '@opengis/fastify-file/utils.js';
6
+
7
+ function getFileColumns({ data }) {
8
+ if (data?.features) {
9
+ return Object.keys(data?.features[0]?.properties); // geojson / shapefile
10
+ }
11
+ if (Array.isArray(data)) {
12
+ return Object.keys(data[0])?.filter((el) => el); // csv / xls
13
+ }
14
+ return Object.values(data[Object.keys(data)?.[0]]?.[0]); // else?
15
+ }
16
+
17
+ export default async function prepareData({
18
+ req, table, file_path: filePath, data: data1,
19
+ }) {
20
+ const rootDir = getFolder(req);
21
+ const fullPath = path.join(rootDir, filePath || '');
22
+ const fileExists = await isFileExists(fullPath);
23
+ if (!fileExists && !data1) {
24
+ return { message: `file not found: ${filePath}`, status: 404 };
25
+ }
26
+
27
+ const data11 = data1 || await file2json(fullPath);
28
+ const data = (['.xls', '.xlsx'].includes(path.extname(fullPath)) && !Array.isArray(data11)) ? data11[Object.keys(data11)[0]] : data11;
29
+
30
+ /* generate column list based on input data */
31
+ const columns = getFileColumns({ data })
32
+ ?.filter((el) => !['editor_date', 'cdate', 'uid', 'editor_id', 'files'].includes(el.toLowerCase()))
33
+ ?.map((el, index) => ({ name: `col_${index}`, ua: el, format: 'text' }));
34
+
35
+ const columnList1 = columns?.map((el) => el.name)?.join(',');
36
+ const columns1 = ','.concat(columns?.map((el) => `features->'properties'->>'${el?.ua}'`)?.join(','));
37
+ const columns2 = columns?.map((el) => `d->>'${el.ua}'`).join(',');
38
+ const q1 = data?.features ? `(SELECT json_array_elements('${JSON.stringify(data.features).replace(/'/g, "''")}'::json) AS features)q` : null;
39
+ const q2 = !data?.features ? `( SELECT json_array_elements('${JSON.stringify(Array.isArray(data)
40
+ ? data
41
+ : data[Object?.keys(data)?.[0]])
42
+ .replace(/'/g, "''")
43
+ .replace(/\\"/g, '`')}'::json) AS d )q`
44
+ : null;
45
+ const insertData = data?.features
46
+ ? `INSERT into ${table} (geom ${','.concat(columnList1)}) SELECT public.ST_GeomFromGeoJSON((features->>'geometry')::json) AS geom ${columns1} FROM ${q1}`
47
+ : `INSERT into ${table} (${columnList1}) SELECT ${columns2} FROM ${q2}`;
48
+ return { columns, insertData };
49
+ };
@@ -0,0 +1,19 @@
1
+ import biDatasetPost from './controllers/bi.dataset.import.js';
2
+ import biDatasetDemoAdd from './controllers/bi.dataset.demo.add.js';
3
+
4
+ const biSchema = {};
5
+
6
+ export default async function route(fastify, opts) {
7
+ fastify.route({
8
+ method: 'POST',
9
+ url: '/bi-dataset/:id',
10
+ schema: biSchema,
11
+ handler: biDatasetPost,
12
+ });
13
+ fastify.route({
14
+ method: 'GET',
15
+ url: '/bi-dataset-demo',
16
+ schema: biSchema,
17
+ handler: biDatasetDemoAdd,
18
+ });
19
+ }
@@ -1,4 +1,6 @@
1
- export default async function widgetAdd({ pg, funcs, params = {}, body }) {
1
+ import { dataInsert } from "@opengis/fastify-table/utils.js";
2
+
3
+ export default async function widgetAdd({ pg, body }) {
2
4
  try {
3
5
  const time = Date.now();
4
6
  const tableName = body.table_name;
@@ -7,7 +9,7 @@ export default async function widgetAdd({ pg, funcs, params = {}, body }) {
7
9
  [tableName]
8
10
  );
9
11
  if (!checkTable.rows.length) return { message: 'bad params', status: 401 };
10
- const res = await funcs.dataInsert({
12
+ const res = await dataInsert({
11
13
  table: 'bi.dashboard',
12
14
  data: body,
13
15
  });
@@ -1,5 +1,7 @@
1
+ import { dataUpdate } from "@opengis/fastify-table/utils.js";
2
+
1
3
  export default async function dashboardEdit(
2
- { pg, funcs, params = {}, body = {} },
4
+ { pg, params = {}, body = {} },
3
5
  reply
4
6
  ) {
5
7
  try {
@@ -24,7 +26,7 @@ export default async function dashboardEdit(
24
26
  .then((res1) => res1.rows?.[0] || {});
25
27
  const { dashboard_id: dashboardId } = row;
26
28
 
27
- const res = await funcs.dataUpdate({
29
+ const res = await dataUpdate({
28
30
  table: 'bi.dashboard',
29
31
  id: dashboardId,
30
32
  data: body,
@@ -1,3 +1,5 @@
1
+ import { dataInsert, dataUpdate } from "@opengis/fastify-table/utils.js";
2
+
1
3
  /* eslint-disable import/extensions */
2
4
  import { yamlSafe } from '../../../../utils.js';
3
5
 
@@ -7,7 +9,7 @@ function generateUniqueName(prefix = 'bar') {
7
9
  return `${prefix}_${randomPart}_${timestamp}`;
8
10
  }
9
11
 
10
- export default async function widgetAdd({ pg, funcs, params = {}, body = {} }) {
12
+ export default async function widgetAdd({ pg, params = {}, body = {} }) {
11
13
  const { name: dashboardName } = params;
12
14
  if (!dashboardName) {
13
15
  return { message: 'not enough params: id', status: 400 };
@@ -46,7 +48,7 @@ export default async function widgetAdd({ pg, funcs, params = {}, body = {} }) {
46
48
  data?.metrics,
47
49
  });
48
50
 
49
- const res = await funcs.dataUpdate({
51
+ const res = await dataUpdate({
50
52
  table: 'bi.dashboard',
51
53
  id: dashboardId,
52
54
  data: {
@@ -58,7 +60,7 @@ export default async function widgetAdd({ pg, funcs, params = {}, body = {} }) {
58
60
  });
59
61
  const widgetData = { ...data, data, dashboard_id: dashboardId };
60
62
  if (body?.yml) Object.assign(widgetData, { yml: body.yml });
61
- await funcs.dataInsert({
63
+ await dataInsert({
62
64
  table: 'bi.widget',
63
65
  data: widgetData,
64
66
  });
@@ -1,5 +1,4 @@
1
- import dataDelete from '@opengis/fastify-table/crud/funcs/dataDelete.js';
2
- import dataUpdate from '@opengis/fastify-table/crud/funcs/dataUpdate.js';
1
+ import { dataDelete, dataUpdate } from '@opengis/fastify-table/utils.js';
3
2
 
4
3
  export default async function widgetDel({ pg = {}, params = {} }) {
5
4
  const { widget: widgetName, name: dashboardName } = params;
@@ -2,14 +2,10 @@ import { dataUpdate } from '@opengis/fastify-table/utils.js';
2
2
 
3
3
  import { yamlSafe } from '../../../../utils.js';
4
4
 
5
- export default async function widgetEdit({
6
- pg = {},
7
- funcs = {},
8
- body = {},
9
- params = {},
10
- }) {
5
+ export default async function widgetEdit({ pg, body, params, }) {
11
6
  const { widget: widgetName, name: dashboardName } = params;
12
- const data = body.yml ? yamlSafe.load(body.yml) : body;
7
+ const data = body.yml && !body.style ? yamlSafe.load(body.yml) : body;
8
+
13
9
  const tableName =
14
10
  data.data?.table || data?.table || data.table_name || body.table_name;
15
11
 
@@ -20,84 +16,37 @@ export default async function widgetEdit({
20
16
  };
21
17
  }
22
18
 
23
- try {
24
- const row = await pg
25
- .query(
26
- `select a.widget_id, b.dashboard_id from bi.widget a, bi.dashboard b
19
+
20
+ const { widget_id: widgetId } = await pg.query(
21
+ `select a.widget_id , b.dashboard_id from bi.widget a, bi.dashboard b
27
22
  where $2 in (a.widget_id, a.name) and $1 in (b.dashboard_id, b.name) order by 1,2`,
28
- [dashboardName, widgetName]
29
- )
30
- .then((res1) => res1.rows?.[0] || {});
31
- const { widget_id: widgetId, dashboard_id: dashboardId } = row || {};
23
+ [dashboardName, widgetName]
24
+ ).then((res1) => res1.rows?.[0] || {});
32
25
 
33
- // const checkTable = await pg.query(
34
- // `select * from bi.widget where $1 in (table_name)`,
35
- // [tableName]
36
- // );
37
- // if (!checkTable.rows.length) {
38
- // return { message: 'bad params', status: 401 };
39
- // }
26
+ if (!widgetId) {
27
+ return { message: `widget not found ${widgetName}`, status: 404 };
28
+ }
40
29
 
41
- if (!tableName || !pg.pk?.[tableName]) {
42
- return { message: 'bad params: table', status: 400 };
43
- }
30
+ if (!tableName || !pg.pk?.[tableName]) {
31
+ return { message: 'bad params: table', status: 400 };
32
+ }
44
33
 
45
- if (!widgetId) {
46
- return { message: `widget not found ${widgetName}`, status: 404 };
47
- }
48
34
 
49
- const widgetData = { ...data, data };
50
- if (body?.yml) Object.assign(widgetData, { yml: body.yml });
51
- await funcs.dataUpdate({
52
- table: 'bi.widget',
53
- id: widgetId,
54
- data: widgetData,
55
- });
56
35
 
57
- // \====================
58
- const currentDashboard = await pg
59
- .query(
60
- `select * from bi.dashboard
61
- where $1 in (dashboard_id, name)`,
62
- [dashboardName]
63
- )
64
- .then((res1) => res1.rows?.[0] || {});
65
- const bodyDashboard = currentDashboard;
36
+ // const widgetData = { ...data, data };
37
+ const widgetData = body.yml && !body.style ? data : { style: data.style, data: { x: data.x, metrics: data.metrics }, controls: data.controls, type: data.type, title: data.title, table_name: data.table_name };
38
+ // console.log(widgetId, widgetData)
39
+ if (body?.yml) Object.assign(widgetData, { yml: body.yml });
40
+ const rows = await dataUpdate({
41
+ table: 'bi.widget',
42
+ id: widgetId,
43
+ data: widgetData,
44
+ });
66
45
 
67
- if (!currentDashboard) {
68
- return { message: `dashboard not found ${dashboardName}`, status: 404 };
69
- }
70
- bodyDashboard.panels =
71
- Array.isArray(bodyDashboard.panels) && bodyDashboard.panels?.length
72
- ? bodyDashboard.panels?.map((panel) => {
73
- if (panel.widget === widgetName) {
74
- panel.col = body.col;
75
- }
76
- return panel;
77
- })
78
- : undefined;
79
- bodyDashboard.widgets =
80
- Array.isArray(bodyDashboard.widgets) && bodyDashboard?.widgets?.length
81
- ? bodyDashboard.widgets?.map((widget) => {
82
- if (widget.name === widgetName) {
83
- return body;
84
- }
85
- return widget;
86
- })
87
- : undefined;
46
+ return {
47
+ message: rows,
48
+ status: 200,
88
49
 
89
- const res1 = await dataUpdate({
90
- table: 'bi.dashboard',
91
- id: dashboardId,
92
- data: bodyDashboard,
93
- });
50
+ };
94
51
 
95
- return {
96
- message: `Edited widget ${widgetName}`,
97
- status: 200,
98
- rows: res1,
99
- };
100
- } catch (err) {
101
- return { error: err.toString(), status: 500 };
102
- }
103
52
  }
@@ -1,6 +1,12 @@
1
+ import { getFilterSQL } from '@opengis/fastify-table/utils.js';
2
+
1
3
  import { getWidget } from '../../../../utils.js';
2
4
 
3
- export default async function cluster({ pg, funcs, query = {}, log }) {
5
+ import downloadClusterData from './utils/downloadClusterData.js';
6
+
7
+ const clusterExists = {};
8
+
9
+ export default async function cluster({ pg, query = {}, log }) {
4
10
  const { widget, filter, dashboard, search } = query;
5
11
 
6
12
  if (!widget) {
@@ -43,10 +49,17 @@ export default async function cluster({ pg, funcs, query = {}, log }) {
43
49
  }
44
50
 
45
51
  if (!clusterTable?.name) {
46
- return {
47
- message: 'not enough widget params: clusterTable name',
48
- status: 400,
49
- };
52
+ Object.assign(clusterTable, {
53
+ name: 'bi.cluster',
54
+ title: 'title',
55
+ query: `type='${cluster}'`,
56
+ });
57
+ }
58
+
59
+ if (cluster && !clusterExists[cluster]) {
60
+ const res = await downloadClusterData({ pg, log, cluster });
61
+ if (res) return res;
62
+ clusterExists[cluster] = 1;
50
63
  }
51
64
 
52
65
  if (clusterTable?.name && !pg.pk?.[clusterTable?.name]) {
@@ -56,35 +69,27 @@ export default async function cluster({ pg, funcs, query = {}, log }) {
56
69
  };
57
70
  }
58
71
 
59
- const { total = 0 } = pg.pk?.[table]
60
- ? await pg
61
- .queryCache(
62
- `select oid::regclass as table, reltuples AS total from pg_class`
63
- )
64
- .then((res) => res.rows?.find((row) => row.table === table))
65
- : {};
66
-
67
- const { bbox } = await pg
68
- .query(
69
- `select st_asgeojson(box2d(geom))::json as bbox from ${table} where ${where || '1=1'}`
70
- )
71
- .then((res1) => res1.rows?.[0] || {});
72
+ const { bounds, extentStr } = await pg.query(`select count(*),
73
+ st_asgeojson(st_extent(geom))::json as bounds,
74
+ replace(regexp_replace(st_extent(geom)::box2d::text,'BOX\\(|\\)','','g'),' ',',') as "extentStr"
75
+ from ${table} where ${where || '1=1'}`).then((res) => res.rows?.[0] || {});
76
+ const extent = extentStr ? extentStr.split(',') : undefined;
72
77
 
73
78
  // get sql
74
79
  const { optimizedSQL } =
75
80
  filter || search
76
- ? await funcs.getFilterSQL({ pg, table, filter, search })
81
+ ? await getFilterSQL({ pg, table, filter, search })
77
82
  : {};
78
83
 
79
84
  const q = `select "${cluster}" as name, sum("${metrics[0]}")::float as metric
80
85
  from ${optimizedSQL ? `(${optimizedSQL})` : table} q
81
86
  left join lateral (select "${pg.pk?.[clusterTable?.name]}" as id from ${clusterTable?.name} where ${clusterTable?.title}=q."${cluster}" limit 1)b on 1=1
82
- where ${where} group by ${cluster}, b.id`;
87
+ where ${where} group by ${cluster}, b.id order by sum("${metrics[0]}")::float desc`;
83
88
 
84
89
  if (query.sql === '1') return q;
85
90
 
86
91
  // auto Index
87
- // funcs.autoIndex({ table, columns: (metrics || []).concat([cluster]) });
92
+ // autoIndex({ table, columns: (metrics || []).concat([cluster]) });
88
93
 
89
94
  const { rows = [] } = await pg.query(q);
90
95
  const vals = rows.map((el) => el.metric - 0).sort((a, b) => a - b);
@@ -96,7 +101,7 @@ export default async function cluster({ pg, funcs, query = {}, log }) {
96
101
  vals[Math.floor(len * 0.75)],
97
102
  vals[len - 1],
98
103
  ];
99
- return { sizes, rows, bbox, count: rows.length, total };
104
+ return { sizes, rows, bounds, extent, count: rows.length, total: rows?.reduce((acc, curr) => (curr.metric || 0) + acc, 0) };
100
105
  } catch (err) {
101
106
  log.error('bi/cluster', { error: err.toString(), query });
102
107
  return { error: err.toString(), status: 500 };