better loading free table data

This commit is contained in:
Jan Prochazka
2020-10-29 10:07:09 +01:00
parent b39af32426
commit 3f14fec678
7 changed files with 158 additions and 51 deletions

View File

@@ -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;
},
}; };

View 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;

View File

@@ -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,
}; };

View File

@@ -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,
}, },

View File

@@ -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>
); );
} }

View File

@@ -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

View File

@@ -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;
}
} }