mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
active chart - load data from query
This commit is contained in:
parent
61217a944b
commit
0c4d5b5356
@ -433,9 +433,10 @@ export abstract class GridDisplay {
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExportQuery() {
|
getExportQuery(postprocessSelect = null) {
|
||||||
const select = this.createSelect({ isExport: true });
|
const select = this.createSelect({ isExport: true });
|
||||||
if (!select) return null;
|
if (!select) return null;
|
||||||
|
if (postprocessSelect) postprocessSelect(select);
|
||||||
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { dumpSqlCondition } from './dumpSqlCondition';
|
|||||||
export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||||
dmp.put('^select ');
|
dmp.put('^select ');
|
||||||
if (cmd.topRecords) {
|
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) {
|
if (cmd.distinct) {
|
||||||
dmp.put('^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);
|
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) {
|
export function dumpSqlUpdate(dmp: SqlDumper, cmd: Update) {
|
||||||
|
@ -8,6 +8,10 @@ import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar
|
|||||||
import { FormCheckboxField, FormSelectField, FormTextField } from '../utility/forms';
|
import { FormCheckboxField, FormSelectField, FormTextField } from '../utility/forms';
|
||||||
import DataChart from './DataChart';
|
import DataChart from './DataChart';
|
||||||
import { FormProviderCore } from '../utility/FormProvider';
|
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`
|
const LeftContainer = styled.div`
|
||||||
background-color: ${(props) => props.theme.manager_background};
|
background-color: ${(props) => props.theme.manager_background};
|
||||||
@ -15,10 +19,57 @@ const LeftContainer = styled.div`
|
|||||||
flex: 1;
|
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 [managerSize, setManagerSize] = React.useState(0);
|
||||||
const theme = useTheme();
|
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 (
|
return (
|
||||||
<FormProviderCore values={config} setValues={setConfig}>
|
<FormProviderCore values={config} setValues={setConfig}>
|
||||||
@ -38,14 +89,16 @@ export default function ChartEditor({ data, config, setConfig }) {
|
|||||||
<FormTextField label="Color seed" name="colorSeed" />
|
<FormTextField label="Color seed" name="colorSeed" />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
<WidgetColumnBarItem title="Data" name="data">
|
<WidgetColumnBarItem title="Data" name="data">
|
||||||
<FormSelectField label="Label column" name="labelColumn">
|
{availableColumnNames.length > 0 && (
|
||||||
<option value=""></option>
|
<FormSelectField label="Label column" name="labelColumn">
|
||||||
{availableColumnNames.map((col) => (
|
<option value=""></option>
|
||||||
<option value={col} key={col}>
|
{availableColumnNames.map((col) => (
|
||||||
{col}
|
<option value={col} key={col}>
|
||||||
</option>
|
{col}
|
||||||
))}
|
</option>
|
||||||
</FormSelectField>
|
))}
|
||||||
|
</FormSelectField>
|
||||||
|
)}
|
||||||
{availableColumnNames.map((col) => (
|
{availableColumnNames.map((col) => (
|
||||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} key={col} />
|
<FormCheckboxField label={col} name={`dataColumn_${col}`} key={col} />
|
||||||
))}
|
))}
|
||||||
@ -53,7 +106,7 @@ export default function ChartEditor({ data, config, setConfig }) {
|
|||||||
</WidgetColumnBar>
|
</WidgetColumnBar>
|
||||||
</LeftContainer>
|
</LeftContainer>
|
||||||
|
|
||||||
<DataChart data={data} />
|
<DataChart data={data || loadedData} />
|
||||||
</HorizontalSplitter>
|
</HorizontalSplitter>
|
||||||
</FormProviderCore>
|
</FormProviderCore>
|
||||||
);
|
);
|
||||||
|
@ -84,17 +84,22 @@ function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartTyp
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DataChart({ data }) {
|
export function extractDataColumns(values) {
|
||||||
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
|
|
||||||
const { values } = useForm();
|
|
||||||
|
|
||||||
const { labelColumn } = values;
|
|
||||||
const dataColumns = [];
|
const dataColumns = [];
|
||||||
for (const key in values) {
|
for (const key in values) {
|
||||||
if (key.startsWith('dataColumn_') && values[key]) {
|
if (key.startsWith('dataColumn_') && values[key]) {
|
||||||
dataColumns.push(key.substring('dataColumn_'.length));
|
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 (
|
return (
|
||||||
<ChartWrapper ref={containerRef}>
|
<ChartWrapper ref={containerRef}>
|
||||||
|
83
packages/web/src/charts/chartDataLoader.ts
Normal file
83
packages/web/src/charts/chartDataLoader.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -12,7 +12,8 @@ export default function DataGridContextMenu({
|
|||||||
filterSelectedValue,
|
filterSelectedValue,
|
||||||
openQuery,
|
openQuery,
|
||||||
openFreeTable,
|
openFreeTable,
|
||||||
openChart,
|
openChartSelection,
|
||||||
|
openActiveChart,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -54,7 +55,8 @@ export default function DataGridContextMenu({
|
|||||||
)}
|
)}
|
||||||
{openQuery && <DropDownMenuItem onClick={openQuery}>Open query</DropDownMenuItem>}
|
{openQuery && <DropDownMenuItem onClick={openQuery}>Open query</DropDownMenuItem>}
|
||||||
<DropDownMenuItem onClick={openFreeTable}>Open selection in free table editor</DropDownMenuItem>
|
<DropDownMenuItem onClick={openFreeTable}>Open selection in free table editor</DropDownMenuItem>
|
||||||
<DropDownMenuItem onClick={openChart}>Open chart from selection</DropDownMenuItem>
|
<DropDownMenuItem onClick={openChartSelection}>Open chart from selection</DropDownMenuItem>
|
||||||
|
{openActiveChart && <DropDownMenuItem onClick={openActiveChart}>Open active chart</DropDownMenuItem>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,7 @@ export default function DataGridCore(props) {
|
|||||||
isLoadedAll,
|
isLoadedAll,
|
||||||
loadedTime,
|
loadedTime,
|
||||||
exportGrid,
|
exportGrid,
|
||||||
|
openActiveChart,
|
||||||
allRowCount,
|
allRowCount,
|
||||||
openQuery,
|
openQuery,
|
||||||
onSave,
|
onSave,
|
||||||
@ -377,7 +378,8 @@ export default function DataGridCore(props) {
|
|||||||
filterSelectedValue={display.filterable ? filterSelectedValue : null}
|
filterSelectedValue={display.filterable ? filterSelectedValue : null}
|
||||||
openQuery={openQuery}
|
openQuery={openQuery}
|
||||||
openFreeTable={handleOpenFreeTable}
|
openFreeTable={handleOpenFreeTable}
|
||||||
openChart={handleOpenChart}
|
openChartSelection={handleOpenChart}
|
||||||
|
openActiveChart={openActiveChart}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -80,6 +80,26 @@ export default function SqlDataGridCore(props) {
|
|||||||
initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : [];
|
initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : [];
|
||||||
showModal((modalState) => <ImportExportModal modalState={modalState} initialValues={initialValues} />);
|
showModal((modalState) => <ImportExportModal modalState={modalState} initialValues={initialValues} />);
|
||||||
}
|
}
|
||||||
|
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() {
|
function openQuery() {
|
||||||
openNewTab(setOpenedTabs, {
|
openNewTab(setOpenedTabs, {
|
||||||
title: 'Query',
|
title: 'Query',
|
||||||
@ -131,6 +151,7 @@ export default function SqlDataGridCore(props) {
|
|||||||
<LoadingDataGridCore
|
<LoadingDataGridCore
|
||||||
{...props}
|
{...props}
|
||||||
exportGrid={exportGrid}
|
exportGrid={exportGrid}
|
||||||
|
openActiveChart={openActiveChart}
|
||||||
openQuery={openQuery}
|
openQuery={openQuery}
|
||||||
loadDataPage={loadDataPage}
|
loadDataPage={loadDataPage}
|
||||||
dataPageAvailable={dataPageAvailable}
|
dataPageAvailable={dataPageAvailable}
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createFreeTableModel } from 'dbgate-datalib';
|
import { createFreeTableModel } from 'dbgate-datalib';
|
||||||
import useUndoReducer from '../utility/useUndoReducer';
|
import useUndoReducer from '../utility/useUndoReducer';
|
||||||
import { useSetOpenedTabs } from '../utility/globalState';
|
import { useSetOpenedTabs, useUpdateDatabaseForTab } from '../utility/globalState';
|
||||||
import useGridConfig from '../utility/useGridConfig';
|
import useGridConfig from '../utility/useGridConfig';
|
||||||
import FreeTableGrid from '../freetable/FreeTableGrid';
|
import FreeTableGrid from '../freetable/FreeTableGrid';
|
||||||
import SaveArchiveModal from '../modals/SaveArchiveModal';
|
import SaveArchiveModal from '../modals/SaveArchiveModal';
|
||||||
@ -15,12 +15,13 @@ import useEditorData from '../utility/useEditorData';
|
|||||||
import SaveTabModal from '../modals/SaveTabModal';
|
import SaveTabModal from '../modals/SaveTabModal';
|
||||||
import ChartEditor from '../charts/ChartEditor';
|
import ChartEditor from '../charts/ChartEditor';
|
||||||
|
|
||||||
export default function ChartTab({ tabVisible, toolbarPortalRef, tabid }) {
|
export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database, tabid }) {
|
||||||
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
||||||
const saveFileModalState = useModalState();
|
const saveFileModalState = useModalState();
|
||||||
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
||||||
tabid,
|
tabid,
|
||||||
});
|
});
|
||||||
|
useUpdateDatabaseForTab(tabVisible, conid, database);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -54,6 +55,9 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, tabid }) {
|
|||||||
data={modelState.value && modelState.value.data}
|
data={modelState.value && modelState.value.data}
|
||||||
config={modelState.value ? modelState.value.config || {} : {}}
|
config={modelState.value ? modelState.value.config || {} : {}}
|
||||||
setConfig={setConfig}
|
setConfig={setConfig}
|
||||||
|
sql={modelState.value && modelState.value.sql}
|
||||||
|
conid={conid}
|
||||||
|
database={database}
|
||||||
/>
|
/>
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
modalState={saveFileModalState}
|
||||||
|
Loading…
Reference in New Issue
Block a user