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}
/>