diff --git a/resources/biseps b/resources/biseps index 22d06ad27c3e77ce71ed3c7cd02117ef6a7a64c2..ee3bcb244d424a7ae1fad451a41885f3bdceb884 160000 --- a/resources/biseps +++ b/resources/biseps @@ -1 +1 @@ -Subproject commit 22d06ad27c3e77ce71ed3c7cd02117ef6a7a64c2 +Subproject commit ee3bcb244d424a7ae1fad451a41885f3bdceb884 diff --git a/resources/snakeReport.yaml b/resources/snakeReport.yaml new file mode 100644 index 0000000000000000000000000000000000000000..960a435bf90c716b9e29347a22b96b6ff88717cd --- /dev/null +++ b/resources/snakeReport.yaml @@ -0,0 +1,126 @@ +name: /home/shatira/mnt/local_workflows/bisspropgui/resources/snakemake +channels: + - anaconda + - bioconda + - defaults + - conda-forge +dependencies: + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=1_gnu + - amply=0.1.4=py_0 + - appdirs=1.4.4=pyh9f0ad1d_0 + - atk-1.0=2.36.0=h3371d22_4 + - attrs=20.3.0=pyhd3deb0d_0 + - brotlipy=0.7.0=py39h3811e60_1001 + - ca-certificates=2020.10.14=0 + - cairo=1.16.0=hf32fb01_1 + - certifi=2021.5.30=py39h06a4308_0 + - cffi=1.14.5=py39he32792d_0 + - chardet=4.0.0=py39hf3d152e_1 + - coincbc=2.10.5=hcee13e7_1 + - configargparse=1.3=pyhd8ed1ab_0 + - cryptography=3.4.4=py39h95dcef6_0 + - datrie=0.8.2=py39h07f9747_1 + - decorator=4.4.2=py_0 + - docutils=0.16=py39hf3d152e_3 + - expat=2.4.1=h2531618_2 + - font-ttf-dejavu-sans-mono=2.37=h6964260_0 + - font-ttf-inconsolata=2.001=hcb22688_0 + - font-ttf-source-code-pro=2.030=h7457263_0 + - font-ttf-ubuntu=0.83=h8b1ccd4_0 + - fontconfig=2.13.1=h6c09931_0 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - freetype=2.10.4=h5ab3b9f_0 + - fribidi=1.0.10=h7b6447c_0 + - gdk-pixbuf=2.42.6=h04a7f16_0 + - gettext=0.19.8.1=h9b4dc7a_1 + - giflib=5.1.4=h14c3975_1 + - gitdb=4.0.5=pyhd8ed1ab_1 + - gitpython=3.1.13=pyhd8ed1ab_0 + - glib=2.68.3=h9c3ff4c_0 + - glib-tools=2.68.3=h9c3ff4c_0 + - graphite2=1.3.14=h23475e2_0 + - graphviz=2.48.0=h85b4f2f_0 + - gtk2=2.24.33=h539f30e_1 + - gts=0.7.6=h64030ff_2 + - harfbuzz=2.8.2=h83ec7ef_0 + - icu=68.1=h2531618_0 + - idna=2.10=pyh9f0ad1d_0 + - importlib-metadata=3.4.0=py39hf3d152e_0 + - importlib_metadata=3.4.0=hd8ed1ab_0 + - ipython_genutils=0.2.0=py_1 + - jinja2=2.11.2=py_0 + - jpeg=9d=h36c2ea0_0 + - jsonschema=3.2.0=py_2 + - jupyter_core=4.7.1=py39hf3d152e_0 + - ld_impl_linux-64=2.35.1=hea4e1c9_2 + - libblas=3.9.0=8_openblas + - libcblas=3.9.0=8_openblas + - libffi=3.3=h58526e2_2 + - libgcc-ng=11.1.0=hc902ee8_8 + - libgd=2.3.2=h78a0170_0 + - libgfortran-ng=9.3.0=hff62375_18 + - libgfortran5=9.3.0=hff62375_18 + - libglib=2.68.3=h3e27bee_0 + - libgomp=11.1.0=hc902ee8_8 + - libiconv=1.16=h516909a_0 + - liblapack=3.9.0=8_openblas + - libopenblas=0.3.12=pthreads_h4812303_1 + - libpng=1.6.37=hbc83047_0 + - librsvg=2.50.7=hc3c00ef_0 + - libstdcxx-ng=9.3.0=h6de172a_18 + - libtiff=4.2.0=h85742a9_0 + - libtool=2.4.6=h7b6447c_1005 + - libuuid=1.0.3=h1bed415_2 + - libwebp=1.1.0=hd31223b_0 + - libwebp-base=1.1.0=h7b6447c_3 + - libxcb=1.14=h7b6447c_0 + - libxml2=2.9.12=h72842e0_0 + - lz4-c=1.9.2=heb0550a_3 + - markupsafe=2.0.1=py39h27cfd23_0 + - nbformat=5.1.2=pyhd8ed1ab_1 + - ncurses=6.2=h58526e2_4 + - networkx=2.5=py_0 + - numpy=1.20.1=py39hdbf815f_0 + - openssl=1.1.1k=h27cfd23_0 + - pandas=1.2.2=py39ha9443f7_0 + - pango=1.48.8=hb8ff022_0 + - pcre=8.44=he6710b0_0 + - pip=21.0.1=pyhd8ed1ab_0 + - pixman=0.40.0=h7b6447c_0 + - psutil=5.8.0=py39h3811e60_1 + - pulp=2.3.1=py39hde42818_0 + - pycparser=2.20=pyh9f0ad1d_2 + - pygments=2.7.1=py_0 + - pygraphviz=1.7=py39h78163bd_0 + - pyopenssl=20.0.1=pyhd8ed1ab_0 + - pyparsing=2.4.7=pyh9f0ad1d_0 + - pyrsistent=0.17.3=py39h3811e60_2 + - pysocks=1.7.1=py39hf3d152e_3 + - python=3.9.2=hffdb5ce_0_cpython + - python-dateutil=2.8.1=py_0 + - python_abi=3.9=1_cp39 + - pytz=2021.1=pyhd3eb1b0_0 + - pyyaml=5.4.1=py39h3811e60_0 + - ratelimiter=1.2.0=py_1002 + - readline=8.1=h27cfd23_0 + - requests=2.25.1=pyhd3deb0d_0 + - setuptools=52.0.0=py39h06a4308_0 + - six=1.15.0=pyh9f0ad1d_0 + - smmap=3.0.5=pyh44b312d_0 + - snakemake-minimal=5.32.2=py_0 + - sqlite=3.34.0=h74cdb3f_0 + - tk=8.6.10=hed695b0_1 + - toposort=1.6=pyhd8ed1ab_0 + - traitlets=5.0.5=py_0 + - tzdata=2021a=he74cb21_0 + - urllib3=1.26.3=pyhd8ed1ab_0 + - wheel=0.36.2=pyhd3deb0d_0 + - wrapt=1.12.1=py39h3811e60_3 + - xz=5.2.5=h516909a_1 + - yaml=0.2.5=h516909a_0 + - zipp=3.4.0=py_0 + - zlib=1.2.11=h516909a_1010 + - zstd=1.4.5=h9ceee32_0 +prefix: /home/shatira/mnt/local_workflows/bisspropgui/resources/snakemake diff --git a/resources/snakemake.sh b/resources/snakemake.sh index 239fa79cb674c6667c9695832c945905f05a1f08..1331d8dc1eac19b475996aa178f274905475fb2f 100755 --- a/resources/snakemake.sh +++ b/resources/snakemake.sh @@ -7,5 +7,7 @@ which python source $sourceEnv/activate cd $workflow pwd -$sourceEnv/snakemake --profile $profile --unlock > $profile/../../../biseps.txt -$sourceEnv/snakemake --profile $profile --rerun-incomplete >> $profile/../../../biseps.txt \ No newline at end of file +$sourceEnv/snakemake --profile $profile --unlock &> $profile/../../../biseps.txt +$sourceEnv/snakemake --profile $profile --rerun-incomplete &>> $profile/../../../biseps.txt +$sourceEnv/snakemake --profile $profile --report $profile/../../../report.html + diff --git a/src/backend/helpers/spawnJbrowse.js b/src/backend/helpers/spawnJbrowse.js index d76a540ca49b0c886ef92b3c63d20dfd7fae8954..b3db2528c34ffa7e5bf4eb0a414d7370f4667122 100644 --- a/src/backend/helpers/spawnJbrowse.js +++ b/src/backend/helpers/spawnJbrowse.js @@ -76,7 +76,7 @@ const spawnChild = (body) => { }); if (exitCode) { - throw new Error(`subprocess error exit ${exitCode}, ${error}`); + // throw new Error(`subprocess error exit ${exitCode}, ${error}`); } return data; }); diff --git a/src/backend/models/Comparison.js b/src/backend/models/Comparison.js index fdaae34e42a6c9675d418b0bdd76abcc4d8c2d77..232b91597f9bd72c9d25c8ccbd674ea74b117b97 100644 --- a/src/backend/models/Comparison.js +++ b/src/backend/models/Comparison.js @@ -7,7 +7,12 @@ const comparisonSchema = new Schema({ outdir: { type: String, required: true }, remoteDir: { type: String, required: false }, method: { type: String, required: false }, + date: { + type: Date, + default: Date.now, + }, stat: { type: String, required: false }, + contexts: [{ type: String, required: false }], binSize: { type: Number, required: false }, pseudocountN: { type: Number, required: false }, pseudocountM: { type: Number, required: false }, diff --git a/src/backend/routes/api/comparisonController.js b/src/backend/routes/api/comparisonController.js index b7b351f1ee6e7571f2f1bf215ac4d7f4b6d84d1e..76729e519b6947afe43482d942a9e411bd49a05e 100644 --- a/src/backend/routes/api/comparisonController.js +++ b/src/backend/routes/api/comparisonController.js @@ -13,7 +13,7 @@ const validateConfigurationInput = require("../../validation/comparisonConfigura const Comparison = require("../../models/Comparison"); const User = require("../../models/User"); -// @route POST api/runs/Run +// @route POST api/comparisons/Run // @desc Run // @access Public @@ -51,6 +51,7 @@ router.post("/comparison", (req, res) => { comparisons: req.body.comparisons, method: req.body.method, stat: req.body.stat, + contexts: req.body.contexts, binSize: req.body.binSize, pseudocountN: req.body.pseudocountN, pseudocountM: req.body.pseudocountM, @@ -105,6 +106,7 @@ router.post("/comparison", (req, res) => { comparisons: req.body.comparisons, method: req.body.method, stat: req.body.stat, + contexts: req.body.contexts, binSize: req.body.binSize, pseudocountN: req.body.pseudocountN, pseudocountM: req.body.pseudocountM, @@ -154,4 +156,39 @@ router.get("/", function (req, res) { }).populate("createdBy"); console.log("GET method"); }); +router.delete("/:id", function (req, res) { + const id = req.params.id; + const userId = req.user._id; + console.log(req.outdir); + console.log(req.user); + Comparison.deleteOne({ _id: id }) + .then((result) => { + res.json(`Deleted ${id}`); + }) + .catch((error) => console.error(error)); + User.findByIdAndUpdate( + userId, + { $pull: { comparisons: id } }, + { safe: true, upsert: true, new: true }, + function (err, model) { + console.log(err); + } + ); +}); + +router.post("/rerun", function (req, res) { + console.log(req.body); + + if (!req.body.remote) { + const profile = path.join(req.body.outdir, "config/profiles/local"); + spawnChild(req.body, profile); + console.log("Rerun Snakemake", profile); + } else { + const profile = path.join(req.body.remoteDir, "config/profiles/local"); + const uniqueDir = req.body.outdir; + const uniqueDirRemote = req.body.remoteDir; + const homeDir = path.join(req.body.remoteDir, uniqueDirRemote); + spawnChild(req.body, profile, uniqueDir, uniqueDirRemote, homeDir); + } +}); module.exports = router; diff --git a/src/backend/routes/api/runController.js b/src/backend/routes/api/runController.js index f60ea7b7ead126cb22e4c9a8c5531fb7e14b9f74..fc6de7d8fa07c35f290d63078fb0c5fdcd9fd6a4 100644 --- a/src/backend/routes/api/runController.js +++ b/src/backend/routes/api/runController.js @@ -161,13 +161,13 @@ router.get("/:id", function (req, res) { }); router.post("/rerun", function (req, res) { console.log(req.body); + const uniqueDir = req.body.outdir; if (!req.body.remote) { const profile = path.join(req.body.outdir, "config/profiles/local"); - spawnChild(req.body, profile); + spawnChild(req.body, profile, uniqueDir); console.log("Rerun Snakemake", profile); } else { - const uniqueDir = req.body.outdir; const uniqueDirRemote = req.body.remoteDir; const homeDir = path.join(req.body.remoteDir, uniqueDirRemote); spawnChild(req.body, profile, uniqueDir, uniqueDirRemote, homeDir); diff --git a/src/backend/snakemake.js b/src/backend/snakemake.js index 1de0fd507d324fb04e5694dbe28198794b057882..cc10116f9a3c98bca5178326864cea7a13e0afa3 100644 --- a/src/backend/snakemake.js +++ b/src/backend/snakemake.js @@ -5,7 +5,7 @@ const spawnChild = async ( uniqueDirRemote, homeDir ) => { - const { execFile, exec } = require("child_process"); + const { execFile, exec, spawn } = require("child_process"); let Client = require("ssh2-sftp-client"); const path = require("path"); const env = path.join(__dirname, "../resources/snakemake/bin"); @@ -50,7 +50,7 @@ const spawnChild = async ( }); if (exitCode) { - throw new Error(`subprocess error exit ${exitCode}, ${error}`); + // throw new Error(`subprocess error exit ${exitCode}, ${error}`); } const connect = require("ssh2-connect"); @@ -153,8 +153,19 @@ const spawnChild = async ( } else { console.log("this is env", env); console.log("this is profile", profile); - + console.log(options); console.log("this is workflow", workflow); + const logfile = `${uniqueDir}/biseps.txt`; + // const child = spawn( + // process.platform === "darwin" ? "bash" : "bash", + + // [localScript, env, profile, workflow], + // options + // ); + // const child = exec( + // `bash ${localScript} ${env} ${profile} ${workflow} > ${logfile}`, + // options + // ); const child = execFile(localScript, [env, profile, workflow], options); let data = ""; @@ -172,7 +183,7 @@ const spawnChild = async ( }); if (exitCode) { - throw new Error(`subprocess error exit ${exitCode}, ${error}`); + // throw new Error(`subprocess error exit ${exitCode}, ${error}`); } return data; } diff --git a/src/components/Comparisons/ComparisonForm.js b/src/components/Comparisons/ComparisonForm.js index 410c5209f713ce54e76486c223b282f090f0c47e..84abfef715cf9adcd6af76fd843da38dc940a65a 100644 --- a/src/components/Comparisons/ComparisonForm.js +++ b/src/components/Comparisons/ComparisonForm.js @@ -41,6 +41,8 @@ import classnames from "classnames"; const path = require("path"); const fs = require("fs"); const http = require("http"); +const homedir = require("os").homedir(); +const bisepsTemp = path.join(homedir, ".bisepsTemp/"); function descendingComparator(a, b, orderBy) { if (b[orderBy] < a[orderBy]) { @@ -336,11 +338,18 @@ export default function ComparisonForm() { let result = []; data.map((row) => { row.samples.map((sample) => { - result.push( - `${row.outdir}/results/${sample.samplePath}/methylation_extraction_bismark/${sample.samplePath}.deduplicated.CX_report.txt` - ); + if (row.remote) { + result.push( + `${bisepsTemp}${sample.samplePath}.deduplicated.CX_report.txt` + ); + } else { + result.push( + `${row.outdir}/results/${sample.samplePath}/methylation_extraction_bismark/${sample.samplePath}.deduplicated.CX_report.txt` + ); + } }); }); + console.log(result); // const blankSample = {}; // const helper = {}; // const result = data.reduce(function (r, o) { diff --git a/src/components/Comparisons/ComparisonTable.js b/src/components/Comparisons/ComparisonTable.js index 502e382404d2aded2764cd105fc178c9eed43a9e..e86f6238bcf343ac645f3418244336cb968cdf78 100644 --- a/src/components/Comparisons/ComparisonTable.js +++ b/src/components/Comparisons/ComparisonTable.js @@ -21,12 +21,26 @@ import FolderIcon from "@material-ui/icons/Folder"; import DeleteIcon from "@material-ui/icons/Delete"; import { Link } from "react-router-dom"; import Iframe from "react-iframe"; - +import Icon from "@material-ui/core/Icon"; +import RefreshIcon from "@material-ui/icons/Refresh"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import TextField from "@material-ui/core/TextField"; +import { useAuth } from "../../hooks/useAuth"; +import Snackbar from "@material-ui/core/Snackbar"; +import MuiAlert from "@material-ui/lab/Alert"; +const path = require("path"); +function Alert(props) { + return <MuiAlert elevation={6} variant="filled" {...props} />; +} const fs = require("fs"); const portastic = require("portastic"); const electron = window.require("electron"); const remote = electron.remote; -const { BrowserWindow } = remote; +const { BrowserWindow, shell } = remote; const http = require("http"); const useStyles = makeStyles((theme) => ({ @@ -38,6 +52,9 @@ const useStyles = makeStyles((theme) => ({ title: { margin: theme.spacing(4, 0, 2), }, + button: { + margin: theme.spacing(1), + }, })); export default function InteractiveList() { @@ -45,6 +62,114 @@ export default function InteractiveList() { const [dense, setDense] = useState(false); const [secondary, setSecondary] = useState(false); const [data, setData] = useState([]); + const [open, setOpen] = useState(false); + const [deleted, setDeleted] = useState(""); + const [openAlert, setOpenAlert] = useState(false); + const { user } = useAuth(); + const [errors, setErrors] = useState(""); + + const [selectedRow, setSelectedRow] = useState({}); + const handleChange = (e) => { + setDeleted(e.target.value); + }; + const handleDelete = (id, user, outdir, deleted) => { + console.log(id); + + if (deleted === "DELETE") { + const request = { + user: user.user, + }; + const token = sessionStorage.jwtToken; + const options = { + method: "DELETE", + path: `http://localhost/api/comparisons/${id}`, + socketPath: sessionStorage.Sock, + hostname: "unix", + port: null, + data: request, + headers: { + "Content-Type": "application/json", + Authorization: token, + }, + }; + const req = http.request(options, function (res) { + const chunks = []; + console.log("STATUS: " + res.statusCode); + console.log("HEADERS: " + JSON.stringify(res.headers)); + res.on("data", function (chunk) { + chunks.push(chunk); + }); + res.on("error", (err) => console.log(err)); + res.on("end", function () { + const body = Buffer.concat(chunks).toString(); + + const jsbody = JSON.parse(body); + if (res.statusCode !== 200) { + console.log("failed post request"); + } else { + console.log("successful post request"); + } + }); + }); + req.on("error", (err) => console.log(err)); + console.log(request); + req.end(); + fs.rmdirSync(outdir, { recursive: true }); + + handleClose(); + window.location.reload(false); + } else { + console.log("write DELETE to confirm"); + setErrors("write DELETE to confirm or Cancel"); + handleOpenAlert(); + } + }; + const handleCloseAlert = () => { + setOpenAlert(false); + }; + const handleOpenAlert = () => { + setOpenAlert(true); + }; + const handleRerun = (row) => { + const request = { + ...row, + }; + const token = sessionStorage.jwtToken; + const options = { + method: "POST", + path: "http://localhost/api/comparisons/rerun", + socketPath: sessionStorage.Sock, + hostname: "unix", + port: null, + headers: { + "Content-Type": "application/json", + Authorization: token, + }, + }; + const req = http.request(options, function (res) { + const chunks = []; + console.log("STATUS: " + res.statusCode); + console.log("HEADERS: " + JSON.stringify(res.headers)); + res.on("data", function (chunk) { + chunks.push(chunk); + }); + res.on("error", (err) => console.log(err)); + res.on("end", function () { + const body = Buffer.concat(chunks).toString(); + + const jsbody = JSON.parse(body); + if (res.statusCode !== 200) { + console.log("failed post request"); + } else { + console.log("successful post request"); + } + }); + }); + req.on("error", (err) => console.log(err)); + req.write(JSON.stringify(request)); + req.end(); + window.location.reload(false); + }; useEffect(() => { const fetchData = async () => { const token = sessionStorage.jwtToken; @@ -114,9 +239,69 @@ export default function InteractiveList() { console.log(port); }); }; + const openInFolder = (path) => { + shell.showItemInFolder(path); + }; + console.log(data); + const handleLog = (row) => { + console.log(row); + let remotePath = `${row.remoteDir}/biseps.txt`; + let localPath = row.date + "biseps.txt"; + console.log(path.join(bisepsTemp, localPath)); + sftp + .connect({ + host: row.machine.hostname, + port: row.machine.port, + username: row.machine.username, + ...(!(row.machine.privateKey === "") && { + privateKey: require("fs").readFileSync(row.machine.privateKey), + }), + password: row.machine.password, + }) + .then(() => { + console.log(remotePath); + console.log(localPath); + console.log("made it all the way here?"); + if (!fs.existsSync(path.join(bisepsTemp, localPath))) { + return sftp.fastGet(remotePath, path.join(bisepsTemp, localPath)); + } + }) + .then((data) => { + console.log("done done done"); + createBrowserWindow(path.join(bisepsTemp, localPath)); + sftp.end(); + }) + .catch((err) => { + console.log(err, "catch error"); + setErrors("File isn't ready yet"); + handleOpenAlert(); + sftp.end(); + }); + }; + const handleClickOpen = (row) => { + setSelectedRow(row); + setOpen(true); + }; + const handleClose = () => { + setOpen(false); + }; return ( - <Container maxWidth="lg" className={classes.container} gutterBottom> - <Grid container direction="column" alignItems="center" gutterBottom> + <Container maxWidth="lg" className={classes.container}> + <Snackbar + open={openAlert} + autoHideDuration={10000} + onClose={handleCloseAlert} + > + <Alert + onClose={handleCloseAlert} + severity={errors && errors.length > 0 ? "error" : "success"} + > + {errors && errors.length > 0 + ? `Error : ${errors}` + : "Remote file copied locally successfully"} + </Alert> + </Snackbar> + <Grid container direction="column" alignItems="center"> <Box m={3}> <Button alignItems="center" @@ -132,39 +317,134 @@ export default function InteractiveList() { </Grid> <Grid container spacing={2}> + <Dialog + key={selectedRow._id} + open={open} + onClose={handleClose} + aria-labelledby="form-dialog-title" + > + <DialogTitle id="form-dialog-title">Confirm Delete for</DialogTitle> + <DialogContent> + <DialogContentText> + This action will definitively delete all Run information as well + as corresponding files, please Confirm by writing DELETE in all + caps. + </DialogContentText> + <TextField + autoFocus + margin="dense" + id="confirmation" + value={deleted} + onChange={handleChange} + label="write DELETE" + type="text" + fullWidth + /> + </DialogContent> + <DialogActions> + <Button onClick={handleClose} color="primary"> + Cancel + </Button> + <Button + onClick={() => { + handleDelete( + selectedRow._id, + user, + selectedRow.outdir, + deleted + ); + }} + color="primary" + > + Delete + </Button> + </DialogActions> + </Dialog> {data.length > 0 ? ( data.map((row) => ( <Grid key={row._id} item xs={12} md={6}> <Typography variant="h6" className={classes.title}> - Comparison created by {row.createdBy.name} + Comparison created by {row.createdBy.name} on{" "} + {/* {row.date.split("T")[0]} */} </Typography> + <div> + <Button + variant="contained" + color="secondary" + onClick={() => handleClickOpen(row)} + className={classes.button} + startIcon={<DeleteIcon />} + > + Delete + </Button> + {/* This Button uses a Font Icon, see the installation instructions in the Icon component docs. */} + + <Button + variant="contained" + color="primary" + disabled={row.remote ? true : false} + onClick={() => openInFolder(`${row.outdir}/results`)} + className={classes.button} + endIcon={row.remote ? <Icon>cloud</Icon> : <Icon>send</Icon>} + > + {row.remote ? "Remote" : "Open Folder"} + </Button> + <Button + variant="contained" + color="default" + onClick={() => handleRerun(row)} + className={classes.button} + startIcon={<RefreshIcon />} + > + Rerun + </Button> + + <Button + variant="contained" + color="default" + onClick={ + row.remote + ? () => handleLog(row) + : () => + createBrowserWindow( + path.join(row.outdir, "biseps.txt") + ) + } + className={classes.button} + startIcon={<RefreshIcon />} + > + Show Log + </Button> + </div> <div className={classes.demo}> <List dense={dense}> - {row.comparisons.map((comparison) => ( - <ListItem - button - // disabled={ - // fileExist( - // `${row.outdir}/results/${sample.samplePath}/multiqc_report.html` - // ) - // ? false - // : true - // } - key={comparison._id} - // onClick={() => { - // const path = `${row.outdir}/results/${sample.samplePath}/multiqc_report.html`; - // console.log(path); - // createBrowserWindow(path); - // }} - > - <ListItemAvatar> - <Avatar> - <FolderIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText primary={comparison.name} /> - </ListItem> - ))} + {row.comparisons.map((comparison) => + row.contexts.map((context) => ( + <ListItem + key={context} + button + disabled={ + fileExist( + `${row.outdir}/${comparison.id}/${comparison.id}-CG.bed` + ) + ? false + : true + } + onClick={() => { + const path = `${row.outdir}/${comparison.id}/${comparison.id}-${context}.bed`; + console.log(path); + createBrowserWindow(path); + }} + > + <ListItemAvatar> + <Avatar> + <FolderIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText primary={`${comparison.id}-${context}`} /> + </ListItem> + )) + )} {/* {generate( <ListItem> <ListItemAvatar> diff --git a/src/components/Table/Table.js b/src/components/Table/Table.js index 383c49af969b37619dd857828890f49939adbd89..49e2591499666c611c4e3a04d6b68ec6a342560f 100644 --- a/src/components/Table/Table.js +++ b/src/components/Table/Table.js @@ -100,6 +100,7 @@ import ActionRowing from "material-ui/svg-icons/action/rowing"; import uuid from "react-uuid"; import Snackbar from "@material-ui/core/Snackbar"; import MuiAlert from "@material-ui/lab/Alert"; +const { clipboard } = require("electron"); function Alert(props) { return <MuiAlert elevation={6} variant="filled" {...props} />; @@ -108,7 +109,6 @@ const fs = require("fs"); const path = require("path"); const portastic = require("portastic"); let Client = require("ssh2-sftp-client"); -let sftp = new Client(); const electron = window.require("electron"); const remote = electron.remote; const { BrowserWindow, shell } = remote; @@ -130,7 +130,7 @@ const useStyles = makeStyles((theme) => ({ margin: theme.spacing(1), }, })); - +// let clipboardStr = clipboard.readText(); function generate(element) { return [0, 1, 2].map((value) => React.cloneElement(element, { @@ -160,6 +160,8 @@ export default function InteractiveList() { setOpen(true); }; const handleLog = (row) => { + let sftp = new Client(); + console.log(row); let remotePath = `${row.remoteDir}/biseps.txt`; let localPath = row.date + "biseps.txt"; @@ -178,9 +180,8 @@ export default function InteractiveList() { console.log(remotePath); console.log(localPath); console.log("made it all the way here?"); - if (!fs.existsSync(path.join(bisepsTemp, localPath))) { - return sftp.fastGet(remotePath, path.join(bisepsTemp, localPath)); - } + + return sftp.fastGet(remotePath, path.join(bisepsTemp, localPath)); }) .then((data) => { console.log("done done done"); @@ -194,12 +195,125 @@ export default function InteractiveList() { sftp.end(); }); }; + const getFile = () => { + console.log("getfile"); + }; + // const downloadFiles = (row, reports) => { + // if (!fs.existsSync(bisepsTemp)) { + // fs.mkdirSync(bisepsTemp); + // } + + // sftp + // .connect({ + // host: row.machine.hostname, + // port: row.machine.port, + // username: row.machine.username, + // ...(!(row.machine.privateKey === "") && { + // privateKey: require("fs").readFileSync(row.machine.privateKey), + // }), + // password: row.machine.password, + // }) + // .then(async () => { + // for (const report in reports) { + // if ( + // !fs.existsSync( + // path.join(bisepsTemp, path.basename(reports[report])) + // ) + // ) { + // console.log(report); + // console.log(path.join(bisepsTemp, path.basename(reports[report]))); + // try { + // await sftp.fastGet( + // reports[report], + // path.join(bisepsTemp, path.basename(reports[report])) + // ); + // } catch (err) { + // console.log(err); + // } + // } + // } + // // }); + // }) + // .finally((data) => { + // console.log("done done done"); + // sftp.end(); + // }) + // .catch((err) => { + // console.log(err, "catch error"); + // sftp.end(); + // }); + // }; + const downloadFiles = (row, sample, tracks) => { + let sftp = new Client(); + + console.log(tracks); + console.log(row, sample); + console.log("download files"); + + console.log(homedir); + console.log(row.machine); + console.log(sample); + + if (!fs.existsSync(bisepsTemp)) { + fs.mkdirSync(bisepsTemp); + } + + sftp + .connect({ + host: row.machine.hostname, + port: row.machine.port, + username: row.machine.username, + ...(!(row.machine.privateKey === "") && { + privateKey: require("fs").readFileSync(row.machine.privateKey), + }), + password: row.machine.password, + }) + .then(async () => { + console.log("made it all the way here?"); + // return sftp.fastGet(remotePath, path.join(bisepsTemp, localPath)); + // tracks.map((track) => { + // console.log(track); + for (const track in tracks) { + if ( + !fs.existsSync(path.join(bisepsTemp, path.basename(tracks[track]))) + ) { + console.log(path.join(bisepsTemp, path.basename(tracks[track]))); + try { + await sftp.fastGet( + tracks[track], + path.join(bisepsTemp, path.basename(tracks[track])) + ); + } catch (err) { + console.log(err); + } + } + } + // }); + }) + .finally((data) => { + createBrowserWindow( + path.join( + bisepsTemp, + path.join(`${sample.samplePath}-multiqc_report.html`) + ) + ); + console.log("done done done"); + sftp.end(); + }) + .catch((err) => { + console.log(err, "catch error"); + setErrors("File isn't ready yet"); + handleOpenAlert(); + sftp.end(); + }); + }; + const handleRemoteFiles = (row, sample) => { if (!fs.existsSync(bisepsTemp)) { fs.mkdirSync(bisepsTemp); } let remoteDir = row.remoteDir; - let remotePath = `${remoteDir}/results/${sample.samplePath}/multiqc_report.html`; + let remotePath = `${remoteDir}/results/${sample.samplePath}/${sample.samplePath}-multiqc_report.html`; let localPath = sample.samplePath + "-multiqc_report.html"; sftp .connect({ @@ -209,9 +323,7 @@ export default function InteractiveList() { ...(!(row.machine.privateKey === "") && { privateKey: require("fs").readFileSync(row.machine.privateKey), }), - ...(row.machine.privateKey === "" && { - password: row.machine.password, - }), + password: row.machine.password, }) .then(() => { console.log(remotePath); @@ -536,100 +648,122 @@ export default function InteractiveList() { </DialogActions> </Dialog> {data.length > 0 ? ( - data.map((row) => ( - <Grid key={row._id} item xs={12} md={6}> - <Typography variant="h6" className={classes.title}> - Analysis created by {row.createdBy.name} on{" "} - {row.date.split("T")[0]} - </Typography> + data.map((row) => { + const reports = []; + row.samples.map((sample) => { + reports.push( + `${row.remoteDir}/results/${sample.samplePath}/methylation_extraction_bismark/${sample.samplePath}.deduplicated.CX_report.txt` + ); + }); + return ( + <Grid key={row._id} item xs={12} md={6}> + <Typography variant="h6" className={classes.title}> + {row.remote ? "Remote " : "Local "}Analysis created by{" "} + {row.createdBy.name} on {row.date.split("T")[0]} + </Typography> - <div> - <Button - variant="contained" - color="secondary" - onClick={() => handleClickOpen(row)} - className={classes.button} - startIcon={<DeleteIcon />} - > - Delete - </Button> - {/* This Button uses a Font Icon, see the installation instructions in the Icon component docs. */} + <div> + <Button + variant="contained" + color="secondary" + onClick={() => handleClickOpen(row)} + className={classes.button} + startIcon={<DeleteIcon />} + > + Delete + </Button> + {/* This Button uses a Font Icon, see the installation instructions in the Icon component docs. */} - <Button - variant="contained" - color="primary" - disabled={row.remote ? true : false} - onClick={() => openInFolder(`${row.outdir}/results`)} - className={classes.button} - endIcon={row.remote ? <Icon>cloud</Icon> : <Icon>send</Icon>} - > - {row.remote ? "Remote" : "Open Folder"} - </Button> - <Button - variant="contained" - color="default" - onClick={() => handleRerun(row)} - className={classes.button} - startIcon={<RefreshIcon />} - > - Rerun - </Button> + <Button + variant="contained" + color="primary" + onClick={ + row.remote + ? () => clipboard.writeText(`${row.remoteDir}/results/`) + : () => openInFolder(`${row.outdir}/results`) + } + className={classes.button} + endIcon={ + row.remote ? <Icon>cloud</Icon> : <Icon>send</Icon> + } + > + {row.remote ? "Copy Remote Path" : "Open Folder"} + </Button> + <Button + variant="contained" + color="default" + onClick={() => handleRerun(row)} + className={classes.button} + startIcon={<RefreshIcon />} + > + Rerun + </Button> - <Button - variant="contained" - color="default" - onClick={ - row.remote - ? () => handleLog(row) - : () => - createBrowserWindow( - path.join(row.outdir, "biseps.txt") - ) - } - className={classes.button} - startIcon={<RefreshIcon />} - > - Show Log - </Button> - </div> - <div className={classes.demo}> - <List dense={dense}> - {row.samples.map((sample) => ( - <ListItem - button - disabled={ - row.remote - ? false - : fileExist( - `${row.outdir}/results/${sample.samplePath}/multiqc_report.html` + <Button + variant="contained" + color="default" + onClick={ + row.remote + ? () => handleLog(row) + : () => + createBrowserWindow( + path.join(row.outdir, "biseps.txt") ) - ? false - : true - } - key={sample._id} - onClick={() => { - if (row.remote) { - handleRemoteFiles(row, sample); - } else { - console.log(sample); - const path = `${row.outdir}/results/${sample.samplePath}/multiqc_report.html`; - console.log(path); - createBrowserWindow(path); - } - }} - > - <ListItemAvatar> - <Avatar> - <FolderIcon /> - </Avatar> - </ListItemAvatar> - <ListItemText - primary={`${sample.sample} MultiQC Report`} - secondary={secondary ? "Secondary text" : null} - /> - </ListItem> - ))} - {/* {generate( + } + className={classes.button} + startIcon={<RefreshIcon />} + > + Show Log + </Button> + </div> + <div className={classes.demo}> + <List dense={dense}> + {row.samples.map((sample) => { + const outdir = row.remote + ? `${row.remoteDir}` + : row.outdir; + + const Multiqc = `${outdir}/results/${sample.samplePath}/${sample.samplePath}-multiqc_report.html`; + const CX = `${outdir}/results/${sample.samplePath}/methylation_extraction_bismark/${sample.samplePath}.deduplicated.CX_report.txt`; + const tracks = [Multiqc, CX]; + return ( + <ListItem + button + disabled={ + row.remote + ? false + : fileExist( + `${row.outdir}/results/${sample.samplePath}/${sample.samplePath}-multiqc_report.html` + ) + ? false + : true + } + key={sample._id} + onClick={() => { + if (row.remote) { + // handleRemoteFiles(row, sample); + downloadFiles(row, sample, tracks); + } else { + console.log(sample); + const path = `${row.outdir}/results/${sample.samplePath}/${sample.samplePath}-multiqc_report.html`; + console.log(path); + createBrowserWindow(path); + } + }} + > + <ListItemAvatar> + <Avatar> + <FolderIcon /> + </Avatar> + </ListItemAvatar> + <ListItemText + primary={`${sample.sample} MultiQC Report`} + secondary={secondary ? "Secondary text" : null} + /> + </ListItem> + ); + })} + {/* {generate( <ListItem> <ListItemAvatar> <Avatar> @@ -647,10 +781,11 @@ export default function InteractiveList() { </ListItemSecondaryAction> </ListItem> )} */} - </List> - </div> - </Grid> - )) + </List> + </div> + </Grid> + ); + }) ) : ( <Grid key={0} diff --git a/src/components/Visualization/VisualizationFill.js b/src/components/Visualization/VisualizationFill.js index 570dd17ef21b6443609746ab01245b7ff5f768b9..d86ca52ca080f1e0178dadfc90a6082be199958a 100644 --- a/src/components/Visualization/VisualizationFill.js +++ b/src/components/Visualization/VisualizationFill.js @@ -48,6 +48,9 @@ const useStyles = makeStyles((theme) => ({ maxWidth: 360, backgroundColor: theme.palette.background.paper, }, + button: { + margin: theme.spacing(1), + }, })); export default function VisualizationFill() { @@ -156,9 +159,7 @@ export default function VisualizationFill() { if (!fs.existsSync(bisepsTemp)) { fs.mkdirSync(bisepsTemp); } - let remoteDir = row.remoteDir; - let remotePath = `${remoteDir}/results/${sample.samplePath}/multiqc_report.html`; - let localPath = sample.samplePath + "-multiqc_report.html"; + sftp .connect({ host: row.machine.hostname, @@ -170,8 +171,6 @@ export default function VisualizationFill() { password: row.machine.password, }) .then(async () => { - console.log(remotePath); - console.log(localPath); console.log("made it all the way here?"); // return sftp.fastGet(remotePath, path.join(bisepsTemp, localPath)); // tracks.map((track) => { @@ -339,6 +338,7 @@ export default function VisualizationFill() { aria-label="outlined primary button group" > <Button + className={classes.button} alignItems="center" variant="contained" color="default" @@ -347,6 +347,7 @@ export default function VisualizationFill() { Start Jbrowse{" "} </Button> <Button + className={classes.button} onClick={handleReset} alignItems="center" variant="contained" @@ -355,6 +356,7 @@ export default function VisualizationFill() { Reset Jbrowse{" "} </Button> <Button + className={classes.button} onClick={handlePopulate} alignItems="center" variant="contained" @@ -408,7 +410,7 @@ export default function VisualizationFill() { ); })} </List> - <List subheader={result.length > 0 ? "Bam Files" : ""}> + <List subheader={result.length > 0 ? "Alignments" : ""}> {data.map((row) => { const labelId = `checkbox-list-label-${row}`; return ( diff --git a/src/hooks/useConfig.js b/src/hooks/useConfig.js index a4206409ce1250fd42a6d34fac2a2ba30c069f47..5eaabc392f35d95a14c99f49939b529d85832aae 100644 --- a/src/hooks/useConfig.js +++ b/src/hooks/useConfig.js @@ -20,7 +20,6 @@ const useProvideConfig = () => { genome: "", outdir: "", remoteDir: "", - machine: {}, minlen: 80, minscore: -0.6,