JupyterHub service

Could someone help me figure out how to get a dash app running as a JupyterHub managed service? The error is in the dash-render api.js, which is pretty foreign to me. The toy app below runs fine when started from the console and accessed w/out JupyterHub. However, when I start up JupyterHub like so …

$ sudo jupyterhub -f /etc/jupyterhub/jupyterhub_config.py

where the config file contains …

c.JupyterHub.services = [
    {
        'name': 'hello-world',
        'admin': False,
        'url': 'http://127.0.0.1:8051',
        'cwd': '/srv/hello-world',
        'command': ['python', 'main.py', '--port', '8051'],
    }
]

When I navigate to http://127.0.0.1:8000/services/hello-world, it’s just “Error loading dependencies” and in the javascript console:

api.js:73 Error: Given action "SET_LAYOUT", reducer "layout" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.
    at combineReducers.js:123
    at reducer.js:61
    at reducer.js:101
    at p (createStore.js:165)
    at index.js:11
    at e.value (APIController.react.js:47)
    at e.value (APIController.react.js:29)
    at p.updateComponent (react-dom.min.js:13)
    at p.receiveComponent (react-dom.min.js:13)
    at Object.receiveComponent (react-dom.min.js:14)
(anonymous) @ api.js:73
Promise.catch (async)
(anonymous) @ api.js:71
(anonymous) @ index.js:8
value @ APIController.react.js:44
value @ APIController.react.js:25
e.notifyAll @ react-dom.min.js:12
close @ react-dom.min.js:14
closeAll @ react-dom.min.js:15
perform @ react-dom.min.js:15
perform @ react-dom.min.js:15
perform @ react-dom.min.js:14
T @ react-dom.min.js:14
closeAll @ react-dom.min.js:15
perform @ react-dom.min.js:15
batchedUpdates @ react-dom.min.js:14
i @ react-dom.min.js:14
_renderNewRootComponent @ react-dom.min.js:14
_renderSubtreeIntoContainer @ react-dom.min.js:14
render @ react-dom.min.js:14
(anonymous) @ index.js:9
n @ bootstrap:19
(anonymous) @ transition.js:53
n @ bootstrap:19
(anonymous) @ bootstrap:83
(anonymous) @ bootstrap:83

api.js:73 TypeError: Cannot read property 'forEach' of undefined
    at e.default (dependencyGraph.js:11)
    at combineReducers.js:120
    at reducer.js:61
    at reducer.js:101
    at p (createStore.js:165)
    at index.js:11
    at e.value (APIController.react.js:59)
    at e.value (APIController.react.js:29)
    at p.updateComponent (react-dom.min.js:13)
    at p.receiveComponent (react-dom.min.js:13)
(anonymous) @ api.js:73
Promise.catch (async)
(anonymous) @ api.js:71
(anonymous) @ index.js:8
value @ APIController.react.js:54
value @ APIController.react.js:25
e.notifyAll @ react-dom.min.js:12
close @ react-dom.min.js:14
closeAll @ react-dom.min.js:15
perform @ react-dom.min.js:15
perform @ react-dom.min.js:15
perform @ react-dom.min.js:14
T @ react-dom.min.js:14
closeAll @ react-dom.min.js:15
perform @ react-dom.min.js:15
batchedUpdates @ react-dom.min.js:14
i @ react-dom.min.js:14
_renderNewRootComponent @ react-dom.min.js:14
_renderSubtreeIntoContainer @ react-dom.min.js:14
render @ react-dom.min.js:14
(anonymous) @ index.js:9
n @ bootstrap:19
(anonymous) @ transition.js:53
n @ bootstrap:19
(anonymous) @ bootstrap:83
(anonymous) @ bootstrap:83

Here’s /srv/hello-world/main.py.

from os import environ
from argparse import ArgumentParser
from dash import Dash
import dash_html_components as tag

parser = ArgumentParser()
parser.add_argument('-p', '--port', default=8050)
args = parser.parse_args()

app = Dash(__name__)
app.layout = tag.Div('Hello, World!')
app.config.update({
    'routes_pathname_prefix': '',
    'requests_pathname_prefix': environ.get('JUPYTERHUB_SERVICE_PREFIX', '/'),
})

if __name__ == '__main__':
    app.run_server(port=args.port)

As you can see, I’ve been guessing at the prefixes, but am really just taking shots in the dark here.

I’ve seen this before. Over here: IIS deployment - Error "Given action "SET_LAYOUT" ..."

Similar issue in that that it works when run locally, but when hosted (in this case with IIS) the problem occurs.

I’m not really familiar with dash-render. Any ideas @valentijnnieman, @Marc-Andre, @Philippe, @alexcjohnson?

Hello,

In fact I have the same issue. And I have too custom ‘routes_pathname_prefix’ and ‘requests_pathname_prefix’. So I think it’s a good line of approach.

I now suspect this is the result of no jupyterhub authentication being implemented in my service (I hoped it was sort of automatic). No, nevermind.

Here’s the response of the dash server running w/out jupyterhub:

$ curl 127.0.0.1:8050/_dash-layout
{"props": {"children": "Hello, World!"}, "type": "Div", "namespace": "dash_html_components"}

Whereas the response to the jupyterhub service simply returns the dash generated “Loading…” static html.

$ curl 127.0.0.1:8000/services/hello-world/_dash-layout
<!DOCTYPE html>
<html>
...

I am looking into how to use HubAuth as in the flask-whoami demo.

Ah! Found the answer.

Here’s the working /srv/hello-world/main.py:

from os import environ
from argparse import ArgumentParser
from dash import Dash
import dash_html_components as tag

parser = ArgumentParser()
parser.add_argument('-p', '--port', default=8050)
args = parser.parse_args()

app = Dash(
    __name__,
    url_base_pathname=environ.get('JUPYTERHUB_SERVICE_PREFIX', '/')
)
app.layout = tag.Div('Hello, World!')

if __name__ == '__main__':
    app.run_server(port=args.port)

1 Like

Thank you a lot, that fix this issue for me too !

1 Like

@_Ian @Yru4ma Can you guys explain how you are using dash within Jupyter Hub? Is it strictly internal to your team or do you also “deploy” your solutions to external partners / customers with some sort of limited permissions?

I’m at a university and our clients are researchers (at our university and others) that authenticate through JupyterHub to access hosted Jupyter lab/notebook. Not sure whether that fits your idea of internal or external. We are just testing this, but the prototype is a dashboard as a hosted service managed by JupyterHub and available to a limited set of users.