1
0
Fork 0
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:
Steffen Vogel 2021-06-01 14:32:37 +02:00
parent 2263589359
commit 288a3fcc3b
4 changed files with 265 additions and 1 deletions

View file

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

View file

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

View 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'/>&nbsp;
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'/>&nbsp;
Download Jupyter Notebook
</Button>
</Dialog>
);
}
}
export default ResultPythonDialog;

View file

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