mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 01:03:58 +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;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
const getCurrentEditor = () => getActiveComponent('ChartTab');
|
|
||||||
|
|
||||||
registerFileCommands({
|
|
||||||
idPrefix: 'chart',
|
|
||||||
category: 'Chart',
|
|
||||||
getCurrentEditor,
|
|
||||||
folder: 'charts',
|
|
||||||
format: 'json',
|
|
||||||
fileExtension: 'chart',
|
|
||||||
|
|
||||||
undoRedo: true,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { derived } from 'svelte/store';
|
|
||||||
import ChartEditor from '../charts/ChartEditor.svelte';
|
|
||||||
import invalidateCommands from '../commands/invalidateCommands';
|
|
||||||
import registerCommand from '../commands/registerCommand';
|
|
||||||
import { registerFileCommands } from '../commands/stdCommands';
|
|
||||||
|
|
||||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
|
||||||
|
|
||||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
|
||||||
|
|
||||||
import useEditorData from '../query/useEditorData';
|
|
||||||
import { getContextMenu, registerMenu } from '../utility/contextMenu';
|
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
|
||||||
import createUndoReducer from '../utility/createUndoReducer';
|
|
||||||
import resolveApi from '../utility/resolveApi';
|
|
||||||
|
|
||||||
export let tabid;
|
|
||||||
export let conid;
|
|
||||||
export let database;
|
|
||||||
|
|
||||||
export const activator = createActivator('ChartTab', true);
|
|
||||||
|
|
||||||
export function getData() {
|
|
||||||
return $editorState.value || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
|
||||||
tabid,
|
|
||||||
onInitialData: value => {
|
|
||||||
dispatchModel({ type: 'reset', value });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [modelState, dispatchModel] = createUndoReducer({
|
|
||||||
tables: [],
|
|
||||||
references: [],
|
|
||||||
columns: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
$: setEditorData($modelState.value);
|
|
||||||
|
|
||||||
$: {
|
|
||||||
$modelState;
|
|
||||||
invalidateCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
const setConfig = config =>
|
|
||||||
// @ts-ignore
|
|
||||||
dispatchModel({
|
|
||||||
type: 'compute',
|
|
||||||
compute: v => ({ ...v, config: _.isFunction(config) ? config(v.config) : config }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const configDerivedStore = derived(modelState, ($modelState: any) =>
|
|
||||||
$modelState.value ? $modelState.value.config || {} : {}
|
|
||||||
);
|
|
||||||
const configStore = {
|
|
||||||
...configDerivedStore,
|
|
||||||
update: setConfig,
|
|
||||||
set: setConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function canUndo() {
|
|
||||||
return $modelState.canUndo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function undo() {
|
|
||||||
dispatchModel({ type: 'undo' });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canRedo() {
|
|
||||||
return $modelState.canRedo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function redo() {
|
|
||||||
dispatchModel({ type: 'redo' });
|
|
||||||
}
|
|
||||||
|
|
||||||
registerMenu(
|
|
||||||
{ command: 'chart.save' },
|
|
||||||
{ command: 'chart.saveAs' },
|
|
||||||
{ placeTag: 'export' },
|
|
||||||
{ divider: true },
|
|
||||||
{ command: 'chart.undo' },
|
|
||||||
{ command: 'chart.redo' }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $editorState.isLoading}
|
|
||||||
<LoadingInfo wrapper message="Loading data" />
|
|
||||||
{:else if $editorState.errorMessage}
|
|
||||||
<ErrorInfo message={$editorState.errorMessage} />
|
|
||||||
{:else}
|
|
||||||
<ChartEditor
|
|
||||||
data={$modelState.value && $modelState.value.data}
|
|
||||||
{configStore}
|
|
||||||
sql={$modelState.value && $modelState.value.sql}
|
|
||||||
{conid}
|
|
||||||
{database}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
const getCurrentEditor = () => getActiveComponent('ProfilerTab');
|
|
||||||
|
|
||||||
registerCommand({
|
|
||||||
id: 'profiler.start',
|
|
||||||
category: 'Profiler',
|
|
||||||
name: 'Start profiling',
|
|
||||||
icon: 'icon play',
|
|
||||||
testEnabled: () => getCurrentEditor()?.startProfilingEnabled(),
|
|
||||||
onClick: () => getCurrentEditor().startProfiling(),
|
|
||||||
});
|
|
||||||
|
|
||||||
registerCommand({
|
|
||||||
id: 'profiler.stop',
|
|
||||||
category: 'Profiler',
|
|
||||||
name: 'Stop profiling',
|
|
||||||
icon: 'icon play-stop',
|
|
||||||
testEnabled: () => getCurrentEditor()?.stopProfilingEnabled(),
|
|
||||||
onClick: () => getCurrentEditor().stopProfiling(),
|
|
||||||
});
|
|
||||||
|
|
||||||
registerCommand({
|
|
||||||
id: 'profiler.save',
|
|
||||||
category: 'Profiler',
|
|
||||||
name: 'Save',
|
|
||||||
icon: 'icon save',
|
|
||||||
testEnabled: () => getCurrentEditor()?.saveEnabled(),
|
|
||||||
onClick: () => getCurrentEditor().save(),
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { findEngineDriver } from 'dbgate-tools';
|
|
||||||
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
|
|
||||||
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
|
|
||||||
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
|
|
||||||
import invalidateCommands from '../commands/invalidateCommands';
|
|
||||||
import registerCommand from '../commands/registerCommand';
|
|
||||||
import JslDataGrid from '../datagrid/JslDataGrid.svelte';
|
|
||||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
|
||||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
|
||||||
import { showModal } from '../modals/modalTools';
|
|
||||||
import SaveArchiveModal from '../modals/SaveArchiveModal.svelte';
|
|
||||||
import { currentArchive, selectedWidget } from '../stores';
|
|
||||||
import { apiCall } from '../utility/api';
|
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
|
||||||
import { useConnectionInfo } from '../utility/metadataLoaders';
|
|
||||||
import { extensions } from '../stores';
|
|
||||||
import ChartCore from '../charts/ChartCore.svelte';
|
|
||||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
|
||||||
import randomcolor from 'randomcolor';
|
|
||||||
|
|
||||||
export const activator = createActivator('ProfilerTab', true);
|
|
||||||
|
|
||||||
export let conid;
|
|
||||||
export let database;
|
|
||||||
export let engine;
|
|
||||||
export let jslidLoad;
|
|
||||||
|
|
||||||
let jslidSession;
|
|
||||||
|
|
||||||
let isProfiling = false;
|
|
||||||
let sessionId;
|
|
||||||
let isLoadingChart = false;
|
|
||||||
|
|
||||||
let intervalId;
|
|
||||||
let chartData;
|
|
||||||
|
|
||||||
$: connection = useConnectionInfo({ conid });
|
|
||||||
$: driver = findEngineDriver(engine || $connection, $extensions);
|
|
||||||
$: jslid = jslidSession || jslidLoad;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
intervalId = setInterval(() => {
|
|
||||||
if (sessionId) {
|
|
||||||
apiCall('sessions/ping', {
|
|
||||||
sesid: sessionId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 15 * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (jslidLoad && driver) {
|
|
||||||
loadChart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function startProfiling() {
|
|
||||||
isProfiling = true;
|
|
||||||
|
|
||||||
let sesid = sessionId;
|
|
||||||
if (!sesid) {
|
|
||||||
const resp = await apiCall('sessions/create', {
|
|
||||||
conid,
|
|
||||||
database,
|
|
||||||
});
|
|
||||||
sesid = resp.sesid;
|
|
||||||
sessionId = sesid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = await apiCall('sessions/start-profiler', {
|
|
||||||
sesid,
|
|
||||||
});
|
|
||||||
jslidSession = resp.jslid;
|
|
||||||
|
|
||||||
invalidateCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startProfilingEnabled() {
|
|
||||||
return conid && database && !isProfiling;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadChart() {
|
|
||||||
isLoadingChart = true;
|
|
||||||
|
|
||||||
const colors = randomcolor({
|
|
||||||
count: driver.profilerChartMeasures.length,
|
|
||||||
seed: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await apiCall('jsldata/extract-timeline-chart', {
|
|
||||||
jslid,
|
|
||||||
timestampFunction: driver.profilerTimestampFunction,
|
|
||||||
aggregateFunction: driver.profilerChartAggregateFunction,
|
|
||||||
measures: driver.profilerChartMeasures,
|
|
||||||
});
|
|
||||||
chartData = {
|
|
||||||
...data,
|
|
||||||
labels: data.labels.map(x => new Date(x)),
|
|
||||||
datasets: data.datasets.map((x, i) => ({
|
|
||||||
...x,
|
|
||||||
borderColor: colors[i],
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
isLoadingChart = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function stopProfiling() {
|
|
||||||
isProfiling = false;
|
|
||||||
await apiCall('sessions/stop-profiler', { sesid: sessionId });
|
|
||||||
await apiCall('sessions/kill', { sesid: sessionId });
|
|
||||||
sessionId = null;
|
|
||||||
|
|
||||||
invalidateCommands();
|
|
||||||
|
|
||||||
loadChart();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stopProfilingEnabled() {
|
|
||||||
return conid && database && isProfiling;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveEnabled() {
|
|
||||||
return !!jslidSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doSave(folder, file) {
|
|
||||||
await apiCall('archive/save-jsl-data', { folder, file, jslid });
|
|
||||||
currentArchive.set(folder);
|
|
||||||
selectedWidget.set('archive');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function save() {
|
|
||||||
showModal(SaveArchiveModal, {
|
|
||||||
// folder: archiveFolder,
|
|
||||||
// file: archiveFile,
|
|
||||||
onSave: doSave,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// const data = [
|
|
||||||
// { year: 2010, count: 10 },
|
|
||||||
// { year: 2011, count: 20 },
|
|
||||||
// { year: 2012, count: 15 },
|
|
||||||
// { year: 2013, count: 25 },
|
|
||||||
// { year: 2014, count: 22 },
|
|
||||||
// { year: 2015, count: 30 },
|
|
||||||
// { year: 2016, count: 28 },
|
|
||||||
// ];
|
|
||||||
// {
|
|
||||||
// labels: data.map(row => row.year),
|
|
||||||
// datasets: [
|
|
||||||
// {
|
|
||||||
// label: 'Acquisitions by year',
|
|
||||||
// data: data.map(row => row.count),
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ToolStripContainer>
|
|
||||||
{#if jslid}
|
|
||||||
<VerticalSplitter allowCollapseChild1 allowCollapseChild2>
|
|
||||||
<svelte:fragment slot="1">
|
|
||||||
{#key jslid}
|
|
||||||
<JslDataGrid {jslid} listenInitializeFile formatterFunction={driver?.profilerFormatterFunction} />
|
|
||||||
{/key}
|
|
||||||
</svelte:fragment>
|
|
||||||
<svelte:fragment slot="2">
|
|
||||||
{#if isLoadingChart}
|
|
||||||
<LoadingInfo wrapper message="Loading chart" />
|
|
||||||
{:else}
|
|
||||||
<ChartCore
|
|
||||||
title="Profile data"
|
|
||||||
data={chartData}
|
|
||||||
options={{
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</svelte:fragment>
|
|
||||||
</VerticalSplitter>
|
|
||||||
{:else}
|
|
||||||
<ErrorInfo message="Profiler not yet started" alignTop />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<svelte:fragment slot="toolstrip">
|
|
||||||
<ToolStripCommandButton command="profiler.start" />
|
|
||||||
<ToolStripCommandButton command="profiler.stop" />
|
|
||||||
<ToolStripCommandButton command="profiler.save" />
|
|
||||||
</svelte:fragment>
|
|
||||||
</ToolStripContainer>
|
|
||||||
@@ -6,7 +6,6 @@ import * as QueryTab from './QueryTab.svelte';
|
|||||||
import * as ShellTab from './ShellTab.svelte';
|
import * as ShellTab from './ShellTab.svelte';
|
||||||
import * as ArchiveFileTab from './ArchiveFileTab.svelte';
|
import * as ArchiveFileTab from './ArchiveFileTab.svelte';
|
||||||
import * as PluginTab from './PluginTab.svelte';
|
import * as PluginTab from './PluginTab.svelte';
|
||||||
import * as ChartTab from './ChartTab.svelte';
|
|
||||||
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
||||||
import * as MarkdownViewTab from './MarkdownViewTab.svelte';
|
import * as MarkdownViewTab from './MarkdownViewTab.svelte';
|
||||||
import * as MarkdownPreviewTab from './MarkdownPreviewTab.svelte';
|
import * as MarkdownPreviewTab from './MarkdownPreviewTab.svelte';
|
||||||
@@ -23,7 +22,6 @@ import * as QueryDataTab from './QueryDataTab.svelte';
|
|||||||
import * as ConnectionTab from './ConnectionTab.svelte';
|
import * as ConnectionTab from './ConnectionTab.svelte';
|
||||||
import * as MapTab from './MapTab.svelte';
|
import * as MapTab from './MapTab.svelte';
|
||||||
import * as ServerSummaryTab from './ServerSummaryTab.svelte';
|
import * as ServerSummaryTab from './ServerSummaryTab.svelte';
|
||||||
import * as ProfilerTab from './ProfilerTab.svelte';
|
|
||||||
import * as ImportExportTab from './ImportExportTab.svelte';
|
import * as ImportExportTab from './ImportExportTab.svelte';
|
||||||
import * as SqlObjectTab from './SqlObjectTab.svelte';
|
import * as SqlObjectTab from './SqlObjectTab.svelte';
|
||||||
|
|
||||||
@@ -38,7 +36,6 @@ export default {
|
|||||||
ShellTab,
|
ShellTab,
|
||||||
ArchiveFileTab,
|
ArchiveFileTab,
|
||||||
PluginTab,
|
PluginTab,
|
||||||
ChartTab,
|
|
||||||
MarkdownEditorTab,
|
MarkdownEditorTab,
|
||||||
MarkdownViewTab,
|
MarkdownViewTab,
|
||||||
MarkdownPreviewTab,
|
MarkdownPreviewTab,
|
||||||
@@ -55,7 +52,6 @@ export default {
|
|||||||
ConnectionTab,
|
ConnectionTab,
|
||||||
MapTab,
|
MapTab,
|
||||||
ServerSummaryTab,
|
ServerSummaryTab,
|
||||||
ProfilerTab,
|
|
||||||
ImportExportTab,
|
ImportExportTab,
|
||||||
SqlObjectTab,
|
SqlObjectTab,
|
||||||
...protabs,
|
...protabs,
|
||||||
|
|||||||
Reference in New Issue
Block a user