Browse Source

Fix the examples, migrate to jupyterlab_server (#5316)

* Update the example app to use jupyterlab_launcher.

Requires some small changes in jupyterlab_launcher.

* Simplify js function that removes the token from the URL.

* jupyterlab_launcher -> jupyterlab_server

* Remove debug printing

* Remove the token in the example app.

* Fix other examples too.

* Remove unused error template.

* Bump required server version

* Fix example app to serve from a different directory than `/lab`.

* Title the example app with the configured application name.

* Don’t try to copy the error.html template we deleted.

* Bump server version

* Override the build check from the jupyterlab server extension.

If the jupyterlab server extension happens to be installed and enabled, it will automatically add the build check to the page config data, so we override it.

* Turn off Jupyter config system in the example app.

This makes sure that, for example, any server extensions are not loaded, including the JupyterLab server extension that might modify the page_config_data.
Jason Grout 6 years ago
parent
commit
d6005474a3

+ 1 - 1
CONTRIBUTING.md

@@ -105,7 +105,7 @@ Notes:
   rebuild.
 
 - If `pip` gives a `VersionConflict` error, it usually means that the installed
-  version of `jupyterlab_launcher` is out of date. Run `pip install --upgrade jupyterlab_launcher` to get the latest version.
+  version of `jupyterlab_server` is out of date. Run `pip install --upgrade jupyterlab_server` to get the latest version.
 
 - To install JupyterLab in isolation for a single conda/virtual environment, you can add the `--sys-prefix` flag to the extension activation above; this will tie the installation to the `sys.prefix` location of your environment, without writing anything in your user-wide settings area (which are visible to all your envs):
 

+ 0 - 1
dev_mode/webpack.config.js

@@ -40,7 +40,6 @@ fs.ensureDirSync(buildDir);
 
 fs.writeFileSync(path.join(buildDir, 'index.out.js'), result);
 fs.copySync('./package.json', path.join(buildDir, 'package.json'));
-fs.copySync('./templates/error.html', path.join(buildDir, 'error.html'));
 
 // Set up variables for watch mode.
 var localLinked = {};

+ 0 - 17
examples/app/index.html

@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>JupyterLab Custom App</title>
-</head>
-<body>
-  <script id='jupyter-config-data' type="application/json">{
-  {% for key, value in page_config.items() -%}
-  "{{ key }}": "{{ value }}",
-  {% endfor -%}
-  "baseUrl": "{{base_url}}",
-  "wsUrl": "{{ws_url}}",
-  "terminalsAvailable": "{{terminals_available}}"
- }</script>
-  <script src="/example/static/bundle.js" main="index"></script>
-</body>
-</html>

+ 0 - 1
examples/app/index.js

@@ -17,7 +17,6 @@ window.addEventListener('load', function() {
     require('@jupyterlab/csvviewer-extension'),
     require('@jupyterlab/docmanager-extension'),
     require('@jupyterlab/fileeditor-extension'),
-    require('@jupyterlab/faq-extension'),
     require('@jupyterlab/filebrowser-extension'),
     require('@jupyterlab/help-extension'),
     require('@jupyterlab/imageviewer-extension'),

+ 26 - 67
examples/app/main.py

@@ -1,82 +1,41 @@
-"""
-Copyright (c) Jupyter Development Team.
-Distributed under the terms of the Modified BSD License.
-"""
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
 
+from jupyterlab_server import LabServerApp, LabConfig
 import os
-from jinja2 import FileSystemLoader
-from notebook.base.handlers import IPythonHandler, FileFindHandler
-from notebook.utils import url_path_join as ujoin
-from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
-from jupyterlab_launcher.handlers import SettingsHandler, ThemesHandler
 
 HERE = os.path.dirname(__file__)
 
+# Turn off the Jupyter configuration system so configuration files on disk do
+# not affect this app. This helps this app to truly be standalone.
+os.environ["JUPYTER_NO_CONFIG"]="1"
 
-class ExampleHandler(IPythonHandler):
-    """Handle requests between the main app page and notebook server."""
+class ExampleApp(LabServerApp):
 
-    def get(self):
-        """Get the main page for the application's interface."""
-        return self.write(self.render_template("index.html",
-            static=self.static_url, base_url=self.base_url,
-            terminals_available=self.settings['terminals_available'],
-            page_config=self.settings['page_config_data']))
+    default_url = Unicode('/exampleapp',
+                          help='The default URL to redirect to from `/`')
 
-    def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
-        return loader.load(self.settings['jinja2_env'], name)
+    lab_config = LabConfig(
+        app_name = 'JupyterLab Example App',
+        app_settings_dir = os.path.join(HERE, 'build', 'application_settings'),
+        page_url = '/exampleapp',
+        schemas_dir = os.path.join(HERE, 'build', 'schemas'),
+        settings_dir = os.path.join(HERE, 'build', 'settings'),
+        static_dir = os.path.join(HERE, 'build'),
+        templates_dir = os.path.join(HERE, 'templates'),
+        themes_dir = os.path.join(HERE, 'build', 'themes'),
+        user_settings_dir = os.path.join(HERE, 'build', 'user_settings'),
+        workspaces_dir = os.path.join(HERE, 'build', 'workspaces'),
+    )
 
+    def start(self):
+        settings = self.web_app.settings
 
-class ExampleApp(NotebookApp):
-
-    default_url = Unicode('/example')
-
-    def init_webapp(self):
-        """initialize tornado webapp and httpserver.
-        """
-        super(ExampleApp, self).init_webapp()
-        wsettings = self.web_app.settings
-        base_url = wsettings['base_url']
-        page_url = self.default_url
-        settings_path = ujoin(
-            base_url, page_url, 'api', 'settings', '(?P<section_name>.+)'
-        )
-        themes_path = ujoin(
-            base_url, page_url, 'api', 'themes'
-        )
-
-        wsettings.setdefault('page_config_data', dict())
-        wsettings['page_config_data']['token'] = self.token
-        wsettings['page_config_data']['pageUrl'] = page_url
-        wsettings['page_config_data']['themesUrl'] = themes_path
-        mathjax_config = wsettings.get('mathjax_config',
-                'TeX-AMS_HTML-full,Safe')
-        wsettings['page_config_data'].setdefault('mathjaxConfig',
-                mathjax_config)
-        wsettings['page_config_data'].setdefault('mathjaxUrl', self.mathjax_url)
-
-        default_handlers = [
-            (ujoin(base_url, '/example?'), ExampleHandler),
-            ((settings_path, SettingsHandler, {
-                'schemas_dir': os.path.join(HERE, 'build', 'schemas'),
-                'settings_dir': os.path.join(HERE, 'build', 'settings'),
-                'app_settings_dir': os.path.join(HERE, 'build', 'schemas'),
-            })),
-            ((ujoin(themes_path, "(.*)"), ThemesHandler, {
-                'themes_url': themes_path,
-                'path': os.path.join(HERE, 'build', 'themes')
-            })),
-            (ujoin(base_url, '/example/static/(.*)'), FileFindHandler,
-                {'path': 'build'}),
-            # Let the lab handler act as the fallthrough option instead
-            # of a 404.
-            (ujoin(base_url, page_url, r'/?.*'), ExampleHandler)
-        ]
-
-        self.web_app.add_handlers(".*$", default_handlers)
+        # By default, make terminals available.
+        settings.setdefault('terminals_available', True)
 
+        super().start()
 
 if __name__ == '__main__':
     ExampleApp.launch_instance()

+ 21 - 4
jupyterlab/staging/templates/error.html → examples/app/templates/error.html

@@ -28,15 +28,32 @@ div#header, div#site {
 
 <div class="error">
     {% block h1_error %}
-    <h2>JupyterLab assets not detected, please rebuild</h2>
-    <script>
-    console.error('Missing assets in "{{static_dir}}"');
-    </script>
+    <h1>{{status_code}} : {{status_message}}</h1>
     {% endblock h1_error %}
+    {% block error_detail %}
+    {% if message %}
+    <p>The error was:</p>
+    <div class="traceback-wrapper">
+    <pre class="traceback">{{message}}</pre>
+    </div>
+    {% endif %}
+    {% endblock %}
 </header>
 
 {% endblock %}
 
+{% block script %}
+<script type='text/javascript'>
+window.onload = function () {
+  var tb = document.getElementsByClassName('traceback')[0];
+  tb.scrollTop = tb.scrollHeight;
+  {% if message %}
+  console.error("{{message}}")
+  {% endif %}
+};
+</script>
+{% endblock script %}
+
 </body>
 
 </html>

+ 27 - 0
examples/app/templates/index.html

@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>{{page_config['appName']}}</title>
+</head>
+<body>
+  <script id='jupyter-config-data' type="application/json">{
+  {% for key, value in page_config.items() -%}
+  "{{ key }}": "{{ value }}",
+  {% endfor -%}
+  "baseUrl": "{{base_url}}",
+  "wsUrl": "{{ws_url}}"
+ }</script>
+  <script src="{{page_config['publicUrl']}}bundle.js" main="index"></script>
+
+  <script type="text/javascript">
+    /* Remove token from URL. */
+    (function () {
+      var parsedUrl = new URL(window.location.href);
+      if (parsedUrl.searchParams.get('token')) {
+        parsedUrl.searchParams.delete('token');
+        window.history.replaceState({ }, '', parsedUrl.href);
+      }
+    })();
+  </script>
+</body>
+</html>

+ 2 - 6
examples/app/webpack.config.js

@@ -1,22 +1,18 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-// Support for Node 0.10
-// See https://github.com/webpack/css-loader/issues/144
-require('es6-promise').polyfill();
-
 module.exports = {
   entry: ['whatwg-fetch', './index.js'],
   output: {
     path: __dirname + '/build',
-    filename: 'bundle.js',
-    publicPath: 'example/static'
+    filename: 'bundle.js'
   },
   node: {
     fs: 'empty'
   },
   bail: true,
   devtool: 'cheap-source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 12 - 0
examples/cell/index.html

@@ -10,5 +10,17 @@
         "token": "{{token}}"
     }</script>
     <script src="example/bundle.js"></script>
+
+    <script type="text/javascript">
+      /* Remove token from URL. */
+      (function () {
+        var parsedUrl = new URL(window.location.href);
+        if (parsedUrl.searchParams.get('token')) {
+          parsedUrl.searchParams.delete('token');
+          window.history.replaceState({ }, '', parsedUrl.href);
+        }
+      })();
+    </script>
+  
   </body>
 </html>

+ 3 - 2
examples/cell/main.py

@@ -17,6 +17,7 @@ from notebook.base.handlers import IPythonHandler, FileFindHandler
 from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
 
+HERE = os.path.dirname(__file__)
 
 class ExampleHandler(IPythonHandler):
     """Handle requests between the main app page and notebook server."""
@@ -28,7 +29,7 @@ class ExampleHandler(IPythonHandler):
             token=self.settings['token']))
 
     def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
+        loader = FileSystemLoader(HERE)
         return loader.load(self.settings['jinja2_env'], name)
 
 
@@ -43,7 +44,7 @@ class ExampleApp(NotebookApp):
         default_handlers = [
             (r'/example/?', ExampleHandler),
             (r"/example/(.*)", FileFindHandler,
-                {'path': 'build'}),
+                {'path': os.path.join(HERE, 'build')}),
         ]
         self.web_app.add_handlers(".*$", default_handlers)
 

+ 1 - 0
examples/cell/webpack.config.js

@@ -7,6 +7,7 @@ module.exports = {
   },
   bail: true,
   devtool: 'cheap-source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 12 - 0
examples/console/index.html

@@ -10,5 +10,17 @@
         "token": "{{token}}"
     }</script>
     <script src="example/bundle.js"></script>
+
+    <script type="text/javascript">
+      /* Remove token from URL. */
+      (function () {
+        var parsedUrl = new URL(window.location.href);
+        if (parsedUrl.searchParams.get('token')) {
+          parsedUrl.searchParams.delete('token');
+          window.history.replaceState({ }, '', parsedUrl.href);
+        }
+      })();
+    </script>
+  
   </body>
 </html>

+ 3 - 2
examples/console/main.py

@@ -17,6 +17,7 @@ from notebook.base.handlers import IPythonHandler, FileFindHandler
 from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
 
+HERE = os.path.dirname(__file__)
 
 class ExampleHandler(IPythonHandler):
     """Handle requests between the main app page and notebook server."""
@@ -28,7 +29,7 @@ class ExampleHandler(IPythonHandler):
             token=self.settings['token']))
 
     def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
+        loader = FileSystemLoader(HERE)
         return loader.load(self.settings['jinja2_env'], name)
 
 
@@ -43,7 +44,7 @@ class ExampleApp(NotebookApp):
         default_handlers = [
             (r'/example/?', ExampleHandler),
             (r"/example/(.*)", FileFindHandler,
-                {'path': 'build'}),
+                {'path': os.path.join(HERE, 'build')}),
         ]
         self.web_app.add_handlers(".*$", default_handlers)
 

+ 1 - 0
examples/console/webpack.config.js

@@ -7,6 +7,7 @@ module.exports = {
   },
   bail: true,
   devtool: 'source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 12 - 0
examples/filebrowser/index.html

@@ -9,5 +9,17 @@
         "token": "{{token}}"
     }</script>
     <script src="example/bundle.js"></script>
+
+    <script type="text/javascript">
+      /* Remove token from URL. */
+      (function () {
+        var parsedUrl = new URL(window.location.href);
+        if (parsedUrl.searchParams.get('token')) {
+          parsedUrl.searchParams.delete('token');
+          window.history.replaceState({ }, '', parsedUrl.href);
+        }
+      })();
+    </script>
+  
   </body>
 </html>

+ 3 - 2
examples/filebrowser/main.py

@@ -17,6 +17,7 @@ from notebook.base.handlers import IPythonHandler, FileFindHandler
 from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
 
+HERE = os.path.dirname(__file__)
 
 class ExampleHandler(IPythonHandler):
     """Handle requests between the main app page and notebook server."""
@@ -28,7 +29,7 @@ class ExampleHandler(IPythonHandler):
             token=self.settings['token']))
 
     def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
+        loader = FileSystemLoader(HERE)
         return loader.load(self.settings['jinja2_env'], name)
 
 
@@ -43,7 +44,7 @@ class ExampleApp(NotebookApp):
         default_handlers = [
             (r'/example/?', ExampleHandler),
             (r"/example/(.*)", FileFindHandler,
-                {'path': 'build'}),
+                {'path': os.path.join(HERE, 'build')}),
         ]
         self.web_app.add_handlers(".*$", default_handlers)
 

+ 1 - 0
examples/filebrowser/webpack.config.js

@@ -10,6 +10,7 @@ module.exports = {
   },
   bail: true,
   devtool: 'cheap-source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 12 - 0
examples/notebook/index.html

@@ -10,5 +10,17 @@
     "token": "{{token}}"
     }</script>
     <script src="example/bundle.js"></script>
+
+    <script type="text/javascript">
+      /* Remove token from URL. */
+      (function () {
+        var parsedUrl = new URL(window.location.href);
+        if (parsedUrl.searchParams.get('token')) {
+          parsedUrl.searchParams.delete('token');
+          window.history.replaceState({ }, '', parsedUrl.href);
+        }
+      })();
+    </script>
+  
   </body>
 </html>

+ 3 - 2
examples/notebook/main.py

@@ -17,6 +17,7 @@ from notebook.base.handlers import IPythonHandler, FileFindHandler
 from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
 
+HERE = os.path.dirname(__file__)
 
 class ExampleHandler(IPythonHandler):
     """Handle requests between the main app page and notebook server."""
@@ -28,7 +29,7 @@ class ExampleHandler(IPythonHandler):
             token=self.settings['token']))
 
     def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
+        loader = FileSystemLoader(HERE)
         return loader.load(self.settings['jinja2_env'], name)
 
 
@@ -43,7 +44,7 @@ class ExampleApp(NotebookApp):
         default_handlers = [
             (r'/example/?', ExampleHandler),
             (r"/example/(.*)", FileFindHandler,
-                {'path': 'build'}),
+                {'path': os.path.join(HERE, 'build')}),
         ]
         self.web_app.add_handlers(".*$", default_handlers)
 

+ 1 - 0
examples/notebook/webpack.config.js

@@ -7,6 +7,7 @@ module.exports = {
   },
   bail: true,
   devtool: 'cheap-source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 12 - 0
examples/terminal/index.html

@@ -10,5 +10,17 @@
       "token": "{{token}}"
     }</script>
     <script src="example/bundle.js"></script>
+
+    <script type="text/javascript">
+      /* Remove token from URL. */
+      (function () {
+        var parsedUrl = new URL(window.location.href);
+        if (parsedUrl.searchParams.get('token')) {
+          parsedUrl.searchParams.delete('token');
+          window.history.replaceState({ }, '', parsedUrl.href);
+        }
+      })();
+    </script>
+  
   </body>
 </html>

+ 3 - 2
examples/terminal/main.py

@@ -17,6 +17,7 @@ from notebook.base.handlers import IPythonHandler, FileFindHandler
 from notebook.notebookapp import NotebookApp
 from traitlets import Unicode
 
+HERE = os.path.dirname(__file__)
 
 class ExampleHandler(IPythonHandler):
     """Handle requests between the main app page and notebook server."""
@@ -29,7 +30,7 @@ class ExampleHandler(IPythonHandler):
             terminals_available=self.settings['terminals_available']))
 
     def get_template(self, name):
-        loader = FileSystemLoader(os.getcwd())
+        loader = FileSystemLoader(HERE)
         return loader.load(self.settings['jinja2_env'], name)
 
 
@@ -44,7 +45,7 @@ class ExampleApp(NotebookApp):
         default_handlers = [
             (r'/example/?', ExampleHandler),
             (r"/example/(.*)", FileFindHandler,
-                {'path': 'build'}),
+                {'path': os.path.join(HERE, 'build')}),
         ]
         self.web_app.add_handlers(".*$", default_handlers)
 

+ 1 - 0
examples/terminal/webpack.config.js

@@ -10,6 +10,7 @@ module.exports = {
   },
   bail: true,
   devtool: 'cheap-source-map',
+  mode: 'production',
   module: {
     rules: [
       { test: /\.css$/, use: ['style-loader', 'css-loader'] },

+ 1 - 1
jupyterlab/commands.py

@@ -23,7 +23,7 @@ from urllib.request import Request, urlopen, urljoin, quote
 from urllib.error import URLError
 
 from jupyter_core.paths import jupyter_config_path
-from jupyterlab_launcher.process import which, Process, WatchHelper
+from jupyterlab_server.process import which, Process, WatchHelper
 from notebook.nbextensions import GREEN_ENABLED, GREEN_OK, RED_DISABLED, RED_X
 
 from .semver import Range, gte, lt, lte, gt, make_semver

+ 1 - 1
jupyterlab/extension.py

@@ -30,7 +30,7 @@ def load_jupyter_server_extension(nbapp):
     """
     # Delay imports to speed up jlpmapp
     from json import dumps
-    from jupyterlab_launcher import add_handlers, LabConfig
+    from jupyterlab_server import add_handlers, LabConfig
     from notebook.utils import url_path_join as ujoin, url_escape
     from notebook._version import version_info
     from tornado.ioloop import IOLoop

+ 1 - 1
jupyterlab/jlpmapp.py

@@ -6,7 +6,7 @@
 import sys
 
 import os
-from jupyterlab_launcher.process import which, subprocess
+from jupyterlab_server.process import which, subprocess
 
 HERE = os.path.dirname(os.path.abspath(__file__))
 YARN_PATH = os.path.join(HERE, 'staging', 'yarn.js')

+ 10 - 23
jupyterlab/staging/templates/template.html

@@ -8,29 +8,16 @@
 </head>
 <body>
 
-<script type="text/javascript">
-  /* Remove token from URL. */
-  (function () {
-    var location = window.location;
-    var search = location.search;
-
-    // If there is no query string, bail.
-    if (search.length <= 1) {
-      return;
-    }
-
-    // Rebuild the query string without the `token`.
-    var query = '?' + search.slice(1).split('&')
-      .filter(function (param) { return param.split('=')[0] !== 'token'; })
-      .join('&');
-
-    // Rebuild the URL with the new query string.
-    var url = location.origin + location.pathname +
-      (query !== '?' ? query : '') + location.hash;
-
-    window.history.replaceState({ }, '', url);
-  })();
-</script>
+  <script type="text/javascript">
+    // Remove token from URL.
+    (function () {
+      var parsedUrl = new URL(window.location.href);
+      if (parsedUrl.searchParams.get('token')) {
+        parsedUrl.searchParams.delete('token');
+        window.history.replaceState({ }, '', parsedUrl.href);
+      }
+    })();
+  </script>
 
 </body>
 </html>

+ 0 - 1
jupyterlab/staging/webpack.config.js

@@ -40,7 +40,6 @@ fs.ensureDirSync(buildDir);
 
 fs.writeFileSync(path.join(buildDir, 'index.out.js'), result);
 fs.copySync('./package.json', path.join(buildDir, 'package.json'));
-fs.copySync('./templates/error.html', path.join(buildDir, 'error.html'));
 
 // Set up variables for watch mode.
 var localLinked = {};

+ 3 - 3
jupyterlab/tests/test_app.py

@@ -23,8 +23,8 @@ from ipykernel.kernelspec import write_kernel_spec
 import jupyter_core
 from jupyter_core.application import base_aliases
 
-from jupyterlab_launcher.process_app import ProcessApp
-import jupyterlab_launcher
+from jupyterlab_server.process_app import ProcessApp
+import jupyterlab_server
 
 
 HERE = osp.realpath(osp.dirname(__file__))
@@ -53,7 +53,7 @@ def _create_schemas_dir():
     os.makedirs(extension_dir)
 
     # Get schema content.
-    schema_package = jupyterlab_launcher.__name__
+    schema_package = jupyterlab_server.__name__
     schema_path = 'tests/schemas/@jupyterlab/apputils-extension/themes.json'
     themes = pkg_resources.resource_string(schema_package, schema_path)
 

+ 1 - 1
jupyterlab/tests/test_build_api.py

@@ -3,7 +3,7 @@ from tempfile import TemporaryDirectory
 import threading
 
 from jupyterlab.labapp import LabApp
-from jupyterlab_launcher.tests.utils import APITester, LabTestBase
+from jupyterlab_server.tests.utils import APITester, LabTestBase
 from notebook.tests.launchnotebook import assert_http_error
 
 

+ 2 - 2
packages/services/examples/node/main.py

@@ -4,8 +4,8 @@
 from __future__ import print_function, absolute_import
 import json
 import os
-from jupyterlab_launcher.process import which
-from jupyterlab_launcher.process_app import ProcessApp
+from jupyterlab_server.process import which
+from jupyterlab_server.process_app import ProcessApp
 
 HERE = os.path.dirname(os.path.realpath(__file__))
 

+ 1 - 1
setup.py

@@ -129,7 +129,7 @@ setup_args = dict(
 
 setup_args['install_requires'] = [
     'notebook>=4.3.1',
-    'jupyterlab_launcher>=0.13.1,<0.14.0'
+    'jupyterlab_server>=0.2.0,<0.3.0'
 ]
 
 setup_args['extras_require'] = {