Skip to content

Commit

Permalink
Merge pull request #127 from jgwerner/fix/link-generator-nb
Browse files Browse the repository at this point in the history
Update ipynb with newer query parameters and toggles
  • Loading branch information
yuvipanda authored Jun 26, 2020
2 parents 08b93db + 19dbd71 commit e93429c
Showing 1 changed file with 109 additions and 28 deletions.
137 changes: 109 additions & 28 deletions binder/link_generator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,132 @@
"2. The git repository referred to in the nbgitpuller link is made up to date in their home directory (keeping local changes if there are merge conflicts)\n",
"3. They are shown the specific notebook / directory referred to in the nbgitpuller link.\n",
"\n",
"This is a great way to distribute materials to students."
"This is a great way to distribute materials to students.\n",
"\n",
"# Generate `nbgitpuller` links for your JupyterHub\n",
"\n",
"## Sequence of events when users click an `nbgitpuller` link pointing to your JupyterHub,\n",
"\n",
"1. They are asked to log in to the JupyterHub if they have not already\n",
"2. The git repository referred to in the nbgitpuller link is made up to date in their home directory (keeping local changes if there are merge conflicts)\n",
"3. They are shown the specific notebook / directory referred to in the nbgitpuller link.\n",
"\n",
"This is a great way to distribute materials to students.\n",
"\n",
"## Canvas LMS: Assignment Links vs Custom Fields\n",
"\n",
"The Canvas LMS expects the assignment link to include URL encoded parameters since the request is sent to the External Tool as a POST request (in this case IllumiDesk is the External Tool). However, all characters (even those considered safe) after the domain and `next=` part should be URL encoded, such as the `/`, `&`, and `=` characters.\n",
"\n",
"The `Custom Fields` text box in the App -> Settings section, on the other hand, does not expect all characters to be URL encoded. The `/` characters that are assigned as part of the query parameter values should be encoded, but not the `&` and `=` characters.\n",
"\n",
"## Usage\n",
"\n",
"- **Assignment Link**: creates a string value which represents an `Assignment` link by toggling the check box next to the `is_assignment_link` label. If unchecked, the tool will create a string to add to the Custom Field section.\n",
"- **Jupyter Lab Link**: creates a string value which redirects the user to a `Jupyter Lab` workspace instead of the `Jupyter Classic` workspace.\n",
"- **LTI Launches**: adds the route associated to the LTI 1.1 login handler. If disabled, it is assumed that the user is using the default authentication class bound to the root of the `domain_url` value.\n",
"- **Default Values**: to avoid having to enter the same values in the widget's text fields on a repetitive basis, add the string values to the function's parameters. For example, the `branch` parameter defaults to `master`."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"metadata": {},
"outputs": [],
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "'https://my.hub.com/hub/lti/launch?next=%2Fuser-redirect%2Fgit-pull?repo%3D%26branch%3Dmaster%26urlpath%3Dlab%252Ftree%252F.%252F%253Fautodecode'"
},
"metadata": {}
}
],
"source": [
"import os\n",
"from ipywidgets import interact\n",
"from urllib.parse import urlunparse, urlparse, urlencode, parse_qs\n",
"from urllib.parse import urlunparse, urlparse, urlencode, parse_qs, parse_qsl, quote\n",
"from IPython.display import Markdown\n",
"\n",
"\n",
"@interact\n",
"def make_nbgitpuller_link(hub_url='', repo_url='', branch='', filepath='', app=['notebook', 'lab']):\n",
" \"\"\"Generate an ipywidget form that creates an interact link.\"\"\"\n",
"def make_launch_link(is_assignment_link=True, is_jupyterlab=True, is_lti11=True, branch='master', hub_url='https://my.hub.com', repo_url='', urlpath=''):\n",
" \"\"\"\n",
" Generate a launch request which clones and merges source files from a git-based\n",
" repository.\n",
"\n",
" Args:\n",
" is_assignment_link (bool): set to True to create a full assignment link, defaults to True.\n",
" is_jupyterlab (bool): set to True to launch Jupyter Lab workspaces, defaults to True.\n",
" is_lti11 (bool): set to True to initiate launch requests with the LTI 1.1 standard.\n",
" branch (str): git repo branch\n",
" hub_url (str): full hub url which needs to include scheme (http or https) and netloc (full domain).\n",
" repo_url (str): full git repo url which needs to include scheme (http or https), netloc (full domain) and path.\n",
" url_path (str): a path to redirect users to after the workspace has successfully spawned (started).\n",
"\n",
" Returns:\n",
" An interactive IPython.display.Markdown object.\n",
" \"\"\"\n",
"\n",
" # Parse the query to its constituent parts\n",
" domain_scheme, domain_netloc, domain_path, domain_params, domain_query_str, domain_fragment = urlparse(hub_url.strip())\n",
" \n",
" # Don't do anything if we don't have a hub_url or repo_url\n",
" if not (len(hub_url) > 0 and len(repo_url) > 0):\n",
" return\n",
" repo_scheme, repo_netloc, repo_path, repo_params, repo_query_str, repo_fragment = urlparse(repo_url.strip())\n",
" folder_from_repo_url_path = os.path.basename(os.path.normpath(repo_path))\n",
" \n",
" # Parse the query to its constituent parts\n",
" scheme, netloc, path, params, query_str, fragment = urlparse(hub_url.strip())\n",
" # Make sure the path doesn't contain multiple slashes\n",
" if not domain_path.endswith('/'):\n",
" domain_path += '/'\n",
" domain_path += 'user-redirect/git-pull'\n",
" \n",
" # nbgitpuller takes arguments via query parameters.\n",
" # However, your hub might already require query parameters to function (for example, to pick a profile to launch in)\n",
" # So we preserve the parameters we get & amend them to include the git-pull info we want\n",
" query = parse_qs(query_str, keep_blank_values=True)\n",
" # With Canvas using LTI 11 Assignment launch requests all characters after the netloc are considered unsafe.\n",
" # When adding custom parameters within the App Settings -> Custom Fields section, only items after the \n",
" path_encoded = ''\n",
" if is_assignment_link:\n",
" path_encoded = quote(domain_path, safe='')\n",
" else:\n",
" path_encoded = quote(domain_path)\n",
"\n",
" path_redirect_url = f'next={path_encoded}'\n",
" if is_lti11:\n",
" assignment_link_path = f'/hub/lti/launch?next={path_encoded}'\n",
" else:\n",
" assignment_link_path = f'/hub?next={path_encoded}'\n",
" \n",
" # Create a tuple of query params from original domain link\n",
" query_params_from_hub_url = parse_qsl(domain_query_str, keep_blank_values=True)\n",
" \n",
" # Make sure the path doesn't contain multiple slashes\n",
" if not path.endswith('/'):\n",
" path += '/'\n",
" path += 'hub/user-redirect/git-pull'\n",
" # Set path based on whether or not the user would like to spawn JupyterLab or Jupyter Classic\n",
" urlpath_workspace = ''\n",
" if is_jupyterlab:\n",
" urlpath_workspace = f'lab/tree/{folder_from_repo_url_path}/{urlpath}?autodecode'\n",
" else:\n",
" urlpath_workspace = f'tree/{folder_from_repo_url_path}/{urlpath}'\n",
" \n",
" # Construct query parameters from \n",
" for name, val in [('repo', repo_url), ('branch', branch), ('subPath', filepath), ('app', app)]:\n",
" if len(val) > 0:\n",
" query[name] = val.strip().strip('/')\n",
" # Create a tuple of query params for git functionality. Check whether or not we want to launch with\n",
" # jupyterlab to add additional items to the path.\n",
" query_params_for_git = [('repo', repo_url), ('branch', branch), ('urlpath', urlpath_workspace)]\n",
" \n",
" url = urlunparse((scheme, netloc, path, params, urlencode(query, doseq=True), fragment))\n",
" # Merge query params into one list of tuples\n",
" query_params_all = query_params_from_hub_url + query_params_for_git\n",
" \n",
" # FIXME: Have this return something instead of print so we can unit test\n",
" print(url)"
" # First build urlencoded query params where the &, =, and / are considered safe. Then, percent encode\n",
" # all characters.\n",
" encoded_query_params = urlencode(query_params_all)\n",
" encoded_query_params_without_safe_chars = quote(urlencode(query_params_all), safe='')\n",
" \n",
" assignment_link_url = urlunparse((domain_scheme, domain_netloc, assignment_link_path, domain_params, encoded_query_params_without_safe_chars, domain_fragment))\n",
" path_url = urlunparse(('', '', path_redirect_url, domain_params, encoded_query_params, domain_fragment))\n",
" \n",
" if is_assignment_link:\n",
" return assignment_link_url\n",
" return path_url"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -75,9 +156,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
"version": "3.8.1-final"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
}

0 comments on commit e93429c

Please sign in to comment.