mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-21 17:36:01 +00:00
remove web
This commit is contained in:
@@ -1,154 +0,0 @@
|
||||
import React from 'react';
|
||||
import Chart from 'react-chartjs-2';
|
||||
import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import { HorizontalSplitter } from '../widgets/Splitter';
|
||||
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';
|
||||
import { FormFieldTemplateTiny } from '../utility/formStyle';
|
||||
import { ManagerInnerContainer } from '../datagrid/ManagerStyles';
|
||||
import { presetPrimaryColors } from '@ant-design/colors';
|
||||
import ErrorInfo from '../widgets/ErrorInfo';
|
||||
|
||||
const LeftContainer = styled.div`
|
||||
background-color: ${props => props.theme.manager_background};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export default function ChartEditor({ data, config, setConfig, sql, conid, database }) {
|
||||
const [managerSize, setManagerSize] = React.useState(0);
|
||||
const theme = useTheme();
|
||||
const extensions = useExtensions();
|
||||
const [error, setError] = React.useState(null);
|
||||
|
||||
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;
|
||||
try {
|
||||
const columns = await loadChartStructure(driver, conid, database, sql);
|
||||
setAvailableColumnNames(columns);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
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, availableColumnNames]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<ErrorInfo message={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProviderCore values={config} setValues={setConfig} template={FormFieldTemplateTiny}>
|
||||
<HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}>
|
||||
<LeftContainer theme={theme}>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Style" name="style" height="40%">
|
||||
<ManagerInnerContainer style={{ maxWidth: managerSize }}>
|
||||
<FormSelectField label="Chart type" name="chartType">
|
||||
<option value="bar">Bar</option>
|
||||
<option value="line">Line</option>
|
||||
{/* <option value="radar">Radar</option> */}
|
||||
<option value="pie">Pie</option>
|
||||
<option value="polarArea">Polar area</option>
|
||||
{/* <option value="bubble">Bubble</option>
|
||||
<option value="scatter">Scatter</option> */}
|
||||
</FormSelectField>
|
||||
<FormTextField label="Color set" name="colorSeed" />
|
||||
<FormSelectField label="Truncate from" name="truncateFrom">
|
||||
<option value="begin">Begin</option>
|
||||
<option value="end">End (most recent data for datetime)</option>
|
||||
</FormSelectField>
|
||||
<FormTextField label="Truncate limit" name="truncateLimit" />
|
||||
<FormCheckboxField label="Show relative values" name="showRelativeValues" />
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<ManagerInnerContainer style={{ maxWidth: managerSize }}>
|
||||
{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 => (
|
||||
<React.Fragment key={col}>
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} />
|
||||
{config[`dataColumn_${col}`] && (
|
||||
<FormSelectField label="Color" name={`dataColumnColor_${col}`}>
|
||||
<option value="">Random</option>
|
||||
|
||||
{_.keys(presetPrimaryColors).map(color => (
|
||||
<option value={color} key={color}>
|
||||
{_.startCase(color)}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</LeftContainer>
|
||||
|
||||
<DataChart data={data || loadedData} />
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import ToolbarButton from '../widgets/ToolbarButton';
|
||||
|
||||
export default function ChartToolbar({ modelState, dispatchModel }) {
|
||||
return (
|
||||
<>
|
||||
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
||||
Undo
|
||||
</ToolbarButton>
|
||||
<ToolbarButton disabled={!modelState.canRedo} onClick={() => dispatchModel({ type: 'redo' })} icon="icon redo">
|
||||
Redo
|
||||
</ToolbarButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Chart from 'react-chartjs-2';
|
||||
import randomcolor from 'randomcolor';
|
||||
import styled from 'styled-components';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import { useForm } from '../utility/FormProvider';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import moment from 'moment';
|
||||
|
||||
const ChartWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
function getTimeAxis(labels) {
|
||||
const res = [];
|
||||
for (const label of labels) {
|
||||
const parsed = moment(label);
|
||||
if (!parsed.isValid()) return null;
|
||||
const iso = parsed.toISOString();
|
||||
if (iso < '1850-01-01T00:00:00' || iso > '2150-01-01T00:00:00') return null;
|
||||
res.push(parsed);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getLabels(labelValues, timeAxis, chartType) {
|
||||
if (!timeAxis) return labelValues;
|
||||
if (chartType === 'line') return timeAxis.map(x => x.toDate());
|
||||
return timeAxis.map(x => x.format('D. M. YYYY'));
|
||||
}
|
||||
|
||||
function getOptions(timeAxis, chartType) {
|
||||
if (timeAxis && chartType === 'line') {
|
||||
return {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
|
||||
time: {
|
||||
tooltipFormat: 'D. M. YYYY HH:mm',
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'D.M hA',
|
||||
day: 'D. M.',
|
||||
week: 'D. M. YYYY',
|
||||
month: 'MM-YYYY',
|
||||
quarter: '[Q]Q - YYYY',
|
||||
year: 'YYYY',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartType, dataColumnColors, theme) {
|
||||
if (!freeData || !labelColumn || !dataColumns || !freeData.rows || dataColumns.length == 0) return [{}, {}];
|
||||
const colors = randomcolor({
|
||||
count: _.max([freeData.rows.length, dataColumns.length, 1]),
|
||||
seed: colorSeed,
|
||||
});
|
||||
let backgroundColor = null;
|
||||
let borderColor = null;
|
||||
const labelValues = freeData.rows.map(x => x[labelColumn]);
|
||||
const timeAxis = getTimeAxis(labelValues);
|
||||
const labels = getLabels(labelValues, timeAxis, chartType);
|
||||
const res = {
|
||||
labels,
|
||||
datasets: dataColumns.map((dataColumn, columnIndex) => {
|
||||
if (chartType == 'line' || chartType == 'bar') {
|
||||
const color = dataColumnColors[dataColumn];
|
||||
if (color) {
|
||||
backgroundColor = theme.main_palettes[color][4] + '80';
|
||||
borderColor = theme.main_palettes[color][7];
|
||||
} else {
|
||||
backgroundColor = colors[columnIndex] + '80';
|
||||
borderColor = colors[columnIndex];
|
||||
}
|
||||
} else {
|
||||
backgroundColor = colors;
|
||||
}
|
||||
|
||||
return {
|
||||
label: dataColumn,
|
||||
data: freeData.rows.map(row => row[dataColumn]),
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
borderWidth: 1,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const options = getOptions(timeAxis, chartType);
|
||||
return [res, options];
|
||||
}
|
||||
|
||||
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 function extractDataColumnColors(values, dataColumns) {
|
||||
const res = {};
|
||||
for (const column of dataColumns) {
|
||||
const color = values[`dataColumnColor_${column}`];
|
||||
if (color) res[column] = color;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export default function DataChart({ data }) {
|
||||
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
|
||||
const { values } = useForm();
|
||||
const theme = useTheme();
|
||||
|
||||
const { labelColumn } = values;
|
||||
const dataColumns = extractDataColumns(values);
|
||||
const dataColumnColors = extractDataColumnColors(values, dataColumns);
|
||||
const [chartData, options] = createChartData(
|
||||
data,
|
||||
labelColumn,
|
||||
dataColumns,
|
||||
values.colorSeed || '5',
|
||||
values.chartType,
|
||||
dataColumnColors,
|
||||
theme
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper ref={containerRef}>
|
||||
<Chart
|
||||
key={`${values.chartType}|${containerWidth}|${containerHeight}`}
|
||||
width={containerWidth}
|
||||
height={containerHeight}
|
||||
data={chartData}
|
||||
type={values.chartType}
|
||||
options={{
|
||||
...options,
|
||||
// elements: {
|
||||
// point: {
|
||||
// radius: 0,
|
||||
// },
|
||||
// },
|
||||
// tooltips: {
|
||||
// mode: 'index',
|
||||
// intersect: false,
|
||||
// },
|
||||
}}
|
||||
/>
|
||||
</ChartWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { dumpSqlSelect, Select } from 'dbgate-sqltree';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import axios from '../utility/axios';
|
||||
import _ from 'lodash';
|
||||
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 });
|
||||
if (resp.data.errorMessage) throw new Error(resp.data.errorMessage);
|
||||
return resp.data.columns.map(x => x.columnName);
|
||||
}
|
||||
|
||||
export async function loadChartData(driver: EngineDriver, conid, database, sql, config) {
|
||||
const dataColumns = extractDataColumns(config);
|
||||
const { labelColumn, truncateFrom, truncateLimit, showRelativeValues } = config;
|
||||
if (!labelColumn || !dataColumns || dataColumns.length == 0) return null;
|
||||
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
|
||||
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: truncateLimit || 100,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
groupBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
},
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
direction: truncateFrom == 'end' ? 'DESC' : 'ASC',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s });
|
||||
let { rows, columns } = resp.data;
|
||||
if (truncateFrom == 'end' && rows) {
|
||||
rows = _.reverse([...rows]);
|
||||
}
|
||||
if (showRelativeValues) {
|
||||
const maxValues = dataColumns.map(col => _.max(rows.map(row => row[col])));
|
||||
for (const [col, max] of _.zip(dataColumns, maxValues)) {
|
||||
if (!max) continue;
|
||||
if (!_.isNumber(max)) continue;
|
||||
if (!(max > 0)) continue;
|
||||
rows = rows.map(row => ({
|
||||
...row,
|
||||
[col]: (row[col] / max) * 100,
|
||||
}));
|
||||
// columns = columns.map((x) => {
|
||||
// if (x.columnName == col) {
|
||||
// return { columnName: `${col} %` };
|
||||
// }
|
||||
// return x;
|
||||
// });
|
||||
}
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user