mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-02 04:23:57 +00:00
better loading free table data
This commit is contained in:
@@ -11,7 +11,18 @@ const scriptTemplate = (script) => `
|
|||||||
const dbgateApi = require(process.env.DBGATE_API || "@dbgate/api");
|
const dbgateApi = require(process.env.DBGATE_API || "@dbgate/api");
|
||||||
require=null;
|
require=null;
|
||||||
async function run() {
|
async function run() {
|
||||||
${script}
|
const reader = ${script}
|
||||||
|
}
|
||||||
|
dbgateApi.runScript(run);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const loaderScriptTemplate = (functionName, props, runid) => `
|
||||||
|
const dbgateApi = require(process.env.DBGATE_API || "@dbgate/api");
|
||||||
|
require=null;
|
||||||
|
async function run() {
|
||||||
|
const reader=await dbgateApi.${functionName}(${JSON.stringify(props)});
|
||||||
|
const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
|
||||||
|
await dbgateApi.copyStream(reader, writer);
|
||||||
}
|
}
|
||||||
dbgateApi.runScript(run);
|
dbgateApi.runScript(run);
|
||||||
`;
|
`;
|
||||||
@@ -19,9 +30,10 @@ dbgateApi.runScript(run);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
/** @type {import('@dbgate/types').OpenedRunner[]} */
|
/** @type {import('@dbgate/types').OpenedRunner[]} */
|
||||||
opened: [],
|
opened: [],
|
||||||
|
requests: {},
|
||||||
|
|
||||||
dispatchMessage(runid, message) {
|
dispatchMessage(runid, message) {
|
||||||
// console.log('DISPATCHING', message);
|
if (message) console.log('...', message.message);
|
||||||
if (_.isString(message)) {
|
if (_.isString(message)) {
|
||||||
socket.emit(`runner-info-${runid}`, {
|
socket.emit(`runner-info-${runid}`, {
|
||||||
message,
|
message,
|
||||||
@@ -40,12 +52,24 @@ module.exports = {
|
|||||||
|
|
||||||
handle_ping() {},
|
handle_ping() {},
|
||||||
|
|
||||||
start_meta: 'post',
|
handle_freeData(runid, { freeData }) {
|
||||||
async start({ script }) {
|
const [resolve, reject] = this.requests[runid];
|
||||||
const runid = uuidv1();
|
resolve(freeData);
|
||||||
|
delete this.requests[runid];
|
||||||
|
},
|
||||||
|
|
||||||
|
rejectRequest(runid, error) {
|
||||||
|
if (this.requests[runid]) {
|
||||||
|
const [resolve, reject] = this.requests[runid];
|
||||||
|
reject(error);
|
||||||
|
delete this.requests[runid];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startCore(runid, scriptText) {
|
||||||
const directory = path.join(rundir(), runid);
|
const directory = path.join(rundir(), runid);
|
||||||
const scriptFile = path.join(uploadsdir(), runid + '.js');
|
const scriptFile = path.join(uploadsdir(), runid + '.js');
|
||||||
fs.writeFileSync(`${scriptFile}`, scriptTemplate(script));
|
fs.writeFileSync(`${scriptFile}`, scriptText);
|
||||||
fs.mkdirSync(directory);
|
fs.mkdirSync(directory);
|
||||||
console.log(`RUNNING SCRIPT ${scriptFile}`);
|
console.log(`RUNNING SCRIPT ${scriptFile}`);
|
||||||
const subprocess = fork(scriptFile, ['--checkParent'], {
|
const subprocess = fork(scriptFile, ['--checkParent'], {
|
||||||
@@ -61,9 +85,13 @@ module.exports = {
|
|||||||
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
|
byline(subprocess.stdout).on('data', pipeDispatcher('info'));
|
||||||
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
|
byline(subprocess.stderr).on('data', pipeDispatcher('error'));
|
||||||
subprocess.on('exit', (code) => {
|
subprocess.on('exit', (code) => {
|
||||||
|
this.rejectRequest(runid, { message: 'No data retured, maybe input data source is too big' });
|
||||||
|
console.log('... EXIT process', code);
|
||||||
socket.emit(`runner-done-${runid}`, code);
|
socket.emit(`runner-done-${runid}`, code);
|
||||||
});
|
});
|
||||||
subprocess.on('error', (error) => {
|
subprocess.on('error', (error) => {
|
||||||
|
this.rejectRequest(runid, { message: error && (error.message || error.toString()) });
|
||||||
|
console.error('... ERROR subprocess', error);
|
||||||
this.dispatchMessage({
|
this.dispatchMessage({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
message: error.toString(),
|
message: error.toString(),
|
||||||
@@ -81,6 +109,12 @@ module.exports = {
|
|||||||
return newOpened;
|
return newOpened;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
start_meta: 'post',
|
||||||
|
async start({ script }) {
|
||||||
|
const runid = uuidv1();
|
||||||
|
return this.startCore(runid, scriptTemplate(script));
|
||||||
|
},
|
||||||
|
|
||||||
cancel_meta: 'post',
|
cancel_meta: 'post',
|
||||||
async cancel({ runid }) {
|
async cancel({ runid }) {
|
||||||
const runner = this.opened.find((x) => x.runid == runid);
|
const runner = this.opened.find((x) => x.runid == runid);
|
||||||
@@ -106,4 +140,14 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadReader_meta: 'post',
|
||||||
|
async loadReader({ functionName, props }) {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
const runid = uuidv1();
|
||||||
|
this.requests[runid] = [resolve, reject];
|
||||||
|
this.startCore(runid, loaderScriptTemplate(functionName, props, runid));
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
33
packages/api/src/shell/collectorWriter.js
Normal file
33
packages/api/src/shell/collectorWriter.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const stream = require('stream');
|
||||||
|
|
||||||
|
class CollectorWriterStream extends stream.Writable {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.rows = [];
|
||||||
|
this.structure = null;
|
||||||
|
this.runid = options.runid;
|
||||||
|
}
|
||||||
|
_write(chunk, enc, next) {
|
||||||
|
if (!this.structure) this.structure = chunk;
|
||||||
|
else this.rows.push(chunk);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
_final(callback) {
|
||||||
|
process.send({
|
||||||
|
msgtype: 'freeData',
|
||||||
|
runid: this.runid,
|
||||||
|
freeData: { rows: this.rows, structure: this.structure },
|
||||||
|
});
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectorWriter({ runid }) {
|
||||||
|
return new CollectorWriterStream({
|
||||||
|
objectMode: true,
|
||||||
|
runid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = collectorWriter;
|
||||||
@@ -13,6 +13,7 @@ const jsonLinesReader = require('./jsonLinesReader');
|
|||||||
const jslDataReader = require('./jslDataReader');
|
const jslDataReader = require('./jslDataReader');
|
||||||
const archiveWriter = require('./archiveWriter');
|
const archiveWriter = require('./archiveWriter');
|
||||||
const archiveReader = require('./archiveReader');
|
const archiveReader = require('./archiveReader');
|
||||||
|
const collectorWriter = require('./collectorWriter');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
queryReader,
|
queryReader,
|
||||||
@@ -30,4 +31,5 @@ module.exports = {
|
|||||||
jslDataReader,
|
jslDataReader,
|
||||||
archiveWriter,
|
archiveWriter,
|
||||||
archiveReader,
|
archiveReader,
|
||||||
|
collectorWriter,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,14 +29,20 @@ function Menu({ data, setOpenedTabs }) {
|
|||||||
openArchive(setOpenedTabs, data.fileName, data.folderName);
|
openArchive(setOpenedTabs, data.fileName, data.folderName);
|
||||||
};
|
};
|
||||||
const handleOpenWrite = async () => {
|
const handleOpenWrite = async () => {
|
||||||
const resp = await axios.post('archive/load-free-table', { file: data.fileName, folder: data.folderName });
|
// const resp = await axios.post('archive/load-free-table', { file: data.fileName, folder: data.folderName });
|
||||||
|
|
||||||
openNewTab(setOpenedTabs, {
|
openNewTab(setOpenedTabs, {
|
||||||
title: data.fileName,
|
title: data.fileName,
|
||||||
icon: 'freetable.svg',
|
icon: 'freetable.svg',
|
||||||
tabComponent: 'FreeTableTab',
|
tabComponent: 'FreeTableTab',
|
||||||
props: {
|
props: {
|
||||||
initialData: resp.data,
|
initialData: {
|
||||||
|
functionName: 'archiveReader',
|
||||||
|
props: {
|
||||||
|
fileName: data.fileName,
|
||||||
|
folderName: data.folderName,
|
||||||
|
},
|
||||||
|
},
|
||||||
archiveFile: data.fileName,
|
archiveFile: data.fileName,
|
||||||
archiveFolder: data.folderName,
|
archiveFolder: data.folderName,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -90,22 +90,6 @@ const RowCountLabel = styled.div`
|
|||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LoadingInfoWrapper = styled.div`
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
`;
|
|
||||||
const LoadingInfoBox = styled.div`
|
|
||||||
background-color: #ccc;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid gray;
|
|
||||||
`;
|
|
||||||
|
|
||||||
/** @param props {import('./types').DataGridProps} */
|
/** @param props {import('./types').DataGridProps} */
|
||||||
export default function DataGridCore(props) {
|
export default function DataGridCore(props) {
|
||||||
const {
|
const {
|
||||||
@@ -296,14 +280,7 @@ export default function DataGridCore(props) {
|
|||||||
return `Rows: ${allRowCount.toLocaleString()}`;
|
return `Rows: ${allRowCount.toLocaleString()}`;
|
||||||
}, [selectedCells, allRowCount, grider, visibleRealColumns]);
|
}, [selectedCells, allRowCount, grider, visibleRealColumns]);
|
||||||
|
|
||||||
if (!columns || columns.length == 0)
|
if (!columns || columns.length == 0) return <LoadingInfo wrapper message="Waiting for structure" />;
|
||||||
return (
|
|
||||||
<LoadingInfoWrapper>
|
|
||||||
<LoadingInfoBox>
|
|
||||||
<LoadingInfo message="Waiting for structure" />
|
|
||||||
</LoadingInfoBox>
|
|
||||||
</LoadingInfoWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
return <ErrorInfo message={errorMessage} />;
|
return <ErrorInfo message={errorMessage} />;
|
||||||
@@ -936,8 +913,8 @@ export default function DataGridCore(props) {
|
|||||||
)}
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody ref={tableBodyRef}>
|
<TableBody ref={tableBodyRef}>
|
||||||
{_.range(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound)
|
{_.range(firstVisibleRowScrollIndex, firstVisibleRowScrollIndex + visibleRowCountUpperBound).map(
|
||||||
.map((rowIndex) => (
|
(rowIndex) => (
|
||||||
<DataGridRow
|
<DataGridRow
|
||||||
key={rowIndex}
|
key={rowIndex}
|
||||||
grider={grider}
|
grider={grider}
|
||||||
@@ -952,7 +929,8 @@ export default function DataGridCore(props) {
|
|||||||
display={display}
|
display={display}
|
||||||
focusedColumn={display.focusedColumn}
|
focusedColumn={display.focusedColumn}
|
||||||
/>
|
/>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<HorizontalScrollBar
|
<HorizontalScrollBar
|
||||||
@@ -972,23 +950,14 @@ export default function DataGridCore(props) {
|
|||||||
viewportRatio={visibleRowCountUpperBound / grider.rowCount}
|
viewportRatio={visibleRowCountUpperBound / grider.rowCount}
|
||||||
/>
|
/>
|
||||||
{allRowCount && <RowCountLabel>{rowCountInfo}</RowCountLabel>}
|
{allRowCount && <RowCountLabel>{rowCountInfo}</RowCountLabel>}
|
||||||
{props.toolbarPortalRef && props.toolbarPortalRef.current &&
|
{props.toolbarPortalRef &&
|
||||||
|
props.toolbarPortalRef.current &&
|
||||||
tabVisible &&
|
tabVisible &&
|
||||||
ReactDOM.createPortal(
|
ReactDOM.createPortal(
|
||||||
<DataGridToolbar
|
<DataGridToolbar reload={() => display.reload()} save={handleSave} grider={grider} />,
|
||||||
reload={() => display.reload()}
|
|
||||||
save={handleSave}
|
|
||||||
grider={grider}
|
|
||||||
/>,
|
|
||||||
props.toolbarPortalRef.current
|
props.toolbarPortalRef.current
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
{isLoading && <LoadingInfo wrapper message="Loading data" />}
|
||||||
<LoadingInfoWrapper>
|
|
||||||
<LoadingInfoBox>
|
|
||||||
<LoadingInfo message="Loading data" />
|
|
||||||
</LoadingInfoBox>
|
|
||||||
</LoadingInfoWrapper>
|
|
||||||
)}
|
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,33 @@ import FreeTableGrid from '../freetable/FreeTableGrid';
|
|||||||
import SaveArchiveModal from '../modals/SaveArchiveModal';
|
import SaveArchiveModal from '../modals/SaveArchiveModal';
|
||||||
import useModalState from '../modals/useModalState';
|
import useModalState from '../modals/useModalState';
|
||||||
import axios from '../utility/axios';
|
import axios from '../utility/axios';
|
||||||
|
import LoadingInfo from '../widgets/LoadingInfo';
|
||||||
import { changeTab } from '../utility/common';
|
import { changeTab } from '../utility/common';
|
||||||
|
import ErrorInfo from '../widgets/ErrorInfo';
|
||||||
|
|
||||||
export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialData }) {
|
export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialData }) {
|
||||||
const [config, setConfig] = useGridConfig(tabid);
|
const [config, setConfig] = useGridConfig(tabid);
|
||||||
const [modelState, dispatchModel] = useUndoReducer(initialData || createFreeTableModel());
|
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
||||||
const storageKey = `tabdata_freetable_${tabid}`;
|
const storageKey = `tabdata_freetable_${tabid}`;
|
||||||
const saveSqlFileModalState = useModalState();
|
const saveSqlFileModalState = useModalState();
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [errorMessage, setErrorMessage] = React.useState(null);
|
||||||
|
|
||||||
|
const handleLoadInitialData = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const resp = await axios.post('runners/load-reader', initialData);
|
||||||
|
// @ts-ignore
|
||||||
|
dispatchModel({ type: 'reset', value: resp.data });
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setIsLoading(false);
|
||||||
|
const errorMessage = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed';
|
||||||
|
setErrorMessage(errorMessage);
|
||||||
|
console.error(err.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const existingData = localStorage.getItem(storageKey);
|
const existingData = localStorage.getItem(storageKey);
|
||||||
@@ -24,6 +43,8 @@ export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, to
|
|||||||
const value = JSON.parse(existingData);
|
const value = JSON.parse(existingData);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
dispatchModel({ type: 'reset', value });
|
dispatchModel({ type: 'reset', value });
|
||||||
|
} else if (initialData) {
|
||||||
|
handleLoadInitialData();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -40,6 +61,13 @@ export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, to
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingInfo wrapper message="Loading data" />;
|
||||||
|
}
|
||||||
|
if (errorMessage) {
|
||||||
|
return <ErrorInfo message={errorMessage} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FreeTableGrid
|
<FreeTableGrid
|
||||||
|
|||||||
@@ -13,8 +13,24 @@ const Spinner = styled.div`
|
|||||||
margin: 10px;
|
margin: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function LoadingInfo({ message }) {
|
const LoadingInfoWrapper = styled.div`
|
||||||
return (
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
`;
|
||||||
|
const LoadingInfoBox = styled.div`
|
||||||
|
background-color: #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function LoadingInfo({ message, wrapper = false }) {
|
||||||
|
const core = (
|
||||||
<Container>
|
<Container>
|
||||||
<Spinner>
|
<Spinner>
|
||||||
<i className="fas fa-spinner fa-spin" />
|
<i className="fas fa-spinner fa-spin" />
|
||||||
@@ -22,4 +38,13 @@ export default function LoadingInfo({ message }) {
|
|||||||
{message}
|
{message}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
if (wrapper) {
|
||||||
|
return (
|
||||||
|
<LoadingInfoWrapper>
|
||||||
|
<LoadingInfoBox>{core}</LoadingInfoBox>
|
||||||
|
</LoadingInfoWrapper>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return core;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user