I have sketched a JupyterHub Service and a JupyterLab extension (bundled with a tiny server extension to support it) that enables users within the same Hub to easily share notebooks along with information about the environment they should be run in. It’s similar to Google Docs’ feature “Anyone with link can read (and make a copy)”.
In this demo GIF, Alice logs in, opens a notebook, and clicks a button to create a shareable link. A dialog box appears, saying:
For the next hour, any other user on this JupyterHub who has this link will be able to fetch a copy of your latest saved version of dask-examples/array.ipynb.
She copies the link and gives it to Bob. Then, on the right, Bob logs in and pastes the link into his browser. He is given a copy of Alice’s notebook.
See danielballan/jupyterhub-share-link for the source and instructions for trying it.
Notable aspects of this design:
- This works for local process spawners and container-based spawners.
- If a container-based spawner is used, the share link encodes which container image the sender was running, and it opens a server running the same image for the recipient.
- The copying happens entirely via the Jupyter REST API. It does not use the file system directly at all; users do not need to be on a shared file system.
- There is no extra database involved; the only state is a key pair controlled by the Hub Service, which it uses to sign and verify the share links.
See the Uses and Limitations section of the README for thoughts on how this fits into other kinds of sharing folks might want.
This work owes a lot to conversations about sharing notebooks at the Jupyter for Scientific User Facilities and HPC Community Workshop I have not put this in front of users yet, but I intend to deploy it on NSLS-II’s JupyterHub soon. Feedback and ideas welcome!
attn @parente @Shreyas_Cholia
What happens if Bob shares the same notebook back to Alice (sorry, not had time to try this yet… looks interesting…)
Currently, if Alice clicks the link, Bob’s notebook would overwrite hers. We could invoke the same ContentsManager machinery that generates “Untitled-X.ipynb” to avoid the name collision, or prompt the user what they would like the copied file to be named.
That sounds cool; I have had something similar in place in the context of a MOOC, for about 5 years now; we went for a slightly different approach though, in the sense that our shared content is a read-only html snapshot, with no time limit
Primarily this is used for sharing code in the course forum (say, discourse)
It might make sense to support both angles
Very cool Dan! THANKS! I’m going to be out of pocket, but I’ll try it out in Aug.
The Nice. Yes, that’s an important mode of sharing also. @parente and I used to maintain nbexamples to address a very similar use case to yours. Users can access an HTML preview and, if they like, request a copy of the notebook document be made into their home directory.
To the larger point, I think many solutions will be necessary to cover the gamut of what is meant by “sharing”. I made some attempt to organize my thoughts on that in a blog post.
This is awesome! What about hooking into the existing jupyterlab “copy shareable link” UI? I think it might be a little confusing to have two things with the same name.
Overriding this link would solve a super longstanding jupyterhub usability issue, and I think some version of this should probably be on by default in jupyterhub.
There are a few definitions of what folks want from “sharing” that depend on the context:
- it’s my notebook and I want to send folks a copy (sounds like your case)
- it’s a notebook in a shared directory and the same relative URL works for everyone (user-redirect URLs work for this now)
- share a link to visit my own server and do real-time collaboration (RTC is still in-progress, but this is the link shared now by the default UI, which hits permission errors and in some circumstances can end up at case 2 via an elaborate sequence of authentication and redirects)
Unfortunately, different folks definitely want all of these, and many seem to feel that their use case is the obvious one, but the basic scaffolding of presenting share UI is common to all of them.
Thanks! Good suggestion. I’ll remove the button and hook into that “Copy Shareable Link” UI.
Overriding the link leaves Hub administrator to decide which of those definitions of “sharing” is activated. Do you think it’s possible to leave that up to the user without making the UI too confusing? For example, we could make two context menu items:
- Copy Invitation to Edit [RTC, when it’s ready]
- Copy Shareable Link
Then “Copy Shareable Link” could be configured by the Hub admin to always do (1), always do (2), or prompt the user with a dialog box to choose. Is that too confusing for users who don’t have a clear mental model of JupyterHub?
I think it might be hard to leave it to the user. JupyterHub is often deployed in scenarios where users don’t know sufficient context about what other users have access to. Part of the goal of JupyterHub is enabling shifting of these concerns from users to admins, since they can get complicated.
Instead, what I’d probably do is keep exactly the functionality you have, but build it in such a way knowing that you may want to enable customizing what gets shared and the description of sharing. Second, because sharing can mean so many things, a clear description of the sharing event for the user “users with this link will be able to get a copy on their own server for an hour” etc. is a good idea.
Adding specific context: unlike your copy-via-api sharing, the other sharing makes deployment assumptions that the single-user server and user don’t know without being told out of band. So if they are presented as options, they should be opt-in options selected in configuration. Because even when we may want to give users a share-time choice, we don’t want to include options that aren’t going to work.
Updated to override behavior of “Copy Shareable Link” context menu item instead of adding a button.
One of the things that
nbgrader provides is an “exchange” directory that can be used by an instructor to manage the distribution (1:N) and collection (N:1) of assignments such that only course instructors see the N and individual students don’t see other students’ files.
Is that idea, of an exchange directory, something that might be useful as a generic JupyterHub server plugin that sharing services could then build around?
Offhand, I don’t remember how the nbgallery approach works (I don’t remember if there was a clone button you could use to clone a notebook from a someone else’s account into your own gallery).
Yes, I think we definitely want that as well. The nbexamples Classic Notebook extension is a stripped-down fork of
nbgrader that follows that same 1:N model. Like
nbgrader and unlike
jupyterhub-share-link, it involves persistent storage of the shared notebooks. While using nbexamples for a couple years, we encountered two problems:
- The directory of shared notebooks quickly becomes a junk drawer.
- There is no information about the environment that the notebooks should be run in.
The solution to (1) may be to scope the shared notebooks into the user/org that shared them. The solution to (2) may be to share Binder-compatible repositories rather than single notebooks. To satisfy those requirements, I would like to add JupyterLab UI for turning a directory into a repo2docker image and Spawner UI for launching repo2docker images JupyterHub (not BinderHub) with or without the user’s home directory mounted. I see that as a distinct use case from what “Copy Share Link” does, but a use case that I am no less interested in.
@danielballan Something that pastebin lets you do for sharing code snippets is set an expiry time, so an exchange could perhaps support a time out function that lets a user share a copy of a notebook in from their own personal filestore, and s/one else take a copy of it out into their filestore, and then have the exchange expire / delete it automatically?
Cool. In fact, that’s how
jupyterhub-share-link works now. When the sender creates a shareable link, a deadline is assigned (default 1 hour, but configurable up to a max of a couple days via a query parameter). It’s a good way to limit the scope of this feature to short-term, low-effort sharing. Long-term sharing requires a little more effort, e.g. making a Binder-compatible repo.
hey @danielballan - any updates on this one? I’m at a workshop right now that would benefit from this
Great! There are packages on pip and npm, so the installation process is not bad. Configuration is documented here: https://github.com/danielballan/jupyterhub-share-link
- Explore the consequences of making a Shareable Link for a directory not just one notebook, still using our current approach of relying on the Contents API and the Hub to make the bridge (no direct file system access).
- Prompt the recipient in the event of a name collision (currently, the shared notebook overwrites any existing notebook of the same name).
I would love to get more feedback from folks using this, and I’m happy to make myself available for support.
There are some changes to the way we try to deliver a shared notebook in an environment that is compatible with the one it was shared from.
Formerly, the when a shareable link was created, we captured
JUPYTER_IMAGE_SPEC from the server’s environment using a server extension. If there
JUPYTER_IMAGE_SPEC was not set, we spawned a server with the default options and hoped for the best.
Now, we capture the
user_options that the server was spawned with. This means we don’t need any server extension at all, and we can work with most (all?) spawners, generally capturing how they were spawned so that the recipient(s) should receive the notebook in a compatible environment.
user_options from the Hub API requires a one-line change to JupyterHub, submitted for consideration here: https://github.com/jupyterhub/jupyterhub/pull/2755
This is now running successfully in production on a JupyterHub at https://jupyter.sdcc.bnl.gov (regrettably not public).
fantastic! great to see the improvements.