Skip to content

Commit

Permalink
generate shell script
Browse files Browse the repository at this point in the history
  • Loading branch information
janproch committed Jun 6, 2020
1 parent f03a8c2 commit 74aa90f
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 32 deletions.
14 changes: 13 additions & 1 deletion packages/web/src/datagrid/DataGridCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,19 @@ export default function DataGridCore(props) {
}

function exportGrid() {
showModal((modalState) => <ImportExportModal modalState={modalState} />);
showModal((modalState) => (
<ImportExportModal
modalState={modalState}
initialValues={{
sourceStorageType: 'database',
sourceConnectionId: conid,
sourceDatabaseName: database,
sourceTables: [
`${display.baseTable && display.baseTable.schemaName}.${display.baseTable && display.baseTable.pureName}`,
],
}}
/>
));
}

function setCellValue(chs, cell, value) {
Expand Down
46 changes: 27 additions & 19 deletions packages/web/src/impexp/ImportExportConfigurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { Formik, Form, useFormik, useFormikContext } from 'formik';
import styled from 'styled-components';
import Select from 'react-select';
import { FontIcon } from '../icons';
import { FormButtonRow, FormSubmit, FormReactSelect, FormConnectionSelect, FormDatabaseSelect } from '../utility/forms';
import {
FormButtonRow,
FormSubmit,
FormReactSelect,
FormConnectionSelect,
FormDatabaseSelect,
FormTablesSelect,
} from '../utility/forms';
import { useConnectionList, useDatabaseList } from '../utility/metadataLoaders';

const Wrapper = styled.div`
Expand Down Expand Up @@ -62,6 +69,7 @@ function SourceTargetConfig({
storageTypeField,
connectionIdField,
databaseNameField,
tablesField,
}) {
const types = [
{ value: 'database', label: 'Database' },
Expand All @@ -80,6 +88,8 @@ function SourceTargetConfig({
<FormConnectionSelect name={connectionIdField} />
<Label>Database</Label>
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} />
<Label>Tables/views</Label>
<FormTablesSelect conidName={connectionIdField} databaseName={databaseNameField} name={tablesField} />
</>
)}
</Column>
Expand All @@ -88,23 +98,21 @@ function SourceTargetConfig({

export default function ImportExportConfigurator() {
return (
<Formik onSubmit={null} initialValues={{ sourceStorageType: 'database', targetStorageType: 'csv' }}>
<Form>
<Wrapper>
<SourceTargetConfig
isSource
storageTypeField="sourceStorageType"
connectionIdField="sourceConnectionId"
databaseNameField="sourceDatabaseName"
/>
<SourceTargetConfig
isTarget
storageTypeField="targetStorageType"
connectionIdField="targetConnectionId"
databaseNameField="targetDatabaseName"
/>
</Wrapper>
</Form>
</Formik>
<Wrapper>
<SourceTargetConfig
isSource
storageTypeField="sourceStorageType"
connectionIdField="sourceConnectionId"
databaseNameField="sourceDatabaseName"
tablesField="sourceTables"
/>
<SourceTargetConfig
isTarget
storageTypeField="targetStorageType"
connectionIdField="targetConnectionId"
databaseNameField="targetDatabaseName"
tablesField="targetTables"
/>
</Wrapper>
);
}
49 changes: 49 additions & 0 deletions packages/web/src/impexp/ScriptCreator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ScriptWriter from './ScriptWriter';

export default class ScriptCreator {
constructor() {
this.varCount = 0;
this.commands = [];
}
allocVariable(prefix = 'var') {
this.varCount += 1;
return `${prefix}${this.varCount}`;
}
getCode() {
const writer = new ScriptWriter();
for (const command of this.commands) {
const { type } = command;
switch (type) {
case 'assign':
{
const { variableName, functionName, props } = command;
writer.assign(variableName, functionName, props);
}
break;
case 'copyStream':
{
const { sourceVar, targetVar } = command;
writer.copyStream(sourceVar, targetVar);
}
break;
}
}
writer.finish();
return writer.s;
}
assign(variableName, functionName, props) {
this.commands.push({
type: 'assign',
variableName,
functionName,
props,
});
}
copyStream(sourceVar, targetVar) {
this.commands.push({
type: 'copyStream',
sourceVar,
targetVar,
});
}
}
26 changes: 26 additions & 0 deletions packages/web/src/impexp/ScriptWriter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default class ScriptWriter {
constructor() {
this.s = '';
this.put('const dbgateApi = require("@dbgate/api");');
this.put('async function run() {');
}

put(s) {
this.s += s;
this.s += '\n';
}

finish() {
this.put('await dbgateApi.copyStream(queryReader, csvWriter);');
this.put('dbgateApi.runScript(run);');
this.put('}');
}

assign(variableName, functionName, props) {
this.put(`const ${variableName} = await dbgateApi.${functionName}(${JSON.stringify(props)});`);
}

copyStream(sourceVar, targetVar) {
this.put(`await dbgateApi.copyStream(${sourceVar}, ${targetVar});`);
}
}
50 changes: 50 additions & 0 deletions packages/web/src/impexp/createImpExpScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ScriptCreator from './ScriptCreator';
import getAsArray from '../utility/getAsArray';
import { getConnectionInfo } from '../utility/metadataLoaders';
import engines from '@dbgate/engines';

function splitFullName(name) {
const i = name.indexOf('.');
if (i >= 0)
return {
schemaName: name.substr(0, i),
pureName: name.substr(i + 1),
};
return {
schemaName: null,
pureName: name,
};
}
function quoteFullName(dialect, { schemaName, pureName }) {
if (schemaName) return `${dialect.quoteIdentifier(schemaName)}.${dialect.quoteIdentifier(pureName)}`;
return `${dialect.quoteIdentifier(pureName)}`;
}

export default async function createImpExpScript(values) {
const script = new ScriptCreator();
if (values.sourceStorageType == 'database') {
const tables = getAsArray(values.sourceTables);
for (const table of tables) {
const sourceVar = script.allocVariable();
const connection = await getConnectionInfo({ conid: values.sourceConnectionId });
const driver = engines(connection);

const fullName = splitFullName(table);
script.assign(sourceVar, 'queryReader', {
connection: {
...connection,
database: values.sourceDatabaseName,
},
sql: `select * from ${quoteFullName(driver.dialect, fullName)}`,
});

const targetVar = script.allocVariable();
script.assign(targetVar, 'csvWriter', {
fileName: `${fullName.pureName}.csv`,
});

script.copyStream(sourceVar, targetVar);
}
}
return script.getCode();
}
1 change: 1 addition & 0 deletions packages/web/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'ace-builds/src-noconflict/mode-sql';
import 'ace-builds/src-noconflict/mode-mysql';
import 'ace-builds/src-noconflict/mode-pgsql';
import 'ace-builds/src-noconflict/mode-sqlserver';
import 'ace-builds/src-noconflict/mode-javascript';
import 'ace-builds/src-noconflict/theme-github';
import 'ace-builds/src-noconflict/ext-searchbox';
import 'ace-builds/src-noconflict/ext-language_tools';
Expand Down
42 changes: 34 additions & 8 deletions packages/web/src/modals/ImportExportModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,43 @@ import ModalFooter from './ModalFooter';
import ModalContent from './ModalContent';
import { useConnectionList, useDatabaseList } from '../utility/metadataLoaders';
import ImportExportConfigurator from '../impexp/ImportExportConfigurator';
import createImpExpScript from '../impexp/createImpExpScript';
import { openNewTab } from '../utility/common';
import { useSetOpenedTabs } from '../utility/globalState';
import { Formik, Form, useFormik, useFormikContext } from 'formik';

export default function ImportExportModal({ modalState }) {
export default function ImportExportModal({ modalState, initialValues }) {
const setOpenedTabs = useSetOpenedTabs();

const handleSubmit = async (values) => {
const code = await createImpExpScript(values);
openNewTab(setOpenedTabs, {
title: 'Shell',
icon: 'trigger.svg',
tabComponent: 'ShellTab',
props: {
initialScript: code,
},
});
modalState.close();
};
return (
<ModalBase modalState={modalState}>
<ModalHeader modalState={modalState}>Import/Export</ModalHeader>
<ModalContent>
<ImportExportConfigurator />
</ModalContent>
<ModalFooter>
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
</ModalFooter>
<Formik
onSubmit={handleSubmit}
initialValues={{ sourceStorageType: 'database', targetStorageType: 'csv', ...initialValues }}
>
<Form>
<ModalHeader modalState={modalState}>Import/Export</ModalHeader>
<ModalContent>
<ImportExportConfigurator />
</ModalContent>
<ModalFooter>
<FormStyledButton type="submit" value="Export" />
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
</ModalFooter>
</Form>
</Formik>
</ModalBase>
);
}
69 changes: 69 additions & 0 deletions packages/web/src/sqleditor/JavaScriptEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import styled from 'styled-components';
import AceEditor from 'react-ace';
import useDimensions from '../utility/useDimensions';

const Wrapper = styled.div`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
`;

export default function JavaScriptEditor({
value = undefined,
readOnly = false,
onChange = undefined,
tabVisible = false,
onKeyDown = undefined,
editorRef = undefined,
focusOnCreate = false,
}) {
const [containerRef, { height, width }] = useDimensions();
const ownEditorRef = React.useRef(null);

const currentEditorRef = editorRef || ownEditorRef;

React.useEffect(() => {
if ((tabVisible || focusOnCreate) && currentEditorRef.current && currentEditorRef.current.editor)
currentEditorRef.current.editor.focus();
}, [tabVisible, focusOnCreate]);

const handleKeyDown = React.useCallback(
async (data, hash, keyString, keyCode, event) => {
if (onKeyDown) onKeyDown(data, hash, keyString, keyCode, event);
},
[onKeyDown]
);

React.useEffect(() => {
if ((onKeyDown || !readOnly) && currentEditorRef.current) {
currentEditorRef.current.editor.keyBinding.addKeyboardHandler(handleKeyDown);
}
return () => {
currentEditorRef.current.editor.keyBinding.removeKeyboardHandler(handleKeyDown);
};
}, [handleKeyDown]);

return (
<Wrapper ref={containerRef}>
<AceEditor
ref={currentEditorRef}
mode="javascript"
theme="github"
onChange={onChange}
name="UNIQUE_ID_OF_DIV"
editorProps={{ $blockScrolling: true }}
setOptions={{
showPrintMargin: false,
}}
value={value}
readOnly={readOnly}
fontSize="11pt"
width={`${width}px`}
height={`${height}px`}
/>
</Wrapper>
);
}
Loading

0 comments on commit 74aa90f

Please sign in to comment.