From 0c4d5b53567d50e581dfcb73c340ae9296883c51 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 5 Dec 2020 20:47:31 +0100 Subject: [PATCH] active chart - load data from query --- packages/datalib/src/GridDisplay.ts | 3 +- packages/sqltree/src/dumpSqlCommand.ts | 5 +- packages/web/src/charts/ChartEditor.js | 75 ++++++++++++++--- packages/web/src/charts/DataChart.js | 15 ++-- packages/web/src/charts/chartDataLoader.ts | 83 +++++++++++++++++++ .../web/src/datagrid/DataGridContextMenu.js | 6 +- packages/web/src/datagrid/DataGridCore.js | 4 +- packages/web/src/datagrid/SqlDataGridCore.js | 21 +++++ packages/web/src/tabs/ChartTab.js | 8 +- 9 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 packages/web/src/charts/chartDataLoader.ts diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 177a0b71..2c4d638d 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -433,9 +433,10 @@ export abstract class GridDisplay { return sql; } - getExportQuery() { + getExportQuery(postprocessSelect = null) { const select = this.createSelect({ isExport: true }); if (!select) return null; + if (postprocessSelect) postprocessSelect(select); const sql = treeToSql(this.driver, select, dumpSqlSelect); return sql; } diff --git a/packages/sqltree/src/dumpSqlCommand.ts b/packages/sqltree/src/dumpSqlCommand.ts index 849d1225..a9764097 100644 --- a/packages/sqltree/src/dumpSqlCommand.ts +++ b/packages/sqltree/src/dumpSqlCommand.ts @@ -7,7 +7,7 @@ import { dumpSqlCondition } from './dumpSqlCondition'; export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) { dmp.put('^select '); if (cmd.topRecords) { - dmp.put('^top %s ', cmd.topRecords); + if (!dmp.dialect.rangeSelect || dmp.dialect.offsetFetchRangeSyntax) dmp.put('^top %s ', cmd.topRecords); } if (cmd.distinct) { dmp.put('^distinct '); @@ -51,6 +51,9 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) { dmp.put('^limit %s ^offset %s ', cmd.range.limit, cmd.range.offset); } } + if (cmd.topRecords) { + if (dmp.dialect.rangeSelect && !dmp.dialect.offsetFetchRangeSyntax) dmp.put('^limit %s ', cmd.topRecords); + } } export function dumpSqlUpdate(dmp: SqlDumper, cmd: Update) { diff --git a/packages/web/src/charts/ChartEditor.js b/packages/web/src/charts/ChartEditor.js index d84459b0..253a5670 100644 --- a/packages/web/src/charts/ChartEditor.js +++ b/packages/web/src/charts/ChartEditor.js @@ -8,6 +8,10 @@ import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar import { FormCheckboxField, FormSelectField, FormTextField } from '../utility/forms'; import DataChart from './DataChart'; import { FormProviderCore } from '../utility/FormProvider'; +import { loadChartData, loadChartStructure } from './chartDataLoader'; +import useExtensions from '../utility/useExtensions'; +import { getConnectionInfo } from '../utility/metadataLoaders'; +import { findEngineDriver } from 'dbgate-tools'; const LeftContainer = styled.div` background-color: ${(props) => props.theme.manager_background}; @@ -15,10 +19,57 @@ const LeftContainer = styled.div` flex: 1; `; -export default function ChartEditor({ data, config, setConfig }) { +export default function ChartEditor({ data, config, setConfig, sql, conid, database }) { const [managerSize, setManagerSize] = React.useState(0); const theme = useTheme(); - const availableColumnNames = data ? data.structure.columns.map((x) => x.columnName) : []; + const extensions = useExtensions(); + + const [availableColumnNames, setAvailableColumnNames] = React.useState([]); + const [loadedData, setLoadedData] = React.useState(null); + + const getDriver = async () => { + const conn = await getConnectionInfo({ conid }); + if (!conn) return; + const driver = findEngineDriver(conn, extensions); + return driver; + }; + + const handleLoadColumns = async () => { + const driver = await getDriver(); + if (!driver) return; + const columns = await loadChartStructure(driver, conid, database, sql); + setAvailableColumnNames(columns); + }; + + const handleLoadData = async () => { + const driver = await getDriver(); + if (!driver) return; + const loaded = await loadChartData(driver, conid, database, sql, config); + if (!loaded) return; + const { columns, rows } = loaded; + setLoadedData({ + structure: columns, + rows, + }); + }; + + React.useEffect(() => { + if (sql && conid && database) { + handleLoadColumns(); + } + }, [sql, conid, database, extensions]); + + React.useEffect(() => { + if (data) { + setAvailableColumnNames(data ? data.structure.columns.map((x) => x.columnName) : []); + } + }, [data]); + + React.useEffect(() => { + if (config.labelColumn && sql && conid && database) { + handleLoadData(); + } + }, [config, sql, conid, database]); return ( @@ -38,14 +89,16 @@ export default function ChartEditor({ data, config, setConfig }) { - - - {availableColumnNames.map((col) => ( - - ))} - + {availableColumnNames.length > 0 && ( + + + {availableColumnNames.map((col) => ( + + ))} + + )} {availableColumnNames.map((col) => ( ))} @@ -53,7 +106,7 @@ export default function ChartEditor({ data, config, setConfig }) { - + ); diff --git a/packages/web/src/charts/DataChart.js b/packages/web/src/charts/DataChart.js index 8a1cad05..80c5389b 100644 --- a/packages/web/src/charts/DataChart.js +++ b/packages/web/src/charts/DataChart.js @@ -84,17 +84,22 @@ function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartTyp return res; } -export default function DataChart({ data }) { - const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions(); - const { values } = useForm(); - - const { labelColumn } = values; +export function extractDataColumns(values) { const dataColumns = []; for (const key in values) { if (key.startsWith('dataColumn_') && values[key]) { dataColumns.push(key.substring('dataColumn_'.length)); } } + return dataColumns; +} + +export default function DataChart({ data }) { + const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions(); + const { values } = useForm(); + + const { labelColumn } = values; + const dataColumns = extractDataColumns(values); return ( diff --git a/packages/web/src/charts/chartDataLoader.ts b/packages/web/src/charts/chartDataLoader.ts new file mode 100644 index 00000000..07b8a351 --- /dev/null +++ b/packages/web/src/charts/chartDataLoader.ts @@ -0,0 +1,83 @@ +import { dumpSqlSelect, Select } from 'dbgate-sqltree'; +import { EngineDriver } from 'dbgate-types'; +import axios from '../utility/axios'; +import { extractDataColumns } from './DataChart'; + +export async function loadChartStructure(driver: EngineDriver, conid, database, sql) { + const select: Select = { + commandType: 'select', + selectAll: true, + topRecords: 1, + from: { + subQueryString: sql, + alias: 'subq', + }, + }; + + const dmp = driver.createDumper(); + dumpSqlSelect(dmp, select); + const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s }); + return resp.data.columns.map((x) => x.columnName); +} + +export async function loadChartData(driver: EngineDriver, conid, database, sql, config) { + const dataColumns = extractDataColumns(config); + const { labelColumn } = config; + if (!labelColumn || !dataColumns || dataColumns.length == 0) return null; + + const select: Select = { + commandType: 'select', + // columns:[ + // { + // exprType:'call', + // func:'SUM', + // args: [ + // { + // exprType: 'column', + // columnName, + // source: { alias: 'subq' }, + // } + // ] + // } + // ], + + columns: [ + { + exprType: 'column', + source: { alias: 'subq' }, + columnName: labelColumn, + alias: labelColumn, + }, + // @ts-ignore + ...dataColumns.map((columnName) => ({ + exprType: 'call', + func: 'SUM', + args: [ + { + exprType: 'column', + columnName, + source: { alias: 'subq' }, + }, + ], + alias: columnName, + })), + ], + topRecords: 500, + from: { + subQueryString: sql, + alias: 'subq', + }, + groupBy: [ + { + exprType: 'column', + source: { alias: 'subq' }, + columnName: labelColumn, + }, + ], + }; + + const dmp = driver.createDumper(); + dumpSqlSelect(dmp, select); + const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s }); + return resp.data; +} diff --git a/packages/web/src/datagrid/DataGridContextMenu.js b/packages/web/src/datagrid/DataGridContextMenu.js index f9b0178d..1201c95a 100644 --- a/packages/web/src/datagrid/DataGridContextMenu.js +++ b/packages/web/src/datagrid/DataGridContextMenu.js @@ -12,7 +12,8 @@ export default function DataGridContextMenu({ filterSelectedValue, openQuery, openFreeTable, - openChart, + openChartSelection, + openActiveChart, }) { return ( <> @@ -54,7 +55,8 @@ export default function DataGridContextMenu({ )} {openQuery && Open query} Open selection in free table editor - Open chart from selection + Open chart from selection + {openActiveChart && Open active chart} ); } diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index e6630165..c8087819 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -106,6 +106,7 @@ export default function DataGridCore(props) { isLoadedAll, loadedTime, exportGrid, + openActiveChart, allRowCount, openQuery, onSave, @@ -377,7 +378,8 @@ export default function DataGridCore(props) { filterSelectedValue={display.filterable ? filterSelectedValue : null} openQuery={openQuery} openFreeTable={handleOpenFreeTable} - openChart={handleOpenChart} + openChartSelection={handleOpenChart} + openActiveChart={openActiveChart} /> ); }; diff --git a/packages/web/src/datagrid/SqlDataGridCore.js b/packages/web/src/datagrid/SqlDataGridCore.js index c7f19658..ec322b7d 100644 --- a/packages/web/src/datagrid/SqlDataGridCore.js +++ b/packages/web/src/datagrid/SqlDataGridCore.js @@ -80,6 +80,26 @@ export default function SqlDataGridCore(props) { initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : []; showModal((modalState) => ); } + function openActiveChart() { + openNewTab( + setOpenedTabs, + { + title: 'Chart', + icon: 'img chart', + tabComponent: 'ChartTab', + props: { + conid, + database, + }, + }, + { + config: { chartType: 'bar' }, + sql: display.getExportQuery((select) => { + select.orderBy = null; + }), + } + ); + } function openQuery() { openNewTab(setOpenedTabs, { title: 'Query', @@ -131,6 +151,7 @@ export default function SqlDataGridCore(props) { { // @ts-ignore @@ -54,6 +55,9 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, tabid }) { data={modelState.value && modelState.value.data} config={modelState.value ? modelState.value.config || {} : {}} setConfig={setConfig} + sql={modelState.value && modelState.value.sql} + conid={conid} + database={database} />