Repository: MetaCell/NetPyNE-UI (language: python)

Issue:
NETPYNE-184 fix dev deployment



Discussion / hints:

Fix patch:
diff --git a/Dockerfile b/Dockerfile
index 26e69b30..bb4b2b5f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,67 +1,88 @@
-FROM node:14.21.3-bullseye as jsbuild
+FROM node:14 as jsbuild
 
-WORKDIR /app
-
-COPY --chown=1000:1000 webapp/package.json .
-COPY --chown=1000:1000 webapp/yarn.lock .
+ENV FOLDER=netpyne
+RUN echo "no-cache 2023-7-14"
 
+WORKDIR $FOLDER/webapp
+COPY webapp/package.json .
+COPY webapp/yarn.lock .
 
 RUN yarn install  --network-timeout 1000000000
 
-COPY webapp/ .
+COPY webapp .
 RUN yarn build-dev
 
-
 RUN rm -Rf node_modules/*
 
+
+
 ###
 FROM jupyter/base-notebook:hub-1.5.0
 ENV NB_UID=jovyan
 ENV FOLDER=netpyne
-ARG BUILD_ARGS=""
-ARG WORKSPACE_VERSION=master
-# ARG GEPPETTO_VERSION=development
-# ARG NETPYNE_VERSION=master
-
-ENV FOLDER=/home/jovyan/work/NetPyNE-UI
+ENV NP_LFPYKIT_HEAD_FILE=/home/jovyan/nyhead.mat
 
 USER root
 
 RUN rm -rf /var/lib/apt/lists
 RUN apt-get update -qq &&\
-    apt-get install python3-tk vim nano unzip git make libtool g++ -qq pkg-config libfreetype6-dev libpng-dev libopenmpi-dev  openjdk-11-jre-headless -y -y
+    apt-get install python3-tk vim nano unzip git make libtool g++ -qq pkg-config libfreetype6-dev libpng-dev libopenmpi-dev -y
+RUN apt-get install openjdk-11-jre-headless -y
 RUN conda install python=3.7 -y
 
+
 WORKDIR $FOLDER
-COPY --chown=1000:1000 requirements.txt requirements.txt
-RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip && pip install -r requirements.txt --prefer-binary
+COPY  --chown=1000:1000 requirements.txt requirements.txt
+RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\
+    pip install -r requirements.txt --prefer-binary
+
+COPY . .
+COPY --from=jsbuild --chown=1000:1000 $FOLDER/webapp/build webapp/build
+
+# ToDo: fixme, for now remove the jupyter hub config json file because it overrides the default
+# and thus removes the frame ancestor cors settings
+RUN rm -f ~/.jupyter/*.json
+RUN chown $NB_UID .
+RUN chown $NB_UID /opt
+RUN rm -Rf workspace
+
+USER $NB_UID
+
+# sym link workspace pvc to $FOLDER
+RUN mkdir -p /opt/workspace
+RUN mkdir -p /opt/user
 
-COPY --chown=jovyan:1000 . .
-COPY --from=jsbuild --chown=jovyan:1000 /app webapp
 
 
+
+ENV NEURON_HOME=/opt/conda
+
+
+USER root
+
 RUN jupyter nbextension install --py --symlink --sys-prefix jupyter_geppetto
 RUN jupyter nbextension enable --py --sys-prefix jupyter_geppetto
 RUN jupyter nbextension enable --py --sys-prefix widgetsnbextension
 RUN jupyter serverextension enable --py --sys-prefix jupyter_geppetto
 
-RUN python utilities/install.py ${BUILD_ARGS} --workspace $WORKSPACE_VERSION
+RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\ 
+  python utilities/install.py --npm-skip --no-test
 
-RUN jupyter labextension disable @jupyterlab/hub-extension
 
-RUN chown  $NB_UID .
-RUN chown -R $NB_UID workspace
+RUN mv workspace /opt/workspace/tutorials
+RUN chown -R $NB_UID /opt/workspace/tutorials
+RUN ln -s /opt/workspace workspace
 
-# Temp fixes for eeg plots
-ENV NEURON_HOME=/opt/conda
-# For lfpykit 0.4
-# RUN wget -P $(pip show LFPykit | grep "Location:" | awk '{print $2"/lfpykit"}') https://www.parralab.org/nyhead/sa_nyhead.mat
-# For lpfykit 0.5
-ENV NP_LFPYKIT_HEAD_FILE=/home/jovyan/nyhead.mat
+RUN jupyter labextension disable @jupyterlab/hub-extension
 RUN wget --no-check-certificate -O $NP_LFPYKIT_HEAD_FILE https://www.parralab.org/nyhead/sa_nyhead.mat
 
 USER $NB_UID
 
+
 EXPOSE 8888
 
+ENTRYPOINT ["tini", "-g", "--"]
+
+
 
+CMD ./NetPyNE-UI
diff --git a/jupyter_hub/Dockerfile_spawner b/jupyter_hub/Dockerfile_spawner
index 6c919e57..6a1b503a 100644
--- a/jupyter_hub/Dockerfile_spawner
+++ b/jupyter_hub/Dockerfile_spawner
@@ -16,15 +16,15 @@ RUN /bin/bash -c "INCUBATOR_VER=${INCUBATOR_VER} source activate snakes && pip i
 # Clone NetPyNE-UI and install the development version
 RUN wget https://github.com/MetaCell/NetPyNE-UI/archive/$netpyneuiBranch.zip -q
 RUN unzip $netpyneuiBranch.zip
-WORKDIR /home/jovyan/work/NetPyNE-UI-$netpyneuiBranch/utilities
+WORKDIR /opt/workspace-$netpyneuiBranch/utilities
 RUN /bin/bash -c "source activate snakes && python --version"
 RUN /bin/bash -c "source activate snakes && exec python install.py branch $netpyneuiBranch"
 WORKDIR /home/jovyan
 RUN git clone https://github.com/Neurosim-lab/netpyne_workspace
 WORKDIR /home/jovyan/workspace
-RUN ln -sfn /home/jovyan/work/NetPyNE-UI-$netpyneuiBranch/netpyne_ui/tests tests
+RUN ln -sfn /opt/workspace-$netpyneuiBranch/netpyne_ui/tests tests
 
 # Copy jupyterhub_config
-# RUN cp /home/jovyan/work/NetPyNE-UI-$netpyneuiBranch/jupyterhub_config.py .
+# RUN cp /opt/workspace-$netpyneuiBranch/jupyterhub_config.py .
 
 CMD /bin/bash -c "source activate snakes && exec jupyterhub-singleuser --debug --NotebookApp.default_url=/geppetto --NotebookApp.token='' --library=netpyne_ui"
diff --git a/k8s/cf_values.yaml b/k8s/cf_values.yaml
index 8f8039b4..a20f554e 100644
--- a/k8s/cf_values.yaml
+++ b/k8s/cf_values.yaml
@@ -11,7 +11,7 @@ hub:
   shutdownOnLogout: true
   extraConfig:
     timing: |
-      c.Spawner.notebook_dir = '/home/jovyan/work/NetPyNE-UI'
+      c.Spawner.notebook_dir = '/opt/workspace'
       c.Spawner.default_url = '/geppetto'
     spawner: >-
       c.Spawner.args = ["--library=netpyne_ui"]
diff --git a/k8s/minikube_values.yaml b/k8s/minikube_values.yaml
index 79d1b855..a70ed44d 100644
--- a/k8s/minikube_values.yaml
+++ b/k8s/minikube_values.yaml
@@ -20,7 +20,7 @@ hub:
       c.Spawner.port = 8000
       c.Spawner.http_timeout = 300
       c.Spawner.start_timeout = 300
-      c.Spawner.notebook_dir = '/home/jovyan/work/NetPyNE-UI'
+      c.Spawner.notebook_dir = '/opt/workspace'
       c.Spawner.default_url = '/geppetto'
       c.Spawner.debug = True
       c.Spawner.disable_check_xsrf=True
diff --git a/webapp/GeppettoConfiguration.json b/webapp/GeppettoConfiguration.json
index 5a88d708..f5b97848 100644
--- a/webapp/GeppettoConfiguration.json
+++ b/webapp/GeppettoConfiguration.json
@@ -1,8 +1,8 @@
 {
     "_README" : "http://docs.geppetto.org/en/latest/build.html",
     "contextPath": "org.geppetto.frontend",
-    "useSsl": false,
-    "embedded": false,
+    "useSsl": true,
+    "embedded": true,
     "embedderURL": ["/"],
     "rootRedirect":"",
     "noTest": false,
diff --git a/webapp/components/definition/cellRules/sections/mechanisms/NetPyNEMechanism.js b/webapp/components/definition/cellRules/sections/mechanisms/NetPyNEMechanism.js
index e2691c70..20195872 100644
--- a/webapp/components/definition/cellRules/sections/mechanisms/NetPyNEMechanism.js
+++ b/webapp/components/definition/cellRules/sections/mechanisms/NetPyNEMechanism.js
@@ -37,7 +37,7 @@ export default class NetPyNEMechanism extends React.Component {
           name={name}
           model={`${tag}['${name}']`}
           label={name}
-          realType="float"
+          realType="func"
         />
       </Box>
     ));
diff --git a/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRule.js b/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRule.js
new file mode 100644
index 00000000..ad9b0c65
--- /dev/null
+++ b/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRule.js
@@ -0,0 +1,502 @@
+import React from 'react';
+import TextField from '@material-ui/core/TextField';
+import FontIcon from '@material-ui/core/Icon';
+import { BottomNavigation, BottomNavigationAction } from '@material-ui/core';
+import Dialog from '@material-ui/core/Dialog/Dialog';
+import Button from '@material-ui/core/Button';
+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 MenuItem from '@material-ui/core/MenuItem';
+import Box from '@material-ui/core/Box';
+
+import {
+  NetPyNESelectField,
+  NetPyNEField,
+  NetPyNETextField,
+  ListComponent,
+  NetPyNECoordsRange,
+} from 'netpyne/components';
+import Select from 'netpyne/components/general/Select';
+import Utils from '../../../Utils';
+
+const densityStrings = ["uniform", "1DMap", "2DMap", "distance"]
+
+export default class NetPyNESubCellsConnectivityRule extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      currentName: props.name,
+      selectedIndex: 0,
+      sectionId: 'General',
+      errorMessage: undefined,
+      errorDetails: undefined,
+      type: 'uniform',
+      coord: '',
+    };
+  }
+
+  handleRenameChange = (event) => {
+    const storedValue = this.props.name;
+    const newValue = Utils.nameValidation(event.target.value);
+    const updateCondition = this.props.renameHandler(newValue);
+    const triggerCondition = Utils.handleUpdate(
+      updateCondition,
+      newValue,
+      event.target.value,
+      this,
+      'ConnectionRule',
+    );
+
+    if (triggerCondition) {
+      this.triggerUpdate(() => {
+        // Rename the population in Python
+        Utils.renameKey(
+          'netParams.subConnParams',
+          storedValue,
+          newValue,
+          (response, newValue) => {
+            this.renaming = false;
+          },
+        );
+        this.renaming = true;
+      });
+    }
+  };
+
+  triggerUpdate (updateMethod) {
+    // common strategy when triggering processing of a value change, delay it, every time there is a change we reset
+    // eslint-disable-next-line eqeqeq
+    if (this.updateTimer !== undefined) {
+      clearTimeout(this.updateTimer);
+    }
+    this.updateTimer = setTimeout(updateMethod, 1000);
+  }
+
+  select = (index, sectionId) => this.setState({
+    selectedIndex: index,
+    sectionId,
+  });
+
+  getBottomNavigationAction (index, sectionId, label, icon, id) {
+    return (
+      <BottomNavigationAction
+        id={id}
+        key={sectionId}
+        label={label}
+        icon={<FontIcon className={`fa ${icon}`} />}
+        onClick={() => this.select(index, sectionId)}
+      />
+    );
+  }
+
+  postProcessMenuItems (pythonData, selected) {
+    return pythonData.map((name) => (
+      <MenuItem
+        id={`${name}MenuItem`}
+        key={name}
+        checked={selected.indexOf(name) > -1}
+        value={name}
+      >
+        {name}
+      </MenuItem>
+    ));
+  }
+
+  UNSAFE_componentWillReceiveProps (nextProps) {
+    this.setState({ currentName: nextProps.name });
+  }
+
+  handleDensity (value) {
+    this.setState({ type: value })
+    if (value === 'uniform') {
+      Utils.execPythonMessage(
+        `netpyne_geppetto.netParams.subConnParams['${this.props.name}']['density'] = 'uniform'`,
+      )
+    } else {
+      Utils.execPythonMessage(
+        `netpyne_geppetto.netParams.subConnParams['${this.props.name}']['density'] = {}`,
+      )
+    }
+  }
+
+  handleCoord(value) {
+    this.setState({ coord: value })
+    if (value === 'cartesian') {
+      Utils.execPythonMessage(
+        `netpyne_geppetto.netParams.subConnParams['${this.props.name}']['density']['coord'] = '${value}'`,
+      )
+      Utils.execPythonMessage(`netpyne_geppetto.netParams.defineCellShapes = True`)
+    } else {
+      Utils.execPythonMessage(`netpyne_geppetto.netParams.defineCellShapes = False`)
+      Utils.execPythonMessage(
+        `del netpyne_geppetto.netParams.subConnParams['${this.props.name}']['density']['coord']`,
+      )
+    }
+  }
+
+  densityExtraFun () {
+    switch (this.state.type) {
+      case '2DMap':
+      case '1DMap':
+        Utils.execPythonMessage(`netpyne_geppetto.netParams.defineCellShapes = True`)
+        return (
+          <>
+            <NetPyNEField id="netParams.subConnParams.density.gridY" className="listStyle">
+              <ListComponent
+                model={`netParams.subConnParams['${this.props.name}']['density']['gridY']`}
+              />
+            </NetPyNEField>
+            {this.state.type === "2DMap" ?
+              <NetPyNEField id="netParams.subConnParams.density.gridX" className="listStyle">
+                <ListComponent
+                  model={`netParams.subConnParams['${this.props.name}']['density']['gridX']`}
+                />
+            </NetPyNEField> : <></>}
+            <NetPyNEField id="netParams.subConnParams.density.fixedSomaY" className="listStyle">
+              <ListComponent
+                model={`netParams.subConnParams['${this.props.name}']['density']['fixedSomaY']`}
+              />
+            </NetPyNEField>
+            <NetPyNEField id="netParams.subConnParams.density.gridValues" className="listStyle">
+              <ListComponent
+                model={`netParams.subConnParams['${this.props.name}']['density']['gridValues']`}
+              />
+            </NetPyNEField>
+
+          </>
+        )
+      case 'distance':
+        Utils.execPythonMessage(`netpyne_geppetto.netParams.defineCellShapes = False`)
+        return (
+          <>
+            <NetPyNEField id="netParams.subConnParams.density.ref_sec">
+              <NetPyNETextField
+                fullWidth
+                variant="filled"
+                model={`netParams.subConnParams['${this.props.name}']['density']['ref_sec']`}
+              />
+            </NetPyNEField>
+            <NetPyNEField id="netParams.subConnParams.density.ref_seg">
+              <NetPyNETextField
+                fullWidth
+                variant="filled"
+                model={`netParams.subConnParams['${this.props.name}']['density']['ref_seg']`}
+              />
+            </NetPyNEField>
+            <NetPyNEField id="netParams.subConnParams.density.target_distance">
+            <NetPyNETextField
+                fullWidth
+                variant="filled"
+                model={`netParams.subConnParams['${this.props.name}']['density']['target_distance']`}
+              />
+            </NetPyNEField>
+            <NetPyNEField id="netParams.subConnParams.density.coord">
+              <Select
+                onChange={(event) => this.handleCoord(event.target.value)}
+                value={this.state.coord}
+                fullWidth >
+                {["", "cartesian"].map((name, idx) => (
+                  <MenuItem id={`${name}MenuItem`} key={`_${name}`} value={name}>
+                    {name}
+                  </MenuItem>
+                ))}
+              </Select>
+            </NetPyNEField>
+          </>
+        )
+      default:
+        Utils.execPythonMessage(`netpyne_geppetto.netParams.defineCellShapes = False`)
+    }
+  }
+
+  render () {
+    const densityExtras = this.densityExtraFun();
+    const dialogPop = this.state.errorMessage !== undefined ? (
+      <Dialog open style={{ whiteSpace: 'pre-wrap' }}>
+        <DialogTitle id="alert-dialog-title">
+          {this.state.errorMessage}
+        </DialogTitle>
+        <DialogContent style={{ overflow: 'auto' }}>
+          <DialogContentText id="alert-dialog-description">
+            {this.state.errorDetails}
+          </DialogContentText>
+        </DialogContent>
+        <DialogActions>
+          <Button
+            variant="contained"
+            color="primary"
+            onClick={() => this.setState({
+              errorMessage: undefined,
+              errorDetails: undefined,
+            })}
+          >
+            BACK
+          </Button>
+        </DialogActions>
+      </Dialog>
+    ) : undefined;
+
+    if (this.state.sectionId === 'General') {
+      var content = (
+        <Box className="scrollbar scrollchild" mt={1}>
+          <Box mb={1}>
+            <TextField
+              fullWidth
+              variant="filled"
+              id="ConnectivityName"
+              onChange={this.handleRenameChange}
+              value={this.state.currentName}
+              disabled={this.renaming}
+              label="The name of the connectivity rule"
+            />
+          </Box>
+
+          <NetPyNEField id="netParams.subConnParams.groupSynMechs">
+            <NetPyNESelectField
+              multiple={1}
+              model={
+                `netParams.subConnParams['${this.props.name}']['groupSynMechs']`
+              }
+              fullWidth
+              method="netpyne_geppetto.getAvailableSynMech"
+              postProcessItems={(pythonData, selected) => pythonData.map((name) => (
+                <MenuItem id={`${name}MenuItem`} key={name} value={name}>
+                  {name}
+                </MenuItem>
+              ))}
+            />
+          </NetPyNEField>
+
+          <NetPyNEField id="netParams.subConnParams.sec" className="listStyle">
+            <ListComponent
+              model={`netParams.subConnParams['${this.props.name}']['sec']`}
+            />
+          </NetPyNEField>
+
+          <NetPyNEField id="netParams.subConnParams.density">
+            <Select variant="filled" value={this.state.type} onChange={(event) => this.handleDensity(event.target.value)}>
+              {densityStrings.map((item) => (
+                <MenuItem id={`${item}MenuItem`} key={item} value={item}>
+                  {`${item}`}
+                </MenuItem>
+              ))}
+            </Select>
+          </NetPyNEField>
+
+          {densityExtras}
+
+          {dialogPop}
+        </Box>
+      );
+    } else if (this.state.sectionId === 'Pre Conditions') {
+      var content = (
+        <Box className="scrollbar scrollchild" mt={1}>
+          <NetPyNEField id="netParams.subConnParams.preConds.pop">
+            <NetPyNESelectField
+              model={
+                `netParams.subConnParams['${
+                  this.props.name
+                }']['preConds']['pop']`
+              }
+              method="netpyne_geppetto.getAvailablePops"
+              postProcessItems={this.postProcessMenuItems}
+              multiple
+            />
+          </NetPyNEField>
+
+          <NetPyNEField id="netParams.subConnParams.preConds.cellType">
+            <NetPyNESelectField
+              fullWidth
+              model={
+                `netParams.subConnParams['${
+                  this.props.name
+                }']['preConds']['cellType']`
+              }
+              method="netpyne_geppetto.getAvailableCellTypes"
+              postProcessItems={this.postProcessMenuItems}
+              multiple
+            />
+          </NetPyNEField>
+
+          <NetPyNECoordsRange
+            id="xRangePreConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="preConds"
+            items={[
+              {
+                value: 'x',
+                label: 'Absolute',
+              },
+              {
+                value: 'xnorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+
+          <NetPyNECoordsRange
+            id="yRangePreConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="preConds"
+            items={[
+              {
+                value: 'y',
+                label: 'Absolute',
+              },
+              {
+                value: 'ynorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+
+          <NetPyNECoordsRange
+            id="zRangePreConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="preConds"
+            items={[
+              {
+                value: 'z',
+                label: 'Absolute',
+              },
+              {
+                value: 'znorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+        </Box>
+      );
+    } else if (this.state.sectionId === 'Post Conditions') {
+      var content = (
+        <Box className="scrollbar scrollchild" mt={1}>
+          <NetPyNEField id="netParams.subConnParams.postConds.pop">
+            <NetPyNESelectField
+              model={
+                `netParams.subConnParams['${
+                  this.props.name
+                }']['postConds']['pop']`
+              }
+              fullWidth
+              method="netpyne_geppetto.getAvailablePops"
+              postProcessItems={this.postProcessMenuItems}
+              multiple
+            />
+          </NetPyNEField>
+
+          <NetPyNEField id="netParams.subConnParams.postConds.cellType">
+            <NetPyNESelectField
+              model={
+                `netParams.subConnParams['${
+                  this.props.name
+                }']['postConds']['cellType']`
+              }
+              method="netpyne_geppetto.getAvailableCellTypes"
+              postProcessItems={this.postProcessMenuItems}
+              multiple
+              fullWidth
+            />
+          </NetPyNEField>
+
+          <NetPyNECoordsRange
+            id="xRangePostConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="postConds"
+            items={[
+              {
+                value: 'x',
+                label: 'Absolute',
+              },
+              {
+                value: 'xnorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+
+          <NetPyNECoordsRange
+            id="yRangePostConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="postConds"
+            items={[
+              {
+                value: 'y',
+                label: 'Absolute',
+              },
+              {
+                value: 'ynorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+
+          <NetPyNECoordsRange
+            id="zRangePostConn"
+            name={this.props.name}
+            model="netParams.subConnParams"
+            conds="postConds"
+            items={[
+              {
+                value: 'z',
+                label: 'Absolute',
+              },
+              {
+                value: 'znorm',
+                label: 'Normalized',
+              },
+            ]}
+          />
+        </Box>
+      );
+    }
+
+    // Generate Menu
+    let index = 0;
+    const bottomNavigationItems = [];
+    bottomNavigationItems.push(
+      this.getBottomNavigationAction(
+        index++,
+        'General',
+        'General',
+        'fa-bars',
+        'generalConnTab',
+      ),
+    );
+    bottomNavigationItems.push(
+      this.getBottomNavigationAction(
+        index++,
+        'Pre Conditions',
+        'Pre-synaptic cells conditions',
+        'fa-caret-square-o-left',
+        'preCondsConnTab',
+      ),
+    );
+    bottomNavigationItems.push(
+      this.getBottomNavigationAction(
+        index++,
+        'Post Conditions',
+        'Post-synaptic cells conditions',
+        'fa-caret-square-o-right',
+        'postCondsConnTab',
+      ),
+    );
+
+    return (
+      <div className="layoutVerticalFitInner">
+        <BottomNavigation showLabels value={this.state.selectedIndex}>
+          {bottomNavigationItems}
+        </BottomNavigation>
+        {content}
+      </div>
+    );
+  }
+
+  handleChange = (event, index, values) => this.setState({ values });
+}
diff --git a/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRules.js b/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRules.js
new file mode 100644
index 00000000..b828eced
--- /dev/null
+++ b/webapp/components/definition/subcellConnectivity/NetPyNESubcellsConnectivityRules.js
@@ -0,0 +1,320 @@
+import React, { Component } from 'react';
+import Dialog from '@material-ui/core/Dialog/Dialog';
+
+import Button from '@material-ui/core/Button';
+import { NetPyNEThumbnail, GridLayout, Filter } from 'netpyne/components';
+import Box from '@material-ui/core/Box';
+import Utils from '../../../Utils';
+import NetPyNEHome from '../../general/NetPyNEHome';
+import NetPyNEAddNew from '../../general/NetPyNEAddNew';
+
+import NetPyNEConnectivityRule from './NetPyNESubcellsConnectivityRule';
+
+import RulePath from '../../general/RulePath';
+import Accordion from '../../general/ExpansionPanel';
+
+export default class NetPyNESubCellsConnectivityRules extends Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      drawerOpen: false,
+      selectedConnectivityRule: undefined,
+      deletedConnectivityRule: undefined,
+      page: 'main',
+      errorMessage: undefined,
+      errorDetails: undefined,
+      filterValue: null,
+    };
+
+    this.selectPage = this.selectPage.bind(this);
+
+    this.selectConnectivityRule = this.selectConnectivityRule.bind(this);
+    this.handleNewConnectivityRule = this.handleNewConnectivityRule.bind(this);
+
+    this.handleRenameChildren = this.handleRenameChildren.bind(this);
+  }
+
+  handleToggle = () => this.setState((prevState) => ({ drawerOpen: !prevState.drawerOpen }));
+
+  selectPage (page) {
+    this.setState({ page });
+  }
+
+  /* Method that handles button click */
+  selectConnectivityRule (connectivityRule) {
+    this.setState({ selectedConnectivityRule: connectivityRule });
+  }
+
+  handleNewConnectivityRule () {
+    const defaultConnectivityRules = {
+      ConnectivityRule: {
+        preConds: {},
+        postConds: {},
+      },
+    };
+    // Get Key and Value
+    const key = Object.keys(defaultConnectivityRules)[0];
+    const value = defaultConnectivityRules[key];
+    // eslint-disable-next-line react/no-access-state-in-setstate
+    const model = { ...this.state.value };
+
+    // Get New Available ID
+    const connectivityRuleId = Utils.getAvailableKey(model, key);
+    const newConnectivityRule = {
+      name: connectivityRuleId,
+      ...value,
+    };
+    // Create Cell Rule Client side
+    Utils.execPythonMessage(
+      `netpyne_geppetto.netParams.subConnParams["${
+        connectivityRuleId
+      }"] = ${
+        JSON.stringify(value)}`,
+    );
+    model[connectivityRuleId] = newConnectivityRule;
+    // Update state
+    this.setState({
+      value: model,
+      selectedConnectivityRule: connectivityRuleId,
+    });
+  }
+
+  hasSelectedConnectivityRuleBeenRenamed (prevState, currentState) {
+    const currentModel = prevState.value;
+    const model = currentState.value;
+    // deal with rename
+    if (currentModel != undefined && model != undefined) {
+      const oldP = Object.keys(currentModel);
+      const newP = Object.keys(model);
+      if (oldP.length == newP.length) {
+        // if it's the same lenght there could be a rename
+        for (let i = 0; i < oldP.length; i++) {
+          if (oldP[i] != newP[i]) {
+            if (prevState.selectedConnectivityRule != undefined) {
+              if (oldP[i] == prevState.selectedConnectivityRule) {
+                return newP[i];
+              }
+            }
+          }
+        }
+      }
+    }
+    return undefined;
+  }
+
+  componentDidUpdate (prevProps, prevState) {
+    // we need to check if any of the three entities have been renamed and if that's the case change the state for the selection variable
+    const newConnectivityRuleName = this.hasSelectedConnectivityRuleBeenRenamed(
+      prevState,
+      this.state,
+    );
+    if (newConnectivityRuleName !== undefined) {
+      this.setState({
+        selectedConnectivityRule: newConnectivityRuleName,
+        deletedConnectivityRule: undefined,
+      });
+    } else if (
+      prevState.value !== undefined
+      && Object.keys(prevState.value).length
+      !== Object.keys(this.state.value).length
+    ) {
+      /*
+       * logic into this if to check if the user added a new object from the python backend and
+       * if the name convention pass the checks, differently rename this and open dialog to inform.
+       */
+      const model = this.state.value;
+      for (var m in model) {
+        if (prevState.value !== '' && !(m in prevState.value)) {
+          var newValue = Utils.nameValidation(m);
+          if (newValue != m) {
+            newValue = Utils.getAvailableKey(model, newValue);
+            model[newValue] = model[m];
+            delete model[m];
+            this.setState(
+              {
+                value: model,
+                errorMessage: 'Error',
+                errorDetails:
+                  `Leading digits or whitespaces are not allowed in ConnectivityRule names.\n${
+                    m
+                  } has been renamed ${
+                    newValue}`,
+              },
+              () => {
+                Utils.renameKey(
+                  'netParams.subConnParams',
+                  m,
+                  newValue,
+                  (response, newValue) => {
+                  },
+                );
+              },
+            );
+          }
+        }
+      }
+    }
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    const itemRenamed = this.hasSelectedConnectivityRuleBeenRenamed(this.state, nextState)
+      !== undefined;
+    let newItemCreated = false;
+    const selectionChanged = this.state.selectedConnectivityRule != nextState.selectedConnectivityRule;
+    const pageChanged = this.state.page != nextState.page;
+    const newModel = this.state.value == undefined;
+    if (!newModel) {
+      newItemCreated = Object.keys(this.state.value).length
+        != Object.keys(nextState.value).length;
+    }
+    const errorDialogOpen = this.state.errorDetails !== nextState.errorDetails;
+    const filterValueChanged = nextState.filterValue !== this.state.filterValue;
+    return (
+      filterValueChanged
+      || newModel
+      || newItemCreated
+      || itemRenamed
+      || selectionChanged
+      || pageChanged
+      || errorDialogOpen
+    );
+  }
+
+  handleRenameChildren (childName) {
+    childName = childName.replace(/\s*$/, '');
+    const childrenList = Object.keys(this.state.value);
+    for (let i = 0; childrenList.length > i; i++) {
+      if (childName === childrenList[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  getCopyPath () {
+    const {
+      value: model,
+      selectedConnectivityRule,
+    } = this.state;
+    return (
+      model
+      && model[selectedConnectivityRule]
+      && `netParams.subConnParams["${selectedConnectivityRule}"]`
+    );
+  }
+
+  render () {
+    const actions = [
+      <Button
+        variant="contained"
+        color="primary"
+        label="BACK"
+        onTouchTap={() => this.setState({
+          errorMessage: undefined,
+          errorDetails: undefined,
+        })}
+      />,
+    ];
+    const title = this.state.errorMessage;
+    const children = this.state.errorDetails;
+    const dialogPop = this.state.errorMessage != undefined ? (
+      <Dialog
+        title={title}
+        open
+        actions={actions}
+        bodyStyle={{ overflow: 'auto' }}
+        style={{ whiteSpace: 'pre-wrap' }}
+      >
+        {children}
+      </Dialog>
+    ) : (
+      undefined
+    );
+
+    const model = this.state.value;
+    if (this.state.page == 'main') {
+      const filterName = this.state.filterValue === null ? '' : this.state.filterValue;
+      var ConnectivityRules = Object.keys(model || [])
+        .filter((connName) => connName.toLowerCase()
+          .includes(filterName.toLowerCase()))
+        .map((connName) => (
+          <NetPyNEThumbnail
+            name={connName}
+            key={connName}
+            selected={connName == this.state.selectedConnectivityRule}
+            paramPath="subConnParams"
+            handleClick={this.selectConnectivityRule}
+          />
+        ));
+
+      var selectedConnectivityRule;
+      if (
+        this.state.selectedConnectivityRule !== undefined
+        && Object.keys(model)
+          .indexOf(this.state.selectedConnectivityRule) > -1
+      ) {
+        selectedConnectivityRule = (
+          <NetPyNEConnectivityRule
+            name={this.state.selectedConnectivityRule}
+            model={this.state.value[this.state.selectedConnectivityRule]}
+            selectPage={this.selectPage}
+            renameHandler={this.handleRenameChildren}
+          />
+        );
+      }
+    }
+
+    return (
+      <GridLayout>
+        <div>
+          <Accordion>
+            <div className="breadcrumb">
+              <div>
+                <NetPyNEHome
+                  selection={this.state.selectedConnectivityRule}
+                  handleClick={() => this.setState({ selectedConnectivityRule: undefined })}
+                />
+
+                <div style={{ opacity: 0 }}>H</div>
+              </div>
+              <div>
+                <NetPyNEAddNew
+                  title="Create new connectivity rule"
+                  id="newConnectivityRuleButton"
+                  handleClick={this.handleNewConnectivityRule}
+                />
+                <div
+                  style={{
+                    textAlign: 'center',
+                    fontFamily: 'Source Sans Pro',
+                    maxWidth: 40,
+                    overflow: 'visible',
+                    display: 'flex',
+                    justifyContent: 'center',
+                  }}
+                >
+                  Connectivity
+                </div>
+              </div>
+            </div>
+            <Box p={1}>
+              <RulePath text={this.getCopyPath()} />
+              <Box mb={1} />
+              <Filter
+                value={this.state.filterValue}
+                label="Filter connectivity rule by name..."
+                handleFilterChange={(newValue) => this.setState({ filterValue: newValue })}
+                options={model === undefined ? [] : Object.keys(model)}
+              />
+            </Box>
+          </Accordion>
+        </div>
+        <Box className="scrollbar scrollchild" mt={1}>
+          {ConnectivityRules}
+        </Box>
+        {selectedConnectivityRule}
+        {dialogPop}
+      </GridLayout>
+    );
+  }
+}
diff --git a/webapp/components/general/GeppettoJupyterUtils.js b/webapp/components/general/GeppettoJupyterUtils.js
index b7cba324..71d8956f 100644
--- a/webapp/components/general/GeppettoJupyterUtils.js
+++ b/webapp/components/general/GeppettoJupyterUtils.js
@@ -40,11 +40,15 @@ const execPythonMessage = function (command, callback = handle_output) {
   }));
 };
 
+const addslashes = function (str) {
+  return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
+}
+
 const evalPythonMessage = function (command, parameters, parse = true) {
   let parametersString = '';
   if (parameters) {
     if (parameters.length > 0) {
-      parametersString = `(${parameters.map((parameter) => `utils.convertToPython('${JSON.stringify(parameter)}')`).join(',')})`;
+      parametersString = `(${parameters.map((parameter) => `utils.convertToPython('${addslashes(JSON.stringify(parameter))}')`).join(',')})`;
     } else {
       parametersString = '()';
     }
diff --git a/webapp/components/general/NetPyNEField.js b/webapp/components/general/NetPyNEField.js
index 65f4cd2e..c5b67380 100644
--- a/webapp/components/general/NetPyNEField.js
+++ b/webapp/components/general/NetPyNEField.js
@@ -46,7 +46,11 @@ export default class NetPyNEField extends Component {
         !this.model.split('.')[0].startsWith('simConfig')
         || this.model.split('.')[1].startsWith('analysis')
       ) {
-        Utils.execPythonMessage(`del netpyne_geppetto.${this.model}`);
+        Utils.execPythonMessage(`
+        try:
+          del netpyne_geppetto.${this.model}
+        except KeyError:
+          ...`);
       }
     }
     return value;
diff --git a/webapp/components/index.js b/webapp/components/index.js
index 1c49b10c..30528ed5 100644
--- a/webapp/components/index.js
+++ b/webapp/components/index.js
@@ -36,6 +36,7 @@ import {
 
 import _NetPyNECellRules from './definition/cellRules/NetPyNECellRules';
 import _NetPyNEConnectivityRules from './definition/connectivity/NetPyNEConnectivityRules';
+import _NetPyNESubcellsConnectivityRules from './definition/subcellConnectivity/NetPyNESubcellsConnectivityRules';
 import _NetPyNEPlots from './definition/plots/NetPyNEPlots';
 import _ListComponent from './general/List';
 import _AdapterComponent from './general/AdapterComponent';
@@ -122,16 +123,12 @@ export const NetPyNEConnectivityRules = connect(
   ),
 );
 
-export const NetPyNEPlots = connect(
-  (state, ownProps) => ({
-    ...ownProps,
-    commands: state.console.commands,
-  }),
-  null,
-)(
-  PythonControlledCapability.createPythonControlledComponent(
-    _NetPyNEPlots,
-  ),
+export const NetPyNESubcellsConnectivityRules = PythonControlledCapability.createPythonControlledComponent(
+  _NetPyNESubcellsConnectivityRules
+);
+
+export const NetPyNEPlots = PythonControlledCapability.createPythonControlledComponent(
+  _NetPyNEPlots,
 );
 
 export const ListComponent = PythonControlledCapability.createPythonControlledControl(
diff --git a/webapp/components/layout/componentsMap.js b/webapp/components/layout/componentsMap.js
index cb48d755..cad2c8a0 100644
--- a/webapp/components/layout/componentsMap.js
+++ b/webapp/components/layout/componentsMap.js
@@ -1,9 +1,10 @@
 /* eslint-disable quote-props */
-import * as React from 'react';
+import * as React from 'react'
 import {
   NetPyNEInstantiated,
   NetPyNESynapses,
   NetPyNEConnectivityRules,
+  NetPyNESubcellsConnectivityRules,
   NetPyNECellRules,
   NetPyNEStimulationSources,
   NetPyNEStimulationTargets,
@@ -13,10 +14,10 @@ import {
   NetPyNEPythonConsole,
   ExperimentManager,
   ExperimentControlPanel,
-  Rxd,
-} from '..';
+  Rxd
+} from '..'
 
-import PlotViewer from '../general/PlotViewer';
+import PlotViewer from '../general/PlotViewer'
 
 /**
  * Key of the component is the `component` attribute of the widgetConfiguration.
@@ -31,13 +32,14 @@ const componentMap = {
   'cellParams': NetPyNECellRules,
   'synMechParams': NetPyNESynapses,
   'connParams': NetPyNEConnectivityRules,
+  'subConnParams': NetPyNESubcellsConnectivityRules,
   'stimSourceParams': NetPyNEStimulationSources,
   'stimTargetParams': NetPyNEStimulationTargets,
   'simConfig': NetPyNESimConfig,
   'analysis': NetPyNEPlots,
   'experimentManager': ExperimentManager,
   'experimentControlPanel': ExperimentControlPanel,
-  'rxd': Rxd,
-};
+  'rxd': Rxd
+}
 
-export default componentMap;
+export default componentMap
diff --git a/webapp/constants.js b/webapp/constants.js
index dd246436..c7cc0722 100644
--- a/webapp/constants.js
+++ b/webapp/constants.js
@@ -1,28 +1,28 @@
-import { WidgetStatus } from '@metacell/geppetto-meta-client/common/layout/model';
+import { WidgetStatus } from '@metacell/geppetto-meta-client/common/layout/model'
 
-export const MINIMIZED_PANEL = 'border_bottom';
-export { WidgetStatus };
-export const TOP_PANEL = 'hlsPanel';
-export const TOOLS_LIST = 'tools';
+export const MINIMIZED_PANEL = 'border_bottom'
+export { WidgetStatus }
+export const TOP_PANEL = 'hlsPanel'
+export const TOOLS_LIST = 'tools'
 
-export const DEFAULT_CONFIRMATION_DIALOG_MESSAGE = 'A NetPyNE model has already been instantiated or simulated.'
-+ ' Continuing with this action will use the old value of netParams and simConfig for the new model. Do you want to continue?';
+export const DEFAULT_CONFIRMATION_DIALOG_MESSAGE = 'A NetPyNE model has already been instantiated or simulated.' +
+' Continuing with this action will use the old value of netParams and simConfig for the new model. Do you want to continue?'
 
 export const THEMES = {
   DARK: 'gui',
   BLACK: 'guiBlack',
-  LIGHT: 'guiWhite',
-};
+  LIGHT: 'guiWhite'
+}
 
 export const MODEL_STATE = {
   NOT_INSTANTIATED: 'NOT_INSTANTIATED',
   INSTANTIATED: 'INSTANTIATED',
-  SIMULATED: 'SIMULATED',
-};
+  SIMULATED: 'SIMULATED'
+}
 
 export const DEFAULT_COLOR = {
-  g: 0.50, b: 0.60, r: 1, a: 1,
-};
+  g: 0.50, b: 0.60, r: 1, a: 1
+}
 
 export const NETPYNE_COMMANDS = {
   instantiateModel: 'netpyne_geppetto.instantiateNetPyNEModelInGeppetto',
@@ -38,8 +38,8 @@ export const NETPYNE_COMMANDS = {
   cloneExperiment: 'netpyne_geppetto.cloneExperiment',
   viewExperimentResults: 'netpyne_geppetto.viewExperimentResult',
   importNeuroML: 'netpyne_geppetto.importNeuroML',
-  importLEMS: 'netpyne_geppetto.importLEMS',
-};
+  importLEMS: 'netpyne_geppetto.importLEMS'
+}
 
 export const REAL_TYPE = {
   INT: 'int',
@@ -48,8 +48,8 @@ export const REAL_TYPE = {
   STR: 'str',
   FUNC: 'func',
   DICT: 'dict',
-  DICT_DICT: 'dict(dict)',
-};
+  DICT_DICT: 'dict(dict)'
+}
 
 export const PYTHON_CONSOLE_WIDGET = {
   id: 'python',
@@ -65,8 +65,8 @@ export const PYTHON_CONSOLE_WIDGET = {
   enableRename: false,
   hideOnClose: true,
   pos: 1000,
-  specification: TOOLS_LIST,
-};
+  specification: TOOLS_LIST
+}
 /*
  * ------------------------------------------------------------------------------ //
  * ------------------------------------------------------------------------------ //
@@ -92,7 +92,7 @@ export const TOPBAR_CONSTANTS = {
   NETWORK_MODEL: 'NETWORK_MODEL',
   IMPORT_NEUROML: 'IMPORT_NEUROML',
   IMPORT_LEMS: 'IMPORT_LEMS'
-};
+}
 
 /*
  * ------------------------------------------------------------------------------ //
@@ -116,9 +116,9 @@ export const NETWORK_PLOT_WIDGETS = {
       method: {
         plotKey: 'plotConn',
         plotMethod: 'iplotConn',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   d2NetPlot: {
     id: 'd2NetPlot',
@@ -137,11 +137,11 @@ export const NETWORK_PLOT_WIDGETS = {
       method: {
         plotKey: 'plot2Dnet',
         plotMethod: 'iplot2Dnet',
-        plotType: false,
-      },
-    },
-  },
-};
+        plotType: false
+      }
+    }
+  }
+}
 
 export const PLOT_WIDGETS = {
   ...NETWORK_PLOT_WIDGETS,
@@ -163,9 +163,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotTraces',
         plotMethod: 'iplotTraces',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   rasterPlot: {
     id: 'rasterPlot',
@@ -185,9 +185,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotRaster',
         plotMethod: 'iplotRaster',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   spikePlot: {
     id: 'spikePlot',
@@ -207,10 +207,10 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotSpikeHist',
         plotMethod: 'iplotSpikeHist',
-        plotType: false,
+        plotType: false
       },
-      pos: 5,
-    },
+      pos: 5
+    }
   },
   spikeStatsPlot: {
     id: 'spikeStatsPlot',
@@ -230,9 +230,9 @@ export const PLOT_WIDGETS = {
       method: {
         key: 'plotSpikeStats',
         plotMethod: 'iplotSpikeStats',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   ratePSDPlot: {
     id: 'ratePSDPlot',
@@ -248,7 +248,7 @@ export const PLOT_WIDGETS = {
     method: {
       key: 'plotRatePSD',
       plotMethod: 'iplotRatePSD',
-      plotType: false,
+      plotType: false
     },
     pos: 7,
     config: {
@@ -257,9 +257,9 @@ export const PLOT_WIDGETS = {
       method: {
         key: 'plotRatePSD',
         plotMethod: 'iplotRatePSD',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   LFPTimeSeriesPlot: {
     id: 'LFPTimeSeriesPlot',
@@ -275,7 +275,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotLFP',
       plotMethod: 'iplotLFP',
-      plotType: 'timeSeries',
+      plotType: 'timeSeries'
     },
     pos: 8,
     config: {
@@ -284,9 +284,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotLFP',
         plotMethod: 'iplotLFP',
-        plotType: 'timeSeries',
-      },
-    },
+        plotType: 'timeSeries'
+      }
+    }
   },
   LFPPSDPlot: {
     id: 'LFPPSDPlot',
@@ -302,7 +302,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotLFP',
       plotMethod: 'iplotLFP',
-      plotType: 'PSD',
+      plotType: 'PSD'
     },
     pos: 9,
     config: {
@@ -311,9 +311,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotLFP',
         plotMethod: 'iplotLFP',
-        plotType: 'PSD',
-      },
-    },
+        plotType: 'PSD'
+      }
+    }
   },
   LFPSpectrogramPlot: {
     id: 'LFPSpectrogramPlot',
@@ -329,7 +329,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotLFP',
       plotMethod: 'iplotLFP',
-      plotType: 'spectrogram',
+      plotType: 'spectrogram'
     },
     pos: 10,
     config: {
@@ -338,9 +338,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotLFP',
         plotMethod: 'iplotLFP',
-        plotType: 'spectrogram',
-      },
-    },
+        plotType: 'spectrogram'
+      }
+    }
   },
   grangerPlot: {
     id: 'grangerPlot',
@@ -356,7 +356,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'granger',
       plotMethod: 'granger',
-      plotType: false,
+      plotType: false
     },
     pos: 11,
     config: {
@@ -365,9 +365,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'granger',
         plotMethod: 'granger',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   rxdConcentrationPlot: {
     id: 'rxdConcentrationPlot',
@@ -383,7 +383,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotRxDConcentration',
       plotMethod: 'iplotRxDConcentration',
-      plotType: false,
+      plotType: false
     },
     pos: 12,
     config: {
@@ -392,9 +392,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotRxDConcentration',
         plotMethod: 'iplotRxDConcentration',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   plotDipole: {
     id: 'plotDipole',
@@ -410,7 +410,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotDipole',
       plotMethod: 'plotDipole',
-      plotType: false,
+      plotType: false
     },
     pos: 13,
     config: {
@@ -420,9 +420,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotDipole',
         plotMethod: 'plotDipole',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   plotEEG: {
     id: 'plotEEG',
@@ -438,7 +438,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotEEG',
       plotMethod: 'plotEEG',
-      plotType: false,
+      plotType: false
     },
     pos: 14,
     config: {
@@ -448,9 +448,9 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotEEG',
         plotMethod: 'plotEEG',
-        plotType: false,
-      },
-    },
+        plotType: false
+      }
+    }
   },
   plotRateSpectrogram: {
     id: 'plotRateSpectrogram',
@@ -466,7 +466,7 @@ export const PLOT_WIDGETS = {
     method: {
       plotKey: 'plotRateSpectrogram',
       plotMethod: 'plotRateSpectrogram',
-      plotType: false,
+      plotType: false
     },
     pos: 15,
     config: {
@@ -475,11 +475,11 @@ export const PLOT_WIDGETS = {
       method: {
         plotKey: 'plotRateSpectrogram',
         plotMethod: 'plotRateSpectrogram',
-        plotType: false,
-      },
-    },
-  },
-};
+        plotType: false
+      }
+    }
+  }
+}
 
 export const DEFAULT_NETWORK_WIDGETS = {
   D3Canvas: {
@@ -490,7 +490,7 @@ export const DEFAULT_NETWORK_WIDGETS = {
     panelName: 'morphoPanel',
     enableRename: false,
     hideOnClose: true,
-    pos: 0,
+    pos: 0
   },
   experimentManager: {
     id: 'experimentManager',
@@ -502,7 +502,7 @@ export const DEFAULT_NETWORK_WIDGETS = {
     defaultPanel: TOP_PANEL,
     enableRename: false,
     pos: 14,
-    specification: TOOLS_LIST,
+    specification: TOOLS_LIST
   },
   experimentControlPanel: {
     id: 'experimentControlPanel',
@@ -514,11 +514,11 @@ export const DEFAULT_NETWORK_WIDGETS = {
     defaultPanel: 'plotPanel',
     enableRename: false,
     pos: 13,
-    specification: TOOLS_LIST,
+    specification: TOOLS_LIST
   },
   ...PLOT_WIDGETS,
-  [PYTHON_CONSOLE_WIDGET.id]: PYTHON_CONSOLE_WIDGET,
-};
+  [PYTHON_CONSOLE_WIDGET.id]: PYTHON_CONSOLE_WIDGET
+}
 
 export const EDIT_WIDGETS = {
   [PYTHON_CONSOLE_WIDGET.id]: PYTHON_CONSOLE_WIDGET,
@@ -533,8 +533,8 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     pos: 0,
     props: {
-      model: 'netParams.cellParams',
-    },
+      model: 'netParams.cellParams'
+    }
   },
   popParams: {
     id: 'popParams',
@@ -547,8 +547,8 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     pos: 1,
     props: {
-      model: 'netParams.popParams',
-    },
+      model: 'netParams.popParams'
+    }
   },
   synMechParams: {
     id: 'synMechParams',
@@ -561,8 +561,8 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     pos: 2,
     props: {
-      model: 'netParams.synMechParams',
-    },
+      model: 'netParams.synMechParams'
+    }
   },
   connParams: {
     id: 'connParams',
@@ -575,8 +575,22 @@ export const EDIT_WIDGETS = {
     enableRename: false,
     pos: 3,
     props: {
-      model: 'netParams.connParams',
-    },
+      model: 'netParams.connParams'
+    }
+  },
+  subConnParams: {
+    id: 'subConnParams',
+    name: 'Sub-cellular Connectivity Rules',
+    model: 'netParams.subConnParams',
+    status: WidgetStatus.HIDDEN,
+    component: 'subConnParams',
+    panelName: TOP_PANEL,
+    hideOnClose: true,
+    enableRename: false,
+    pos: 4,
+    props: {
+      model: 'netParams.subConnParams'
+    }
   },
   stimSourceParams: {
     id: 'stimSourceParams',
@@ -587,10 +601,10 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 4,
+    pos: 5,
     props: {
-      model: 'netParams.stimSourceParams',
-    },
+      model: 'netParams.stimSourceParams'
+    }
   },
   stimTargetParams: {
     id: 'stimTargetParams',
@@ -601,10 +615,10 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 5,
+    pos: 6,
     props: {
-      model: 'netParams.stimTargetParams',
-    },
+      model: 'netParams.stimTargetParams'
+    }
   },
   rxd: {
     id: 'rxd',
@@ -615,10 +629,10 @@ export const EDIT_WIDGETS = {
     component: 'rxd',
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 6,
+    pos: 7,
     props: {
-      model: 'netParams.rxdParams',
-    },
+      model: 'netParams.rxdParams'
+    }
   },
   analysis: {
     id: 'analysis',
@@ -629,10 +643,10 @@ export const EDIT_WIDGETS = {
     component: 'analysis',
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 7,
+    pos: 8,
     props: {
-      model: 'simConfig.analysis',
-    },
+      model: 'simConfig.analysis'
+    }
   },
 
   simConfig: {
@@ -643,7 +657,7 @@ export const EDIT_WIDGETS = {
     hideOnClose: true,
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 8,
+    pos: 9
   },
   experimentManager: {
     id: 'experimentManager',
@@ -653,10 +667,10 @@ export const EDIT_WIDGETS = {
     component: 'experimentManager',
     panelName: TOP_PANEL,
     enableRename: false,
-    pos: 9,
-    specification: TOOLS_LIST,
-  },
-};
+    pos: 10,
+    specification: TOOLS_LIST
+  }
+}
 
 export const EXPERIMENT_STATE = {
   DESIGN: 'DESIGN',
@@ -665,14 +679,14 @@ export const EXPERIMENT_STATE = {
   SIMULATED: 'SIMULATED',
   INSTANTIATING: 'INSTANTIATING',
   INSTANTIATED: 'INSTANTIATED',
-  ERROR: 'ERROR',
-};
+  ERROR: 'ERROR'
+}
 
 export const SIDEBAR_HEADINGS = {
   MODEL: 'Model Specification',
   TOOLS: 'Tools',
-  PLOTS: 'Plots',
-};
+  PLOTS: 'Plots'
+}
 
 export const EXPERIMENT_TEXTS = {
   WARNING: 'Warning: You need at least two parameters for the grouping to work.',
@@ -686,30 +700,30 @@ export const EXPERIMENT_TEXTS = {
   CLONE_EXPERIMENT: 'Clone Experiment',
   CLONE_EXPERIMENT_MESSAGE: 'Replaces the Experiment in design with the stored Experiment.',
   VIEW_EXPERIMENTS_RESULTS: 'View simulation results',
-  VIEW_EXPERIMENTS_RESULTS_MESSAGE: 'This will replace the currently loaded results in Explore, '
-    + 'are you sure you want to proceed?',
+  VIEW_EXPERIMENTS_RESULTS_MESSAGE: 'This will replace the currently loaded results in Explore, ' +
+    'are you sure you want to proceed?',
   LOAD_TRIAL_MODEL_SPEC: 'Load Model Specification',
-  LOAD_TRIAL_MODEL_SPEC_MESSAGE: 'This will replace the currently loaded model specification, '
-    + 'are you sure you want to proceed?',
+  LOAD_TRIAL_MODEL_SPEC_MESSAGE: 'This will replace the currently loaded model specification, ' +
+    'are you sure you want to proceed?',
   ERROR_EXPERIMENT_WITH_NAME_EXISTS: 'This name is already taken',
-  ERROR_EXPERIMENT_EMPTY: 'Please enter experiment name',
-};
+  ERROR_EXPERIMENT_EMPTY: 'Please enter experiment name'
+}
 
 export const TUTORIALS_LIST = {
-  tut1: 'Model 1: Simple cell network',
-  tut2: 'Model 2: Detailed cell network',
-  tut3: 'Model 3a: Multiscale network (low IP3)',
-  tut3_ip3high: 'Model 3b: Multiscale network (high IP3)',
-  tut3_norxd: 'Model 3c: Multiscale network (no RxD)',
-  tut_osc: 'Model 4: Simple oscillatory network',
-};
+  tut1: 'Tut 1: Simple cell network',
+  tut2: 'Tut 2: Detailed cell network',
+  tut3: 'Tut 3a: Multiscale network (low IP3)',
+  tut3_ip3high: 'Tut 3b: Multiscale network (high IP3)',
+  tut3_norxd: 'Tut 3c: Multiscale network (no RxD)',
+  tut_osc: 'Tut 4: Simple oscillatory network'
+}
 
 export const EXPERIMENT_VIEWS = {
   list: 'list',
   viewExperiment: 'viewExperiment',
   jsonViewer: 'jsonViewer',
-  edit: 'edit',
-};
+  edit: 'edit'
+}
 
 export const LAUNCH_MODAL = {
   title: 'What do you want to simulate ?',
@@ -717,18 +731,18 @@ export const LAUNCH_MODAL = {
   experimentState: 'experiment',
   actionSimulate: 'Simulate',
   defaultResource: 'Server',
-  errorText: 'Please check the input',
-};
+  errorText: 'Please check the input'
+}
 
 export const geometryClasses = [
   'DistributedBoundary',
   'FractionalVolume',
   'FixedCrossSection',
   'ScalableBorder',
-  'Shell',
-];
+  'Shell'
+]
 
 export const geometryStrings = [
   'inside',
-  'membrane',
-];
+  'membrane'
+]


Test patch:
diff --git a/tests/frontend/e2e/tests/ExperimentManager_Tut#1.test.js b/tests/frontend/e2e/tests/ExperimentManager_Tut#1.test.js
index 23ce48c9..f7eae3f2 100644
--- a/tests/frontend/e2e/tests/ExperimentManager_Tut#1.test.js
+++ b/tests/frontend/e2e/tests/ExperimentManager_Tut#1.test.js
@@ -202,13 +202,15 @@ describe('Experiment Manager test using Tut#1', () => {
 
     it('Simulate All conditions', async () => {
 
-        await page.waitForSelector(selectors.SIMULATE_BUTTON_SELECTOR)
-        await click(page, selectors.SIMULATE_BUTTON_SELECTOR, { timeout: TIMEOUT });
+        await page.waitForSelector(selectors.MODEL_BUTTON_SELECTOR)
+        await click(page, selectors.MODEL_BUTTON_SELECTOR, { timeout: TIMEOUT });
+        await page.waitForSelector(selectors.SIMULATE_NETWORK_SELECTOR)
+        await click(page, selectors.SIMULATE_NETWORK_SELECTOR, { timeout: TIMEOUT });
 
         console.log('Simulating all conditions')
-
         await page.waitForSelector(selectors.SIMULATE_POPUP_SELECTOR)
         await page.click(selectors.SIMULATE_POPUP_SELECTOR)
+        await page.waitForSelector(selectors.CONFIRM_SIMULATE_SELECTOR)
         await page.click(selectors.CONFIRM_SIMULATE_SELECTOR)
 
         await page.waitForTimeout(PAGE_WAIT);
@@ -217,8 +219,8 @@ describe('Experiment Manager test using Tut#1', () => {
         await page.click(selectors.CONFIRM_EXPERIMENT_STARTED_SELECTOR)
 
         await page.waitForSelector(selectors.SIMULATION_PAGE_SELECTOR, { timeout: TIMEOUT * 2 });
-        await page.waitForSelector(selectors.SIMULATION_LOADER_SELECTOR, { hidden: false, timeout: TIMEOUT * 2 })
-        await page.waitForSelector(selectors.SIMULATION_LOADER_SELECTOR, { hidden: true, timeout: TIMEOUT * 5 })
+        await page.waitForSelector(selectors.SIMULATION_LOADER_SELECTOR, { hidden: false, timeout: TIMEOUT * 5 })
+        await page.waitForSelector(selectors.SIMULATION_LOADER_SELECTOR, { hidden: true, timeout: TIMEOUT * 10 })
         console.log('Experiment Simulation finished')
 
     });
diff --git a/tests/frontend/e2e/tests/RxD.test.js b/tests/frontend/e2e/tests/RxD.test.js
index aef64f39..fa8cefd8 100644
--- a/tests/frontend/e2e/tests/RxD.test.js
+++ b/tests/frontend/e2e/tests/RxD.test.js
@@ -98,10 +98,10 @@ describe('RxD testing', () => {
         console.log('Loading Tutorial #3b ...')
 
         await page.waitForTimeout(PAGE_WAIT)
-
+        await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
         await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
-        await page.click("li[id='Model 3b: Multiscale network (high IP3)']", { timeout: TIMEOUT })
+        await page.waitForSelector(selectors.TUTORIAL_3B_SELECTOR, { timeout: TIMEOUT })
+        await page.click(selectors.TUTORIAL_3B_SELECTOR, { timeout: TIMEOUT })
         await page.waitForSelector('#E')
         await page.waitForSelector('#I')
         await page.waitForTimeout(PAGE_WAIT)
@@ -123,8 +123,9 @@ describe('RxD testing', () => {
         await page.waitForTimeout(PAGE_WAIT * 3)
 
         await page.waitForSelector('div[title="3D Representation"][aria-disabled="false"]')
-
+        await page.waitForSelector(selectors.MODEL_BUTTON_SELECTOR, { timeout: TIMEOUT });
         await page.click(selectors.MODEL_BUTTON_SELECTOR, { timeout: TIMEOUT });
+        await page.waitForSelector(selectors.SIMULATE_NETWORK_SELECTOR, { timeout: TIMEOUT });
         await page.click(selectors.SIMULATE_NETWORK_SELECTOR, { timeout: TIMEOUT });
         console.log('Simulating network ...')
 
@@ -230,11 +231,7 @@ describe('RxD testing', () => {
 
         await page.waitForSelector('button[aria-selected="true"][id = "simple-tab-1"]')
 
-        const no_regions_text = await page.$$eval('#simple-tabpanel-1', no_regions_text => {
-            return no_regions_text.map(no_regions_text => no_regions_text.innerText)
-        })
-
-        expect(no_regions_text).toContain('There are no Species yet.')
+        await page.waitForSelector('#ip3')
         console.log('Species tab opened')
         await page.waitForTimeout(PAGE_WAIT)
         
diff --git a/tests/frontend/e2e/tests/SaveOpenFile.test.js b/tests/frontend/e2e/tests/SaveOpenFile.test.js
index 7dd8c9bd..396a2858 100644
--- a/tests/frontend/e2e/tests/SaveOpenFile.test.js
+++ b/tests/frontend/e2e/tests/SaveOpenFile.test.js
@@ -382,7 +382,7 @@ describe('Save / Open File testing', () => {
             return pwd_code_outputs.map(pwd_code_output => pwd_code_output.innerText)
         })
 
-        expect(first_code_output[0]).toBe("'/home/jovyan/work/NetPyNE-UI/workspace'")
+        expect(first_code_output[0]).toBe("'/opt/workspace/workspace'")
 
         const code_lines = await python_frame.$$(selectors.PYTHON_CELL_SELECTOR)
 
@@ -398,7 +398,7 @@ describe('Save / Open File testing', () => {
             return cd_code_outputs.map(cd_code_output => cd_code_output.innerText)
         })
 
-        expect(second_code_output[0]).toBe("/home/jovyan/work/NetPyNE-UI/workspace/saved_models/aut_test/src\n")
+        expect(second_code_output[0]).toBe("/opt/workspace/workspace/saved_models/aut_test/src\n")
 
         const ls_code_lines = await python_frame.$$(selectors.PYTHON_CELL_SELECTOR)
 
diff --git a/tests/frontend/e2e/tests/Tut#1_smoke.test.js b/tests/frontend/e2e/tests/Tut#1_smoke.test.js
index 02db7d7e..207f7a0b 100644
--- a/tests/frontend/e2e/tests/Tut#1_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#1_smoke.test.js
@@ -95,9 +95,9 @@ describe('Tutorial #1 for Smoke Testing', () => {
     console.log('Tutorial #1')
 
     await page.waitForTimeout(PAGE_WAIT)
-
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
     await click(page, selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_1_SELECTOR, { timeout: TIMEOUT })
     await click(page, selectors.TUTORIAL_1_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.PYR_CELL_SELECTOR)
     await page.waitForTimeout(PAGE_WAIT)
diff --git a/tests/frontend/e2e/tests/Tut#2_smoke.test.js b/tests/frontend/e2e/tests/Tut#2_smoke.test.js
index 9b19f65d..9ac5921b 100644
--- a/tests/frontend/e2e/tests/Tut#2_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#2_smoke.test.js
@@ -93,9 +93,9 @@ describe('Tutorial #2 for Smoke Testing', () => {
     console.log('Tutorial #2')
 
     await page.waitForTimeout(PAGE_WAIT)
-
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { visible: true })
     await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_2_SELECTOR, { visible: true })
     await page.click(selectors.TUTORIAL_2_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.PYR_2_CELL_SELECTOR)
     await page.waitForSelector(selectors.INT_CELL_SELECTOR)
@@ -225,7 +225,7 @@ describe('Tutorial #2 for Smoke Testing', () => {
   });
 
   it('Rate Spectogram Plot', async () => {
-
+    await page.waitForSelector(selectors.RATE_SPECTROGRAM_PLOT_SELECTOR, { visible: true })
     await page.click(selectors.RATE_SPECTROGRAM_PLOT_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.CANVAS_SELECTOR, { timeout: TIMEOUT })
 
diff --git a/tests/frontend/e2e/tests/Tut#3a_smoke.test.js b/tests/frontend/e2e/tests/Tut#3a_smoke.test.js
index 709501d3..d7aafeae 100644
--- a/tests/frontend/e2e/tests/Tut#3a_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#3a_smoke.test.js
@@ -94,9 +94,10 @@ describe('Tutorial #3a for Smoke Testing', () => {
     console.log('Tutorial #3a')
 
     await page.waitForTimeout(PAGE_WAIT)
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { visible: true })
 
     await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_3A_SELECTOR, { visible: true })
     await page.click(selectors.TUTORIAL_3A_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.E_CELL_TYPE_SELECTOR)
     await page.waitForSelector(selectors.I_CELL_TYPE_SELECTOR)
diff --git a/tests/frontend/e2e/tests/Tut#3b_smoke.test.js b/tests/frontend/e2e/tests/Tut#3b_smoke.test.js
index 31a2976d..4f9c7add 100644
--- a/tests/frontend/e2e/tests/Tut#3b_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#3b_smoke.test.js
@@ -95,9 +95,9 @@ describe('Tutorial #3b for Smoke Testing', () => {
     console.log('Tutorial #3b')
 
     await page.waitForTimeout(PAGE_WAIT)
-
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_3B_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIAL_3B_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.E_CELL_TYPE_SELECTOR)
     await page.waitForSelector(selectors.I_CELL_TYPE_SELECTOR)
diff --git a/tests/frontend/e2e/tests/Tut#3c_smoke.test.js b/tests/frontend/e2e/tests/Tut#3c_smoke.test.js
index dd9405bc..64a59c7c 100644
--- a/tests/frontend/e2e/tests/Tut#3c_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#3c_smoke.test.js
@@ -96,9 +96,9 @@ describe('Tutorial #3c for Smoke Testing', () => {
     console.log('Tutorial #3c')
 
     await page.waitForTimeout(PAGE_WAIT)
-
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_3C_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIAL_3C_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.E_CELL_TYPE_SELECTOR)
     await page.waitForSelector(selectors.I_CELL_TYPE_SELECTOR)
diff --git a/tests/frontend/e2e/tests/Tut#4_smoke.test.js b/tests/frontend/e2e/tests/Tut#4_smoke.test.js
index 35bd8b94..bc88cf88 100644
--- a/tests/frontend/e2e/tests/Tut#4_smoke.test.js
+++ b/tests/frontend/e2e/tests/Tut#4_smoke.test.js
@@ -95,9 +95,9 @@ describe('Tutorial #4 for Smoke Testing', () => {
     console.log('Tutorial #4')
 
     await page.waitForTimeout(PAGE_WAIT)
-
+    await page.waitForSelector(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIALS_BUTTON_SELECTOR, { timeout: TIMEOUT })
-
+    await page.waitForSelector(selectors.TUTORIAL_4_SELECTOR, { timeout: TIMEOUT })
     await page.click(selectors.TUTORIAL_4_SELECTOR, { timeout: TIMEOUT })
     await page.waitForSelector(selectors.PYR_2_CELL_SELECTOR)
     await page.waitForTimeout(PAGE_WAIT)
diff --git a/tests/frontend/e2e/tests/selectors.js b/tests/frontend/e2e/tests/selectors.js
index 6ec03aab..ad70c467 100644
--- a/tests/frontend/e2e/tests/selectors.js
+++ b/tests/frontend/e2e/tests/selectors.js
@@ -13,12 +13,12 @@ export const USERNAME_SELECTOR = '#username_input';
 export const PASSWORD_SELECTOR = '#password_input';
 export const LOGIN_BUTTON_SELECTOR = '#login_submit';
 export const SELECT_CELL_BUTTON_SELECTOR = '#selectCellButton';
-export const TUTORIAL_1_SELECTOR = "li[id='Model 1: Simple cell network']";
-export const TUTORIAL_2_SELECTOR = "li[id='Model 2: Detailed cell network']"
-export const TUTORIAL_3A_SELECTOR =  "li[id='Model 3a: Multiscale network (low IP3)']"
-export const TUTORIAL_3B_SELECTOR = "li[id='Model 3b: Multiscale network (high IP3)']"
-export const TUTORIAL_3C_SELECTOR = "li[id='Model 3c: Multiscale network (no RxD)']"
-export const TUTORIAL_4_SELECTOR = "li[id='Model 4: Simple oscillatory network']"
+export const TUTORIAL_1_SELECTOR = "li[id='Tut 1: Simple cell network']";
+export const TUTORIAL_2_SELECTOR = "li[id='Tut 2: Detailed cell network']"
+export const TUTORIAL_3A_SELECTOR =  "li[id='Tut 3a: Multiscale network (low IP3)']"
+export const TUTORIAL_3B_SELECTOR = "li[id='Tut 3b: Multiscale network (high IP3)']"
+export const TUTORIAL_3C_SELECTOR = "li[id='Tut 3c: Multiscale network (no RxD)']"
+export const TUTORIAL_4_SELECTOR = "li[id='Tut 4: Simple oscillatory network']"
 export const PYR_CELL_SELECTOR = '#pyr';
 export const PYR_2_CELL_SELECTOR = '#PYR'
 export const INT_CELL_SELECTOR = '#INT'
@@ -57,10 +57,13 @@ export const TO_VALUE_SELECTOR = '#undefined-to'
 export const STEP_VALUE_SELECTOR = '#undefined-step'
 export const CREATE_EXPERIMENT_BUTTON_SELECTOR = 'button[class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"]'
 export const EXPERIMENT_TABLE_HEADER_SELECTOR = 'th[class="MuiTableCell-root MuiTableCell-body"]'
-export const SIMULATE_POPUP_SELECTOR = 'div[class="MuiBox-root MuiBox-root-185 wrap"]'
+// export const SIMULATE_POPUP_SELECTOR = 'div[class="MuiBox-root MuiBox-root-185 wrap"]'
+export const SIMULATE_POPUP_SELECTOR = '.MuiDialogContent-root > .MuiBox-root.custom-radio > :nth-child(2) > div'
+
 export const CONFIRM_SIMULATE_SELECTOR = '#appBarPerformActionButton'
 export const CONFIRM_EXPERIMENT_STARTED_SELECTOR = 'button[class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"]'
-export const SIMULATION_LOADER_SELECTOR = 'div[class = "MuiBox-root MuiBox-root-186 MuiChip-icon MuiChipLoader"]'
+// export const SIMULATION_LOADER_SELECTOR = 'div[class = "MuiBox-root MuiBox-root-186 MuiChip-icon MuiChipLoader"]'
+export const SIMULATION_LOADER_SELECTOR = '.MuiTableCell-root.MuiTableCell-body.experimentTableCell.MuiTableCell-alignLeft > div > .MuiChipLoader '
 export const BACK_TO_EDIT_SELECTOR = 'span[class="MuiButton-startIcon MuiButton-iconSizeMedium"]'
 export const CELL_TYPES_TAB_SELECTOR = 'div[title="Cell Types"]'
 export const EXPERIMENT_TABLE_SELECTOR = 'table[class="MuiTable-root"]'