diff --git a/Dockerfile b/Dockerfile index 16860882..0cf96ffd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} -LABEL maintainer="RStudio Connect " +LABEL maintainer="Posit Connect " ARG NB_UID ARG NB_GID diff --git a/README.md b/README.md index 8c6b376a..99a3187d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [rsconnect-jupyter](https://www.github.com/rstudio/rsconnect-jupyter/) is a plugin for [Jupyter Notebook](https://jupyter.org/) that enables -publishing notebooks to [RStudio -Connect](https://www.rstudio.com/products/connect/). +publishing notebooks to [Posit +Connect](https://www.posit.co/products/enterprise/connect/). # Requirements @@ -11,7 +11,7 @@ Connect](https://www.rstudio.com/products/connect/). - 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 +- [Posit Connect](https://www.posit.co/download/posit-connect/) v1.7.0 or higher, configured with Python support. # Documentation diff --git a/docs/Dockerfile b/docs/Dockerfile index 35b0c0e3..63144063 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,6 +1,6 @@ # Using dated tags from https://hub.docker.com/_/ubuntu/ FROM ubuntu:bionic-20201119 -MAINTAINER RStudio Connect +MAINTAINER Posit 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 diff --git a/docs/README.md b/docs/README.md index f92ef7cf..1c43c381 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,9 @@ -# RStudio Connect Jupyter User Guide +# Posit Connect Jupyter User Guide -This directory contains the RStudio Connect: Jupyter User Guide. We use +This directory contains the Posit 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. +The Jupyter User Guide is geared towards the people who will publish Jupyter Notebooks to Posit Connect. ## Docker diff --git a/docs/docs/additional.md b/docs/docs/additional.md index 839f6d89..d201f1d3 100644 --- a/docs/docs/additional.md +++ b/docs/docs/additional.md @@ -9,8 +9,8 @@ User Guide resources: How-to Guide: - For a step-by-step guide for creating and publishing a new Jupyter Notebook to -RStudio Connect, view our [How To Publish a Jupyter Notebook to RStudio Connect](https://docs.rstudio.com/how-to-guides/users/basic/publish-jupyter-notebook/). +Posit Connect, view our [How To Publish a Jupyter Notebook to Posit Connect](https://docs.rstudio.com/how-to-guides/users/basic/publish-jupyter-notebook/). Video tutorial: -
\ No newline at end of file +
diff --git a/docs/docs/collaboration.md b/docs/docs/collaboration.md index 0c366f44..fdfb079b 100644 --- a/docs/docs/collaboration.md +++ b/docs/docs/collaboration.md @@ -1,9 +1,9 @@ # Collaboration -To collaborate with others, add them as collaborators in RStudio Connect. During +To collaborate with others, add them as collaborators in Posit 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. Additionally, you may share notebooks if appropriate. -For additional information, please see the [Collaboration](https://docs.rstudio.com/connect/user/publishing/#publishing-collaboration) section of the RStudio Connect User Guide. \ No newline at end of file +For additional information, please see the [Collaboration](https://docs.rstudio.com/connect/user/publishing/#publishing-collaboration) section of the Posit Connect User Guide. diff --git a/docs/docs/css/custom.css b/docs/docs/css/custom.css index dc7c2daa..114a1c39 100644 --- a/docs/docs/css/custom.css +++ b/docs/docs/css/custom.css @@ -34,7 +34,7 @@ */ .md-search-result__more summary { - color: #2196f3 !important; + color: #447099 !important; } .md-search-result mark { @@ -42,21 +42,45 @@ font-weight: bold; } +/* Search bar - black palette +*/ + +[data-md-color-primary=black] .md-search-result mark { + color: #fff !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; + margin-top: .2rem !important; padding: 0; } + +.md-tabs__item { + height: 1.75rem !important; +} + +.md-tabs__link.logo { + background-image: url(../images/positLogoBlack.svg); + background-size: 80px auto; + background-repeat: no-repeat; + background-position: bottom; + height: 1.75rem; + width: 80px; +} + +[data-md-color-primary=black] .md-tabs__link.logo { + background-image: url(../images/positLogoWhite.svg); +} + .md-typeset dl dt { font-style: italic; font-weight: bold; @@ -71,11 +95,22 @@ } [data-md-color-primary=white] .md-typeset a { - color: #4c83b6; + color: #447099; } [data-md-color-primary=white] .md-typeset a:hover { text-decoration: underline; + color: #447099; +} + +[data-md-color-primary=black] .md-typeset a { + color: #447099; + font-weight: bold; +} + +[data-md-color-primary=black] .md-typeset a:hover { + text-decoration: underline; + color: #447099; } .admonition-title { @@ -87,11 +122,7 @@ } .md-typeset .superfences-tabs>label:hover { - color: #4c83b6 !important; -} - -.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { - background-color: #4c83b6; + color: #447099 !important; } .md-footer { @@ -107,13 +138,13 @@ .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; + font-size: 0.65rem; + padding-top: 15px; } .md-footer-meta.md-typeset a { @@ -121,37 +152,57 @@ font-weight: 400; } -.md-footer-nav { - background-color: #fff; - color: #000; +[data-md-color-primary=black] .md-footer { + background-color: #000 !important; + color: #fff !important; } -.md-typeset .tabbed-set>label { - font-size: .64rem !important; +[data-md-color-primary=black] .md-footer__direction { + color: #fff; + font-size: .65rem; } -.md-typeset .tabbed-set>label:hover { - color: #4c83b6 !important; +[data-md-color-primary=black] .md-footer-meta { + background-color: #000; + border-top: 1px solid #fff; + font-weight: 400; } -.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { - background-color: #4c83b6; +[data-md-color-primary=black] .md-footer-copyright__highlight { + color: #fff; + font-weight: 400; +} + +[data-md-color-primary=black] .md-footer-meta.md-typeset a { + color: #fff !important; + font-weight: 400; } +.md-typeset .tabbed-set>label { + font-size: .64rem !important; +} + +.md-typeset .tabbed-set>label:hover { + color: #447099 !important; +} /* Sidebar */ .md-nav__link:focus, .md-nav__link:hover, .md-nav__link:active, .md-nav__link--active, .md-nav__link:active { - color: #4c83b6 !important; + color: #447099 !important; } .md-nav__link--active { font-weight: bold; } +[data-md-color-primary=black] .md-nav { + color: #fff; +} + [data-md-color-primary=white] .md-nav__link--active, [data-md-color-primary=white] .md-nav__link:active { - color: #2196f3; + color: #447099; } @@ -169,148 +220,32 @@ h2.divider { 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; +.code-title { background-color: rgba(219, 219, 219, 0.8); - margin: 1em 0 -1.2em; + border-bottom: 0.05rem solid var(--md-default-fg-color--lightest); + border-top-left-radius: 0.1rem; + border-top-right-radius: 0.1rem; + display: block; + font-size: 0.85em; + font-weight: 400; + font-family: 'Roboto Mono'; + margin-top: 1em; padding: 0.1em 1em; - font-size: 0.9em; - font-family: "Roboto Mono","Courier New",Courier,monospace; + position: relative; +} + +[data-md-color-primary=black] .code-title { + background-color: black !important; } pre .code-noselect { @@ -323,54 +258,7 @@ pre .code-noselect { } 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; + background-color: #447099 !important; } /* Permalinks @@ -454,4 +342,4 @@ img.border { 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 index 46bae48e..02c4d053 100644 --- a/docs/docs/css/external-link-alt-regular.svg +++ b/docs/docs/css/external-link-alt-regular.svg @@ -1,6 +1,6 @@ \ No newline at end of file + diff --git a/docs/docs/css/external-links.css b/docs/docs/css/external-links.css index 99f3f49a..97f721a4 100644 --- a/docs/docs/css/external-links.css +++ b/docs/docs/css/external-links.css @@ -1,7 +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"]) { +div.md-content a[href^="//"]:not([href*="test.rstudio.com"]), +div.md-content a[href^="http://"]:not([href*="test.posit.co"]):after, +div.md-content a[href^="https://"]:not([href*="test.posit.co"]):after, +div.md-content a[href^="//"]:not([href*="test.posit.co"]) { content: url(external-link-alt-regular.svg); vertical-align: 15%; display: inline-block; diff --git a/docs/docs/css/superfences-tabs.css b/docs/docs/css/superfences-tabs.css index 683c15d4..295cb8c6 100644 --- a/docs/docs/css/superfences-tabs.css +++ b/docs/docs/css/superfences-tabs.css @@ -1,43 +1,16 @@ -.tabbed-set { - display: flex; - position: relative; - flex-wrap: wrap; - border: .05rem solid rgba(0,0,0,.07); +.md-typeset .tabbed-set>input:checked+label { + color: #447099 !important; + border: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 0.1rem solid #EE6331; } -.tabbed-set .highlight { - background: #f5f5f5; +.md-typeset .tabbed-set>label:hover { + color: #447099; + border-bottom: 0.1rem solid #447099; } -.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; +[data-md-color-primary=black] .md-typeset .tabbed-alternate input:first-child:checked~.tabbed-labels>:first-child, .md-typeset .tabbed-alternate input:nth-child(2):checked~.tabbed-labels>:nth-child(2), .md-typeset .tabbed-alternate input:nth-child(3):checked~.tabbed-labels>:nth-child(3), .md-typeset .tabbed-alternate input:nth-child(4):checked~.tabbed-labels>:nth-child(4), .md-typeset .tabbed-alternate input:nth-child(5):checked~.tabbed-labels>:nth-child(5), .md-typeset .tabbed-alternate input:nth-child(6):checked~.tabbed-labels>:nth-child(6), .md-typeset .tabbed-alternate input:nth-child(7):checked~.tabbed-labels>:nth-child(7), .md-typeset .tabbed-alternate input:nth-child(8):checked~.tabbed-labels>:nth-child(8), .md-typeset .tabbed-alternate input:nth-child(9):checked~.tabbed-labels>:nth-child(9), .md-typeset .tabbed-alternate input:nth-child(10):checked~.tabbed-labels>:nth-child(10), .md-typeset .tabbed-alternate input:nth-child(11):checked~.tabbed-labels>:nth-child(11), .md-typeset .tabbed-alternate input:nth-child(12):checked~.tabbed-labels>:nth-child(12), .md-typeset .tabbed-alternate input:nth-child(13):checked~.tabbed-labels>:nth-child(13), .md-typeset .tabbed-alternate input:nth-child(14):checked~.tabbed-labels>:nth-child(14), .md-typeset .tabbed-alternate input:nth-child(15):checked~.tabbed-labels>:nth-child(15), .md-typeset .tabbed-alternate input:nth-child(16):checked~.tabbed-labels>:nth-child(16), .md-typeset .tabbed-alternate input:nth-child(17):checked~.tabbed-labels>:nth-child(17), .md-typeset .tabbed-alternate input:nth-child(18):checked~.tabbed-labels>:nth-child(18), .md-typeset .tabbed-alternate input:nth-child(19):checked~.tabbed-labels>:nth-child(19), .md-typeset .tabbed-alternate input:nth-child(20):checked~.tabbed-labels>:nth-child(20).tabbed-labels { + border-top: 1px solid rgba(255, 255, 255, 0.05); + border-left: 1px solid rgba(255, 255, 255, 0.05); + border-right: 1px solid rgba(255, 255, 255, 0.05); } diff --git a/docs/docs/images/add-dialog.png b/docs/docs/images/add-dialog.png index 476a1ee4..f8d4e1d2 100644 Binary files a/docs/docs/images/add-dialog.png and b/docs/docs/images/add-dialog.png differ diff --git a/docs/docs/images/add-files.png b/docs/docs/images/add-files.png index 150ec87f..96a16cb4 100644 Binary files a/docs/docs/images/add-files.png and b/docs/docs/images/add-files.png differ diff --git a/docs/docs/images/deploy-options.png b/docs/docs/images/deploy-options.png index b4f5c939..80644ac9 100644 Binary files a/docs/docs/images/deploy-options.png and b/docs/docs/images/deploy-options.png differ diff --git a/docs/docs/images/favicon.ico b/docs/docs/images/favicon.ico new file mode 100644 index 00000000..3543b489 Binary files /dev/null and b/docs/docs/images/favicon.ico differ diff --git a/docs/docs/images/git-backed.png b/docs/docs/images/git-backed.png index 03199670..41add04c 100644 Binary files a/docs/docs/images/git-backed.png and b/docs/docs/images/git-backed.png differ diff --git a/docs/docs/images/iconPositConnect.svg b/docs/docs/images/iconPositConnect.svg new file mode 100644 index 00000000..6aea8f5d --- /dev/null +++ b/docs/docs/images/iconPositConnect.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/docs/docs/images/positLogoBlack.svg b/docs/docs/images/positLogoBlack.svg new file mode 100644 index 00000000..b85676de --- /dev/null +++ b/docs/docs/images/positLogoBlack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/docs/images/positLogoWhite.svg b/docs/docs/images/positLogoWhite.svg new file mode 100644 index 00000000..fc4636fa --- /dev/null +++ b/docs/docs/images/positLogoWhite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/docs/images/rsconnect-jupyter-usage.png b/docs/docs/images/rsconnect-jupyter-usage.png index d0d9bb42..1f583547 100644 Binary files a/docs/docs/images/rsconnect-jupyter-usage.png and b/docs/docs/images/rsconnect-jupyter-usage.png differ diff --git a/docs/docs/index.md b/docs/docs/index.md index 1b4a7555..d5ce61d8 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -1,6 +1,6 @@ # `rsconnect-jupyter` User Guide -`rsconnect-jupyter` is a plugin for Jupyter Notebooks that enables publishing notebooks to RStudio Connect. +`rsconnect-jupyter` is a plugin for Jupyter Notebooks that enables publishing notebooks to Posit Connect. ## Requirements @@ -8,7 +8,7 @@ - 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 +- [Posit Connect](https://www.posit.co/download/posit-connect/) v1.7.0 or higher, configured with Python support !!! note If using `conda`, `pip` and `wheel` should already be installed. @@ -23,7 +23,7 @@ The installation method depends on the Python environment that you are installin This documentation covers three methods: - [Installing Jupyter within a virtual environment](#installing-jupyter-within-a-virtual-environment) -- [Installing `rsconnect-jupyter` to Jupyter running on RStudio Workbench](#installing-to-jupyter-running-on-rstudio-workbench) +- [Installing `rsconnect-jupyter` to Jupyter running on Posit Workbench](#installing-to-jupyter-running-on-posit-workbench) - [Installation in JupyterHub](#installing-in-jupyterhub) Please navigate to the installation section below that is best for your environment. @@ -63,7 +63,7 @@ To install and use Jupyter within a virtual environment using --- -### Installing to Jupyter running on RStudio Workbench +### Installing to Jupyter running on Posit Workbench - 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) @@ -138,6 +138,6 @@ with `rsconnect-jupyter` installed. ``` 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. + users. From there, you can create a notebook and publish it to Posit 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. + need a compatible Python version installed on your Posit Connect server. diff --git a/docs/docs/usage.md b/docs/docs/usage.md index ca09d039..69edaa75 100644 --- a/docs/docs/usage.md +++ b/docs/docs/usage.md @@ -1,12 +1,12 @@ # Usage -## Publish to RStudio Connect +## Publish to Posit Connect -To publish to RStudio Connect: +To publish to Posit Connect: - Open a Jupyter notebook. -- Click the blue toolbar icon used for publishing the notebook icon (blue publish button) and select **Publish to RStudio Connect** -to publish the current notebook to RStudio Connect. +- Click the blue toolbar icon used for publishing the notebook icon (blue publish button) and select **Publish to Posit Connect** +to publish the current notebook to Posit Connect. !!! note This plugin is only for notebooks using Python kernels. Therefore, R notebooks cannot be published using this plugin. @@ -14,46 +14,46 @@ 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 +prompted to enter the location and a nickname for the Posit Connect server. +- You will also be prompted to enter your API Key. See the [Posit Connect User Guide](http://docs.rstudio.com/connect/user/api-keys) 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. +- When you click the **Add Server** button, `rsconnect-jupyter` will send a request to the Posit 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 certificates that the computer hosting your Jupyter notebook server does not trust), the attempt to contact RStudio Connect may fail with a TLS-related error. +If your Posit Connect server was configured with a self-signed certificate (or other certificates that the computer hosting your Jupyter notebook server does not trust), the attempt to contact Posit Connect may fail with a TLS-related error. You have multiple options in this case, depending on your needs: 1. If your administrator can give you the Certificate Authority (CA) - Bundle for your RStudio Connect server, ask your administrator if it + Bundle for your Posit Connect server, ask your 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. + your secure connection to Posit Connect. 1. If you cannot obtain the CA bundle, you can disable TLS verification completely by selecting the **Disable TLS Certificate Verification** check 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. + Posit Connect will still be encrypted, but you will not be able to verify the + identity of the Posit Connect server. -initial dialog that prompts for the location of RStudio Connect +initial dialog that prompts for the location of Posit Connect ## Publishing options There are two different publication modes: - 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 +packages installed in your environment will be sent to Posit Connect. This enables Posit Connect to recreate the environment and re-run the notebook at a later time. - 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. +publish an HTML snapshot of the notebook to Posit Connect. HTML snapshots are static and +cannot be scheduled or re-run on the Posit Connect server. publish dialog ### Hide Input There are two options for hiding input code cells in Jupyter Notebooks published -to RStudio Connect: +to Posit Connect: - Hide all input code cells - Hide only selected input code cells @@ -92,11 +92,11 @@ 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. +versions, which enables Posit Connect to recreate the same environment. ## Generating Manifests for git Publishing -RStudio Connect can poll git repositories for deployable content and update +Posit Connect can poll git repositories for deployable content and update as you add new commits to your repository. 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`) to be able @@ -118,16 +118,16 @@ informing you of this fact. If you need to regenerate the files, delete them in Dialog titled For more information on git publishing, see the -[RStudio Connect User Guide](https://docs.rstudio.com/connect/user/git-backed#git-backed-publishing). +[Posit Connect User Guide](https://docs.rstudio.com/connect/user/git-backed#git-backed-publishing). ## Handling conflicts -If content that matches your notebook's title is found on RStudio Connect, you +If content that matches your notebook's title is found on Posit 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. +- Choosing **New location** creates a new document in Posit Connect. - You can choose either publication mode: - an HTML snapshot *or* - a document with source code @@ -138,7 +138,7 @@ Upon successful publishing of the document, a notification will be shown in the toolbar. Clicking the notification will open the published -document in the RStudio Connect server you selected in the previous +document in the Posit Connect server you selected in the previous dialog. notification that shows the notebook was published successfully diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f315b475..f8e9501a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,5 @@ site_name: 'rsconnect-jupyter User Guide' -copyright: RStudio, PBC. All Rights Reserved +copyright: Posit Software, PBC. All Rights Reserved # We activate GA only when hosted on our public docs site # and not when installed. @@ -43,9 +43,21 @@ plugins: theme: name: material custom_dir: overrides - logo: 'images/rstudio-logo.svg' + font: + text: Open Sans + logo: 'images/iconPositConnect.svg' + favicon: 'images/favicon.ico' palette: - primary: 'white' + - scheme: default + primary: white + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - scheme: slate + primary: black + toggle: + icon: material/toggle-switch + name: Switch to light mode extra_css: - css/external-links.css diff --git a/docs/overrides/partials/header.html b/docs/overrides/partials/header.html index 1ab0bdbb..a53ecee9 100644 --- a/docs/overrides/partials/header.html +++ b/docs/overrides/partials/header.html @@ -83,9 +83,9 @@ {% endif %} {% if "search" in config["plugins"] %} @@ -100,4 +100,4 @@ {% endif %} - \ No newline at end of file + diff --git a/package.json b/package.json index 998e04d2..2b6b86ac 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lint": "./node_modules/.bin/eslint ./rsconnect_jupyter/static/*.js" }, "repository": "git@github.com:rstudio/rsconnect-jupyter.git", - "author": "Jonathan Curran ", + "author": "Jonathan Curran ", "license": "GPL-2.0", "devDependencies": { "eslint": "^6.4.0" diff --git a/rsconnect_jupyter/__init__.py b/rsconnect_jupyter/__init__.py index 94853a0f..e0bde7c4 100644 --- a/rsconnect_jupyter/__init__.py +++ b/rsconnect_jupyter/__init__.py @@ -93,23 +93,23 @@ def post(self, action): raise web.HTTPError( 400, 'Received an "SSL:UNKNOWN_PROTOCOL" error when trying to connect securely ' - + "to the RStudio Connect server.\n" + + "to the Posit Connect server.\n" + '* Try changing "https://" in the "Server Address" field to "http://".\n' - + "* If the condition persists, contact your RStudio Connect server " + + "* If the condition persists, contact your Posit Connect server " + "administrator.", ) raise web.HTTPError( 400, - "A TLS error occurred when trying to reach the RStudio Connect server.\n" + "A TLS error occurred when trying to reach the Posit Connect server.\n" + "* Ensure that the server address you entered is correct.\n" - + "* Ask your RStudio Connect administrator if you need a certificate bundle and\n" + + "* Ask your Posit Connect administrator if you need a certificate bundle and\n" + ' upload it using "Upload TLS Certificate Bundle" below.', ) except Exception as err: - self.log.exception("Unable to verify that the provided server is running RStudio Connect") + self.log.exception("Unable to verify that the provided server is running Posit Connect") raise web.HTTPError( 400, - "Unable to verify that the provided server is running RStudio Connect: %s" % err, + "Unable to verify that the provided server is running Posit Connect: %s" % err, ) if canonical_address is not None: uri = canonical_address.url @@ -119,7 +119,7 @@ def post(self, action): self.finish( json.dumps( { - "status": "Provided server is running RStudio Connect", + "status": "Provided server is running Posit Connect", "address_hash": address_hash, "server_address": canonical_address.url, } diff --git a/rsconnect_jupyter/static/connect.js b/rsconnect_jupyter/static/connect.js index aafd855c..7a871f3a 100644 --- a/rsconnect_jupyter/static/connect.js +++ b/rsconnect_jupyter/static/connect.js @@ -6,14 +6,14 @@ define([ 'base/js/dialog', 'services/contents', './rsconnect' -], function($, Jupyter, Dialog, Contents, RSConnect) { +], function ($, Jupyter, Dialog, Contents, RSConnect) { /*********************************************************************** * Extension bootstrap (main) ***********************************************************************/ var debugModeEnabled = window.localStorage - // eslint-disable-next-line multiline-ternary - ? window.localStorage.getItem('__RSCONNECT_JUPYTER_DEBUG_MODE__') === 'enabled' : false; + // eslint-disable-next-line multiline-ternary + ? window.localStorage.getItem('__RSCONNECT_JUPYTER_DEBUG_MODE__') === 'enabled' : false; function verbose() { if (debugModeEnabled) { console.error.apply(window, arguments); @@ -56,38 +56,38 @@ define([ } function maybeCreateConfig() { - if (!config) { - config = new RSConnect(debug); - window.RSConnect = config; - config.getVersionInfo() - .then(function(info) { - console.log('rsconnect-jupyter nbextension version:', info.js_version); - console.log('rsconnect-jupyter serverextension version:', info.rsconnect_jupyter_server_extension); - console.log('rsconnect-python version:', info.rsconnect_python_version); - rsconnectVersionInfo = info; - window.rsconnectVersionInfo = info; - if (info.js_version !== info.rsconnect_jupyter_server_extension) { - console.error('Version Mismatch: rsconnect-jupyter has been installed incorrectly.'); - setTimeout(function () { - Dialog.modal({ - title: 'Error', - body: 'Plugin Version Mismatch: rsconnect-jupyter has been installed incorrectly. ' + - 'The javascript extension version reports ' + info.js_version + ' but the server ' + - 'extension reports ' + info.rsconnect_jupyter_server_extension + '.
' + - '
    ' + - '
  • Refer to the ' + - 'installation instructions for more information.
  • ' + - '
  • Try completely uninstalling every version of the plugin and reinstalling. ' + - 'Your server information will be saved.
  • ' + - '
  • Continuing to use the plugin as-is may lead to unexpected issues.
  • ' + - '
', - sanitize: false, - buttons: {Ok: {class: 'btn-primary'}} - }); - }, 1000); - } - }); - } + if (!config) { + config = new RSConnect(debug); + window.RSConnect = config; + config.getVersionInfo() + .then(function (info) { + console.log('rsconnect-jupyter nbextension version:', info.js_version); + console.log('rsconnect-jupyter serverextension version:', info.rsconnect_jupyter_server_extension); + console.log('rsconnect-python version:', info.rsconnect_python_version); + rsconnectVersionInfo = info; + window.rsconnectVersionInfo = info; + if (info.js_version !== info.rsconnect_jupyter_server_extension) { + console.error('Version Mismatch: rsconnect-jupyter has been installed incorrectly.'); + setTimeout(function () { + Dialog.modal({ + title: 'Error', + body: 'Plugin Version Mismatch: rsconnect-jupyter has been installed incorrectly. ' + + 'The javascript extension version reports ' + info.js_version + ' but the server ' + + 'extension reports ' + info.rsconnect_jupyter_server_extension + '.
' + + '
    ' + + '
  • Refer to the ' + + 'installation instructions for more information.
  • ' + + '
  • Try completely uninstalling every version of the plugin and reinstalling. ' + + 'Your server information will be saved.
  • ' + + '
  • Continuing to use the plugin as-is may lead to unexpected issues.
  • ' + + '
', + sanitize: false, + buttons: { Ok: { class: 'btn-primary' } } + }); + }, 1000); + } + }); + } } function init() { @@ -104,7 +104,7 @@ define([ var actionName = actions.register( { icon: 'fa-cloud-upload', - help: 'Publish to RStudio Connect', + help: 'Publish to Posit Connect', help_index: 'zz', handler: debounce(1000, onPublishClicked) }, @@ -125,7 +125,7 @@ define([ var $menuContainer = $('
'); var $menu = $('
'); - var publishItem = $('Publish to RStudio Connect'); + var publishItem = $('Publish to Posit Connect'); publishItem.click(onPublishClicked); $menu.append(publishItem); @@ -147,12 +147,12 @@ define([ ***********************************************************************/ var debug = { - info: function() { + info: function () { var args = [].slice.call(arguments); args.unshift('RSConnect:'); console.info.apply(null, args); }, - error: function() { + error: function () { var args = [].slice.call(arguments); args.unshift('RSConnect:'); console.error.apply(null, args); @@ -161,12 +161,12 @@ define([ function debounce(delay, fn) { var timeoutId = null; - return function() { + return function () { var self = this; if (timeoutId === null) { fn.apply(self, arguments); } - timeoutId = setTimeout(function() { + timeoutId = setTimeout(function () { timeoutId = null; }, delay); }; @@ -186,11 +186,11 @@ define([ .find('.help-block'); helpBlock.empty(); if (helpText.match(/\n/) !== null) { - helpText.split('\n').forEach(function(line) { - helpBlock.append(line+'
'); + helpText.split('\n').forEach(function (line) { + helpBlock.append(line + '
'); }); } else { - helpBlock.append(helpText); + helpBlock.append(helpText); } } } @@ -202,9 +202,9 @@ define([ */ function addWarningMarkup($el, helpText) { $el - .closest('.form-group') - .find('.help-block') - .text(helpText); + .closest('.form-group') + .find('.help-block') + .text(helpText); } function maybeRemoveWarningMarkup($el) { @@ -222,7 +222,7 @@ define([ * @returns {String} filename */ function fileName(path) { - return path.slice(path.lastIndexOf('/')+1); + return path.slice(path.lastIndexOf('/') + 1); } /** @@ -242,12 +242,12 @@ define([ basePath: basePath, notebookPath: notebookPath, excludedFiles: [ - notebookPath, - basePath+'/requirements.txt', - basePath+'/manifest.json', - 'requirements.txt', - 'manifest.json' - ], + notebookPath, + basePath + '/requirements.txt', + basePath + '/manifest.json', + 'requirements.txt', + 'manifest.json' + ], currentPath: basePath, /** * Shows the file selector widget @@ -255,7 +255,7 @@ define([ * @returns {PromiseLike,String>} List of files or rejection message * @public */ - showAddFilesDialog: function() { + showAddFilesDialog: function () { var result = $.Deferred(); var that = this; that.currentPath = that.basePath; @@ -266,8 +266,8 @@ define([ title: 'Add Files to Deploy', body: '' + - '
    ' + - '
', + '
    ' + + '
', buttons: { 'Cancel': { 'id': 'add-files-dialog-cancel', @@ -275,7 +275,7 @@ define([ * Reject the staged files and replace with filelist * Note: `.slice(0)` is how you clone arrays in JS */ - click: function() { + click: function () { that.stagedFiles = that.fileList.slice(0); result.reject('User cancelled'); } @@ -286,7 +286,7 @@ define([ /** * Accept the staged files as the new file list */ - click: function() { + click: function () { that.fileList = that.stagedFiles.slice(0); that.updateListGroupItems(); result.resolve(that.fileList); @@ -297,7 +297,7 @@ define([ /** * Opens the file dialog. */ - open: function() { + open: function () { that.stagedFiles = that.fileList.slice(0); that.fillFileList(); } @@ -310,12 +310,12 @@ define([ * @returns {string} path with basepath removed * @private */ - pathSanitizer: function(path) { + pathSanitizer: function (path) { if (this.basePath === '') { return path; } // Assumption in here is that `path` contains `this.basePath` - return path.slice(this.basePath.length+1); + return path.slice(this.basePath.length + 1); }, /** * fillFileList fills the file selection dialog based on the current @@ -325,88 +325,88 @@ define([ * - stagedFiles (which files are ready to replace the `fileList`) * @private */ - fillFileList: function() { + fillFileList: function () { var that = this; var $container = $('#file-list-container'); var $label = $('#file-list-label'); $label.empty().text(that.currentPath + '/'); $container.empty(); ContentsManager.list_contents(that.currentPath) - .then(function(contents) { - verbose('got contents:', contents); - var content = contents.content.sort(function (a, b) { - // Directories come first - if (a.type === 'directory' ? b.type !== 'directory' : b.type === 'directory') { - return a.type === 'directory' ? -1 : 1; - } - // There is no 0 case because we trust no name collisions in content manager. - // If they have the same normalized-case value, capital comes first. - if (a.name.toLowerCase() === b.name.toLowerCase()) { - return a.name < b.name ? -1 : 1; - } - return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; - }); - // If we're not on the base path, add a ".." - if (that.currentPath !== that.basePath) { - var li = document.createElement('li'); - var i = document.createElement('i'); - i.className = 'fa fa-folder'; - li.appendChild(i); - li.className = 'list-group-item'; - li.appendChild(document.createTextNode(' ..')); - li.addEventListener('click', that.directoryUp()); - $container.append(li); + .then(function (contents) { + verbose('got contents:', contents); + var content = contents.content.sort(function (a, b) { + // Directories come first + if (a.type === 'directory' ? b.type !== 'directory' : b.type === 'directory') { + return a.type === 'directory' ? -1 : 1; + } + // There is no 0 case because we trust no name collisions in content manager. + // If they have the same normalized-case value, capital comes first. + if (a.name.toLowerCase() === b.name.toLowerCase()) { + return a.name < b.name ? -1 : 1; } - content.forEach(function(item) { - // If the file is excluded, don't show - if (that.excludedFiles.indexOf(item.path) !== -1) { - return; + return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; + }); + // If we're not on the base path, add a ".." + if (that.currentPath !== that.basePath) { + var li = document.createElement('li'); + var i = document.createElement('i'); + i.className = 'fa fa-folder'; + li.appendChild(i); + li.className = 'list-group-item'; + li.appendChild(document.createTextNode(' ..')); + li.addEventListener('click', that.directoryUp()); + $container.append(li); + } + content.forEach(function (item) { + // If the file is excluded, don't show + if (that.excludedFiles.indexOf(item.path) !== -1) { + return; + } + var li2 = document.createElement('li'); + if (item.type === 'directory') { + var i2 = document.createElement('i'); + i2.className = 'fa fa-folder'; + li2.appendChild(i2); + li2.addEventListener('click', that.directoryClicked(item.path)); + } else { + var input = document.createElement('input'); + input.type = 'checkbox'; + input.name = item.name; + input.value = item.path; + if (that.stagedFiles.indexOf(item.path) !== -1) { + input.checked = true; } - var li2 = document.createElement('li'); - if (item.type === 'directory') { - var i2 = document.createElement('i'); - i2.className = 'fa fa-folder'; - li2.appendChild(i2); - li2.addEventListener('click', that.directoryClicked(item.path)); - } else { - var input = document.createElement('input'); - input.type = 'checkbox'; - input.name = item.name; - input.value = item.path; - if (that.stagedFiles.indexOf(item.path) !== -1) { - input.checked = true; + $(input).change(function () { + if (input.checked) { + that.stagedFiles.push(item.path); + } else { + that.stagedFiles.splice(that.stagedFiles.indexOf(item.path), 1); } - $(input).change(function () { - if(input.checked) { + }); + li2.appendChild(input); + $(li2).click(function (ev) { + if (ev.target !== input) { + input.checked = !input.checked; + if (input.checked) { that.stagedFiles.push(item.path); } else { that.stagedFiles.splice(that.stagedFiles.indexOf(item.path), 1); } - }); - li2.appendChild(input); - $(li2).click(function(ev) { - if (ev.target !== input) { - input.checked = !input.checked; - if (input.checked) { - that.stagedFiles.push(item.path); - } else { - that.stagedFiles.splice(that.stagedFiles.indexOf(item.path), 1); - } - } - }); - } - li2.className = 'list-group-item'; - li2.appendChild(document.createTextNode(' ' + fileName(item.path))); - $container.append(li2); - }); + } + }); + } + li2.className = 'list-group-item'; + li2.appendChild(document.createTextNode(' ' + fileName(item.path))); + $container.append(li2); }); + }); }, /** * directoryUp is a factory creating a handler for clicking the `..` item in the directory list * @returns {function} click handler * @private */ - directoryUp: function() { + directoryUp: function () { function handler() { var lastSlashIndex = this.currentPath.lastIndexOf('/'); if (lastSlashIndex === -1) { @@ -424,7 +424,7 @@ define([ * @returns {function} click handler * @private */ - directoryClicked: function(directory) { + directoryClicked: function (directory) { function handler() { this.currentPath = directory; this.fillFileList(); @@ -434,7 +434,7 @@ define([ /** * updateListGroupItems updates the list group from the list of staged files */ - updateListGroupItems: function() { + updateListGroupItems: function () { var that = this; that.$listGroup.empty(); that.fileList = that.fileList.sort(); @@ -455,7 +455,7 @@ define([ * @returns {function} bound click handler * @private */ - listGroupItemClicked: function(item) { + listGroupItemClicked: function (item) { function handler() { this.fileList.splice(this.fileList.indexOf(item), 1); this.updateListGroupItems(); @@ -519,7 +519,7 @@ define([ } function getCertificateUpload(ctx) { - return $(ctx).find('#rsc-ca-file')[0]; + return $(ctx).find('#rsc-ca-file')[0]; } function showAddServerDialog(_config, inServerAddress, inServerName) { @@ -546,13 +546,13 @@ define([ } AddServerDialog.prototype = { - init: function() { + init: function () { this.dialog = Dialog.modal({ // pass the existing keyboard manager so all shortcuts are disabled while // modal is active keyboard_manager: Jupyter.notebook.keyboard_manager, - title: 'Add RStudio Connect Server', + title: 'Add Posit Connect Server', body: [ '
', '
', @@ -594,7 +594,7 @@ define([ }); }, - openDialog: function() { + openDialog: function () { disableKeyboardManagerIfNeeded(); // there is no _close_ event so let's improvise. @@ -609,10 +609,10 @@ define([ this.$txtServer.val(this.inServerAddress); this.$txtServerName.val(this.inServerName); this.dialog.find('#version-info').html( - 'rsconnect-jupyter server extension version: ' + - rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + - 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + - 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version + 'rsconnect-jupyter server extension version: ' + + rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + + 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + + 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version ); var that = this; function addCertificateUpload() { @@ -621,7 +621,7 @@ define([ certificateUpload.id = 'rsc-ca-file'; certificateUpload.className = 'rsc-file-dialog'; that.dialog.find('#certificate-upload-container') - .append(certificateUpload); + .append(certificateUpload); } function maybeRemoveCertificateUpload() { var fileDialog = that.dialog.find('#rsc-ca-file'); @@ -632,7 +632,7 @@ define([ function radioTLSChange() { if (that.$radioDisableTLSVerification.is(':checked')) { maybeRemoveCertificateUpload(); - var disableTLSWarning = 'Disabling TLS verification will make your connection to RStudio Connect less secure'; + var disableTLSWarning = 'Disabling TLS verification will make your connection to Posit Connect less secure'; addWarningMarkup(that.$radioDisableTLSVerification, disableTLSWarning); } else if (that.$radioUploadTLSCertificates.is(':checked')) { maybeRemoveWarningMarkup(that.$radioDisableTLSVerification); @@ -656,7 +656,7 @@ define([ this.$btnAdd = $( '' ); - this.$btnAdd.on('click', function() { + this.$btnAdd.on('click', function () { form.trigger('submit'); }); this.dialog @@ -683,15 +683,15 @@ define([ $('#rsc-tls-options').append(helpIcon); }, - closeDialog: function() { + closeDialog: function () { this.dialogResult.reject('canceled'); }, - result: function() { + result: function () { return this.dialogResult; }, - validate: function() { + validate: function () { var server = this.$txtServer.val(); if (server.indexOf('http') !== 0) { this.$txtServer.val('http://' + server); @@ -708,7 +708,7 @@ define([ addValidationMarkup( validServer, this.$txtServer, - 'This should be the location of RStudio Connect: e.g. https://connect.example.com/' + 'This should be the location of Posit Connect: e.g. https://connect.example.com/' ); addValidationMarkup( validServerName, @@ -724,7 +724,7 @@ define([ return (validServer && validServerName && validApiKey); }, - toggleAddButton: function(state) { + toggleAddButton: function (state) { this.dialog.find('fieldset').attr('disabled', state ? null : true); this.$btnAdd .toggleClass('disabled', !state) @@ -732,20 +732,20 @@ define([ .toggleClass('hidden', state); }, - getServerError: function(xhr) { + getServerError: function (xhr) { var msg; if (xhr.status === 400) { if (xhr.responseJSON) { - if (xhr.responseJSON.message) { - msg = xhr.responseJSON.message; - } else { - msg = 'Server returned an unexpected response:' + xhr.responseJSON; - } + if (xhr.responseJSON.message) { + msg = xhr.responseJSON.message; + } else { + msg = 'Server returned an unexpected response:' + xhr.responseJSON; + } } else { - msg = 'Failed to verify that RStudio Connect is running at ' + - this.$txtServer.val() + - '. Please ensure the server address is valid.'; + msg = 'Failed to verify that Posit Connect is running at ' + + this.$txtServer.val() + + '. Please ensure the server address is valid.'; } } else if (xhr.status === 401) { @@ -757,7 +757,7 @@ define([ return msg; }, - onSubmit: function(e) { + onSubmit: function (e) { var self = this; e.preventDefault(); clearValidationMessages(this.dialog); @@ -771,40 +771,40 @@ define([ // if we have a file, we call `addServer` with TLS checking // enabled and provide CA data submit = readFileToPromise(fileCaBundleFile.files[0]) - .then(function (cadata) { - return that.config - .addServer( - that.$txtServer.val(), - that.$txtServerName.val(), - that.$txtApiKey.val(), - false, - cadata - ); - }); - } else { - // if not, we optionally disable TLS checking and leave CA data - // undefined. - submit = that.config + .then(function (cadata) { + return that.config .addServer( that.$txtServer.val(), that.$txtServerName.val(), that.$txtApiKey.val(), - that.$radioDisableTLSVerification.is(':checked') + false, + cadata + ); + }); + } else { + // if not, we optionally disable TLS checking and leave CA data + // undefined. + submit = that.config + .addServer( + that.$txtServer.val(), + that.$txtServerName.val(), + that.$txtApiKey.val(), + that.$radioDisableTLSVerification.is(':checked') ); } submit - .then(function(serverId) { + .then(function (serverId) { self.dialogResult.resolve(serverId); self.dialog.modal('hide'); }) - .fail(function(xhr) { + .fail(function (xhr) { addValidationMarkup( false, self.$txtServer, self.getServerError(xhr) ); }) - .always(function() { + .always(function () { self.toggleAddButton(true); }); } @@ -857,24 +857,24 @@ define([ // if it has an API key. This is needed because we previously // didn't save API keys, so there could be a saved server without one. if (selectedEntryId && - !config.getApiKey(config.servers[selectedEntryId].server)) { - showSelectServerDialog( - null, - null, - null, - null, - null, - environmentOptions - ); + !config.getApiKey(config.servers[selectedEntryId].server)) { + showSelectServerDialog( + null, + null, + null, + null, + null, + environmentOptions + ); } else { showSelectServerDialog( - selectedEntryId, - null, - null, - null, - null, - environmentOptions + selectedEntryId, + null, + null, + null, + null, + environmentOptions ); } } @@ -884,14 +884,14 @@ define([ .addClass('pull-right btn btn-danger btn-xs') .attr('type', 'button') .append($('').addClass('fa fa-remove')) - .on('click', function(e) { + .on('click', function (e) { e.preventDefault(); e.stopPropagation(); var $a = $(this).closest('a'); config .removeServer(id) - .then(function() { + .then(function () { $a.remove(); // if active server is removed, disable publish button if ($a.hasClass('active')) { @@ -900,7 +900,7 @@ define([ updateCheckboxStates(); } }) - .fail(function(err) { + .fail(function (err) { // highly unlikely this will ever be triggered debug.error(err); }); @@ -918,7 +918,7 @@ define([ .append(btnRemove); if (!selectedDeployLocation) { - a.on('click', function() { + a.on('click', function () { var $this = $(this); $this .toggleClass('active') @@ -961,7 +961,7 @@ define([ userEditedTitle || config.getNotebookTitle(selectedEntryId); function selectPreviousAppMode() { - appModeChoices.removeClass('active').addClass(function() { + appModeChoices.removeClass('active').addClass(function () { if ($(this).data('appmode') === appMode) { return 'active'; } @@ -1003,7 +1003,7 @@ define([ var appId = updatedEntry && updatedEntry.appId; if (appId) { - config.getApp(selectedEntryId, appId).then(function(app) { + config.getApp(selectedEntryId, appId).then(function (app) { if (app.title) { txtTitle.val(app.title); } @@ -1040,7 +1040,7 @@ define([ // modal is active keyboard_manager: Jupyter.notebook.keyboard_manager, - title: 'Publish to RStudio Connect', + title: 'Publish to Posit Connect', body: [ '', ' ', '
', - ' ', - '
', + ' ', + ' ', '
', ' ', ' ', '
', '
', ' ', - ' ', - '

', + ' ', + '
', '
', ' ', ' ', @@ -1108,7 +1108,7 @@ define([ // triggered when dialog is visible (would be better if it was // post-node creation but before being visible) - open: function() { + open: function () { // The temporary reassignment of `this` is important for the // content manager promise to avoid losing our broader dialog // scope. @@ -1122,16 +1122,16 @@ define([ var isCondaEnvironment = false; publishModal.find('#version-info').html( - 'rsconnect-jupyter server extension version: ' + - rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + - 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + - 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version + 'rsconnect-jupyter server extension version: ' + + rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + + 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + + 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version ); that.fileListItemManager = new FileListItemManager( - $('#file-list-group'), - files, - notebookDirectory, - Jupyter.notebook.notebook_path + $('#file-list-group'), + files, + notebookDirectory, + Jupyter.notebook.notebook_path ); that.fileListItemManager.updateListGroupItems(); disableKeyboardManagerIfNeeded(); @@ -1139,7 +1139,7 @@ define([ // clicking on links in the modal body prevents the default // behavior (i.e. changing location.hash) - publishModal.find('a.modal-body').on('click', function(e) { + publishModal.find('a.modal-body').on('click', function (e) { var target = $(e.target).attr('target'); if (target !== '_rsconnect' && target !== '_blank') { e.preventDefault(); @@ -1147,81 +1147,81 @@ define([ }); // there is no _close_ event so let's improvise - publishModal.on('hide.bs.modal', function() { + publishModal.on('hide.bs.modal', function () { dialogResult.reject('canceled'); }); // add server button - publishModal.find('#rsc-add-server').on('click', function() { + publishModal.find('#rsc-add-server').on('click', function () { publishModal.modal('hide'); showAddServerDialog(config) - .then(function(selectedServerId) { + .then(function (selectedServerId) { showSelectServerDialog(selectedServerId); }) .fail(reselectPreviousServer); }); // add files button - publishModal.find('#rsc-add-files').on('click', function(ev) { + publishModal.find('#rsc-add-files').on('click', function (ev) { that.fileListItemManager.showAddFilesDialog() - .then(function(result) { - files = result; - }); + .then(function (result) { + files = result; + }); // We `preventDefault` because this was causing the form to submit. // Possibly the default button behavior? ev.preventDefault(); }); var condaDetector = 'import os\n' + - 'print("CONDA_PREFIX\t"+str(os.environ.get("CONDA_PREFIX")))\n' + - 'print("CONDA_DEFAULT_ENV\t"+str(os.environ.get("CONDA_DEFAULT_ENV")))\n'; + 'print("CONDA_PREFIX\t"+str(os.environ.get("CONDA_PREFIX")))\n' + + 'print("CONDA_DEFAULT_ENV\t"+str(os.environ.get("CONDA_DEFAULT_ENV")))\n'; // Detect if we're in a conda environment notebookExecuteToPromise(condaDetector) - .then(function (response) { - var output = response.content.text; - var lines = output.split('\n'); - var map = {}; - lines.forEach(function (line) { - var token = line.split('\t'); - map[token[0]] = token[1]; - if(token[1] !== 'None' && token[0] !== '') { - // TODO: enable when ready - // isCondaEnvironment = true; - } - }); - }) - .then(function() { - return ContentsManager.list_contents(notebookDirectory); - }) - .then(function (result) { - function getRequirements(contents) { - verbose('contents:', contents); - for (var index in contents.content) { - if (contents.content[index].name === 'requirements.txt') { - verbose('Found requirements.txt:', contents.content[index]); - hasRequirementsTxt = true; - } else if (contents.content[index].name === 'environment.yml') { - // TODO: Enable when ready - // hasEnvironmentYml = true; - } - } - } - return legacyPromiseHandler(result, getRequirements); - }) - .then(function(result) { - function doPrepare() { - preparePublishRequirementsTxtDialog( - hasRequirementsTxt, - hasEnvironmentYml, - isCondaEnvironment, - environmentOptions - ); + .then(function (response) { + var output = response.content.text; + var lines = output.split('\n'); + var map = {}; + lines.forEach(function (line) { + var token = line.split('\t'); + map[token[0]] = token[1]; + if (token[1] !== 'None' && token[0] !== '') { + // TODO: enable when ready + // isCondaEnvironment = true; } - return legacyPromiseHandler(result, doPrepare); }); + }) + .then(function () { + return ContentsManager.list_contents(notebookDirectory); + }) + .then(function (result) { + function getRequirements(contents) { + verbose('contents:', contents); + for (var index in contents.content) { + if (contents.content[index].name === 'requirements.txt') { + verbose('Found requirements.txt:', contents.content[index]); + hasRequirementsTxt = true; + } else if (contents.content[index].name === 'environment.yml') { + // TODO: Enable when ready + // hasEnvironmentYml = true; + } + } + } + return legacyPromiseHandler(result, getRequirements); + }) + .then(function (result) { + function doPrepare() { + preparePublishRequirementsTxtDialog( + hasRequirementsTxt, + hasEnvironmentYml, + isCondaEnvironment, + environmentOptions + ); + } + return legacyPromiseHandler(result, doPrepare); + }); // generate server list - var serverItems = Object.keys(config.servers).map(function(id) { + var serverItems = Object.keys(config.servers).map(function (id) { var matchingServer = serverId === id; return mkServerItem(id, matchingServer); }); @@ -1258,7 +1258,7 @@ define([ selectPreviousAppMode(); updateCheckboxStates(); - appModeChoices.on('click', function() { + appModeChoices.on('click', function () { appMode = $(this).data('appmode'); $(this) @@ -1280,21 +1280,21 @@ define([ */ function preparePublishRequirementsTxtDialog(hasRequirements, hasEnvironment, isConda, checked) { verbose( - 'Initializing requirements.txt dialog with arguments: [hasRequirements, hasEnvironment, isConda, checked]', - hasRequirements, - hasEnvironment, - isConda, - checked); + 'Initializing requirements.txt dialog with arguments: [hasRequirements, hasEnvironment, isConda, checked]', + hasRequirements, + hasEnvironment, + isConda, + checked); var requirementsTxtContainer = $('#requirements-txt-container'); requirementsTxtContainer.empty(); requirementsTxtContainer.append( - '
' + '
' ); if (!hasRequirements && !hasEnvironment && !isConda) { var message = '

' + - 'A requirements.txt file was ' + - 'not found in the notebook directory.'; + 'A requirements.txt file was ' + + 'not found in the notebook directory.'; // var message = '

' + // 'A requirements.txt or environment.yml file was ' + // 'not found in the notebook directory.'; @@ -1312,15 +1312,15 @@ define([ checked = 'use-existing-conda'; } requirementsTxtContainer.append( - '' + - '
' + '' + + '
' ); } if (hasRequirements) { @@ -1328,15 +1328,15 @@ define([ checked = 'use-existing-pip'; } requirementsTxtContainer.append( - '' + - '
' + '' + + '
' ); } if (isConda) { @@ -1344,19 +1344,19 @@ define([ checked = 'generate-new-conda'; } requirementsTxtContainer.append( - '' + - '
' + '' + + '
' ); } requirementsTxtContainer.append( - '' + - '' + '' + + '' ); } } @@ -1371,7 +1371,7 @@ define([ $box.prop('checked', updatedEntry[id]); } - $box.on('change', function() { + $box.on('change', function () { if (selectedEntryId) { var innerEntry = config.servers[selectedEntryId]; innerEntry[id] = $box.prop('checked'); @@ -1385,7 +1385,7 @@ define([ bindCheckbox('hide_tagged_input'); // setup app mode choices help icon - (function() { + (function () { var msg = 'To create a new deployment, change the title, ' + 'click "Next", select "New location", and then ' + @@ -1404,11 +1404,11 @@ define([ })(); // setup hide input help icon - (function() { - var msg = + (function () { + var msg = 'Hiding input code cells results in rendering only the output of code cells on publication.
Hide Input Documentation'; - - var helpIcon = $( + + var helpIcon = $( [ '', '' @@ -1421,7 +1421,7 @@ define([ $('#hide-input-wrapper').append(helpIcon); })(); - var form = publishModal.find('form').on('submit', function(e) { + var form = publishModal.find('form').on('submit', function (e) { e.preventDefault(); publishModal.find('.form-group').removeClass('has-error'); publishModal.find('.help-block').text(''); @@ -1462,27 +1462,27 @@ define([ var validTitle = txtTitle.val().length >= 3; addValidationMarkup( - validTitle, - txtTitle, - 'Title must be at least 3 characters.' + validTitle, + txtTitle, + 'Title must be at least 3 characters.' ); function togglePublishButton(enabled) { btnPublish - .toggleClass('disabled', !enabled) - .find('i.fa') - .toggleClass('hidden', enabled); + .toggleClass('disabled', !enabled) + .find('i.fa') + .toggleClass('hidden', enabled); } function handleFailure(xhr) { var msg; if ( - typeof xhr === 'string' && - xhr.match(/No module named .*rsconnect.*/) !== null + typeof xhr === 'string' && + xhr.match(/No module named .*rsconnect.*/) !== null ) { msg = 'The rsconnect-python package is not installed in your current notebook kernel.
' + - 'See the
' + - 'Installation Section of the rsconnect-jupyter documentation for more information.'; + 'See the ' + + 'Installation Section of the rsconnect-jupyter documentation for more information.'; } else if (typeof xhr === 'string') { msg = 'An unexpected error occurred: ' + xhr; } else if (xhr.responseJSON) { @@ -1521,7 +1521,7 @@ define([ if (notebookDirectory.length !== 0) { files.forEach(function (file) { normalizedFiles.push( - file.slice(notebookDirectory.length + 1) + file.slice(notebookDirectory.length + 1) ); }); } else { @@ -1529,47 +1529,47 @@ define([ } config - .publishContent( - selectedEntryId, - appId, - txtTitle.val(), - appMode, - normalizedFiles, - condaMode, - forceGenerate - ) - .always(function () { - togglePublishButton(true); - }) - .fail(handleFailure) - .then(function (result) { - notify.set_message( - ' Successfully published content', - // timeout in milliseconds after which the notification - // should disappear - 15 * 1000, - // click handler - function () { - // note: logs_url is included in result.config - window.open(result.config.config_url, ''); - }, - // options - { - class: 'info', - icon: 'fa fa-link', - // tooltip - title: 'Click to open published content on RStudio Connect' - } - ); - publishModal.modal('hide'); - }); + .publishContent( + selectedEntryId, + appId, + txtTitle.val(), + appMode, + normalizedFiles, + condaMode, + forceGenerate + ) + .always(function () { + togglePublishButton(true); + }) + .fail(handleFailure) + .then(function (result) { + notify.set_message( + ' Successfully published content', + // timeout in milliseconds after which the notification + // should disappear + 15 * 1000, + // click handler + function () { + // note: logs_url is included in result.config + window.open(result.config.config_url, ''); + }, + // options + { + class: 'info', + icon: 'fa fa-link', + // tooltip + title: 'Click to open published content on Posit Connect' + } + ); + publishModal.modal('hide'); + }); } if (selectedEntryId !== null && validTitle) { togglePublishButton(false); var currentNotebookTitle = - config.servers[selectedEntryId].notebookTitle; + config.servers[selectedEntryId].notebookTitle; var currentAppId = config.servers[selectedEntryId].appId; // FIXME: Pull this out into a higher scope @@ -1581,41 +1581,41 @@ define([ } else { // no selection, show content selection dialog config - .appSearch( + .appSearch( + selectedEntryId, + txtTitle.val(), + currentAppId + ) + .fail(handleFailure) + .then(function (searchResults) { + if (searchResults.count === 0) { + // no matching content so publish to new endpoint + selectedDeployLocation = DeploymentLocation.New; + publish(); + } else { + // some search results so let user choose an option. + // note: in case of single match we can't be 100% sure + // that the user wants to overwrite the content + publishModal.modal('hide'); + showSearchDialog( + searchResults, selectedEntryId, txtTitle.val(), - currentAppId - ) - .fail(handleFailure) - .then(function (searchResults) { - if (searchResults.count === 0) { - // no matching content so publish to new endpoint - selectedDeployLocation = DeploymentLocation.New; - publish(); - } else { - // some search results so let user choose an option. - // note: in case of single match we can't be 100% sure - // that the user wants to overwrite the content - publishModal.modal('hide'); - showSearchDialog( - searchResults, - selectedEntryId, - txtTitle.val(), - currentAppId, - appMode, - that.fileListItemManager.fileList, - environmentOptions - ); - } - }); + currentAppId, + appMode, + that.fileListItemManager.fileList, + environmentOptions + ); + } + }); } } if (!currentNotebookTitle) { // never been published before (or would have notebook title) debug.info( - 'publishing for the first time, user selected something: ', - !!selectedDeployLocation + 'publishing for the first time, user selected something: ', + !!selectedDeployLocation ); publishOrSearch(); @@ -1624,8 +1624,8 @@ define([ } else if (currentNotebookTitle !== txtTitle.val()) { // published previously but title changed debug.info( - 'title changed, user selected something: ', - !!selectedDeployLocation + 'title changed, user selected something: ', + !!selectedDeployLocation ); publishOrSearch(); @@ -1645,7 +1645,7 @@ define([ '' ); btnPublish.toggleClass('disabled', serverId === null); - btnPublish.on('click', function() { + btnPublish.on('click', function () { form.trigger('submit'); }); publishModal @@ -1728,7 +1728,7 @@ define([ .addClass('radio') .append(label); - label.on('click', function() { + label.on('click', function () { btnDeploy.text('Deploy'); }); return div; @@ -1742,7 +1742,7 @@ define([ .find('#new-location') .text('New location with title "' + title + '"'); - var radios = searchResults.map(function(app) { + var radios = searchResults.map(function (app) { return mkRadio( app.id, app.title || app.name, @@ -1767,7 +1767,7 @@ define([ // allow raw html sanitize: false, - open: function() { + open: function () { disableKeyboardManagerIfNeeded(); var form = searchDialog.find('form'); @@ -1788,14 +1788,14 @@ define([ var selectedLocation = null; // add footer buttons - btnCancel.on('click', function() { + btnCancel.on('click', function () { backToSelectServerDialog(DeploymentLocation.Canceled); }); - btnDeploy.on('click', function() { + btnDeploy.on('click', function () { backToSelectServerDialog(selectedLocation); }); - newLocationRadio.find('label').on('click', function() { + newLocationRadio.find('label').on('click', function () { btnDeploy.text('Next'); }); searchDialog @@ -1803,7 +1803,7 @@ define([ .append(btnCancel) .append(btnDeploy); - form.on('change', 'input', function() { + form.on('change', 'input', function () { selectedLocation = $(this).val(); selectedAppMode = $(this).data('appmode'); btnDeploy.removeClass('disabled'); @@ -1835,20 +1835,20 @@ define([ Jupyter.notebook .save_notebook() .then(config.fetchConfig()) - .then(function() { + .then(function () { if (Object.keys(config.servers).length === 0) { showAddServerDialog(config).then(showSelectServerDialog); } else { showSelectServerDialog( - config.previousServerId, - null, - null, - null, - null, - null); + config.previousServerId, + null, + null, + null, + null, + null); } }) - .catch(function(err) { + .catch(function (err) { // unlikely but possible if we aren't able to save debug.error('Failed to save notebook:', err); Dialog.modal({ @@ -1885,7 +1885,7 @@ define([ body: [ '

', ' Click Create Manifest to create the files needed ', - ' for publishing to RStudio Connect via git:', + ' for publishing to Posit Connect via git:', '

', '
', '

', @@ -1917,7 +1917,7 @@ define([ // allow raw html sanitize: false, - open: function() { + open: function () { // TODO: Use this in the conda support branch var condaMode = false; var forceGenerate = false; @@ -1928,7 +1928,7 @@ define([ var info = dialog.find('#write-manifest-overwrite-info'); if (!hasRequirements && !hasEnvironment) { var html = '

Neither a requirements.txt nor an' + - 'requirements.txt file was found in the notebook directory.

'; + 'requirements.txt file was found in the notebook directory.

'; if (!isConda) { html += '

One will be generated automatically from your current python environment.

'; } else { @@ -1938,7 +1938,7 @@ define([ ' Generate a requirements.txt from the current python environment.' + '
' + '' + + ' />' + '
'; @@ -1948,26 +1948,26 @@ define([ var requirementsContainer = '
'; if (hasRequirements) { requirementsContainer += '' + - '
'; + '
'; } if (hasEnvironment) { requirementsContainer += '' + - '
'; + '
'; } // Always present the option to generate requirements.txt requirementsContainer += '' + - '
'; + '
'; if (isConda) { requirementsContainer += '' + - ''; + ''; } requirementsContainer += '
'; info.html(requirementsContainer); @@ -1975,57 +1975,57 @@ define([ } dialog.find('#version-info').html( - 'rsconnect-jupyter server extension version: ' + - rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + - 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + - 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version + 'rsconnect-jupyter server extension version: ' + + rsconnectVersionInfo.rsconnect_jupyter_server_extension + '
' + + 'rsconnect-jupyter nbextension version: ' + rsconnectVersionInfo.js_version + '
' + + 'rsconnect-python version:' + rsconnectVersionInfo.rsconnect_python_version ); var condaDetector = 'import os\n' + - 'print("CONDA_PREFIX\t"+str(os.environ.get("CONDA_PREFIX")))\n' + - 'print("CONDA_DEFAULT_ENV\t"+str(os.environ.get("CONDA_DEFAULT_ENV")))\n'; + 'print("CONDA_PREFIX\t"+str(os.environ.get("CONDA_PREFIX")))\n' + + 'print("CONDA_DEFAULT_ENV\t"+str(os.environ.get("CONDA_DEFAULT_ENV")))\n'; // Detect if we're in a conda environment notebookExecuteToPromise(condaDetector) - .then(function (response) { - var output = response.content.text; - var lines = output.split('\n'); - var map = {}; - lines.forEach(function (line) { - var token = line.split('\t'); - map[token[0]] = token[1]; - if(token[1] !== 'None' && token[0] !== '') { + .then(function (response) { + var output = response.content.text; + var lines = output.split('\n'); + var map = {}; + lines.forEach(function (line) { + var token = line.split('\t'); + map[token[0]] = token[1]; + if (token[1] !== 'None' && token[0] !== '') { + // TODO: Enable when ready + // isCondaEnvironment = true; + } + }); + }) + .then(function () { + return ContentsManager.list_contents(notebookDirectory); + }) + .then(function (result) { + function getRequirements(contents) { + verbose('contents:', contents); + for (var index in contents.content) { + if (contents.content[index].name === 'requirements.txt') { + verbose('Found requirements.txt:', contents.content[index]); + hasRequirementsTxt = true; + } else if (contents.content[index].name === 'environment.yml') { // TODO: Enable when ready - // isCondaEnvironment = true; + // hasEnvironmentYml = true; } - }); - }) - .then(function() { - return ContentsManager.list_contents(notebookDirectory); - }) - .then(function (result) { - function getRequirements(contents) { - verbose('contents:', contents); - for (var index in contents.content) { - if (contents.content[index].name === 'requirements.txt') { - verbose('Found requirements.txt:', contents.content[index]); - hasRequirementsTxt = true; - } else if (contents.content[index].name === 'environment.yml') { - // TODO: Enable when ready - // hasEnvironmentYml = true; - } - } - } - return legacyPromiseHandler(result, getRequirements); - }) - .then(function (result) { - function doPrepare() { - prepareManifestRequirementsTxtDialog( - hasRequirementsTxt, - hasEnvironmentYml, - isCondaEnvironment); } - return legacyPromiseHandler(result, doPrepare); - }); + } + return legacyPromiseHandler(result, getRequirements); + }) + .then(function (result) { + function doPrepare() { + prepareManifestRequirementsTxtDialog( + hasRequirementsTxt, + hasEnvironmentYml, + isCondaEnvironment); + } + return legacyPromiseHandler(result, doPrepare); + }); var btnCancel = $( '' @@ -2033,7 +2033,7 @@ define([ var btnCreateManifest = $( '' ); - btnCreateManifest.on('click', function() { + btnCreateManifest.on('click', function () { var $status = $('#rsc-manifest-status'); var $spinner = $(''); btnCreateManifest.append($spinner); @@ -2062,8 +2062,8 @@ define([ condaMode = true; } - config.inspectEnvironment(condaMode, forceGenerate).then(function(environment) { - return config.writeManifest(Jupyter.notebook.get_notebook_name(), environment).then(function(response) { + config.inspectEnvironment(condaMode, forceGenerate).then(function (environment) { + return config.writeManifest(Jupyter.notebook.get_notebook_name(), environment).then(function (response) { var createdLinks = response.created.map(makeEditLink); $status.empty(); if (response.created.length > 0) { @@ -2078,32 +2078,32 @@ define([ $status.append(skippedLinks); } }) - .fail(function(response) { - $status.text(response.responseJSON.message); - }); + .fail(function (response) { + $status.text(response.responseJSON.message); + }); }) - .fail(function(response) { - if ( - typeof response === 'string' && - response.match(/No module named .*rsconnect.*/) !== null - ) { - $status.html( - 'The rsconnect-python package is not installed in your current notebook kernel.
' + + .fail(function (response) { + if ( + typeof response === 'string' && + response.match(/No module named .*rsconnect.*/) !== null + ) { + $status.html( + 'The rsconnect-python package is not installed in your current notebook kernel.
' + 'See the ' + 'Installation Section of the rsconnect-jupyter documentation for more information.' - ); - } else if (typeof response === 'string') { - $status.html( + ); + } else if (typeof response === 'string') { + $status.html( 'An unexpected error occurred while inspecting the environment: ' + response - ); - } else { - $status.text(response.responseJSON.message); - } - }) - .always(function() { - $spinner.remove(); - btnCreateManifest.attr('disabled', false); - }); + ); + } else { + $status.text(response.responseJSON.message); + } + }) + .always(function () { + $spinner.remove(); + btnCreateManifest.attr('disabled', false); + }); }); dialog diff --git a/rsconnect_jupyter/static/rsconnect.js b/rsconnect_jupyter/static/rsconnect.js index a831c176..e8f149f4 100644 --- a/rsconnect_jupyter/static/rsconnect.js +++ b/rsconnect_jupyter/static/rsconnect.js @@ -4,269 +4,269 @@ define([ 'jquery', 'base/js/utils', 'base/js/namespace' - ], function ($, Utils, Jupyter) { +], function ($, Utils, Jupyter) { var debug = { - info: function() { + info: function () { var args = [].slice.call(arguments); args.unshift('RSConnect API:'); console.info.apply(null, args); }, - error: function() { + error: function () { var args = [].slice.call(arguments); args.unshift('RSConnect API:'); console.error.apply(null, args); } }; - function RSConnect() { - /* sample value of `Jupyter.notebook.metadata`: - { version: 1, - previousServerId: "abc-def-ghi-jkl", - servers: { - "xyz-uvw": { server: "http://172.0.0.3:3939/", serverName: "dev" }, - "rst-opq": { server: "http://somewhere/connect/", serverName: "prod", notebookTitle:"Meow", appId: 42, appMode: "static" }, - }, - } - */ - - this.previousServerId = null; - this.servers = {}; - this.apiKeys = {}; - this.certificates = {}; - - // TODO more rigorous checking? - var metadata = JSON.parse(JSON.stringify(Jupyter.notebook.metadata)); - if (metadata.rsconnect && metadata.rsconnect.servers) { - // make a copy - this.servers = metadata.rsconnect.servers; - - // if a server is present but no API key, remove it - // since we can't successfully publish there. - for (var serverId in this.servers) { - if (!this.getApiKey(this.servers[serverId].server)) { - delete this.servers[serverId]; - } + function RSConnect() { + /* sample value of `Jupyter.notebook.metadata`: + { version: 1, + previousServerId: "abc-def-ghi-jkl", + servers: { + "xyz-uvw": { server: "http://172.0.0.3:3939/", serverName: "dev" }, + "rst-opq": { server: "http://somewhere/connect/", serverName: "prod", notebookTitle:"Meow", appId: 42, appMode: "static" }, + }, + } + */ + + this.previousServerId = null; + this.servers = {}; + this.apiKeys = {}; + this.certificates = {}; + + // TODO more rigorous checking? + var metadata = JSON.parse(JSON.stringify(Jupyter.notebook.metadata)); + if (metadata.rsconnect && metadata.rsconnect.servers) { + // make a copy + this.servers = metadata.rsconnect.servers; + + // if a server is present but no API key, remove it + // since we can't successfully publish there. + for (var serverId in this.servers) { + if (!this.getApiKey(this.servers[serverId].server)) { + delete this.servers[serverId]; } - // previousServer may have been removed - this.previousServerId = - metadata.rsconnect.previousServerId in this.servers - ? metadata.rsconnect.previousServerId - : null; } - - this.saveNotebookMetadata = this.saveNotebookMetadata.bind(this); - this.updateServer = this.updateServer.bind(this); - this.verifyServer = this.verifyServer.bind(this); - this.addServer = this.addServer.bind(this); - this.fetchConfig = this.fetchConfig.bind(this); - this.saveConfig = this.saveConfig.bind(this); - this.getApiKey = this.getApiKey.bind(this); - this.getApp = this.getApp.bind(this); - this.removeServer = this.removeServer.bind(this); - this.inspectEnvironment = this.inspectEnvironment.bind(this); - this.publishContent = this.publishContent.bind(this); - this.getNotebookTitle = this.getNotebookTitle.bind(this); + // previousServer may have been removed + this.previousServerId = + metadata.rsconnect.previousServerId in this.servers + ? metadata.rsconnect.previousServerId + : null; } - RSConnect.prototype = { - saveNotebookMetadata: function () { - var result = $.Deferred(); - var self = this; - // overwrite metadata (user may have changed it) - Jupyter.notebook.metadata.rsconnect = { - version: 1, - previousServerId: self.previousServerId, - servers: self.servers - }; - - // save_notebook returns a native Promise while the rest of - // the code including parts of Jupyter return jQuery.Deferred - Jupyter.notebook - .save_notebook() - .then(function () { - // notebook is writable - result.resolve(); - }) - .catch(function (e) { - debug.error(e); - // notebook is read-only (server details will likely not be persisted) - result.resolve(); - }); - return result; - }, + this.saveNotebookMetadata = this.saveNotebookMetadata.bind(this); + this.updateServer = this.updateServer.bind(this); + this.verifyServer = this.verifyServer.bind(this); + this.addServer = this.addServer.bind(this); + this.fetchConfig = this.fetchConfig.bind(this); + this.saveConfig = this.saveConfig.bind(this); + this.getApiKey = this.getApiKey.bind(this); + this.getApp = this.getApp.bind(this); + this.removeServer = this.removeServer.bind(this); + this.inspectEnvironment = this.inspectEnvironment.bind(this); + this.publishContent = this.publishContent.bind(this); + this.getNotebookTitle = this.getNotebookTitle.bind(this); + } - verifyServer: function (server, apiKey, disableTLSCheck, certificateData) { - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/verify_server', - method: 'POST', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({ - server_address: server, - api_key: apiKey, - disable_tls_check: disableTLSCheck, - cadata: certificateData - }) + RSConnect.prototype = { + saveNotebookMetadata: function () { + var result = $.Deferred(); + var self = this; + // overwrite metadata (user may have changed it) + Jupyter.notebook.metadata.rsconnect = { + version: 1, + previousServerId: self.previousServerId, + servers: self.servers + }; + + // save_notebook returns a native Promise while the rest of + // the code including parts of Jupyter return jQuery.Deferred + Jupyter.notebook + .save_notebook() + .then(function () { + // notebook is writable + result.resolve(); + }) + .catch(function (e) { + debug.error(e); + // notebook is read-only (server details will likely not be persisted) + result.resolve(); }); - }, - - /** - * addServer adds a new RStudio Connect server to the list of - * available deployment targets - * @param server {String} URL of the server to be added - * @param serverName {String} Friendly name of the server - * @param apiKey {String} API key of the server - * @param disableTLSCheck {Boolean} Don't verify TLS certificates - * @param certificateData {String} TLS Certificate Authority bundle data - * @returns {*} - */ - addServer: function (server, serverName, apiKey, disableTLSCheck, certificateData) { - var self = this; - if (server[server.length - 1] !== '/') { - server += '/'; - } + return result; + }, - // verify the server exists, then save - return this.verifyServer(server, apiKey, disableTLSCheck, certificateData).then(function (data) { - var id = data.address_hash; - self.servers[id] = { - server: data.server_address, - serverName: serverName, - disableTLSCheck: disableTLSCheck - }; - self.apiKeys[server] = apiKey; - self.certificates[server] = certificateData; - return self - .saveConfig() - .then(self.saveNotebookMetadata) - .then(function () { - return id; - }); - }); - }, + verifyServer: function (server, apiKey, disableTLSCheck, certificateData) { + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/verify_server', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify({ + server_address: server, + api_key: apiKey, + disable_tls_check: disableTLSCheck, + cadata: certificateData + }) + }); + }, - getApiKey: function(server) { - return this.apiKeys[server]; - }, + /** + * addServer adds a new Posit Connect server to the list of + * available deployment targets + * @param server {String} URL of the server to be added + * @param serverName {String} Friendly name of the server + * @param apiKey {String} API key of the server + * @param disableTLSCheck {Boolean} Don't verify TLS certificates + * @param certificateData {String} TLS Certificate Authority bundle data + * @returns {*} + */ + addServer: function (server, serverName, apiKey, disableTLSCheck, certificateData) { + var self = this; + if (server[server.length - 1] !== '/') { + server += '/'; + } - getCAData: function(server) { - return this.certificates[server]; - }, + // verify the server exists, then save + return this.verifyServer(server, apiKey, disableTLSCheck, certificateData).then(function (data) { + var id = data.address_hash; + self.servers[id] = { + server: data.server_address, + serverName: serverName, + disableTLSCheck: disableTLSCheck + }; + self.apiKeys[server] = apiKey; + self.certificates[server] = certificateData; + return self + .saveConfig() + .then(self.saveNotebookMetadata) + .then(function () { + return id; + }); + }); + }, - getApp: function (serverId, appId) { - var self = this; - var entry = this.servers[serverId]; + getApiKey: function (server) { + return this.apiKeys[server]; + }, - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_get', - method: 'POST', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({ - app_id: appId, - server_address: entry.server, - api_key: self.getApiKey(entry.server), - disable_tls_check: entry.disableTLSCheck || false, - cadata: self.getCAData(entry.server) - }) - }); - }, + getCAData: function (server) { + return this.certificates[server]; + }, - saveConfig: function () { - var self = this; - var toSave = {}; + getApp: function (serverId, appId) { + var self = this; + var entry = this.servers[serverId]; + + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_get', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify({ + app_id: appId, + server_address: entry.server, + api_key: self.getApiKey(entry.server), + disable_tls_check: entry.disableTLSCheck || false, + cadata: self.getCAData(entry.server) + }) + }); + }, - for (var serverId in this.servers) { - var src = this.servers[serverId]; + saveConfig: function () { + var self = this; + var toSave = {}; - toSave[serverId] = { - server: src.server, - serverName: src.serverName, - apiKey: self.getApiKey(src.server), - disableTLSCheck: src.disableTLSCheck, - cadata: self.getCAData(src.server) - }; - } - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'api/config/rsconnect_jupyter', - method: 'PUT', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify(toSave) - }); - }, + for (var serverId in this.servers) { + var src = this.servers[serverId]; - fetchConfig: function () { - var self = this; - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'api/config/rsconnect_jupyter', - method: 'GET' - }).then(function (data) { - if (!self.servers) { - self.servers = {}; - } - var didDelete = false; - for (var serverId in data) { - if (!data[serverId].apiKey) { - var deleted = data[serverId]; - delete data[serverId]; - debug.info('deleted server because it had no API key: '+JSON.stringify(deleted)); - didDelete = true; - } else { - // Split out API keys so they're not saved into the notebook metadata. - var entry = data[serverId]; - self.apiKeys[entry.server] = entry.apiKey; - delete entry.apiKey; - self.certificates[entry.server] = entry.cadata; - delete entry.cadata; - - if (!self.servers[serverId]) { - self.servers[serverId] = entry; - } + toSave[serverId] = { + server: src.server, + serverName: src.serverName, + apiKey: self.getApiKey(src.server), + disableTLSCheck: src.disableTLSCheck, + cadata: self.getCAData(src.server) + }; + } + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'api/config/rsconnect_jupyter', + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify(toSave) + }); + }, + + fetchConfig: function () { + var self = this; + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'api/config/rsconnect_jupyter', + method: 'GET' + }).then(function (data) { + if (!self.servers) { + self.servers = {}; + } + var didDelete = false; + for (var serverId in data) { + if (!data[serverId].apiKey) { + var deleted = data[serverId]; + delete data[serverId]; + debug.info('deleted server because it had no API key: ' + JSON.stringify(deleted)); + didDelete = true; + } else { + // Split out API keys so they're not saved into the notebook metadata. + var entry = data[serverId]; + self.apiKeys[entry.server] = entry.apiKey; + delete entry.apiKey; + self.certificates[entry.server] = entry.cadata; + delete entry.cadata; + + if (!self.servers[serverId]) { + self.servers[serverId] = entry; } } - if (didDelete) { - self.saveConfig().then(self.saveNotebookMetadata); - } - debug.info('fetched config:', data); - }); - }, - - updateServer: function (id, appId, notebookTitle, appMode, configUrl) { - this.servers[id].appId = appId; - this.servers[id].notebookTitle = notebookTitle; - this.servers[id].appMode = appMode; - this.servers[id].configUrl = configUrl; - return this.saveNotebookMetadata(); - }, - - removeServer: function (id) { - delete this.servers[id]; - return this.saveConfig().then(this.saveNotebookMetadata); - }, - - getRunningPythonPath: function () { - var cmd = 'import sys; print(sys.executable)'; - var pythonPath = 'python'; - var result = $.Deferred(); - - function handle_output(message) { + } + if (didDelete) { + self.saveConfig().then(self.saveNotebookMetadata); + } + debug.info('fetched config:', data); + }); + }, + + updateServer: function (id, appId, notebookTitle, appMode, configUrl) { + this.servers[id].appId = appId; + this.servers[id].notebookTitle = notebookTitle; + this.servers[id].appMode = appMode; + this.servers[id].configUrl = configUrl; + return this.saveNotebookMetadata(); + }, + + removeServer: function (id) { + delete this.servers[id]; + return this.saveConfig().then(this.saveNotebookMetadata); + }, + + getRunningPythonPath: function () { + var cmd = 'import sys; print(sys.executable)'; + var pythonPath = 'python'; + var result = $.Deferred(); + + function handle_output(message) { try { - pythonPath = message.content.text.trim(); - console.log('Using python: ' + pythonPath); - result.resolve(pythonPath); - } catch(err) { - result.reject(err); + pythonPath = message.content.text.trim(); + console.log('Using python: ' + pythonPath); + result.resolve(pythonPath); + } catch (err) { + result.reject(err); } - } - - var callbacks = { - iopub: { - output: handle_output - } - }; - Jupyter.notebook.kernel.execute(cmd, callbacks); - return result; - }, - - inspectEnvironment: function (condaMode, forceGenerate) { - return this.getRunningPythonPath().then(function(pythonPath) { + } + + var callbacks = { + iopub: { + output: handle_output + } + }; + Jupyter.notebook.kernel.execute(cmd, callbacks); + return result; + }, + + inspectEnvironment: function (condaMode, forceGenerate) { + return this.getRunningPythonPath().then(function (pythonPath) { try { var flags = ''; if (condaMode || forceGenerate) { @@ -318,236 +318,236 @@ define([ Jupyter.notebook.kernel.execute(cmd, callbacks); return result; - }); - }, - - writeManifest: function(notebookTitle, environment) { - var self = this; - var notebookPath = Utils.encode_uri_components( - Jupyter.notebook.notebook_path - ); - - var data = { - notebook_path: notebookPath, - notebook_name: self.getNotebookName(notebookTitle), - environment: environment - }; - - var xhr = Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/write_manifest', - method: 'POST', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify(data) - }); - return xhr; - }, - - /** - * publishContent makes the call to the rsconnect-jupyter backend that will deploy the content - * @param serverId {string} the server identifier - * @param appId {number} the numeric app ID - * @param notebookTitle {string} Title of the notebook to be passed as name/title - * @param appMode {'static'|'jupyter-static'} App mode to deploy. 'static' is not rendered. - * @param files {Array} paths to files to deploy. - * @param condaMode {boolean} whether or not to use conda to build an `environment.yml`. - * @param forceGenerate {boolean} whether to force `requirements.txt` to be generated even if one exists. - * @returns {PromiseLike|*|PromiseLike|Promise} - */ - publishContent: function (serverId, appId, notebookTitle, appMode, files, condaMode, forceGenerate) { - var self = this; - var notebookPath = Utils.encode_uri_components( - Jupyter.notebook.notebook_path - ); - - var entry = this.servers[serverId]; - - var $log = $('#rsc-log').attr('hidden', null); - $log.text('Deploying...\n'); - - function getLogs(deployResult) { - function inner(lastStatus) { - lastStatus = lastStatus || null; - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/get_log', - method: 'POST', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({ - server_address: entry.server, - api_key: self.getApiKey(entry.server), - task_id: deployResult['task_id'], - last_status: lastStatus, - cookies: deployResult.cookies || [], - disable_tls_check: entry.disableTLSCheck || false, - cadata: self.getCAData(entry.server) - - }) - }).then(function (result) { - if (result['last_status'] !== lastStatus) { - lastStatus = result['lastStatus']; - var output = result['status'].join('\n'); - - var logElem = $log.get(0); - var oldScroll = logElem.scrollTop; - var oldMaxScroll = logElem.scrollHeight - logElem.clientHeight; - $log.text(output); - - if (oldScroll >= oldMaxScroll - 1) { - // scroll to new bottom position - $log.scrollTop(logElem.scrollHeight); - } - } - if (result['finished']) { - if (result['code'] !== 0) { - var msg = 'Failed to deploy successfully: ' + result['error']; - return $.Deferred().reject({responseJSON: {message: msg}}); - } - debug.info('logs:', result['status'].join('\n')); - return $.Deferred().resolve(deployResult['app_id']); - } - var next = $.Deferred(); - setTimeout(function () { - return inner(lastStatus).then(next.resolve); - }, 1000); - return next; - }); - } + }); + }, - return inner(); - } + writeManifest: function (notebookTitle, environment) { + var self = this; + var notebookPath = Utils.encode_uri_components( + Jupyter.notebook.notebook_path + ); + + var data = { + notebook_path: notebookPath, + notebook_name: self.getNotebookName(notebookTitle), + environment: environment + }; + + var xhr = Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/write_manifest', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify(data) + }); + return xhr; + }, - function appConfig(receivedAppId) { + /** + * publishContent makes the call to the rsconnect-jupyter backend that will deploy the content + * @param serverId {string} the server identifier + * @param appId {number} the numeric app ID + * @param notebookTitle {string} Title of the notebook to be passed as name/title + * @param appMode {'static'|'jupyter-static'} App mode to deploy. 'static' is not rendered. + * @param files {Array} paths to files to deploy. + * @param condaMode {boolean} whether or not to use conda to build an `environment.yml`. + * @param forceGenerate {boolean} whether to force `requirements.txt` to be generated even if one exists. + * @returns {PromiseLike|*|PromiseLike|Promise} + */ + publishContent: function (serverId, appId, notebookTitle, appMode, files, condaMode, forceGenerate) { + var self = this; + var notebookPath = Utils.encode_uri_components( + Jupyter.notebook.notebook_path + ); + + var entry = this.servers[serverId]; + + var $log = $('#rsc-log').attr('hidden', null); + $log.text('Deploying...\n'); + + function getLogs(deployResult) { + function inner(lastStatus) { + lastStatus = lastStatus || null; return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_config', + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/get_log', method: 'POST', - headers: {'Content-Type': 'application/json'}, + headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ server_address: entry.server, api_key: self.getApiKey(entry.server), - app_id: receivedAppId, + task_id: deployResult['task_id'], + last_status: lastStatus, + cookies: deployResult.cookies || [], disable_tls_check: entry.disableTLSCheck || false, cadata: self.getCAData(entry.server) - }) - }).then(function (config) { - return { - appId: receivedAppId, - config: config - }; - }); - } - - function deploy(environment) { - var data = { - notebook_path: notebookPath, - notebook_title: notebookTitle, - notebook_name: self.getNotebookName(notebookTitle), - app_id: appId, - server_address: entry.server, - api_key: self.getApiKey(entry.server), - app_mode: appMode, - environment: environment, - files: files, - disable_tls_check: entry.disableTLSCheck || false, - cadata: self.getCAData(entry.server), - hide_all_input: entry.hide_all_input, - hide_tagged_input: entry.hide_tagged_input - }; - var xhr = Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/deploy', - method: 'POST', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify(data) - }) - .then(getLogs) - .then(appConfig); - - // update server with title and appId and set recently selected - // server - xhr.then(function (configResult) { - self.previousServerId = serverId; - return self.updateServer( - serverId, - configResult.appId, - notebookTitle, - appMode, - configResult.config.config_url - ); + }) + }).then(function (result) { + if (result['last_status'] !== lastStatus) { + lastStatus = result['lastStatus']; + var output = result['status'].join('\n'); + + var logElem = $log.get(0); + var oldScroll = logElem.scrollTop; + var oldMaxScroll = logElem.scrollHeight - logElem.clientHeight; + $log.text(output); + + if (oldScroll >= oldMaxScroll - 1) { + // scroll to new bottom position + $log.scrollTop(logElem.scrollHeight); + } + } + if (result['finished']) { + if (result['code'] !== 0) { + var msg = 'Failed to deploy successfully: ' + result['error']; + return $.Deferred().reject({ responseJSON: { message: msg } }); + } + debug.info('logs:', result['status'].join('\n')); + return $.Deferred().resolve(deployResult['app_id']); + } + var next = $.Deferred(); + setTimeout(function () { + return inner(lastStatus).then(next.resolve); + }, 1000); + return next; }); - - return xhr; - } - - if (appMode === 'jupyter-static') { - return this.inspectEnvironment(condaMode, forceGenerate).then(deploy); - } else { - return deploy(null); } - }, - appSearch: function (serverId, notebookTitle, appId) { - var self = this; - var entry = this.servers[serverId]; + return inner(); + } + function appConfig(receivedAppId) { return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_search', + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_config', method: 'POST', - headers: {'Content-Type': 'application/json'}, + headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ - notebook_title: notebookTitle, - app_id: appId, server_address: entry.server, api_key: self.getApiKey(entry.server), + app_id: receivedAppId, disable_tls_check: entry.disableTLSCheck || false, cadata: self.getCAData(entry.server) }) + }).then(function (config) { + return { + appId: receivedAppId, + config: config + }; }); - }, - - getNotebookName: function (title) { - // slugify title and make it unique, also ensuring that it - // fits in the 64 character limit after the timestamp is appended. - return ( - title.replace(/[^a-zA-Z0-9_-]+/g, '_').substring(0, 50) + - '-' + - Date.now() - ); - }, - - getNotebookTitle: function (id) { - if (id) { - // it's possible the entry is gone - var e = this.servers[id]; - // if title was saved then return it - if (e && e.notebookTitle) { - return e.notebookTitle; - } - } - // default title - return Jupyter.notebook.get_notebook_name(); - }, + } - getVersionInfo: function () { - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/plugin_version' - }) - .then(function (version_info) { - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'nbextensions/rsconnect_jupyter/version.json' - }) - .then(function (js_version_info) { - version_info.js_version = js_version_info.version; - return version_info; - }); - }); - }, + function deploy(environment) { + var data = { + notebook_path: notebookPath, + notebook_title: notebookTitle, + notebook_name: self.getNotebookName(notebookTitle), + app_id: appId, + server_address: entry.server, + api_key: self.getApiKey(entry.server), + app_mode: appMode, + environment: environment, + files: files, + disable_tls_check: entry.disableTLSCheck || false, + cadata: self.getCAData(entry.server), + hide_all_input: entry.hide_all_input, + hide_tagged_input: entry.hide_tagged_input + }; - getPythonSettings: function() { - return Utils.ajax({ - url: Jupyter.notebook.base_url + 'rsconnect_jupyter/get_python_settings' + var xhr = Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/deploy', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify(data) + }) + .then(getLogs) + .then(appConfig); + + // update server with title and appId and set recently selected + // server + xhr.then(function (configResult) { + self.previousServerId = serverId; + return self.updateServer( + serverId, + configResult.appId, + notebookTitle, + appMode, + configResult.config.config_url + ); }); + + return xhr; } - }; - return RSConnect; - } + if (appMode === 'jupyter-static') { + return this.inspectEnvironment(condaMode, forceGenerate).then(deploy); + } else { + return deploy(null); + } + }, + + appSearch: function (serverId, notebookTitle, appId) { + var self = this; + var entry = this.servers[serverId]; + + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/app_search', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify({ + notebook_title: notebookTitle, + app_id: appId, + server_address: entry.server, + api_key: self.getApiKey(entry.server), + disable_tls_check: entry.disableTLSCheck || false, + cadata: self.getCAData(entry.server) + }) + }); + }, + + getNotebookName: function (title) { + // slugify title and make it unique, also ensuring that it + // fits in the 64 character limit after the timestamp is appended. + return ( + title.replace(/[^a-zA-Z0-9_-]+/g, '_').substring(0, 50) + + '-' + + Date.now() + ); + }, + + getNotebookTitle: function (id) { + if (id) { + // it's possible the entry is gone + var e = this.servers[id]; + // if title was saved then return it + if (e && e.notebookTitle) { + return e.notebookTitle; + } + } + // default title + return Jupyter.notebook.get_notebook_name(); + }, + + getVersionInfo: function () { + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/plugin_version' + }) + .then(function (version_info) { + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'nbextensions/rsconnect_jupyter/version.json' + }) + .then(function (js_version_info) { + version_info.js_version = js_version_info.version; + return version_info; + }); + }); + }, + + getPythonSettings: function () { + return Utils.ajax({ + url: Jupyter.notebook.base_url + 'rsconnect_jupyter/get_python_settings' + }); + } + }; + + return RSConnect; +} ); diff --git a/selenium/docker/mock-connect/Dockerfile b/selenium/docker/mock-connect/Dockerfile index e53723d6..a381249b 100644 --- a/selenium/docker/mock-connect/Dockerfile +++ b/selenium/docker/mock-connect/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.6.6-alpine -MAINTAINER RStudio Quality +MAINTAINER Posit Quality WORKDIR /opt/work COPY requirements.txt requirements.txt diff --git a/selenium/t/pages/main_toolbar.py b/selenium/t/pages/main_toolbar.py index ce879bfb..7c4819e1 100644 --- a/selenium/t/pages/main_toolbar.py +++ b/selenium/t/pages/main_toolbar.py @@ -4,7 +4,7 @@ class MainToolBar(object): @property def rsconnect_dropdown(self): - return s(by.css("[title='Publish to RStudio Connect']")) + return s(by.css("[title='Publish to Posit Connect']")) @property def rsconnect_publish(self): diff --git a/setup.cfg b/setup.cfg index 18794109..48d2f000 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,8 +3,8 @@ universal = 1 [metadata] author = Jonathan Curran -author_email = jonathan.curran@rstudio.com -description = Jupyter Notebook integration with RStudio Connect +author_email = jonathan.curran@posit.co +description = Jupyter Notebook integration with Posit Connect license = GPL-2.0 license_file = LICENSE.md long_description = file:README.md diff --git a/tools/yarn/Dockerfile b/tools/yarn/Dockerfile index 7ece779d..8ff07b5e 100644 --- a/tools/yarn/Dockerfile +++ b/tools/yarn/Dockerfile @@ -1,5 +1,5 @@ FROM node:11.10.1 -LABEL maintainer="RStudio Connect " +LABEL maintainer="Posit Connect " ARG NB_UID ARG NB_GID