Merge branch 'master' into redis

This commit is contained in:
Jan Prochazka
2022-03-13 17:33:35 +01:00
35 changed files with 394 additions and 52 deletions

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "4.7.3-beta.1", "version": "4.7.3-beta.4",
"name": "dbgate-all", "name": "dbgate-all",
"workspaces": [ "workspaces": [
"packages/*", "packages/*",
@@ -11,7 +11,7 @@
"start:api": "yarn workspace dbgate-api start", "start:api": "yarn workspace dbgate-api start",
"start:app": "cd app && yarn start", "start:app": "cd app && yarn start",
"start:api:portal": "yarn workspace dbgate-api start:portal", "start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:covid": "yarn workspace dbgate-api start:covid", "start:api:singledb": "yarn workspace dbgate-api start:singledb",
"start:web": "yarn workspace dbgate-web dev", "start:web": "yarn workspace dbgate-web dev",
"start:sqltree": "yarn workspace dbgate-sqltree start", "start:sqltree": "yarn workspace dbgate-sqltree start",
"start:tools": "yarn workspace dbgate-tools start", "start:tools": "yarn workspace dbgate-tools start",

View File

@@ -53,8 +53,8 @@
}, },
"scripts": { "scripts": {
"start": "env-cmd node src/index.js", "start": "env-cmd node src/index.js",
"start:portal": "env-cmd -f .env-portal node src/index.js", "start:portal": "env-cmd -f env/portal/.env node src/index.js",
"start:singledb": "env-cmd -f .env-singledb node src/index.js", "start:singledb": "env-cmd -f env/singledb/.env node src/index.js",
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db", "start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db",
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test", "start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test",
"ts": "tsc", "ts": "tsc",

View File

@@ -265,4 +265,16 @@ module.exports = {
return true; return true;
}, },
createConfigFile_meta: true,
async createConfigFile({ appFolder, fileName, content }) {
const file = path.join(appdir(), appFolder, fileName);
if (!(await fs.exists(file))) {
await fs.writeFile(file, JSON.stringify(content, undefined, 2));
socket.emitChanged(`app-files-changed-${appFolder}`);
socket.emitChanged('used-apps-changed');
return true;
}
return false;
},
}; };

View File

@@ -98,6 +98,7 @@ module.exports = {
const app = folder.substring('app:'.length); const app = folder.substring('app:'.length);
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data)); await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
socket.emitChanged(`app-files-changed-${app}`); socket.emitChanged(`app-files-changed-${app}`);
socket.emitChanged('used-apps-changed');
apps.emitChangedDbApp(folder); apps.emitChangedDbApp(folder);
return true; return true;
} else { } else {

View File

@@ -89,6 +89,12 @@ module.exports = {
encoding: 'utf-8', encoding: 'utf-8',
}) })
.then(x => JSON.parse(x)); .then(x => JSON.parse(x));
if (!manifest.keywords) {
continue;
}
if (!manifest.keywords.includes('dbgateplugin')) {
continue;
}
const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md'); const readmeFile = path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'README.md');
// @ts-ignore // @ts-ignore
if (await fs.exists(readmeFile)) { if (await fs.exists(readmeFile)) {

View File

@@ -95,7 +95,12 @@ function start() {
if (platformInfo.isNpmDist) { if (platformInfo.isNpmDist) {
app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public'))); app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
getPort({ port: 5000 }).then(port => { getPort({
port: parseInt(
// @ts-ignore
process.env.PORT || 3000
),
}).then(port => {
server.listen(port, () => { server.listen(port, () => {
console.log(`DbGate API listening on port ${port}`); console.log(`DbGate API listening on port ${port}`);
}); });

View File

@@ -1,18 +1,47 @@
const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream'); const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
const Stream = require('stream');
const ColumnMapTransformStream = require('../utility/ColumnMapTransformStream');
function copyStream(input, output, options) {
const { columns } = options || {};
const transforms = [];
if (columns) {
transforms.push(new ColumnMapTransformStream(columns));
}
if (output.requireFixedStructure) {
transforms.push(new EnsureStreamHeaderStream());
}
// return new Promise((resolve, reject) => {
// Stream.pipeline(input, ...transforms, output, err => {
// if (err) {
// reject(err);
// } else {
// resolve();
// }
// });
// });
function copyStream(input, output) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const finisher = output['finisher'] || output; const finisher = output['finisher'] || output;
finisher.on('finish', resolve); finisher.on('finish', resolve);
finisher.on('error', reject); finisher.on('error', reject);
if (output.requireFixedStructure) { let lastStream = input;
const ensureHeader = new EnsureStreamHeaderStream(); for (const tran of transforms) {
input.pipe(ensureHeader); lastStream.pipe(tran);
ensureHeader.pipe(output); lastStream = tran;
} else {
input.pipe(output);
} }
lastStream.pipe(output);
// if (output.requireFixedStructure) {
// const ensureHeader = new EnsureStreamHeaderStream();
// input.pipe(ensureHeader);
// ensureHeader.pipe(output);
// } else {
// input.pipe(output);
// }
}); });
} }

View File

@@ -0,0 +1,21 @@
const stream = require('stream');
const { transformRowUsingColumnMap } = require('dbgate-tools');
class ColumnMapTransformStream extends stream.Transform {
constructor(columns) {
super({ objectMode: true });
this.columns = columns;
}
_transform(chunk, encoding, done) {
if (chunk.__isStreamHeader) {
// skip stream header
done();
return;
}
this.push(transformRowUsingColumnMap(chunk, this.columns));
done();
}
}
module.exports = ColumnMapTransformStream;

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
async function saveFreeTableData(file, data) { async function saveFreeTableData(file, data) {
const { structure, rows } = data; const { structure, rows } = data;
const fileStream = fs.createWriteStream(file); const fileStream = fs.createWriteStream(file);
await fileStream.write(JSON.stringify(structure) + '\n'); await fileStream.write(JSON.stringify({ __isStreamHeader: true, ...structure }) + '\n');
for (const row of rows) { for (const row of rows) {
await fileStream.write(JSON.stringify(row) + '\n'); await fileStream.write(JSON.stringify(row) + '\n');
} }

View File

@@ -166,11 +166,12 @@ export abstract class GridDisplay {
hideAllColumns() { hideAllColumns() {
this.setConfig(cfg => ({ this.setConfig(cfg => ({
...cfg, ...cfg,
hiddenColumns: this.columns.map(x => x.uniqueName), hiddenColumns: this.columns.filter(x => x.uniquePath.length == 1).map(x => x.uniqueName),
})); }));
} }
get hiddenColumnIndexes() { get hiddenColumnIndexes() {
// console.log('GridDisplay.hiddenColumn', this.config.hiddenColumns);
return (this.config.hiddenColumns || []).map(x => _.findIndex(this.allColumns, y => y.uniqueName == x)); return (this.config.hiddenColumns || []).map(x => _.findIndex(this.allColumns, y => y.uniqueName == x));
} }
@@ -569,6 +570,20 @@ export abstract class GridDisplay {
return sql; return sql;
} }
getExportColumnMap() {
const changesDefined = this.config.hiddenColumns?.length > 0 || this.config.addedColumns?.length > 0;
if (this.isDynamicStructure && !changesDefined) {
return null;
}
return this.getColumns(null)
.filter(col => col.isChecked)
.map(col => ({
dst: col.headerText,
src: col.uniqueName,
ignore: !changesDefined,
}));
}
resizeColumn(uniqueName: string, computedSize: number, diff: number) { resizeColumn(uniqueName: string, computedSize: number, diff: number) {
this.setConfig(cfg => { this.setConfig(cfg => {
const columnWidths = { const columnWidths = {

View File

@@ -11,7 +11,7 @@ npm install -g dbgate
After installing, you can run dbgate with command: After installing, you can run dbgate with command:
```sh ```sh
dbgate dbgate-serve
``` ```
Then open http://localhost:5000 in your browser Then open http://localhost:5000 in your browser

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
const path = require('path'); const path = require('path');
require('dotenv').config();
global.dbgateApiModulePath = path.dirname(path.dirname(require.resolve('dbgate-api'))); global.dbgateApiModulePath = path.dirname(path.dirname(require.resolve('dbgate-api')));
global.dbgateApiPackagedPluginsPath = path.dirname(global.dbgateApiModulePath); global.dbgateApiPackagedPluginsPath = path.dirname(global.dbgateApiModulePath);

View File

@@ -10,7 +10,7 @@
"author": "Jan Prochazka", "author": "Jan Prochazka",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"dbgate": "./bin/dbgate.js" "dbgate-serve": "./bin/dbgate-serve.js"
}, },
"keywords": [ "keywords": [
"sql", "sql",
@@ -19,12 +19,14 @@
], ],
"dependencies": { "dependencies": {
"dbgate-api": "^4.1.1", "dbgate-api": "^4.1.1",
"dbgate-web": "^4.1.1",
"dbgate-plugin-csv": "^4.1.1", "dbgate-plugin-csv": "^4.1.1",
"dbgate-plugin-excel": "^4.1.1", "dbgate-plugin-excel": "^4.1.1",
"dbgate-plugin-mongo": "^4.1.1", "dbgate-plugin-mongo": "^4.1.1",
"dbgate-plugin-mysql": "^4.1.1",
"dbgate-plugin-mssql": "^4.1.1", "dbgate-plugin-mssql": "^4.1.1",
"dbgate-plugin-postgres": "^4.1.1" "dbgate-plugin-mysql": "^4.1.1",
"dbgate-plugin-postgres": "^4.1.1",
"dbgate-plugin-xml": "^4.1.1",
"dbgate-web": "^4.1.1",
"dotenv": "^16.0.0"
} }
} }

View File

@@ -1,5 +1,7 @@
import { TableInfo } from 'dbgate-types'; import { TableInfo } from 'dbgate-types';
import _cloneDeep from 'lodash/cloneDeep'; import _cloneDeep from 'lodash/cloneDeep';
import _fromPairs from 'lodash/fromPairs';
import _get from 'lodash/get';
export function prepareTableForImport(table: TableInfo): TableInfo { export function prepareTableForImport(table: TableInfo): TableInfo {
const res = _cloneDeep(table); const res = _cloneDeep(table);
@@ -10,3 +12,12 @@ export function prepareTableForImport(table: TableInfo): TableInfo {
if (res.primaryKey) res.primaryKey.constraintName = null; if (res.primaryKey) res.primaryKey.constraintName = null;
return res; return res;
} }
interface TransformColumnDefinition {
src: string;
dst: string;
}
export function transformRowUsingColumnMap(row, columns: TransformColumnDefinition[]) {
return _fromPairs(columns.map(col => [col.dst, _get(row, col.src)]));
}

View File

@@ -174,6 +174,7 @@
initialValues.sourceDatabaseName = database; initialValues.sourceDatabaseName = database;
initialValues.sourceSql = getExportQuery(); initialValues.sourceSql = getExportQuery();
initialValues.sourceList = [pureName]; initialValues.sourceList = [pureName];
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
showModal(ImportExportModal, { initialValues }); showModal(ImportExportModal, { initialValues });
} }
@@ -208,7 +209,8 @@
sql: getExportQuery(), sql: getExportQuery(),
}, },
}, },
fmt fmt,
display.getExportColumnMap()
); );
}; };

View File

@@ -175,10 +175,12 @@
if (domFocusField) domFocusField.focus(); if (domFocusField) domFocusField.focus();
}} }}
on:setvisibility={e => { on:setvisibility={e => {
for (const name of selectedColumns) { if (selectedColumns.includes(column.uniqueName)) {
const column = items.find(x => x.uniqueName == name); for (const name of selectedColumns) {
if (column) { const column = items.find(x => x.uniqueName == name);
display.setColumnVisibility(column.uniquePath, e.detail); if (column) {
display.setColumnVisibility(column.uniquePath, e.detail);
}
} }
} }
}} }}

View File

@@ -800,6 +800,7 @@
// $: console.log('containerHeight', containerHeight); // $: console.log('containerHeight', containerHeight);
// $: console.log('COLUMNS', columns); // $: console.log('COLUMNS', columns);
// $: console.log('columnSizes.realCount', columnSizes.realCount);
// $: console.log('realColumnUniqueNames', realColumnUniqueNames); // $: console.log('realColumnUniqueNames', realColumnUniqueNames);
// $: console.log('columnSizes.realCount', columnSizes.realCount); // $: console.log('columnSizes.realCount', columnSizes.realCount);

View File

@@ -55,6 +55,7 @@
import RowsArrayGrider from './RowsArrayGrider'; import RowsArrayGrider from './RowsArrayGrider';
export let jslid; export let jslid;
export let display;
export const activator = createActivator('JslDataGridCore', false); export const activator = createActivator('JslDataGridCore', false);
@@ -93,10 +94,12 @@
initialValues.sourceStorageType = 'archive'; initialValues.sourceStorageType = 'archive';
initialValues.sourceArchiveFolder = archiveMatch[1]; initialValues.sourceArchiveFolder = archiveMatch[1];
initialValues.sourceList = [archiveMatch[2]]; initialValues.sourceList = [archiveMatch[2]];
initialValues[`columns_${archiveMatch[2]}`] = display.getExportColumnMap();
} else { } else {
initialValues.sourceStorageType = 'jsldata'; initialValues.sourceStorageType = 'jsldata';
initialValues.sourceJslId = jslid; initialValues.sourceJslId = jslid;
initialValues.sourceList = ['query-data']; initialValues.sourceList = ['query-data'];
initialValues[`columns_query-data`] = display.getExportColumnMap();
} }
showModal(ImportExportModal, { initialValues }); showModal(ImportExportModal, { initialValues });
} }
@@ -113,7 +116,8 @@
fileName: archiveMatch[2], fileName: archiveMatch[2],
}, },
}, },
fmt fmt,
display.getExportColumnMap()
); );
} else { } else {
exportQuickExportFile( exportQuickExportFile(
@@ -124,7 +128,8 @@
jslid, jslid,
}, },
}, },
fmt fmt,
display.getExportColumnMap()
); );
} }
}; };

View File

@@ -99,9 +99,12 @@ export class SeriesSizes {
} }
this.modelIndexes = _.range(0, this.count); this.modelIndexes = _.range(0, this.count);
// console.log('SeriesSize:build:this.modelIndexes-before', this.modelIndexes);
// console.log('SeriesSize:build:this.hiddenAndFrozenModelIndexes', this.hiddenAndFrozenModelIndexes);
if (this.hiddenAndFrozenModelIndexes) { if (this.hiddenAndFrozenModelIndexes) {
this.modelIndexes = this.modelIndexes.filter(col => !this.hiddenAndFrozenModelIndexes.includes(col)); this.modelIndexes = this.modelIndexes.filter(col => !this.hiddenAndFrozenModelIndexes.includes(col));
} }
// console.log('SeriesSize:build:this.modelIndexes-result', this.modelIndexes);
} }
public getScrollIndexOnPosition(position: number): number { public getScrollIndexOnPosition(position: number): number {

View File

@@ -135,6 +135,7 @@
initialValues.sourceDatabaseName = database; initialValues.sourceDatabaseName = database;
initialValues.sourceSql = display.getExportQuery(); initialValues.sourceSql = display.getExportQuery();
initialValues.sourceList = display.baseTableOrSimilar ? [display.baseTableOrSimilar.pureName] : []; initialValues.sourceList = display.baseTableOrSimilar ? [display.baseTableOrSimilar.pureName] : [];
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
showModal(ImportExportModal, { initialValues }); showModal(ImportExportModal, { initialValues });
} }
@@ -193,7 +194,8 @@
sql: display.getExportQuery(), sql: display.getExportQuery(),
}, },
}, },
fmt fmt,
display.getExportColumnMap()
); );
}; };
registerQuickExportHandler(quickExportHandler); registerQuickExportHandler(quickExportHandler);

View File

@@ -17,6 +17,7 @@ export function countColumnSizes(grider: Grider, columns, containerWidth, displa
//return this.context.measureText(txt).width; //return this.context.measureText(txt).width;
// console.log('countColumnSizes', loadedRows.length, containerWidth); // console.log('countColumnSizes', loadedRows.length, containerWidth);
// console.log('countColumnSizes:columns', columns);
columnSizes.maxSize = (containerWidth * 2) / 3; columnSizes.maxSize = (containerWidth * 2) / 3;
columnSizes.count = columns.length; columnSizes.count = columns.length;
@@ -114,10 +115,12 @@ export function countVisibleRealColumns(columnSizes, firstVisibleColumnScrollInd
) { ) {
visibleRealColumnIndexes.push(colIndex + columnSizes.frozenCount); visibleRealColumnIndexes.push(colIndex + columnSizes.frozenCount);
} }
// console.log('countVisibleRealColumns:visibleRealColumnIndexes', visibleRealColumnIndexes);
// real columns // real columns
for (let colIndex of visibleRealColumnIndexes) { for (let colIndex of visibleRealColumnIndexes) {
let modelColumnIndex = columnSizes.realToModel(colIndex); let modelColumnIndex = columnSizes.realToModel(colIndex);
// console.log('countVisibleRealColumns:modelColumnIndex', modelColumnIndex);
modelIndexes[colIndex] = modelColumnIndex; modelIndexes[colIndex] = modelColumnIndex;
let col = columns[modelColumnIndex]; let col = columns[modelColumnIndex];
@@ -129,6 +132,7 @@ export function countVisibleRealColumns(columnSizes, firstVisibleColumnScrollInd
width, width,
}); });
} }
// console.log('countVisibleRealColumns:realColumns', realColumns);
return realColumns; return realColumns;
} }

View File

@@ -24,6 +24,7 @@
export let selectedIndex = 0; export let selectedIndex = 0;
export let clickable = false; export let clickable = false;
export let disableFocusOutline = false; export let disableFocusOutline = false;
export let emptyMessage = null;
export let domTable = undefined; export let domTable = undefined;
@@ -99,6 +100,11 @@
{/each} {/each}
</tr> </tr>
{/each} {/each}
{#if emptyMessage && rows.length == 0}
<tr>
<td colspan={columnList.length}>{emptyMessage}</td>
</tr>
{/if}
</tbody> </tbody>
</table> </table>

View File

@@ -16,6 +16,7 @@
import { createGridCache, FreeTableGridDisplay } from 'dbgate-datalib'; import { createGridCache, FreeTableGridDisplay } from 'dbgate-datalib';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import uuidv1 from 'uuid/v1'; import uuidv1 from 'uuid/v1';
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
import registerCommand from '../commands/registerCommand'; import registerCommand from '../commands/registerCommand';
import DataGridCore from '../datagrid/DataGridCore.svelte'; import DataGridCore from '../datagrid/DataGridCore.svelte';
@@ -24,6 +25,8 @@
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import { registerMenu } from '../utility/contextMenu'; import { registerMenu } from '../utility/contextMenu';
import createActivator, { getActiveComponent } from '../utility/createActivator'; import createActivator, { getActiveComponent } from '../utility/createActivator';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportQuickExportFile } from '../utility/exportFileTools';
import FreeTableGrider from './FreeTableGrider'; import FreeTableGrider from './FreeTableGrider';
import MacroPreviewGrider from './MacroPreviewGrider'; import MacroPreviewGrider from './MacroPreviewGrider';
@@ -51,10 +54,31 @@
initialValues.sourceStorageType = 'jsldata'; initialValues.sourceStorageType = 'jsldata';
initialValues.sourceJslId = jslid; initialValues.sourceJslId = jslid;
initialValues.sourceList = ['editor-data']; initialValues.sourceList = ['editor-data'];
initialValues[`columns_editor-data`] = display.getExportColumnMap();
showModal(ImportExportModal, { initialValues: initialValues }); showModal(ImportExportModal, { initialValues: initialValues });
} }
registerMenu({ command: 'freeTableGrid.export', tag: 'export' }); const quickExportHandler = fmt => async () => {
const jslid = uuidv1();
await apiCall('jsldata/save-free-table', { jslid, data: modelState.value });
exportQuickExportFile(
'editor-data',
{
functionName: 'jslDataReader',
props: {
jslid,
},
},
fmt,
display.getExportColumnMap()
);
};
registerQuickExportHandler(quickExportHandler);
registerMenu(() => ({
...createQuickExportMenu(quickExportHandler, { command: 'freeTableGrid.export' }),
tag: 'export',
}));
</script> </script>
<DataGridCore {...$$props} {grider} {display} frameSelection={!!macroPreview} bind:selectedCellsPublished /> <DataGridCore {...$$props} {grider} {display} frameSelection={!!macroPreview} bind:selectedCellsPublished />

View File

@@ -42,11 +42,14 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import Link from '../elements/Link.svelte';
import TableControl from '../elements/TableControl.svelte'; import TableControl from '../elements/TableControl.svelte';
import CheckboxField from '../forms/CheckboxField.svelte'; import CheckboxField from '../forms/CheckboxField.svelte';
import { getFormContext } from '../forms/FormProviderCore.svelte'; import { getFormContext } from '../forms/FormProviderCore.svelte';
import TextField from '../forms/TextField.svelte'; import TextField from '../forms/TextField.svelte';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import ColumnMapModal from '../modals/ColumnMapModal.svelte';
import { showModal } from '../modals/modalTools';
import { findFileFormat } from '../plugins/fileformats'; import { findFileFormat } from '../plugins/fileformats';
import { extensions } from '../stores'; import { extensions } from '../stores';
import getAsArray from '../utility/getAsArray'; import getAsArray from '../utility/getAsArray';
@@ -189,6 +192,11 @@
header: 'Preview', header: 'Preview',
slot: 0, slot: 0,
}, },
{
fieldName: 'columns',
header: 'Columns',
slot: 2,
},
]} ]}
> >
<svelte:fragment slot="0" let:row> <svelte:fragment slot="0" let:row>
@@ -214,6 +222,18 @@
)} )}
/> />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="2" let:row>
{@const columnCount = ($values[`columns_${row}`] || []).filter(x => !x.skip).length}
<Link
onClick={() => {
showModal(ColumnMapModal, {
value: $values[`columns_${row}`],
onConfirm: value => setFieldValue(`columns_${row}`, value),
});
}}
>{columnCount > 0 ? `(${columnCount} columns)` : '(copy from source)'}
</Link>
</svelte:fragment>
</TableControl> </TableControl>
</div> </div>
</div> </div>

View File

@@ -25,12 +25,20 @@ export default class ScriptWriter {
this.packageNames.push(...extractShellApiPlugins(functionName, props)); this.packageNames.push(...extractShellApiPlugins(functionName, props));
} }
assignValue(variableName, jsonValue) {
this.put(`const ${variableName} = ${JSON.stringify(jsonValue)};`);
}
requirePackage(packageName) { requirePackage(packageName) {
this.packageNames.push(packageName); this.packageNames.push(packageName);
} }
copyStream(sourceVar, targetVar) { copyStream(sourceVar, targetVar, colmapVar = null) {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`); if (colmapVar) {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar}, {columns: ${colmapVar}});`);
} else {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`);
}
} }
comment(s) { comment(s) {

View File

@@ -160,6 +160,21 @@ function getTargetExpr(extensions, sourceName, values, targetConnection, targetD
throw new Error(`Unknown target storage type: ${targetStorageType}`); throw new Error(`Unknown target storage type: ${targetStorageType}`);
} }
export function normalizeExportColumnMap(colmap) {
if (!colmap) {
return null;
}
if (!colmap.find(x => !x.ignore)) {
// all values are ignored, ignore column map
return null;
}
colmap = colmap.filter(x => !x.skip);
if (colmap.length > 0) {
return colmap.map(x => _.omit(x, ['ignore']));
}
return null;
}
export default async function createImpExpScript(extensions, values, addEditorInfo = true) { export default async function createImpExpScript(extensions, values, addEditorInfo = true) {
const script = new ScriptWriter(values.startVariableIndex || 0); const script = new ScriptWriter(values.startVariableIndex || 0);
@@ -186,7 +201,15 @@ export default async function createImpExpScript(extensions, values, addEditorIn
// @ts-ignore // @ts-ignore
script.assign(targetVar, ...getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver)); script.assign(targetVar, ...getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver));
script.copyStream(sourceVar, targetVar); const colmap = normalizeExportColumnMap(values[`columns_${sourceName}`] );
let colmapVar = null;
if (colmap) {
colmapVar = script.allocVariable();
script.assignValue(colmapVar, colmap);
}
script.copyStream(sourceVar, targetVar, colmapVar);
script.put(); script.put();
} }
if (addEditorInfo) { if (addEditorInfo) {

View File

@@ -0,0 +1,91 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import Link from '../elements/Link.svelte';
import TableControl from '../elements/TableControl.svelte';
import CheckboxField from '../forms/CheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import TextField from '../forms/TextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
export let header = 'Configure columns';
export let onConfirm;
export let value = [];
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">{header}</div>
<div class="m-3">
When no columns are defined in this mapping, source row is copied to target without any modifications
</div>
<TableControl
columns={[
{ fieldName: 'use', header: 'Use', slot: 4 },
{ fieldName: 'src', header: 'Source column', slot: 1 },
{ fieldName: 'dst', header: 'Target column', slot: 2 },
{ fieldName: 'actions', header: '', slot: 3 },
]}
rows={value || []}
emptyMessage="No transform defined"
>
<svelte:fragment slot="4" let:row let:index>
<CheckboxField
checked={!row['skip']}
on:change={e =>
(value = (value || []).map((x, i) => (i == index ? { ...x, skip: !e.target.checked, ignore: false } : x)))}
/>
</svelte:fragment>
<svelte:fragment slot="1" let:row let:index>
<TextField
value={row['src']}
on:change={e =>
(value = (value || []).map((x, i) => (i == index ? { ...x, src: e.target.value, ignore: false } : x)))}
/>
</svelte:fragment>
<svelte:fragment slot="2" let:row let:index>
<TextField
value={row['dst']}
on:change={e =>
(value = (value || []).map((x, i) => (i == index ? { ...x, dst: e.target.value, ignore: false } : x)))}
/>
</svelte:fragment>
<svelte:fragment slot="3" let:index>
<Link
onClick={() => {
value = value.filter((x, i) => i != index);
}}>Remove</Link
>
</svelte:fragment>
</TableControl>
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
on:click={() => {
closeCurrentModal();
onConfirm(!value || value.length == 0 ? null : value);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton
type="button"
value="Add column"
on:click={() => {
value = [...(value || []), {}];
}}
/>
<FormStyledButton
type="button"
value="Reset"
on:click={() => {
value = [];
}}
/>
</svelte:fragment>
</ModalBase>
</FormProvider>

View File

@@ -34,7 +34,7 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte'; import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
import ToolStripContainer from '../buttons/ToolStripContainer.svelte'; import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
import ToolStripExportButton from '../buttons/ToolStripExportButton.svelte'; import ToolStripExportButton, { createQuickExportHandlerRef } from '../buttons/ToolStripExportButton.svelte';
import registerCommand from '../commands/registerCommand'; import registerCommand from '../commands/registerCommand';
import DataGrid from '../datagrid/DataGrid.svelte'; import DataGrid from '../datagrid/DataGrid.svelte';
import ErrorInfo from '../elements/ErrorInfo.svelte'; import ErrorInfo from '../elements/ErrorInfo.svelte';
@@ -137,6 +137,8 @@ import ToolStripExportButton from '../buttons/ToolStripExportButton.svelte';
// display is overridden in FreeTableGridCore, this is because of column manager // display is overridden in FreeTableGridCore, this is because of column manager
$: display = new FreeTableGridDisplay($modelState.value, $config, config.update, null, null); $: display = new FreeTableGridDisplay($modelState.value, $config, config.update, null, null);
const quickExportHandlerRef = createQuickExportHandlerRef();
</script> </script>
{#if isLoading} {#if isLoading}
@@ -161,7 +163,7 @@ import ToolStripExportButton from '../buttons/ToolStripExportButton.svelte';
/> />
<svelte:fragment slot="toolstrip"> <svelte:fragment slot="toolstrip">
<ToolStripCommandButton command="freeTable.save" /> <ToolStripCommandButton command="freeTable.save" />
<ToolStripExportButton command="freeTableGrid.export" /> <ToolStripExportButton command="freeTableGrid.export" {quickExportHandlerRef} />
</svelte:fragment> </svelte:fragment>
</ToolStripContainer> </ToolStripContainer>
{/if} {/if}

View File

@@ -22,6 +22,8 @@
import useEditorData from '../query/useEditorData'; import useEditorData from '../query/useEditorData';
import invalidateCommands from '../commands/invalidateCommands'; import invalidateCommands from '../commands/invalidateCommands';
import createActivator, { getActiveComponent } from '../utility/createActivator'; import createActivator, { getActiveComponent } from '../utility/createActivator';
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
import ToolStripSaveButton from '../buttons/ToolStripSaveButton.svelte';
export let tabid; export let tabid;
@@ -70,14 +72,20 @@
} }
</script> </script>
<AceEditor <ToolStripContainer>
value={$editorState.value || ''} <AceEditor
menu={createMenu()} value={$editorState.value || ''}
on:input={e => setEditorData(e.detail)} menu={createMenu()}
on:focus={() => { on:input={e => setEditorData(e.detail)}
activator.activate(); on:focus={() => {
invalidateCommands(); activator.activate();
}} invalidateCommands();
bind:this={domEditor} }}
mode="json" bind:this={domEditor}
/> mode="json"
/>
<svelte:fragment slot="toolstrip">
<ToolStripSaveButton idPrefix="json" />
</svelte:fragment>
</ToolStripContainer>

View File

@@ -3,8 +3,9 @@ import getElectron from './getElectron';
import { showSnackbar, showSnackbarInfo, showSnackbarError, closeSnackbar } from '../utility/snackbar'; import { showSnackbar, showSnackbarInfo, showSnackbarError, closeSnackbar } from '../utility/snackbar';
import resolveApi from './resolveApi'; import resolveApi from './resolveApi';
import { apiCall, apiOff, apiOn } from './api'; import { apiCall, apiOff, apiOn } from './api';
import { normalizeExportColumnMap } from '../impexp/createImpExpScript';
export async function exportQuickExportFile(dataName, reader, format) { export async function exportQuickExportFile(dataName, reader, format, columnMap = null) {
const electron = getElectron(); const electron = getElectron();
let filePath; let filePath;
@@ -31,7 +32,14 @@ export async function exportQuickExportFile(dataName, reader, format) {
const writer = format.createWriter(filePath, dataName); const writer = format.createWriter(filePath, dataName);
script.assign(targetVar, writer.functionName, writer.props); script.assign(targetVar, writer.functionName, writer.props);
script.copyStream(sourceVar, targetVar); const colmap = normalizeExportColumnMap(columnMap);
let colmapVar = null;
if (colmap) {
colmapVar = script.allocVariable();
script.assignValue(colmapVar, colmap);
}
script.copyStream(sourceVar, targetVar, colmapVar);
script.put(); script.put();
const resp = await apiCall('runners/start', { script: script.getScript() }); const resp = await apiCall('runners/start', { script: script.getScript() });

View File

@@ -37,6 +37,7 @@
import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders'; import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders';
import openNewTab from '../utility/openNewTab'; import openNewTab from '../utility/openNewTab';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte'; import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import { showSnackbarError } from '../utility/snackbar';
let filter = ''; let filter = '';
@@ -66,12 +67,27 @@
}); });
} }
async function handleNewConfigFile(fileName, content) {
if (!(await apiCall('apps/create-config-file', { fileName, content, appFolder: $currentApplication }))) {
showSnackbarError('File not created, probably already exists');
}
}
function createAddMenu() { function createAddMenu() {
return [ return [
{ {
text: 'New SQL command', text: 'New SQL command',
onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE), onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE),
}, },
{
text: 'New virtual references file',
onClick: () => handleNewConfigFile('virtual-references.config.json', []),
},
{
text: 'New disctionary descriptions file',
onClick: () => handleNewConfigFile('dictionary-descriptions.config.json', []),
},
// { text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE) }, // { text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE) },
]; ];
} }

View File

@@ -387,15 +387,17 @@
draggingDbGroupTarget = null; draggingDbGroupTarget = null;
}} }}
> >
<FontIcon icon={getDbIcon(tabGroup.tabDbKey)} padRight /> <div class="db-name-inner">
{tabGroup.tabDbName} <FontIcon icon={getDbIcon(tabGroup.tabDbKey)} />
{tabGroup.tabDbName}
</div>
<span <div
class="close-button-right tabCloseButton" class="close-button-right tabCloseButton"
on:click={e => closeMultipleTabs(tab => tabGroup.tabs.find(x => x.tabid == tab.tabid))} on:click={e => closeMultipleTabs(tab => tabGroup.tabs.find(x => x.tabid == tab.tabid))}
> >
<FontIcon icon="icon close" /> <FontIcon icon="icon close" />
</span> </div>
</div> </div>
<div class="db-group"> <div class="db-group">
{#each tabGroup.tabs as tab} {#each tabGroup.tabs as tab}
@@ -505,7 +507,10 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
}
.db-name-inner {
justify-content: center; justify-content: center;
flex-grow: 1;
} }
/* .db-name:hover { /* .db-name:hover {
background-color: var(--theme-bg-3); background-color: var(--theme-bg-3);
@@ -541,7 +546,6 @@
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
color: var(--theme-font-3); color: var(--theme-font-3);
float: right;
} }
.close-button:hover { .close-button:hover {

View File

@@ -3199,6 +3199,11 @@ dbgate-plugin-tools@^1.0.4, dbgate-plugin-tools@^1.0.7:
pacote "^11.1.13" pacote "^11.1.13"
rimraf "^3.0.2" rimraf "^3.0.2"
dbgate-plugin-xml@^4.1.1:
version "4.7.2"
resolved "https://registry.yarnpkg.com/dbgate-plugin-xml/-/dbgate-plugin-xml-4.7.2.tgz#4a1817a7fc3b1ac5191e45dc7b1016b2f97c3158"
integrity sha512-LA52/Wg+blaRp/H+v4ypT+l6kbi4sYsTD/JdPaTbfwErMXhGZew9wtDolvbDjKZZ/zfRig0TDXj9fhLILw2cGw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3: debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -3488,6 +3493,11 @@ dot-prop@^4.1.0:
dependencies: dependencies:
is-obj "^1.0.0" is-obj "^1.0.0"
dotenv@^16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==
duplexer2@~0.0.2: duplexer2@~0.0.2:
version "0.0.2" version "0.0.2"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"