mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-24 03:45:59 +00:00
SYNC: Merge pull request #4 from dbgate/feature/charts
This commit is contained in:
@@ -185,10 +185,6 @@
|
||||
isImport: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/charts') && {
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
];
|
||||
case 'views':
|
||||
return [
|
||||
@@ -245,10 +241,6 @@
|
||||
isExport: true,
|
||||
functionName: 'tableReader',
|
||||
},
|
||||
{
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
];
|
||||
case 'matviews':
|
||||
return [
|
||||
@@ -299,10 +291,6 @@
|
||||
isExport: true,
|
||||
functionName: 'tableReader',
|
||||
},
|
||||
{
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
];
|
||||
case 'queries':
|
||||
return [
|
||||
@@ -472,28 +460,7 @@
|
||||
return driver;
|
||||
};
|
||||
|
||||
if (menu.isActiveChart) {
|
||||
const driver = await getDriver();
|
||||
const dmp = driver.createDumper();
|
||||
dmp.put('^select * from %f', data);
|
||||
openNewTab(
|
||||
{
|
||||
title: data.pureName,
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {
|
||||
conid: data.conid,
|
||||
database: data.database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
config: { chartType: 'bar' },
|
||||
sql: dmp.s,
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (menu.isQueryDesigner) {
|
||||
if (menu.isQueryDesigner) {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
|
||||
@@ -41,16 +41,6 @@
|
||||
label: 'Markdown file',
|
||||
};
|
||||
|
||||
const charts: FileTypeHandler = {
|
||||
icon: 'img chart',
|
||||
format: 'json',
|
||||
tabComponent: 'ChartTab',
|
||||
folder: 'charts',
|
||||
currentConnection: true,
|
||||
extension: 'json',
|
||||
label: 'Chart file',
|
||||
};
|
||||
|
||||
const query: FileTypeHandler = {
|
||||
icon: 'img query-design',
|
||||
format: 'json',
|
||||
@@ -139,7 +129,6 @@
|
||||
sql,
|
||||
shell,
|
||||
markdown,
|
||||
charts,
|
||||
query,
|
||||
sqlite,
|
||||
diagrams,
|
||||
|
||||
@@ -261,13 +261,6 @@
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().openFreeTable(),
|
||||
});
|
||||
registerCommand({
|
||||
id: 'dataGrid.openChartFromSelection',
|
||||
category: 'Data grid',
|
||||
name: 'Open chart from selection',
|
||||
testEnabled: () => getCurrentDataGrid() != null,
|
||||
onClick: () => getCurrentDataGrid().openChartFromSelection(),
|
||||
});
|
||||
registerCommand({
|
||||
id: 'dataGrid.newJson',
|
||||
category: 'Data grid',
|
||||
@@ -469,6 +462,7 @@
|
||||
export let hideGridLeftColumn = false;
|
||||
export let overlayDefinition = null;
|
||||
export let onGetSelectionMenu = null;
|
||||
export let onOpenChart = null;
|
||||
|
||||
export const activator = createActivator('DataGridCore', false);
|
||||
|
||||
@@ -715,23 +709,6 @@
|
||||
openJsonLinesData(getSelectedFreeDataRows());
|
||||
}
|
||||
|
||||
export function openChartFromSelection() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Chart #',
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
data: getSelectedFreeData(),
|
||||
config: { chartType: 'bar' },
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function viewJsonDocumentEnabled() {
|
||||
return isDynamicStructure && _.uniq(selectedCells.map(x => x[0])).length == 1;
|
||||
}
|
||||
@@ -1869,9 +1846,13 @@
|
||||
// ],
|
||||
// },
|
||||
isProApp() && { command: 'dataGrid.sendToDataDeploy' },
|
||||
isProApp() &&
|
||||
onOpenChart && {
|
||||
text: 'Open chart',
|
||||
onClick: () => onOpenChart(),
|
||||
},
|
||||
{ command: 'dataGrid.generateSqlFromData' },
|
||||
{ command: 'dataGrid.openFreeTable' },
|
||||
{ command: 'dataGrid.openChartFromSelection' },
|
||||
{ command: 'dataGrid.openSelectionInMap', hideDisabled: true },
|
||||
{ placeTag: 'chart' }
|
||||
);
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
<script context="module" lang="ts">
|
||||
const getCurrentEditor = () => getActiveComponent('SqlDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.openActiveChart',
|
||||
category: 'Data grid',
|
||||
name: 'Open active chart',
|
||||
testEnabled: () => getCurrentEditor() != null && hasPermission('dbops/charts'),
|
||||
onClick: () => getCurrentEditor().openActiveChart(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.openQuery',
|
||||
category: 'Data grid',
|
||||
@@ -190,28 +182,6 @@
|
||||
openQuery(display.getPageQueryText(0, getIntSettingsValue('dataGrid.pageSize', 100, 5, 1000)));
|
||||
}
|
||||
|
||||
export function openActiveChart() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Chart #',
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
config: { chartType: 'bar' },
|
||||
sql: display.getExportQuery(select => {
|
||||
select.orderBy = null;
|
||||
}),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
exportQuickExportFile(
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
$: size = computeSplitterSize(initialValue, clientWidth, customRatio, initialSizeRight);
|
||||
|
||||
$: if (onChangeSize) onChangeSize(size);
|
||||
$: if (onChangeSize) onChangeSize(size, clientWidth - size);
|
||||
</script>
|
||||
|
||||
<div class="container" bind:clientWidth>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let flex1 = true;
|
||||
export let contentTestId = undefined;
|
||||
export let inlineTabs = false;
|
||||
export let onUserChange = null;
|
||||
|
||||
export function setValue(index) {
|
||||
value = index;
|
||||
@@ -30,8 +31,16 @@
|
||||
<div class="main" class:flex1>
|
||||
<div class="tabs" class:inlineTabs>
|
||||
{#each _.compact(tabs) as tab, index}
|
||||
<div class="tab-item" class:selected={value == index} on:click={() => (value = index)} data-testid={tab.testid}>
|
||||
<span class="ml-2">
|
||||
<div
|
||||
class="tab-item"
|
||||
class:selected={value == index}
|
||||
on:click={() => {
|
||||
value = index;
|
||||
onUserChange?.(index);
|
||||
}}
|
||||
data-testid={tab.testid}
|
||||
>
|
||||
<span class="ml-2 noselect">
|
||||
{tab.label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -139,5 +148,4 @@
|
||||
.container.isInline:not(.tabVisible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
'icon trigger': 'mdi mdi-lightning-bolt',
|
||||
'icon scheduler-event': 'mdi mdi-calendar-blank',
|
||||
'icon arrow-link': 'mdi mdi-arrow-top-right-thick',
|
||||
'icon reset': 'mdi mdi-cancel',
|
||||
|
||||
'icon window-restore': 'mdi mdi-window-restore',
|
||||
'icon window-maximize': 'mdi mdi-window-maximize',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import _, { result } from 'lodash';
|
||||
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { apiOff, apiOn } from '../utility/api';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import AllResultsTab from './AllResultsTab.svelte';
|
||||
import JslChart from '../charts/JslChart.svelte';
|
||||
|
||||
export let tabs = [];
|
||||
export let sessionId;
|
||||
@@ -16,6 +17,8 @@
|
||||
export let driver;
|
||||
|
||||
export let resultCount;
|
||||
export let onSetFrontMatterField;
|
||||
export let onGetFrontMatter;
|
||||
|
||||
onMount(() => {
|
||||
allResultsInOneTab = $allResultsInOneTabDefault;
|
||||
@@ -23,6 +26,7 @@
|
||||
|
||||
let allResultsInOneTab = null;
|
||||
let resultInfos = [];
|
||||
let charts = [];
|
||||
let domTabs;
|
||||
|
||||
$: resultCount = resultInfos.length;
|
||||
@@ -35,6 +39,23 @@
|
||||
if (!currentTab?.isResult) domTabs.setValue(_.findIndex(allTabs, x => x.isResult));
|
||||
};
|
||||
|
||||
const handleCharts = async props => {
|
||||
charts = [
|
||||
...charts,
|
||||
{
|
||||
jslid: props.jslid,
|
||||
charts: props.charts,
|
||||
resultIndex: props.resultIndex,
|
||||
},
|
||||
];
|
||||
const selectedChart = onGetFrontMatter?.()?.['selected-chart'];
|
||||
await tick();
|
||||
if (selectedChart && props.resultIndex == selectedChart - 1) {
|
||||
domTabs.setValue(_.findIndex(allTabs, x => x.isChart && x.resultIndex === props.resultIndex));
|
||||
}
|
||||
// console.log('Charts received for jslid:', props.jslid, 'Charts:', props.charts);
|
||||
};
|
||||
|
||||
$: oneTab = allResultsInOneTab ?? $allResultsInOneTabDefault;
|
||||
|
||||
$: allTabs = [
|
||||
@@ -55,13 +76,27 @@
|
||||
label: `Result ${index + 1}`,
|
||||
isResult: true,
|
||||
component: JslDataGrid,
|
||||
props: { jslid: info.jslid, driver },
|
||||
props: { jslid: info.jslid, driver, onOpenChart: () => handleOpenChart(info.resultIndex) },
|
||||
}))),
|
||||
...charts.map((info, index) => ({
|
||||
label: `Chart ${info.resultIndex + 1}`,
|
||||
isChart: true,
|
||||
resultIndex: info.resultIndex,
|
||||
component: JslChart,
|
||||
props: {
|
||||
jslid: info.jslid,
|
||||
initialCharts: info.charts,
|
||||
onEditDefinition: definition => {
|
||||
onSetFrontMatterField?.(`chart-${info.resultIndex + 1}`, definition ?? undefined);
|
||||
},
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
$: {
|
||||
if (executeNumber >= 0) {
|
||||
resultInfos = [];
|
||||
charts = [];
|
||||
if (domTabs) domTabs.setValue(0);
|
||||
}
|
||||
}
|
||||
@@ -72,8 +107,10 @@
|
||||
function onSession(sid) {
|
||||
if (sid) {
|
||||
apiOn(`session-recordset-${sid}`, handleResultSet);
|
||||
apiOn(`session-charts-${sid}`, handleCharts);
|
||||
return () => {
|
||||
apiOff(`session-recordset-${sid}`, handleResultSet);
|
||||
apiOff(`session-charts-${sid}`, handleCharts);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
@@ -84,6 +121,25 @@
|
||||
allResultsInOneTab = value;
|
||||
$allResultsInOneTabDefault = value;
|
||||
}
|
||||
|
||||
async function handleOpenChart(resultIndex) {
|
||||
const chartTab = _.find(allTabs, x => x.isChart && x.resultIndex === resultIndex);
|
||||
if (chartTab) {
|
||||
domTabs.setValue(_.findIndex(allTabs, x => x.isChart && x.resultIndex === resultIndex));
|
||||
} else {
|
||||
charts = [
|
||||
...charts,
|
||||
{
|
||||
jslid: resultInfos[resultIndex].jslid,
|
||||
charts: [],
|
||||
resultIndex,
|
||||
},
|
||||
];
|
||||
await tick();
|
||||
domTabs.setValue(_.findIndex(allTabs, x => x.isChart && x.resultIndex === resultIndex));
|
||||
}
|
||||
onSetFrontMatterField?.('selected-chart', resultIndex + 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<TabControl
|
||||
@@ -94,6 +150,13 @@
|
||||
? { text: 'Every result in single tab', onClick: () => setOneTabValue(false) }
|
||||
: { text: 'All results in one tab', onClick: () => setOneTabValue(true) },
|
||||
]}
|
||||
onUserChange={value => {
|
||||
if (allTabs[value].isChart) {
|
||||
onSetFrontMatterField?.(`selected-chart`, allTabs[value].resultIndex + 1);
|
||||
} else {
|
||||
onSetFrontMatterField?.(`selected-chart`, undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<slot name="0" slot="0" />
|
||||
<slot name="1" slot="1" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
const getCurrentEditor = () => getActiveComponent('QueryTab');
|
||||
|
||||
@@ -60,6 +61,13 @@
|
||||
getCurrentEditor() != null && !getCurrentEditor()?.isBusy() && getCurrentEditor()?.hasConnection(),
|
||||
onClick: () => getCurrentEditor().executeCurrent(),
|
||||
});
|
||||
registerCommand({
|
||||
id: 'query.toggleAutoExecute',
|
||||
category: 'Query',
|
||||
name: 'Toggle auto execute',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().toggleAutoExecute(),
|
||||
});
|
||||
registerCommand({
|
||||
id: 'query.beginTransaction',
|
||||
category: 'Query',
|
||||
@@ -126,7 +134,7 @@
|
||||
import InsertJoinModal from '../modals/InsertJoinModal.svelte';
|
||||
import useTimerLabel from '../utility/useTimerLabel';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { findEngineDriver, safeJsonParse } from 'dbgate-tools';
|
||||
import { findEngineDriver, getSqlFrontMatter, safeJsonParse, setSqlFrontMatter } from 'dbgate-tools';
|
||||
import AceEditor from '../query/AceEditor.svelte';
|
||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
@@ -147,6 +155,7 @@
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||
import RowsLimitModal from '../modals/RowsLimitModal.svelte';
|
||||
import _ from 'lodash';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -199,6 +208,7 @@
|
||||
let domAiAssistant;
|
||||
let isInTransaction = false;
|
||||
let isAutocommit = false;
|
||||
let splitterInitialValue = undefined;
|
||||
|
||||
const queryRowsLimitLocalStorageKey = `tabdata_limitRows_${tabid}`;
|
||||
function getInitialRowsLimit() {
|
||||
@@ -350,6 +360,7 @@
|
||||
executeStartLine = startLine;
|
||||
executeNumber++;
|
||||
visibleResultTabs = true;
|
||||
const frontMatter = getSqlFrontMatter($editorValue, yaml);
|
||||
|
||||
busy = true;
|
||||
timerLabel.start();
|
||||
@@ -381,6 +392,7 @@
|
||||
sql,
|
||||
autoCommit: driver?.implicitTransactions && isAutocommit,
|
||||
limitRows: queryRowsLimit ? queryRowsLimit : undefined,
|
||||
frontMatter,
|
||||
});
|
||||
}
|
||||
await apiCall('query-history/write', {
|
||||
@@ -550,12 +562,47 @@
|
||||
initialArgs && initialArgs.scriptTemplate
|
||||
? () => applyScriptTemplate(initialArgs.scriptTemplate, $extensions, $$props)
|
||||
: null,
|
||||
|
||||
onInitialData: value => {
|
||||
const frontMatter = getSqlFrontMatter(value, yaml);
|
||||
if (frontMatter?.autoExecute) {
|
||||
executeCore(value, 0);
|
||||
}
|
||||
if (frontMatter?.splitterInitialValue) {
|
||||
splitterInitialValue = frontMatter.splitterInitialValue;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function handleChangeErrors(errors) {
|
||||
errorMessages = errors;
|
||||
}
|
||||
|
||||
function handleSetFrontMatterField(field, value) {
|
||||
const text = $editorValue;
|
||||
setEditorData(
|
||||
setSqlFrontMatter(
|
||||
text,
|
||||
{
|
||||
...getSqlFrontMatter(text, yaml),
|
||||
[field]: value,
|
||||
},
|
||||
yaml
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function toggleAutoExecute() {
|
||||
const frontMatter = getSqlFrontMatter($editorValue, yaml);
|
||||
setEditorData(
|
||||
setSqlFrontMatter(
|
||||
$editorValue,
|
||||
{ ...frontMatter, autoExecute: frontMatter?.autoExecute ? undefined : true },
|
||||
yaml
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function handleKeyDown(event) {
|
||||
if (isProApp()) {
|
||||
if (event.code == 'Space' && event.shiftKey && event.ctrlKey && !isAiAssistantVisible) {
|
||||
@@ -584,6 +631,7 @@
|
||||
{ command: 'query.execute' },
|
||||
{ command: 'query.executeCurrent' },
|
||||
{ command: 'query.kill' },
|
||||
{ command: 'query.toggleAutoExecute' },
|
||||
{ divider: true },
|
||||
{ command: 'query.toggleComment' },
|
||||
{ command: 'query.formatCode' },
|
||||
@@ -625,7 +673,7 @@
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
|
||||
<svelte:fragment slot="1">
|
||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
|
||||
<svelte:fragment slot="1">
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
<SqlEditor
|
||||
@@ -678,7 +726,15 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="2">
|
||||
<ResultTabs tabs={[{ label: 'Messages', slot: 0 }]} {sessionId} {executeNumber} bind:resultCount {driver}>
|
||||
<ResultTabs
|
||||
tabs={[{ label: 'Messages', slot: 0 }]}
|
||||
{sessionId}
|
||||
{executeNumber}
|
||||
bind:resultCount
|
||||
{driver}
|
||||
onSetFrontMatterField={handleSetFrontMatterField}
|
||||
onGetFrontMatter={() => getSqlFrontMatter($editorValue, yaml)}
|
||||
>
|
||||
<svelte:fragment slot="0">
|
||||
<SocketMessageView
|
||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||
|
||||
Reference in New Issue
Block a user