diff --git a/workbench/src/main/createPythonFlaskProcess.js b/workbench/src/main/createPythonFlaskProcess.js index 99b6c68d11..115f8e7fd0 100644 --- a/workbench/src/main/createPythonFlaskProcess.js +++ b/workbench/src/main/createPythonFlaskProcess.js @@ -81,7 +81,43 @@ export function setupServerProcessHandlers(subprocess) { pidToSubprocess[subprocess.pid] = subprocess; } -export async function createJupyterProcess(notebookDir, _port = undefined) { +/** + * Find out if the Jupyter server is online, waiting until it is. + * @param {number} port - the port where the jupyter server is running + * @param {number} i - the number or previous tries + * @param {number} retries - number of recursive calls this function is allowed. + * @returns { Promise } resolves text indicating success. + */ +export async function getJupyterIsReady(port, { i = 0, retries = 41 } = {}) { + try { + logger.debug(`${HOSTNAME}:${port}/?token=${process.env.JUPYTER_TOKEN}`); + await fetch(`${HOSTNAME}:${port}`, { + method: 'get', + }); + } catch (error) { + if (error.code === 'ECONNREFUSED') { + while (i < retries) { + i++; + // Try every X ms, usually takes a couple seconds to startup. + await new Promise((resolve) => setTimeout(resolve, 300)); + logger.debug(`retry # ${i}`); + return getJupyterIsReady(port, { i: i, retries: retries }); + } + logger.error(`Not able to connect to server after ${retries} tries.`); + } + logger.error(error); + throw error; + } +} + +/** + * Launch the voila server running a jupyter notebook for a given plugin. + * @param {string} pluginID - id of the plugin + * @param {number} _port - port to launch the voila server. If undefined, + * an available port will be chosen. + * @returns {Array} [subprocess, port] + */ +export async function createJupyterProcess(pluginID, _port = undefined) { let port = _port; if (port === undefined) { port = await getFreePort(); @@ -90,20 +126,21 @@ export async function createJupyterProcess(notebookDir, _port = undefined) { logger.debug('creating voila server process'); const mamba = settingsStore.get('mamba'); - const modelEnvPath = settingsStore.get('plugins.schistosomiasis.env'); + const modelEnvPath = settingsStore.get(`plugins.${pluginID}.env`); + const relativeNotebookPath = settingsStore.get(`plugins.${pluginID}.notebook_path`); + // Extract the location of the installed plugin from `pip show` + // The notebook should be available there as package data const pipShow = execSync( - `mamba run --prefix "${modelEnvPath}" pip show schistosomiasis`, + `mamba run --prefix "${modelEnvPath}" pip show ${pluginID}`, { windowsHide: true } ).toString(); - logger.info(pipShow); const pluginLocation = pipShow.match(/Location: (.+)/)[1]; - logger.info('location', pluginLocation); - const notebookPath = `${pluginLocation}/notebooks/ipyleaflet.ipynb`; + const notebookPath = `${pluginLocation}/${relativeNotebookPath}`; const args = [ 'run', '--prefix', `"${modelEnvPath}"`, - 'voila', notebookPath, '--debug', '--no-browser', '--port', port + 'voila', notebookPath, '--debug', '--no-browser', '--port', port, ]; logger.debug('spawning command:', mamba, args); @@ -203,34 +240,6 @@ export async function createPluginServerProcess(modelName, _port = undefined) { return pythonServerProcess.pid; } -/** Find out if the Jupyter server is online, waiting until it is. - * - * @param {number} i - the number or previous tries - * @param {number} retries - number of recursive calls this function is allowed. - * @returns { Promise } resolves text indicating success. - */ -export async function getJupyterIsReady(port = undefined, { i = 0, retries = 41 } = {}) { - try { - logger.debug(`${HOSTNAME}:${port}/?token=${process.env.JUPYTER_TOKEN}`) - await fetch(`${HOSTNAME}:${port}`, { - method: 'get', - }); - } catch (error) { - if (error.code === 'ECONNREFUSED') { - while (i < retries) { - i++; - // Try every X ms, usually takes a couple seconds to startup. - await new Promise((resolve) => setTimeout(resolve, 300)); - logger.debug(`retry # ${i}`); - return getJupyterIsReady(port, { i: i, retries: retries }); - } - logger.error(`Not able to connect to server after ${retries} tries.`); - } - logger.error(error); - throw error; - } -} - /** * Kill the process running the Flask app * @param {number} pid - process ID of the child process to shut down diff --git a/workbench/src/main/setupJupyter.js b/workbench/src/main/setupJupyter.js index b36c97eb5a..8070cb3599 100644 --- a/workbench/src/main/setupJupyter.js +++ b/workbench/src/main/setupJupyter.js @@ -38,7 +38,7 @@ function createWindow(parentWindow, isDevMode) { } function serveWorkspace(dir) { - console.log('SERVING', dir) + console.log('SERVING', dir); const app = connect(); app.use(serveStatic(dir)); return http.createServer(app).listen(8080); @@ -46,11 +46,9 @@ function serveWorkspace(dir) { export default function setupJupyter(parentWindow, isDevMode) { ipcMain.on( - ipcMainChannels.OPEN_JUPYTER, async (event, filepath) => { + ipcMainChannels.OPEN_JUPYTER, async (event, filepath, pluginID) => { const httpServer = serveWorkspace(path.dirname(filepath)); - let labDir = `${process.resourcesPath}/notebooks`; - if (isDevMode) { labDir = 'resources/notebooks'; } - const [subprocess, port] = await createJupyterProcess(labDir); + const [subprocess, port] = await createJupyterProcess(pluginID); const child = createWindow(parentWindow, isDevMode); child.loadURL(`http://localhost:${port}/?token=${process.env.JUPYTER_TOKEN}`); child.on('close', async () => { diff --git a/workbench/src/renderer/components/InvestTab/index.jsx b/workbench/src/renderer/components/InvestTab/index.jsx index b809ba37d9..3546dc57fd 100644 --- a/workbench/src/renderer/components/InvestTab/index.jsx +++ b/workbench/src/renderer/components/InvestTab/index.jsx @@ -27,7 +27,7 @@ function handleOpenWorkspace(logfile) { } function handleViewResults(logfile) { - console.log('View Results') + console.log('View Results'); ipcRenderer.send(ipcMainChannels.OPEN_JUPYTER, logfile); } @@ -278,7 +278,7 @@ class InvestTab extends React.Component { handleOpenWorkspace(logfile)} - handleViewResults={() => handleViewResults(logfile)} + handleViewResults={() => handleViewResults(logfile, modelSpec.model_id)} terminateInvestProcess={this.terminateInvestProcess} /> )