mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 07:26:00 +00:00
removed charts & profiler
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
<script lang="ts" context="module">
|
||||
import Chart from 'chart.js/auto';
|
||||
import 'chartjs-adapter-moment';
|
||||
import zoomPlugin from 'chartjs-plugin-zoom';
|
||||
|
||||
const getCurrentEditor = () => getActiveComponent('ChartCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'chart.export',
|
||||
category: 'Chart',
|
||||
toolbarName: 'Export',
|
||||
name: 'Export chart',
|
||||
icon: 'icon report',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
onClick: () => getCurrentEditor().exportChart(),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
});
|
||||
|
||||
Chart.register(zoomPlugin);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, afterUpdate, onDestroy } from 'svelte';
|
||||
import _ from 'lodash';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import contextMenu, { getContextMenu, registerMenu } from '../utility/contextMenu';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { saveFileToDisk } from '../utility/exportFileTools';
|
||||
|
||||
export let data;
|
||||
export let title;
|
||||
export let type = 'line';
|
||||
export let options = {};
|
||||
// export let plugins = {};
|
||||
// export let menu;
|
||||
|
||||
export const activator = createActivator('ChartCore', true);
|
||||
|
||||
let chart = null;
|
||||
let domChart;
|
||||
|
||||
onMount(() => {
|
||||
chart = new Chart(domChart, {
|
||||
type,
|
||||
data: data,
|
||||
// options must be cloned, because chartjs modifies options object, without cloning fails passing options to electron invoke
|
||||
options: _.cloneDeep(options),
|
||||
});
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (!chart) return;
|
||||
chart.data = data;
|
||||
chart.type = type;
|
||||
chart.options = _.cloneDeep(options);
|
||||
// chart.plugins = plugins;
|
||||
chart.update();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
chart = null;
|
||||
});
|
||||
|
||||
export async function exportChart() {
|
||||
saveFileToDisk(async filePath => {
|
||||
await apiCall('files/export-chart', {
|
||||
title,
|
||||
filePath,
|
||||
config: {
|
||||
type,
|
||||
data,
|
||||
options,
|
||||
},
|
||||
image: domChart.toDataURL(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerMenu({ command: 'chart.export', tag: 'export' });
|
||||
|
||||
const menu = getContextMenu();
|
||||
</script>
|
||||
|
||||
<canvas bind:this={domChart} {...$$restProps} use:contextMenu={menu} />
|
||||
@@ -1,170 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||
import ManagerInnerContainer from '../elements/ManagerInnerContainer.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import FormFieldTemplateTiny from '../forms/FormFieldTemplateTiny.svelte';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { extensions } from '../stores';
|
||||
import { loadChartData, loadChartStructure } from './chartDataLoader';
|
||||
import DataChart from './DataChart.svelte';
|
||||
import _ from 'lodash';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import FormColorField from '../forms/FormColorField.svelte';
|
||||
|
||||
export let data;
|
||||
export let configStore;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let sql;
|
||||
// export let menu;
|
||||
|
||||
let availableColumnNames = [];
|
||||
let errorLoadingColumns = null;
|
||||
let errorLoadingData = null;
|
||||
let loadedData = null;
|
||||
|
||||
$: config = $configStore;
|
||||
|
||||
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 {
|
||||
errorLoadingColumns = null;
|
||||
const columns = await loadChartStructure(driver, conid, database, sql);
|
||||
availableColumnNames = columns;
|
||||
// configStore.update(x => ({ ...x, labelColumn: availableColumnNames[0] }));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
errorLoadingColumns = err.message;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadData = async () => {
|
||||
const driver = await getDriver();
|
||||
if (!driver) return;
|
||||
try {
|
||||
errorLoadingData = null;
|
||||
const loaded = await loadChartData(driver, conid, database, sql, config);
|
||||
if (!loaded) return;
|
||||
const { columns, rows } = loaded;
|
||||
loadedData = {
|
||||
structure: columns,
|
||||
rows,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
errorLoadingData = err.message;
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
$extensions;
|
||||
if (sql && conid && database) {
|
||||
handleLoadColumns();
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if (data) {
|
||||
availableColumnNames = data.structure.columns.map(x => x.columnName);
|
||||
}
|
||||
}
|
||||
$: {
|
||||
$extensions;
|
||||
if (config.labelColumn && sql && conid && database) {
|
||||
handleLoadData();
|
||||
}
|
||||
}
|
||||
|
||||
let managerSize;
|
||||
</script>
|
||||
|
||||
<FormProviderCore values={configStore} template={FormFieldTemplateTiny}>
|
||||
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
|
||||
<div class="left" slot="1">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Style" name="style" height="40%">
|
||||
<ManagerInnerContainer width={managerSize}>
|
||||
<FormSelectField
|
||||
label="Chart type"
|
||||
name="chartType"
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'bar', label: 'Bar' },
|
||||
{ value: 'line', label: 'Line' },
|
||||
{ value: 'pie', label: 'Pie' },
|
||||
{ value: 'polarArea', label: 'Polar area' },
|
||||
]}
|
||||
/>
|
||||
<FormTextField label="Chart title" name="chartTitle" />
|
||||
<FormSelectField
|
||||
label="Truncate from"
|
||||
name="truncateFrom"
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'begin', label: 'Begin' },
|
||||
{ value: 'end', label: 'End (most recent data for datetime)' },
|
||||
]}
|
||||
/>
|
||||
<FormTextField label="Truncate limit" name="truncateLimit" />
|
||||
<FormCheckboxField label="Show relative values" name="showRelativeValues" />
|
||||
{#if $configStore.chartType == 'line'}
|
||||
<FormCheckboxField label="Fill" name="fillLineChart" defaultValue={true} />
|
||||
{/if}
|
||||
<FormTextField label="Color set" name="colorSeed" />
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<ManagerInnerContainer width={managerSize}>
|
||||
{#if availableColumnNames.length > 0}
|
||||
<FormSelectField
|
||||
label="Label column"
|
||||
name="labelColumn"
|
||||
isNative
|
||||
options={availableColumnNames.map(col => ({ value: col, label: col }))}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#each availableColumnNames as col (col)}
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} />
|
||||
{#if config[`dataColumn_${col}`]}
|
||||
<FormColorField label="Color" name={`dataColumnColor_${col}`} emptyLabel="Random" />
|
||||
<FormTextField label="Label" name={`dataColumnLabel_${col}`} />
|
||||
{/if}
|
||||
{/each}
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
{#if errorLoadingColumns}
|
||||
<ErrorInfo message={errorLoadingColumns} alignTop />
|
||||
{:else if errorLoadingData}
|
||||
<ErrorInfo message={errorLoadingData} alignTop />
|
||||
{:else}
|
||||
<DataChart data={data || loadedData} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
|
||||
<style>
|
||||
.left {
|
||||
background-color: var(--theme-bg-0);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,198 +0,0 @@
|
||||
<script lang="ts" context="module">
|
||||
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) {
|
||||
const res = {
|
||||
scales: {},
|
||||
};
|
||||
if (timeAxis && chartType === 'line') {
|
||||
res.scales = {
|
||||
x: {
|
||||
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 res;
|
||||
}
|
||||
|
||||
function getPlugins(chartTitle) {
|
||||
const res = {};
|
||||
if (chartTitle) {
|
||||
res['title'] = {
|
||||
display: true,
|
||||
text: chartTitle,
|
||||
};
|
||||
}
|
||||
res['zoom'] = {
|
||||
zoom: {
|
||||
wheel: {
|
||||
enabled: true,
|
||||
},
|
||||
pinch: {
|
||||
enabled: true,
|
||||
},
|
||||
drag: {
|
||||
enabled: true,
|
||||
modifierKey: 'shift',
|
||||
},
|
||||
mode: 'x',
|
||||
},
|
||||
pan: {
|
||||
enabled: true,
|
||||
mode: 'x',
|
||||
},
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function createChartData(
|
||||
freeData,
|
||||
labelColumn,
|
||||
dataColumns,
|
||||
colorSeed,
|
||||
chartType,
|
||||
chartTitle,
|
||||
fillLineChart,
|
||||
dataColumnColors,
|
||||
dataColumnLabels,
|
||||
themeDef
|
||||
) {
|
||||
if (!freeData || !labelColumn || !dataColumns || !freeData.rows || dataColumns.length == 0) return null;
|
||||
const palettes = themeDef?.themeType == 'dark' ? presetDarkPalettes : presetPalettes;
|
||||
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) => {
|
||||
const label = dataColumnLabels[dataColumn];
|
||||
if (chartType == 'line' || chartType == 'bar') {
|
||||
const color = dataColumnColors[dataColumn];
|
||||
if (color) {
|
||||
backgroundColor = palettes[color][4] + '80';
|
||||
borderColor = palettes[color][7];
|
||||
} else {
|
||||
backgroundColor = colors[columnIndex] + '80';
|
||||
borderColor = colors[columnIndex];
|
||||
}
|
||||
} else {
|
||||
backgroundColor = colors;
|
||||
}
|
||||
|
||||
return {
|
||||
label: label || dataColumn,
|
||||
data: freeData.rows.map(row => row[dataColumn]),
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
borderWidth: 1,
|
||||
fill: fillLineChart == false ? false : true,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const options = getOptions(timeAxis, chartType);
|
||||
const plugins = getPlugins(chartTitle);
|
||||
|
||||
// console.log('RES', res);
|
||||
// console.log('OPTIONS', options);
|
||||
|
||||
return [res, options, plugins];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import randomcolor from 'randomcolor';
|
||||
import moment from 'moment';
|
||||
import ChartCore from './ChartCore.svelte';
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import { generate, presetPalettes, presetDarkPalettes, presetPrimaryColors } from '@ant-design/colors';
|
||||
import { extractDataColumnColors, extractDataColumnLabels, extractDataColumns } from './chartDataLoader';
|
||||
import { currentThemeDefinition } from '../stores';
|
||||
|
||||
export let data;
|
||||
// export let menu;
|
||||
|
||||
const { values } = getFormContext();
|
||||
|
||||
let clientWidth;
|
||||
let clientHeight;
|
||||
|
||||
$: dataColumns = extractDataColumns($values);
|
||||
$: dataColumnColors = extractDataColumnColors($values, dataColumns);
|
||||
$: dataColumnLabels = extractDataColumnLabels($values, dataColumns);
|
||||
|
||||
$: chartData = createChartData(
|
||||
data,
|
||||
$values.labelColumn,
|
||||
dataColumns,
|
||||
$values.colorSeed || '5',
|
||||
$values.chartType,
|
||||
$values.chartTitle,
|
||||
$values.fillLineChart,
|
||||
dataColumnColors,
|
||||
dataColumnLabels,
|
||||
$currentThemeDefinition
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="wrapper" bind:clientWidth bind:clientHeight>
|
||||
{#if chartData}
|
||||
{#key `${$values.chartType}|${clientWidth}|${clientHeight}`}
|
||||
<ChartCore
|
||||
width={clientWidth}
|
||||
height={clientHeight}
|
||||
data={chartData[0]}
|
||||
type={$values.chartType}
|
||||
title={$values.chartTitle}
|
||||
options={{ ...chartData[1], plugins: chartData[2] }}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,133 +0,0 @@
|
||||
import type { Select } from 'dbgate-sqltree';
|
||||
import type { EngineDriver } from 'dbgate-types';
|
||||
import _ from 'lodash';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
export async function loadChartStructure(driver: EngineDriver, conid, database, sql) {
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
selectAll: true,
|
||||
topRecords: 1,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
};
|
||||
|
||||
const resp = await apiCall('database-connections/sql-select', { conid, database, select });
|
||||
if (resp.errorMessage) throw new Error(resp.errorMessage);
|
||||
return resp.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 resp = await apiCall('database-connections/sql-select', { conid, database, select });
|
||||
let { rows, columns, errorMessage } = resp;
|
||||
if (errorMessage) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
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;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Loaded chart data', { columns, rows });
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
|
||||
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 function extractDataColumnLabels(values, dataColumns) {
|
||||
const res = {};
|
||||
for (const column of dataColumns) {
|
||||
const label = values[`dataColumnLabel_${column}`];
|
||||
if (label) res[column] = label;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
Reference in New Issue
Block a user