diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08f0c086..26f061ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -155,12 +155,13 @@ jobs: needs: test runs-on: ubuntu-latest steps: + - uses: extractions/setup-just@v1 - uses: actions/checkout@v2 - - run: make docs-build + - run: just docs/ image build - uses: actions/upload-artifact@v2 with: name: docs - path: docs/out/ + path: docs/site/ - uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ID }} diff --git a/README.md b/README.md index 00e5f1dd..334893bc 100644 --- a/README.md +++ b/README.md @@ -14,264 +14,6 @@ Connect](https://www.rstudio.com/products/connect/). - [RStudio Connect](https://www.rstudio.com/products/connect/download-commercial/) v1.7.0 or higher, configured with Python support. -If using `conda`, `pip` and `wheel` should already be installed. +# Documentation -# Installation - -If you are installing `rsconnect-jupyter` for use in Jupyterhub, please see the -[Jupyterhub](#installation-in-jupyterhub) section below. - -We recommend working within a `virtualenv`. If you -are unfamiliar, these commands create and activate a `virtualenv` -at `/my/path`: - -```bash -pip install virtualenv -virtualenv /my/path -source /my/path/bin/activate -``` - -Install Jupyter inside the `virtualenv`: -```bash -pip install jupyter -``` - -> Note: be sure to run Jupyter from the virtual environment, not from a global -> installation. - -Install the `rsconnect-jupyter` package with the following command: - -```bash -pip install rsconnect_jupyter -``` - -Enable the `rsconnect-jupyter` extension with the following commands: - -```bash -# Install `rsconnect-jupyter` as a jupyter extension -jupyter-nbextension install --sys-prefix --py rsconnect_jupyter - -# Enable JavaScript extension -jupyter-nbextension enable --sys-prefix --py rsconnect_jupyter - -# Enable Python extension -jupyter-serverextension enable --sys-prefix --py rsconnect_jupyter -``` - -Note: The above commands only need to be run once when installing -`rsconnect_jupyter`. - -Note: In order to deploy content, you will need at least the -[rsconnect-python](https://github.com/rstudio/rsconnect-python) package -in every kernel you plan to deploy from. - -Note: If you run into an issue during installation please let us know by filing -a bug [here](https://github.com/rstudio/rsconnect-jupyter/issues). - -# Uninstalling - -First disable and remove the `rsconnect-jupyter` notebook extension: - -```bash -# Disable Python extensions found in `rsconnect-jupyter` -jupyter-serverextension disable --sys-prefix --py rsconnect_jupyter - -# Remove JavaScript extension -jupyter-nbextension uninstall --sys-prefix --py rsconnect_jupyter -``` - -Finally, uninstall the `rsconnect-jupyter` python package: - -```bash -pip uninstall rsconnect_jupyter -``` - -# Upgrading - -To upgrade `rsconnect-jupyter`, first uninstall the extension and then -re-install it. - -# Usage - -Open a notebook and click the blue icon and select `Publish to RStudio Connect` -to publish the current notebook to RStudio Connect. - -![blue toolbar icon used for publishing the notebook](docs/images/publish-icon.gif) - -### Entering server information - -If this is your first time publishing a notebook, you will be -prompted to enter the location and a nickname for the RStudio Connect -server. - -You will also be prompted to enter your API Key. See the [RStudio Connect User -Guide](http://docs.rstudio.com/connect/user/api-keys.html) for -instructions on generating API Keys for your user. - -When you click the Add Server button, `rsconnect-jupyter` will send a request -to the RStudio Connect server to verify that it can be reached via the requested -URL and that the API key is valid. - -If your RStudio Connect server was configured with a self-signed certificate -(or other certificate that computer hosting your Jupyter notebook server does -not trust), the attempt to contact RStudio Connect may fail with a -TLS-related error. You have multiple options in this case, depending on your needs: - - 1. If your RStudio Connect Administrator can give you the Certificate Authority (CA) - Bundle for your RStudio Connect server, ask your Jupyter Administrator if it - can be added to the trusted system store. - - 2. If the CA Bundle cannot be added to the trusted system store, you may select - `Upload TLS Certificate Bundle` to upload the bundle to Jupyter, which will verify - your secure connection to RStudio Connect. - - 3. If you cannot obtain the CA bundle, you can disable TLS verification completely - by selecting the `Disable TLS Certificate Verification` box. Your connection to - RStudio Connect will still be encrypted, but you will not be able to verify the - identity of the RStudio Connect server. - -![initial dialog that prompts for the location of RStudio Connect](docs/images/add-dialog.png) - -### Publishing options - -![publish dialog](docs/images/manage.png) - -There are two different publication modes. Selecting `Publish finished document only` will -publish an HTML snapshot of the notebook to RStudio Connect. HTML snapshots are static and -cannot be scheduled or re-run on the RStudio Connect server. - -If you select `Publish document with source code`, the notebook file and a list of the Python -packages installed in your environment will be sent to RStudio Connect. This enables RStudio -Connect to recreate the environment and re-run the notebook at a later time. - -#### Additional Files - -If your notebook needs some external file in order to render, add the file using the -`Select Files` button. You can select any file within the notebook folder. However, -these files may not be made available to users after render. - -#### Environment detection with pip - -The list of packages sent along with the notebook comes from the python -environment where the notebook kernel is running. In order for environment -inspection to work, the `rsconnect-jupyter` package must be installed in the -kernel environment; that is, the environment where the `ipykernel` package is -installed. In most cases that will be the same as the notebook server -environment where `jupyter` is installed. - -The command `pip freeze` will be used to inspect the environment. The output -of `pip freeze` lists all packages currently installed, as well as their -versions, which enables RStudio Connect to recreate the same environment. - -### Generating Manifests for git Publishing - -RStudio Connect can poll git repositories for deployable content and update -as you add new commits to your repository. In order to be deployable, a -directory must have a valid `manifest.json`. Python content should also have -some kind of environment file (i.e.: `requirements.txt`) in order to be able -to restore the package set in your current environment. - -![Deployment drop-down menu showing "Publish to Connect" and "Create Manifest for git Publishing"](docs/images/deploy-options.png) - -To begin, select `Create Manifest for git Publishing`. - -![Dialog titled "Create Manifest" explaining the manifest creation process with "Cancel" and "Create Manifest" options](docs/images/git-backed.png) - -When you click `Create Manifest`, a `manifest.json` and `requirements.txt` -will be generated for the current notebook using your current environment, -if they do not exist. If they do exist, you will be presented with a message -informing you of this fact. If you need to regenerate the files, delete them -in the Jupyter UI or using the console, then repeat this process. - -For more information on git publishing, see the -[RStudio Connect User Guide](https://docs.rstudio.com/connect/user/git-backed.html#git-backed-publishing) - -### Handling conflicts -If content that matches your notebook's title is found on RStudio Connect, you -may choose to overwrite the existing content or create new content. - -![dialog that prompts for overwriting or publishing new content](docs/images/overwrite.png) - -Choosing `New location` will create a new document in RStudio Connect. -You can choose either publication mode - an HTML snapshot or a document -with source code. - -Updating an existing document will not change its publication mode. - - -Upon successful publishing of the document a notification will be -shown in toolbar. Clicking the notification will open the published -document in the RStudio Connect server you selected in the previous -dialog. - -![notification that shows the notebook was published successfully](docs/images/published.gif) - -# Collaboration - -To collaborate with others add them as collaborators in RStudio Connect. During -publishing they should provide their API key and will be able to choose a -content location to publish to if the notebook title is the same. - -You may share notebooks if appropriate. - -# Installation in JupyterHub - -In JupyterHub, follow the directions [above](#installation) to install the -`rsconnect-jupyter` package into the Python environment where the Jupyter -notebook server and kernel are installed. Typically those will be the same -environment. If you've configured separate kernel environments, install the -`rsconnect-jupyter` package in the notebook server environment as well as each -kernel environment. - -The exact install location depends on your Jupyterhub configuration. - - -## JupyterHub Example Configuration - -This section presents a simple working example of a Jupyterhub configuration -with `rsconnect-jupyter` installed. - -This example uses Docker, but you can install the `rsconnect-jupyter` package in -any Jupyterhub installation. Docker is not required. - -Example Dockerfile: - -```dockerfile -FROM jupyterhub/jupyterhub:0.9.4 - -# Install Jupyter notebook into the existing base conda environment -RUN conda install notebook - -# Download and install rsconnect-jupyter in the same environment -# Update this to specify the desired version of the rsconnect-jupyter package, -# or pass `--build-arg VERSION=...` to docker build. -ARG VERSION=RSCONNECT_VERSION -ARG REPOSITORY=https://s3.amazonaws.com/rstudio-rsconnect-jupyter - -RUN wget ${REPOSITORY}/rsconnect_jupyter-${VERSION}-py2.py3-none-any.whl -RUN pip install rsconnect_jupyter-${VERSION}-py2.py3-none-any.whl && \ - jupyter-nbextension install --sys-prefix --py rsconnect_jupyter && \ - jupyter-nbextension enable --sys-prefix --py rsconnect_jupyter && \ - jupyter-serverextension enable --sys-prefix --py rsconnect_jupyter - -# create test users -RUN useradd -m -s /bin/bash user1 && \ - useradd -m -s /bin/bash user2 && \ - useradd -m -s /bin/bash user3 && \ - bash -c 'echo -en "password\npassword" | passwd user1' && \ - bash -c 'echo -en "password\npassword" | passwd user2' && \ - bash -c 'echo -en "password\npassword" | passwd user3' - -CMD ["jupyterhub"] -``` - -Run these commands to build and start the container: -```bash -docker build -t jupyterhub:rsconnect-jupyter . -docker run --rm -p 8000:8000 --name jupyterhub jupyterhub:rsconnect-jupyter -``` - -Connect to Jupyterhub on http://localhost:8000 and log in as one of the test -users. From there, you can create a notebook and publish it to RStudio Connect. -Note that the current Jupyterhub docker image uses Python 3.6.5, so you will -need a compatible Python version installed on your RStudio Connect server. +See the documentation [on GitHub](docs/docs/index.md) or on [docs.rstudio.com](https://docs.rstudio.com/rsconnect-jupyter/). diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..f02fb97f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,4 @@ +# mkdocs output directory +site +# copy of primary jupyter.md +docs/jupyter.md diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 00000000..35b0c0e3 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,23 @@ +# Using dated tags from https://hub.docker.com/_/ubuntu/ +FROM ubuntu:bionic-20201119 +MAINTAINER RStudio Connect + +# Configure apt-get to use the mirror in us-east-1 instead of the Docker default of archive.ubuntu.com +RUN sed -i "s/archive.ubuntu.com/us-east-1.ec2.archive.ubuntu.com/g" /etc/apt/sources.list + +# git is used to examine the repository and compute product version. +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y \ + git \ + python3-pip && \ + rm -rf /var/lib/apt/lists/* + +# Needed with Python3 mkdocs. +# https://click.palletsprojects.com/en/7.x/python3/ +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt && \ + rm -f requirements.txt diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..f92ef7cf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +# RStudio Connect Jupyter User Guide + +This directory contains the RStudio Connect: Jupyter User Guide. We use +[mkdocs-1.0.4](https://www.mkdocs.org) to build this guide. + +The Jupyter User Guide is geared towards the people who will publish Jupyter Notebooks to RStudio Connect. + +## Docker + +The `rsconnect-jupyter-docs` Docker image is used to produce our +documentation. We use a number of plugins and extensions; use the Docker image +rather than a local `mkdocs` installation. + +Create the image: +```bash +just image +``` + +Build documentation: + +```bash +just build +``` + +Launch an auto-reloading documentation server at http://localhost:8001 with +the command: + +```bash +just watch +``` diff --git a/docs/build-doc.sh b/docs/build-doc.sh deleted file mode 100755 index d8259d2c..00000000 --- a/docs/build-doc.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -ex - -TITLE='rsconnect-jupyter User Guide' - -pandoc -f markdown-implicit_figures \ - --self-contained \ - -o docs/out/rsconnect_jupyter-${VERSION}.html \ - -H docs/images/style.fragment.html \ - -T "${TITLE}" \ - -M "title:${TITLE}" \ - README.md - -pandoc -f markdown-implicit_figures \ - -o docs/out/rsconnect_jupyter-${VERSION}.pdf \ - -T "${TITLE}" \ - -M "title:${TITLE}" \ - README.md diff --git a/docs/docs/css/custom.css b/docs/docs/css/custom.css new file mode 100644 index 00000000..e07916a9 --- /dev/null +++ b/docs/docs/css/custom.css @@ -0,0 +1,432 @@ +/* Header +*/ + +.md-header[data-md-state=shadow] { + transition: background-color 0s; + box-shadow: none; +} + +.md-header { + border-bottom: 1px solid rgba(0, 0, 0, 0.15) !important; +} + +@media only screen and (max-width: 76.1875em){ + .left-nav { + display: none; + } +} + +.md-tabs__list { + margin: 0 2em 0 .2rem; + padding-top: .8rem; + list-style: none; + white-space: nowrap; +} + +.md-tabs__link { + margin: 0; + padding: 0; +} + +/* Search bar +*/ + +.md-search-result__more summary { + color: #2196f3 !important; +} + +.md-search-result mark { + color: #000000 !important; + font-weight: bold; +} + + +/* Global +*/ + +.md-tabs__list { + margin: 0 2em 0 .2rem; + padding-top: 15px; + list-style: none; + white-space: nowrap; +} + +.md-tabs__link { + margin: 0; + padding: 0; +} +.md-typeset dl dt { + font-style: italic; + font-weight: bold; +} + +.md-typeset dl dd { + margin-top: 0; +} + +.md-typeset dd p:first-child { + margin-top: 0; +} + +[data-md-color-primary=white] .md-typeset a { + color: #4c83b6; +} + +[data-md-color-primary=white] .md-typeset a:hover { + text-decoration: underline; +} + +.admonition-title { + font-size: .7rem; +} + +.admonition p { + font-size: .7rem; +} + +.md-typeset .superfences-tabs>label:hover { + color: #4c83b6 !important; +} + +.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { + background-color: #4c83b6; +} + +.md-footer-nav { + background-color: #fff; + color: #000; +} + +.md-footer-nav__direction { + color: #000; + font-size: .65rem; +} + +.md-footer-meta { + background-color: #fdfdfd; + border-top: 1px solid rgba(0, 0, 0, 0.15); + color: #404040; + font-weight: 400; +} + +.md-footer-copyright__highlight { + color: #6F6B6B; + font-weight: 400; +} + +.md-footer-meta.md-typeset a { + color: #6F6B6B !important; + font-weight: 400; +} + +.md-typeset .tabbed-set>label { + font-size: .64rem !important; +} + +.md-typeset .tabbed-set>label:hover { + color: #4c83b6 !important; +} + +.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { + background-color: #4c83b6; +} + + +/* Sidebar +*/ + +.md-nav__link:focus, .md-nav__link:hover, .md-nav__link:active, .md-nav__link--active, .md-nav__link:active { + color: #4c83b6 !important; +} + +.md-nav__link--active { + font-weight: bold; +} + +[data-md-color-primary=white] .md-nav__link--active, [data-md-color-primary=white] .md-nav__link:active { + color: #2196f3; +} + +.md-nav__item hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +h2.divider { + text-align: center; + max-width: 650px; +} + +/* Home page grid +*/ + +.feature-list { + list-style: none; + padding: 0; + margin: 0; + font-size: 0; +} + +.feature-list li.item { + display: inline-block; + background: white; + vertical-align: top; + width: 200px; + height: 153px; + padding: 0; + margin: 0 10px; + box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2); +} + + +.feature-btn { + display: block; + height: 100%; + width: 100%; + text-align: center; + position: relative; +} + +.aligner { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + min-height: 24em; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.aligner-item { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + + +.aligner-item--fixed { + -webkit-box-flex: 0; + -webkit-flex: none; + -ms-flex: none; + flex: none; + max-width: 100%; +} + + +.feature-btn .front { + width: 100%; + padding: 0 10px; + /* vertical-align: center; */ +} + +.feature-btn .front img { + height: 40px; +} + +.feature-btn .front h3 { + text-transform: uppercase; + font-size: 16px; + color: #222b37; + line-height: 22px; + margin: 20px 0 0; +} + +.feature-btn .back { + background-color: #75AADB; + width: 100%; + height: 100%; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + padding: 10px; + opacity: 0; + transition: opacity 200ms ease; +} + +.feature-btn:hover .back { + opacity: 1; + transition: opacity 200ms ease; +} + +.feature-btn .back h4 { + font-size: 16px; + font-weight: 400; + color: white; + text-transform: uppercase; + line-height: 22px; + padding-bottom: 23px; + margin: 0; + padding: 0; +} + +.feature-btn .back ul { + color: white; + font-size: 16px; + list-style: disc; + text-align: left; + /* margin: 0px 20px; */ + font-weight: 400; +} + +.feature-btn .back ul li { + margin-bottom: 0; +} + +/* Code blocks +*/ + +p.code-title { + color: #3c3c3c; + background-color: rgba(219, 219, 219, 0.8); + margin: 1em 0 -1.2em; + padding: 0.1em 1em; + font-size: 0.9em; + font-family: "Roboto Mono","Courier New",Courier,monospace; +} + +div.code-title { + color: #3c3c3c; + background-color: rgba(219, 219, 219, 0.8); + margin: 1em 0 -1.2em; + padding: 0.1em 1em; + font-size: 0.9em; + font-family: "Roboto Mono","Courier New",Courier,monospace; +} + +pre .code-noselect { + color: #aaa; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* padding-right: 6px; */ +} + +pre code::-webkit-scrollbar-thumb:hover, .codehilite pre::-webkit-scrollbar-thumb:hover { + background-color: #4c83b6 !important; +} + +/* Drivers logo grid +*/ + +.driver-grid-row { + display: flex; + flex-direction: row; + margin: 0 50px; +} + +figure.driver-card { + display: flex; + flex-direction: column; + align-items: center; + width: 300px; + margin: 20px 20px; +} + +.driver-card img { + max-width: 55px; + margin: auto; + vertical-align: middle; +} + +.driver-card figcaption { + color: #676767; + font-size: 13px; + text-align: center; + margin-top: 10px; +} + +.faux-footer-text { + font-size: 10pt !important; + font-style: italic !important; + border-top: .05rem solid #00000012 !important; + padding: 5px 10px 0px 5px !important; + display: inline-block !important; + margin-top: 20px !important; +} + +.md-typeset ul li ul { + list-style-type: circle; +} + +.md-typeset ul li ul li ul { + list-style-type: square; +} + + + +/* Admonition and details */ +.md-typeset .admonition, +.md-typeset details { + border: none; + transition: box-shadow 400ms; + box-shadow: + 0 0.2rem 0.5rem rgba(0,0,0,.05), + 0 0.025rem 0.05rem rgba(0,0,0,.15) +} + +/* Admonition and details on hover */ +.md-typeset .admonition:hover, +.md-typeset details:hover { + box-shadow: + 0 0.4rem 1.0rem rgba(0,0,0,.10), + 0 0.025rem 0.05rem rgba(0,0,0,.15) +} + +/* Details in closed state */ +.md-typeset details:not([open]) { + transition-duration: 0s; + box-shadow: none; +} + +/* Details title in closed state */ +.md-typeset details:not([open]) > summary { + border-radius: 1rem; +} + +/* Admonition title */ +.md-typeset .admonition-title { + padding-right: 1rem; +} + +/* Admonition and details title */ +.md-typeset .admonition-title, +.md-typeset summary { + position: relative; + display: inline-block; + margin-left: 0; + margin-top: 0.7rem; + border-radius: 1rem; + border-left: none; +} + +/* Icons & images */ + +.icon { + width: 35px; + height: 35px; + position: relative; + top: 5px; + } + + img.inline { + border: solid 2px #f1f1f1; + margin-top: 10px; + margin-bottom: auto; + margin-left: auto; + margin-right: auto; + display: block; + position: relative; +} \ No newline at end of file diff --git a/docs/docs/css/external-link-alt-regular.svg b/docs/docs/css/external-link-alt-regular.svg new file mode 100644 index 00000000..46bae48e --- /dev/null +++ b/docs/docs/css/external-link-alt-regular.svg @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/docs/css/external-links.css b/docs/docs/css/external-links.css new file mode 100644 index 00000000..99f3f49a --- /dev/null +++ b/docs/docs/css/external-links.css @@ -0,0 +1,10 @@ +/* Display an icon after external links */ +div.md-content a[href^="http://"]:not([href*="test.rstudio.com"]):after, +div.md-content a[href^="https://"]:not([href*="test.rstudio.com"]):after, +div.md-content a[href^="//"]:not([href*="test.rstudio.com"]) { + content: url(external-link-alt-regular.svg); + vertical-align: 15%; + display: inline-block; + text-decoration: none; + padding-left: 3px; +} diff --git a/docs/docs/css/superfences-tabs.css b/docs/docs/css/superfences-tabs.css new file mode 100644 index 00000000..683c15d4 --- /dev/null +++ b/docs/docs/css/superfences-tabs.css @@ -0,0 +1,43 @@ +.tabbed-set { + display: flex; + position: relative; + flex-wrap: wrap; + border: .05rem solid rgba(0,0,0,.07); +} + +.tabbed-set .highlight { + background: #f5f5f5; +} + +.tabbed-set .tabbed-content { + display: none; + order: 99; + width: 100%; + background-color: hsla(0,0%,92.5%,.5); +} + +.tabbed-set label { + width: auto; + margin: 0 0.5em; + padding: 0.25em; + cursor: pointer; + font-size: 120%; + font-color: #000 !important; +} + +.tabbed-set input { + position: absolute; + opacity: 0; +} + +.tabbed-set input:nth-child(n+1) { +} + +.tabbed-set input:nth-child(n+1):checked + label { + color: #75aadb; + border-bottom: .1rem solid transparent; +} + +.tabbed-set input:nth-child(n+1):checked + label + .tabbed-content { + display: block; +} diff --git a/docs/docs/images/add-dialog.png b/docs/docs/images/add-dialog.png new file mode 100644 index 00000000..476a1ee4 Binary files /dev/null and b/docs/docs/images/add-dialog.png differ diff --git a/docs/images/deploy-options.png b/docs/docs/images/deploy-options.png similarity index 100% rename from docs/images/deploy-options.png rename to docs/docs/images/deploy-options.png diff --git a/docs/images/git-backed.png b/docs/docs/images/git-backed.png similarity index 100% rename from docs/images/git-backed.png rename to docs/docs/images/git-backed.png diff --git a/docs/docs/images/manage.png b/docs/docs/images/manage.png new file mode 100644 index 00000000..fc2e5593 Binary files /dev/null and b/docs/docs/images/manage.png differ diff --git a/docs/docs/images/overwrite.png b/docs/docs/images/overwrite.png new file mode 100644 index 00000000..17b94b56 Binary files /dev/null and b/docs/docs/images/overwrite.png differ diff --git a/docs/images/publish-icon.gif b/docs/docs/images/publish-icon.gif similarity index 100% rename from docs/images/publish-icon.gif rename to docs/docs/images/publish-icon.gif diff --git a/docs/images/published.gif b/docs/docs/images/published.gif similarity index 100% rename from docs/images/published.gif rename to docs/docs/images/published.gif diff --git a/docs/docs/images/rstudio-logo.png b/docs/docs/images/rstudio-logo.png new file mode 100644 index 00000000..f81a9724 Binary files /dev/null and b/docs/docs/images/rstudio-logo.png differ diff --git a/docs/images/style.fragment.html b/docs/docs/images/style.fragment.html similarity index 100% rename from docs/images/style.fragment.html rename to docs/docs/images/style.fragment.html diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 00000000..26546aa6 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,292 @@ +# `rsconnect-jupyter` User Guide + +`rsconnect-jupyter` is a plugin for Jupyter Notebooks that enables publishing notebooks to RStudio Connect. + +## Requirements + +- Python 2.7.9 or Python 3.5.0 and higher +- Jupyter Notebook 5.x +- [pip](https://pypi.org/project/pip/) +- [wheel](https://pypi.org/project/wheel/) +- [RStudio Connect](https://www.rstudio.com/products/connect/download-commercial/) v1.7.0 or higher, configured with Python support + +!!! note + If using `conda`, `pip` and `wheel` should already be installed. + +## Installation + +- If you are installing `rsconnect-jupyter` for use in Jupyterhub, please see +the [Installation in Jupyterhub](#installation-in-jupyterhub) section below. +- If you are installing `rsconnect-jupyter` to Jupyter running on RStudio Server Pro, see +the [RStudio Server Pro documentation on Jupyter Notebooks](https://docs.rstudio.com/rsp/integration/jupyter-standalone/#4-install-jupyter-notebooks-jupyterlab-and-python-packages) +for instructions on installing the plugin to the right location. +- Otherwise, we recommend using Jupyter within a virtual environment using +`virtualenv`. See the [Running Jupyter in a `virtualenv`](#running-jupyter-in-a-virtualenv), shown below, for instructions +on setting up a `virtualenv`, or read more at the +[`virtualenv` documentation](https://virtualenv.pypa.io/en/latest/). + +The following commands should be run after activating the Python environment where you plan to use `jupyter`. + +- Install the `rsconnect-jupyter` package with the following command: +
Terminal
+ ```bash + pip install rsconnect_jupyter + ``` + +- Enable the `rsconnect-jupyter` extension with the following commands: +
Terminal
+ ```bash + # Install `rsconnect-jupyter` as a jupyter extension + jupyter-nbextension install --sys-prefix --py rsconnect_jupyter + + # Enable JavaScript extension + jupyter-nbextension enable --sys-prefix --py rsconnect_jupyter + + # Enable Python extension + jupyter-serverextension enable --sys-prefix --py rsconnect_jupyter + ``` + +!!! note + - The above commands only need to be run once when installing `rsconnect_jupyter`. + - In order to deploy content, you will need at least the [rsconnect-python](https://github.com/rstudio/rsconnect-python) package in every kernel you plan to deploy from. + - If you run into an issue during installation, please let us know by filing a bug [here](https://github.com/rstudio/rsconnect-jupyter/issues). + +### Running Jupyter in a `virtualenv` + +- These commands create and activate a `virtualenv` at `/my/path`: +
Terminal
+ ```bash + pip install virtualenv + virtualenv /my/path + source /my/path/bin/activate + ``` + +!!! tip + Running `source /my/path/bin/activate` activates the virtual environment. While the `virtualenv` is active, Python-related commands like `python`, `pip`, and `jupyter` will use to copies located inside the virtual environment. You can check which copy of `python` you're using by running `which python`. + +- Install Jupyter inside the `virtualenv`: +
Terminal
+ ```bash + pip install jupyter + ``` + +- [Install rsconnect-python](#installation) with your virtual environment active to install and activate the plugin for that copy of Jupyter. + + !!! note + Be sure to run Jupyter from this virtual environment, not from + another installation, or the `rsconnect-python` extension will + not be available. To do so, you will need to activate the virtual + environment in each new terminal session before you run `jupyter`. + +## Upgrading + +To upgrade `rsconnect-jupyter`: + +- First, uninstall the extension. +- Then, re-install it. + +## Usage + +To publish to RStudio Connect: + +- Open a notebook. +- Click the blue toolbar icon used for publishing the notebook icon (blue publish icon) and select `Publish to RStudio Connect` +to publish the current notebook to RStudio Connect. + +### Entering server information + +- If this is your first time publishing a notebook, you will be +prompted to enter the location and a nickname for the RStudio Connect server. +- You will also be prompted to enter your API Key. See the [RStudio Connect User +Guide](http://docs.rstudio.com/connect/user/api-keys.html) for +instructions on generating API Keys for your user. +- When you click the **Add Server** button, `rsconnect-jupyter` will send a request to the RStudio Connect server to verify that it can be reached via the requested URL and that the API key is valid. + +If your RStudio Connect server was configured with a self-signed certificate (or other certificate that computer hosting your Jupyter notebook server does not trust), the attempt to contact RStudio Connect may fail with a TLS-related error. + +You have multiple options in this case, depending on your needs: + +1. If your RStudio Connect Administrator can give you the Certificate Authority (CA) + Bundle for your RStudio Connect server, ask your Jupyter Administrator if it + can be added to the trusted system store. +1. If the CA Bundle cannot be added to the trusted system store, you may select + `Upload TLS Certificate Bundle` to upload the bundle to Jupyter, which will verify + your secure connection to RStudio Connect. +1. If you cannot obtain the CA bundle, you can disable TLS verification completely + by selecting the `Disable TLS Certificate Verification` box. Your connection to + RStudio Connect will still be encrypted, but you will not be able to verify the + identity of the RStudio Connect server. + +initial dialog that prompts for the location of RStudio Connect + +### Publishing options + +publish dialog + +There are two different publication modes. Selecting **Publish finished document only** will +publish an HTML snapshot of the notebook to RStudio Connect. HTML snapshots are static and +cannot be scheduled or re-run on the RStudio Connect server. + +If you select **Publish document with source code**, the notebook file and a list of the Python +packages installed in your environment will be sent to RStudio Connect. This enables RStudio +Connect to recreate the environment and re-run the notebook at a later time. + +#### Additional Files + +If your notebook needs some external file in order to render, add the file using the +**Select Files** button. You can select any file within the notebook folder. However, +these files may not be made available to users after render. + +#### Environment detection with pip + +The list of packages sent along with the notebook comes from the python +environment where the notebook kernel is running. In order for environment +inspection to work, the `rsconnect-jupyter` package must be installed in the +kernel environment; that is, the environment where the `ipykernel` package is +installed. In most cases that will be the same as the notebook server +environment where `jupyter` is installed. + +The command `pip freeze` will be used to inspect the environment. The output +of `pip freeze` lists all packages currently installed, as well as their +versions, which enables RStudio Connect to recreate the same environment. + +### Generating Manifests for git Publishing + +RStudio Connect can poll git repositories for deployable content and update +as you add new commits to your repository. In order to be deployable, a +directory must have a valid `manifest.json`. Python content should also have +some kind of environment file (i.e.: `requirements.txt`) in order to be able +to restore the package set in your current environment. + +Deployment drop-down
+menu showing + +To begin, select `Create Manifest for git Publishing`. + +Dialog titled + +When you click **Create Manifest**, one of the following will happen: + +- If a `manifest.json` and `requirements.txt` does not exist, they will be generated for the current notebook using your current environment. +- If they do exist, you will be presented with a message +informing you of this fact. If you need to regenerate the files, delete them in the Jupyter UI or using the console, then repeat this process. + +For more information on git publishing, see the +[RStudio Connect User Guide](https://docs.rstudio.com/connect/user/git-backed.html#git-backed-publishing). + +### Handling conflicts + +If content that matches your notebook's title is found on RStudio Connect, you +may choose to overwrite the existing content or create new content. + +dialog that prompts for overwriting or publishing new content + +- Choosing **New location** creates a new document in RStudio Connect. +- You can choose either publication mode: + - an HTML snapshot *or* + - a document with source code + +Updating an existing document will not change its publication mode. + +Upon successful publishing of the document a notification will be +shown in toolbar. Clicking the notification will open the published +document in the RStudio Connect server you selected in the previous +dialog. + +notification that shows the notebook was published successfully + +## Collaboration + +To collaborate with others add them as collaborators in RStudio Connect. During +publishing they should provide their API key and will be able to choose a +content location to publish to if the notebook title is the same. + +You may share notebooks if appropriate. + +## Installation in JupyterHub + +In JupyterHub, follow the directions [above](#installation) to install the +`rsconnect-jupyter` package into the Python environment where the Jupyter +notebook server and kernel are installed. Typically those will be the same +environment. If you've configured separate kernel environments, install the +`rsconnect-jupyter` package in the notebook server environment as well as each +kernel environment. + +The exact install location depends on your Jupyterhub configuration. + +### JupyterHub Example Configuration + +This section presents a simple working example of a Jupyterhub configuration +with `rsconnect-jupyter` installed. + +This example uses Docker, but you can install the `rsconnect-jupyter` package in +any Jupyterhub installation. Docker is not required. + +Example Dockerfile: + +

Dockerfile

+```dockerfile +FROM jupyterhub/jupyterhub:0.9.4 + +# Install Jupyter notebook into the existing base conda environment +RUN conda install notebook + +# Download and install rsconnect-jupyter in the same environment +# Update this to specify the desired version of the rsconnect-jupyter package, +# or pass `--build-arg VERSION=...` to docker build. +ARG VERSION=RSCONNECT_VERSION +ARG REPOSITORY=https://s3.amazonaws.com/rstudio-rsconnect-jupyter + +RUN wget ${REPOSITORY}/rsconnect_jupyter-${VERSION}-py2.py3-none-any.whl +RUN pip install rsconnect_jupyter-${VERSION}-py2.py3-none-any.whl && \ + jupyter-nbextension install --sys-prefix --py rsconnect_jupyter && \ + jupyter-nbextension enable --sys-prefix --py rsconnect_jupyter && \ + jupyter-serverextension enable --sys-prefix --py rsconnect_jupyter + +# create test users +RUN useradd -m -s /bin/bash user1 && \ + useradd -m -s /bin/bash user2 && \ + useradd -m -s /bin/bash user3 && \ + bash -c 'echo -en "password\npassword" | passwd user1' && \ + bash -c 'echo -en "password\npassword" | passwd user2' && \ + bash -c 'echo -en "password\npassword" | passwd user3' + +CMD ["jupyterhub"] +``` + +Run these commands to build and start the container: + +

Terminal

+```bash +docker build -t jupyterhub:rsconnect-jupyter . +docker run --rm -p 8000:8000 --name jupyterhub jupyterhub:rsconnect-jupyter +``` + +Connect to Jupyterhub on http://localhost:8000 and log in as one of the test +users. From there, you can create a notebook and publish it to RStudio Connect. +Note that the current Jupyterhub docker image uses Python 3.6.5, so you will +need a compatible Python version installed on your RStudio Connect server. + +## Uninstalling + +- First disable and remove the `rsconnect-jupyter` notebook extension: +
Terminal
+ ```bash + # Disable Python extensions found in `rsconnect-jupyter` + jupyter-serverextension disable --sys-prefix --py rsconnect_jupyter + + # Remove JavaScript extension + jupyter-nbextension uninstall --sys-prefix --py rsconnect_jupyter + ``` + +- Finally, uninstall the `rsconnect-jupyter` python package: +
Terminal
+ ```bash + pip uninstall rsconnect_jupyter + ``` + + +## Related Documentation + +For a step-by-step guide for creating and publishing a new Jupyter Notebook to +RStudio Connect, view our [How To](https://docs.rstudio.com/how-to-guides/users/basic/publish-jupyter-notebook/). \ No newline at end of file diff --git a/docs/docs/js/custom.js b/docs/docs/js/custom.js new file mode 100644 index 00000000..8c3191ee --- /dev/null +++ b/docs/docs/js/custom.js @@ -0,0 +1,22 @@ +// Terminal CSS +// Iterate all the items with class: `language-terminal` +var codehilites = document.getElementsByClassName("codehilite") +for(var i = 0; i < codehilites.length; i++) { + var codehilite = codehilites[i]; + var pre_block = codehilite.getElementsByTagName("pre")[0]; + + // Modify the text to encapsulate ($,>,>>>) within span tags + var terminal_regex = "^()?\\$ "; + var r_console_regex = '^()?> '; + var python_shell_regex = '^()?>>> '; + regexs = [terminal_regex, r_console_regex, python_shell_regex] + replacements = ["$", ">", ">>>"] + for (j in regexs) { + var regex = regexs[j] + var replacement = replacements[j] + var re = new RegExp(regex, "gm"); + var str = pre_block.innerHTML; + str = str.replace(re, "" + replacement + " "); + pre_block.innerHTML = str; + } +} diff --git a/docs/images/add-dialog.png b/docs/images/add-dialog.png deleted file mode 100644 index 7b2ae4da..00000000 Binary files a/docs/images/add-dialog.png and /dev/null differ diff --git a/docs/images/manage.png b/docs/images/manage.png deleted file mode 100644 index 2f609ee9..00000000 Binary files a/docs/images/manage.png and /dev/null differ diff --git a/docs/images/overwrite.png b/docs/images/overwrite.png deleted file mode 100644 index bff6d0bb..00000000 Binary files a/docs/images/overwrite.png and /dev/null differ diff --git a/docs/justfile b/docs/justfile new file mode 100644 index 00000000..59d8b0fc --- /dev/null +++ b/docs/justfile @@ -0,0 +1,48 @@ +# image name for container +export IMAGE := "rsconnect-jupyter-docs" + +# are we attached to a terminal? +interactive := `tty -s && echo "-it" || echo ""` + +# build documentation +build: + just container mkdocs build + + +# watch and serve documentation over HTTP +watch: + #!/usr/bin/env bash + set -euo pipefail + + export PORT=${PORT:-8001} + echo -e "\n--> Serving on http://localhost:${PORT}/\n" + just container mkdocs serve -a 0.0.0.0:${PORT} \ + | grep -v "Serving on http" + +# clean built documentation +clean: + rm -rf site/ + +# run commands in mkdocs container +container +args: + #!/usr/bin/env bash + set -euo pipefail + + args=("") + if [[ -n "${PORT:-}" ]]; then + args+=(-p ${PORT}:${PORT}) + fi + + cmd=( + docker run --rm --init {{ interactive }} + -u `id -u`:`id -g` + ${args[*]} + -v "`pwd`":/mkdocs + -w /mkdocs + ${IMAGE} {{ args }} + ) + echo ${cmd[*]} + ${cmd[*]} + +image: + docker build -t ${IMAGE} . diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 00000000..e460412a --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,53 @@ +site_name: 'rsconnect-jupyter User Guide' +copyright: RStudio, PBC. All Rights Reserved + +# We activate GA only when hosted on our public docs site +# and not when installed. +# +# See overrides/partials/integrations/analytics.html +google_analytics: + - 'UA-20375833-3' + - 'auto' + +nav: +- 'Introduction': 'index.md' + + +markdown_extensions: + - admonition + - toc: + permalink: true + - attr_list: {} + - def_list: {} + - tables: {} + - pymdownx.superfences: {} + - codehilite: + guess_lang: false + - pymdownx.tabbed + - pymdownx.snippets: + base_path: 'docs/' + check_paths: true + +plugins: + - search: + separator: '[\s\-\.]+' + - macros + +theme: + name: material + custom_dir: overrides + logo: 'images/rstudio-logo.png' + palette: + primary: 'white' + +extra_css: + - css/external-links.css + - css/superfences-tabs.css + - css/custom.css + +extra_javascript: + - js/custom.js + +extra: + search: + tokenizer: '[\s\-\.]+' diff --git a/docs/overrides/partials/footer.html b/docs/overrides/partials/footer.html new file mode 100644 index 00000000..9be29411 --- /dev/null +++ b/docs/overrides/partials/footer.html @@ -0,0 +1,97 @@ +{#- + Make footer navigation stops at separators + Remove mkdocs links + -#} + {% import "partials/language.html" as lang with context %} + + + + \ No newline at end of file diff --git a/docs/overrides/partials/header.html b/docs/overrides/partials/header.html new file mode 100644 index 00000000..1ab0bdbb --- /dev/null +++ b/docs/overrides/partials/header.html @@ -0,0 +1,103 @@ +{#- + This file was automatically generated - do not edit +-#} +{% set site_url = config.site_url | d(nav.homepage.url, true) | url %} +{% if not config.use_directory_urls and site_url[0] == site_url[-1] == "." %} + {% set site_url = site_url ~ "/index.html" %} +{% endif %} +
+ +
\ No newline at end of file diff --git a/docs/overrides/partials/integrations/analytics.html b/docs/overrides/partials/integrations/analytics.html new file mode 100644 index 00000000..f97a822a --- /dev/null +++ b/docs/overrides/partials/integrations/analytics.html @@ -0,0 +1,33 @@ +{% set analytics = config.google_analytics %} + diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..6fd65112 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,9 @@ +mkdocs==1.1 +mkdocs-material==7.1.3 +mkdocs-macros-plugin==0.5.0 +mkdocs-exclude==1.0.2 +markdown==3.2.1 +markdown-include==0.5.1 +pymdown-extensions==7.0 +Pygments==2.5.2 +markdown-fenced-code-tabs==1.0.5 diff --git a/docs/state.dot b/docs/state.dot deleted file mode 100644 index a3eb68cd..00000000 --- a/docs/state.dot +++ /dev/null @@ -1,50 +0,0 @@ -// to generate an image: dot -Tsvg -O state.dot -// requires grapvhiz -digraph RSConnectJupyter { - node[shape=box] - Publish [shape=oval] - Done [shape=oval] - "Save\nDocument"[shape=diamond] - "Server\nmetadata?"[shape=diamond] - "Add Server Dialog"[shape=parallelogram] - Add[shape=diamond] - "Publish Dialog"[shape=parallelogram] - Publish1[shape=diamond] - "Previous Title?"[shape=diamond] - "Search Content Dialog"[shape=parallelogram] - "Publish Request"[shape=diamond] - - Publish -> "Save\nDocument" - - "Save\nDocument" -> Failure -> "Error Dialog" -> Done - "Save\nDocument" -> Success - - Success -> "Server\nmetadata?" -> {No, Yes} - No -> "Add Server Dialog" -> {Cancel, Add} - Yes -> "Publish Dialog" - Cancel -> Done - Add -> {"Invalid Details", "Valid Details"} - "Invalid Details" -> "Add Server Dialog" - "Valid Details" -> "Save Server Info" -> "Publish Dialog" - - Publish1 [label="Publish"] - Invalid1 [label="Invalid Details"] - Valid1 [label="Valid Details"] - No2 [label="No"] - "Publish Dialog" -> Cancel - "Publish Dialog" -> Publish1 -> {Invalid1, Valid1} - Invalid1 -> "Publish Dialog" - Valid1 -> "Previous Title?" -> {No2, "Different Title", "Same Title"} - No2 -> "Search Content Dialog" - "Same Title" -> "Publish Request" - "Different Title" -> "Search Content Dialog" - - Failure1 [label="Failure"] - Success1 [label="Success"] - "Publish Request" -> {Failure1, Success1} - Failure1 -> "Publish Dialog" - Success1 -> "Show Content Link" -> Done - - "Search Content Dialog" -> {Cancel, "New Content", "Overwrite Content"} - {"New Content", "Overwrite Content"} -> "Publish Request" -} diff --git a/docs/state.dot.svg b/docs/state.dot.svg deleted file mode 100644 index af713f9a..00000000 --- a/docs/state.dot.svg +++ /dev/null @@ -1,435 +0,0 @@ - - - - - - -RSConnectJupyter - - - -Publish - -Publish - - - -Save\nDocument - -Save -Document - - - -Publish->Save\nDocument - - - - - -Done - -Done - - - -Failure - -Failure - - - -Save\nDocument->Failure - - - - - -Success - -Success - - - -Save\nDocument->Success - - - - - -Server\nmetadata? - -Server -metadata? - - - -No - -No - - - -Server\nmetadata?->No - - - - - -Yes - -Yes - - - -Server\nmetadata?->Yes - - - - - -Add Server Dialog - -Add Server Dialog - - - -Add - -Add - - - -Add Server Dialog->Add - - - - - -Cancel - -Cancel - - - -Add Server Dialog->Cancel - - - - - -Invalid Details - -Invalid Details - - - -Add->Invalid Details - - - - - -Valid Details - -Valid Details - - - -Add->Valid Details - - - - - -Publish Dialog - -Publish Dialog - - - -Publish1 - -Publish - - - -Publish Dialog->Publish1 - - - - - -Publish Dialog->Cancel - - - - - -Invalid1 - -Invalid Details - - - -Publish1->Invalid1 - - - - - -Valid1 - -Valid Details - - - -Publish1->Valid1 - - - - - -Previous Title? - -Previous Title? - - - -No2 - -No - - - -Previous Title?->No2 - - - - - -Different Title - -Different Title - - - -Previous Title?->Different Title - - - - - -Same Title - -Same Title - - - -Previous Title?->Same Title - - - - - -Search Content Dialog - -Search Content Dialog - - - -Search Content Dialog->Cancel - - - - - -New Content - -New Content - - - -Search Content Dialog->New Content - - - - - -Overwrite Content - -Overwrite Content - - - -Search Content Dialog->Overwrite Content - - - - - -Publish Request - -Publish Request - - - -Failure1 - -Failure - - - -Publish Request->Failure1 - - - - - -Success1 - -Success - - - -Publish Request->Success1 - - - - - -Error Dialog - -Error Dialog - - - -Failure->Error Dialog - - - - - -Error Dialog->Done - - - - - -Success->Server\nmetadata? - - - - - -No->Add Server Dialog - - - - - -Yes->Publish Dialog - - - - - -Cancel->Done - - - - - -Invalid Details->Add Server Dialog - - - - - -Save Server Info - -Save Server Info - - - -Valid Details->Save Server Info - - - - - -Save Server Info->Publish Dialog - - - - - -Invalid1->Publish Dialog - - - - - -Valid1->Previous Title? - - - - - -No2->Search Content Dialog - - - - - -Different Title->Search Content Dialog - - - - - -Same Title->Publish Request - - - - - -Failure1->Publish Dialog - - - - - -Show Content Link - -Show Content Link - - - -Success1->Show Content Link - - - - - -Show Content Link->Done - - - - - -New Content->Publish Request - - - - - -Overwrite Content->Publish Request - - - - - diff --git a/docs/state.html b/docs/state.html deleted file mode 100644 index 0d4e33ce..00000000 --- a/docs/state.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - -
-graph TB - Start((Publish)) --> Save[Save Document] - Save --> SaveSuccess{Success?} - SaveSuccess-. Error .-> Error[Error Dialog] - Error .-> Done((Done)) - - SaveSuccess-- Success --> Metadata{Metadata
Present?} - - Metadata-- No Metadata --> AddServer[Add Server Dialog] - Metadata-- Yes --> FetchAppTitle[Fetch App Title from Server] - - FetchAppTitle --> PublishDialog[Publish Dialog] - - AddServer-- Add --> AddServerValid{Details
valid?} - AddServer-. Cancel .-> Done - - AddServerValid-- Invalid --> AddServer - AddServerValid-- Valid --> SaveServerInfo[Save Server Info,
Use Default Title] - SaveServerInfo --> PublishDialog - - PublishDialog-. Cancel .-> Done - PublishDialog-- Publish --> PublishValidate{Details
Valid?} - - PublishValidate-- Invalid --> PublishDialog - PublishValidate-- Valid --> TitleCheck{Previous
Title?} - - TitleCheck-- No Title --> SearchDialog[Search Content Dialog] - TitleCheck-- Different Title --> SearchDialog - TitleCheck-- Same Title --> PublishRequest[Send Publish Request] - - SearchDialog-. Cancel .-> Done - SearchDialog-- New Content --> PublishRequest - SearchDialog-- Overwrite --> PublishRequest - - PublishRequest --> PublishRequestSuccess{Success?} - PublishRequestSuccess-- Error --> PublishDialog - PublishRequestSuccess-- Success --> ShowLink[Show Content Link] - ShowLink --> Done -
- - - -