active chart - load data from query

This commit is contained in:
Jan Prochazka 2020-12-05 20:47:31 +01:00
parent 61217a944b
commit 0c4d5b5356
9 changed files with 197 additions and 23 deletions

View File

@ -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;
}

View File

@ -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) {

View File

@ -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>
);

View File

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

View 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;
}

View File

@ -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>}
</>
);
}

View File

@ -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}
/>
);
};

View File

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

View File

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