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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 (
|
||||
<FormProviderCore values={config} setValues={setConfig}>
|
||||
@ -38,14 +89,16 @@ export default function ChartEditor({ data, config, setConfig }) {
|
||||
<FormTextField label="Color seed" name="colorSeed" />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<FormSelectField label="Label column" name="labelColumn">
|
||||
<option value=""></option>
|
||||
{availableColumnNames.map((col) => (
|
||||
<option value={col} key={col}>
|
||||
{col}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
{availableColumnNames.length > 0 && (
|
||||
<FormSelectField label="Label column" name="labelColumn">
|
||||
<option value=""></option>
|
||||
{availableColumnNames.map((col) => (
|
||||
<option value={col} key={col}>
|
||||
{col}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
)}
|
||||
{availableColumnNames.map((col) => (
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} key={col} />
|
||||
))}
|
||||
@ -53,7 +106,7 @@ export default function ChartEditor({ data, config, setConfig }) {
|
||||
</WidgetColumnBar>
|
||||
</LeftContainer>
|
||||
|
||||
<DataChart data={data} />
|
||||
<DataChart data={data || loadedData} />
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
);
|
||||
|
@ -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 (
|
||||
<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,
|
||||
openQuery,
|
||||
openFreeTable,
|
||||
openChart,
|
||||
openChartSelection,
|
||||
openActiveChart,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@ -54,7 +55,8 @@ export default function DataGridContextMenu({
|
||||
)}
|
||||
{openQuery && <DropDownMenuItem onClick={openQuery}>Open query</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,
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -80,6 +80,26 @@ export default function SqlDataGridCore(props) {
|
||||
initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : [];
|
||||
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() {
|
||||
openNewTab(setOpenedTabs, {
|
||||
title: 'Query',
|
||||
@ -131,6 +151,7 @@ export default function SqlDataGridCore(props) {
|
||||
<LoadingDataGridCore
|
||||
{...props}
|
||||
exportGrid={exportGrid}
|
||||
openActiveChart={openActiveChart}
|
||||
openQuery={openQuery}
|
||||
loadDataPage={loadDataPage}
|
||||
dataPageAvailable={dataPageAvailable}
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { createFreeTableModel } from 'dbgate-datalib';
|
||||
import useUndoReducer from '../utility/useUndoReducer';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { useSetOpenedTabs, useUpdateDatabaseForTab } from '../utility/globalState';
|
||||
import useGridConfig from '../utility/useGridConfig';
|
||||
import FreeTableGrid from '../freetable/FreeTableGrid';
|
||||
import SaveArchiveModal from '../modals/SaveArchiveModal';
|
||||
@ -15,12 +15,13 @@ import useEditorData from '../utility/useEditorData';
|
||||
import SaveTabModal from '../modals/SaveTabModal';
|
||||
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 saveFileModalState = useModalState();
|
||||
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
||||
tabid,
|
||||
});
|
||||
useUpdateDatabaseForTab(tabVisible, conid, database);
|
||||
|
||||
React.useEffect(() => {
|
||||
// @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}
|
||||
/>
|
||||
<SaveTabModal
|
||||
modalState={saveFileModalState}
|
||||
|
Loading…
Reference in New Issue
Block a user