mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
add dialog for using results in Python / Jupyter
This commit is contained in:
parent
2263589359
commit
288a3fcc3b
4 changed files with 265 additions and 1 deletions
|
@ -39,6 +39,7 @@
|
|||
"react-collapse": "^5.1.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-contexify": "^5.0.0",
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dnd": "^14.0.2",
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dom": "^17.0.2",
|
||||
|
@ -49,6 +50,7 @@
|
|||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-svg-pan-zoom": "^3.10.0",
|
||||
"react-syntax-highlighter": "^15.4.3",
|
||||
"react-trafficlight": "^5.2.1",
|
||||
"superagent": "^6.1.0",
|
||||
"swagger-ui-react": "^3.48.0",
|
||||
|
|
|
@ -243,6 +243,20 @@ class CustomTable extends Component {
|
|||
/>);
|
||||
}
|
||||
|
||||
if (child.props.pythonResultsButton) {
|
||||
cell.push(
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
childKey={childkey++}
|
||||
icon={['fab', 'python']}
|
||||
disabled={child.props.onPythonResults == null}
|
||||
tooltip={"Get Python code"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onPythonResults(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (child.props.downloadAllButton) {
|
||||
cell.push(
|
||||
<IconButton
|
||||
|
|
233
src/result/result-python-dialog.js
Normal file
233
src/result/result-python-dialog.js
Normal file
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import Icon from '../common/icon';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { github } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
|
||||
|
||||
class ResultPythonDialog extends React.Component {
|
||||
villasDataProcessingUrl = 'https://pypi.org/project/villas-dataprocessing/';
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.resultId !== prevProps.resultId) {
|
||||
const result = this.props.results[this.props.resultId];
|
||||
const output = this.getJupyterNotebook(result);
|
||||
const blob = new Blob([JSON.stringify(output)], {
|
||||
'type': 'application/x-ipynb+json'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
this.setState({ fileDownloadUrl: url })
|
||||
}
|
||||
}
|
||||
|
||||
downloadJupyterNotebook() {
|
||||
const result = this.props.results[this.props.resultId];
|
||||
const output = this.getJupyterNotebook(result);
|
||||
const blob = new Blob([JSON.stringify(output)], {
|
||||
'type': 'application/x-ipynb+json'
|
||||
});
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.style = 'display: none';
|
||||
a.href = url;
|
||||
a.download = `villas_web_result_${result.id}.ipynb`;
|
||||
document.body.appendChild(a);
|
||||
|
||||
a.click();
|
||||
|
||||
setTimeout(function(){
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
getPythonDependencies() {
|
||||
return `pip install villas-dataprocessing`
|
||||
}
|
||||
|
||||
getPythonSnippet(result) {
|
||||
let token = localStorage.getItem('token');
|
||||
|
||||
let files = [];
|
||||
let hasCSV = false;
|
||||
for (let file of this.props.files) {
|
||||
if (result.resultFileIDs.includes(file.id)) {
|
||||
files.push(file);
|
||||
if (file.type == 'text/csv')
|
||||
hasCSV = true;
|
||||
}
|
||||
}
|
||||
|
||||
let code = '';
|
||||
|
||||
if (hasCSV)
|
||||
code += 'import pandas\n';
|
||||
|
||||
code += `from villas.web.result import Result
|
||||
|
||||
r = Result(${result.id}, '${token}')
|
||||
|
||||
# print(r) # result details
|
||||
# print(r.files) # list of files of this result set
|
||||
|
||||
# f = r.files[0] # first file
|
||||
# f = r.get_files_by_type('text/csv')[0] # first CSV file
|
||||
# f = r.get_file_by_name('result.csv') # by filename`;
|
||||
|
||||
for (let file of files) {
|
||||
console.log(file);
|
||||
code += `\n\nf${file.id} = r.get_file_by_name('${file.name}')`;
|
||||
|
||||
switch (file.type) {
|
||||
case 'application/zip':
|
||||
code += `\nwith f${file.id}.open_zip('testdata.csv') as f:
|
||||
data = pandas.read_csv(f)`;
|
||||
break;
|
||||
|
||||
case 'text/csv':
|
||||
code += `\nwith f${file.id}.open() as f:
|
||||
data = pandas.read_csv(f)`;
|
||||
break;
|
||||
|
||||
default:
|
||||
code += `\nwith f${file.id}.open() as f:
|
||||
data = f.read()`;
|
||||
break;
|
||||
}
|
||||
|
||||
code += `
|
||||
print(data)`;
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/* Generate random cell ids
|
||||
*
|
||||
* See: https://jupyter.org/enhancement-proposals/62-cell-id/cell-id.html
|
||||
*/
|
||||
getCellId() {
|
||||
return Math.round(1000000 * Math.random()).toString();
|
||||
}
|
||||
|
||||
getJupyterNotebook(result) {
|
||||
let ipynb_cells = [];
|
||||
let cells = [
|
||||
this.getPythonDependencies(),
|
||||
this.getPythonSnippet(result)
|
||||
]
|
||||
|
||||
for (let cell of cells) {
|
||||
ipynb_cells.push({
|
||||
cell_type: 'code',
|
||||
execution_count: null,
|
||||
id: this.getCellId(),
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
source: cell.split('\n')
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
cells: ipynb_cells,
|
||||
metadata: {
|
||||
kernelspec: {
|
||||
display_name: 'Python 3',
|
||||
language: 'python',
|
||||
name: 'python3'
|
||||
},
|
||||
language_info: {
|
||||
codemirror_mode: {
|
||||
name: 'ipython',
|
||||
version: 3
|
||||
},
|
||||
file_extension: '.py',
|
||||
mimetype: 'text/x-python',
|
||||
name: 'python',
|
||||
nbconvert_exporter: 'python',
|
||||
pygments_lexer: 'ipython3',
|
||||
version: '3.9.5'
|
||||
}
|
||||
},
|
||||
nbformat: 4,
|
||||
nbformat_minor: 5
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let result = this.props.results[this.props.resultId];
|
||||
|
||||
if (!result)
|
||||
return null;
|
||||
|
||||
let code = this.getPythonSnippet(result);
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title={'Use Result ' + result.id + ' in Jupyter Notebooks'}
|
||||
buttonTitle='Close'
|
||||
onClose={(cancelled) => this.props.onClose()}
|
||||
valid={true}
|
||||
size='lg'
|
||||
blendOutCancel={true}
|
||||
>
|
||||
<p>Use the following Python code-snippet to fetch and load your results as a Pandas dataframe.</p>
|
||||
|
||||
<p><b>1)</b> Please install the <a href={this.villasDataProcessingUrl}>villas-controller</a> Python package:</p>
|
||||
<SyntaxHighlighter
|
||||
language="bash"
|
||||
style={github}>
|
||||
{this.getPythonDependencies()}
|
||||
</SyntaxHighlighter>
|
||||
|
||||
<p><b>2a)</b> Insert the following snippet your Python code:</p>
|
||||
<SyntaxHighlighter
|
||||
language="python"
|
||||
style={github}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
|
||||
<CopyToClipboard text={code}>
|
||||
<Button>
|
||||
<Icon style={{color: 'white'}} icon='clipboard'/>
|
||||
Copy to Clipboard
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<p style={{marginTop: '2em'}}><b>2b)</b> Or alternatively, download the following generated Jupyter notebook to get started:</p>
|
||||
<Button onClick={(e) => this.downloadJupyterNotebook(e)}>
|
||||
<Icon style={{color: 'white'}} icon='download'/>
|
||||
Download Jupyter Notebook
|
||||
</Button>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultPythonDialog;
|
|
@ -26,6 +26,7 @@ import TableColumn from "../common/table-column";
|
|||
import DeleteDialog from "../common/dialogs/delete-dialog";
|
||||
import EditResultDialog from "./edit-result";
|
||||
import ResultConfigDialog from "./result-configs-dialog";
|
||||
import ResultPythonDialog from "./result-python-dialog";
|
||||
import NewDialog from "../common/dialogs/new-dialog";
|
||||
|
||||
class ResultTable extends Component {
|
||||
|
@ -34,6 +35,7 @@ class ResultTable extends Component {
|
|||
super();
|
||||
|
||||
this.state = {
|
||||
pythonResultsModal: false,
|
||||
editResultsModal: false,
|
||||
modalResultsData: {},
|
||||
modalResultsIndex: 0,
|
||||
|
@ -95,6 +97,10 @@ class ResultTable extends Component {
|
|||
this.setState({ editResultsModal: false });
|
||||
}
|
||||
|
||||
closePythonResultsModal() {
|
||||
this.setState({ pythonResultsModal: false });
|
||||
}
|
||||
|
||||
downloadResultData(param) {
|
||||
let toDownload = [];
|
||||
let zip = false;
|
||||
|
@ -218,9 +224,11 @@ class ResultTable extends Component {
|
|||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
pythonResultsButton
|
||||
downloadAllButton
|
||||
deleteButton
|
||||
onEdit={index => this.setState({ editResultsModal: true, modalResultsIndex: index })}
|
||||
onPythonResults={(index) => this.setState({ pythonResultsModal: true, modalResultsIndex: index })}
|
||||
onEdit={(index) => this.setState({ editResultsModal: true, modalResultsIndex: index })}
|
||||
onDownloadAll={(index) => this.downloadResultData(this.props.results[index])}
|
||||
onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.props.results[index], modalResultsIndex: index })}
|
||||
locked={this.props.locked}
|
||||
|
@ -248,6 +256,13 @@ class ResultTable extends Component {
|
|||
resultNo={this.state.modalResultConfigsIndex}
|
||||
onClose={this.closeResultConfigSnapshots.bind(this)}
|
||||
/>
|
||||
<ResultPythonDialog
|
||||
show={this.state.pythonResultsModal}
|
||||
files={this.props.files}
|
||||
results={this.props.results}
|
||||
resultId={this.state.modalResultsIndex}
|
||||
onClose={this.closePythonResultsModal.bind(this)}
|
||||
/>
|
||||
<NewDialog
|
||||
show={this.state.newResultModal}
|
||||
title="New Result"
|
||||
|
|
Loading…
Add table
Reference in a new issue