Draft

External Content

External content provides another way for publishers to deploy apps, reports, and dashboards.

To deploy external content, package your content as a tarball (*.tar.gz or *.tgz) and then upload it to Connect. Once deployed, find external content in the Content List. The deployed content respects the same access controls as other Connect content.

Prerequisites

  • You must have Publisher or Administrator permissions to add external content to a Connect instance.

Example: Simple external content to list the last ten active users

What if you want to list the last 10 users to log in to your Connect instance? Connect doesn’t directly support this feature. However, creating a Quarto document that uses the Connect public API to list the last 10 users is straightforward. You can create this document and add it as a piece of content to Connect.

Preparing the external content

  1. Copy the following documents into your project directory:
---
title: "Last 10 Active Users"
---

```{python}
#| echo: false

from IPython.display import Markdown
from datetime import *
from posit.connect import Client
from tabulate import tabulate
import json
import os
import pytz

CONNECT_API_KEY = os.getenv('CONNECT_API_KEY')
CONNECT_SERVER = os.getenv('CONNECT_SERVER')

def sort_by_login(user):
  return user['active_time']

def find_users():
  with Client(
  api_key=CONNECT_API_KEY, url=CONNECT_SERVER
  ) as client:
    find_results = client.users.find()
    find_results.sort(key=sort_by_login, reverse=True)
    return find_results


def last_active_time(dt):
  utc_time = pytz.utc.localize(dt)
  local_timezone = pytz.timezone('US/Eastern')
  local_time = utc_time.astimezone(local_timezone)
  # return local_time.strftime("%Y-%m-%d %-I:%M %p")
  return local_time.strftime("%c")

recent_users = find_users()
display_users = []
for user in recent_users[:10]:
  local_time = datetime.strptime(user.active_time, '%Y-%m-%dT%H:%M:%SZ')

  display_users.append({
    'email': user.email,
    'name': f'{user.first_name} {user.last_name}',
    'username': user.username,
    'last_active': last_active_time(local_time)
  })

rows = [x.values() for x in display_users]

Markdown(tabulate( rows, headers=["Email", "Name", "Username", "Last Active"]))
```
{
  "version": 1,
  "locale": "en_US.UTF-8",
  "metadata": {
    "appmode": "quarto-static"
  },
  "quarto": {
    "version": "1.5.57",
    "engines": [
      "jupyter"
    ]
  },
  "python": {
    "version": "3.11.9",
    "package_manager": {
      "name": "pip",
      "version": "24.2",
      "package_file": "requirements.txt"
    }
  },
  "files": {
    "requirements.txt": {
      "checksum": "ff6c426754245786e5d5620c948ac68a"
    },
    "RecentUsers.qmd": {
      "checksum": "f2a7d7999f2cb1aac7d3925bbdc338c7"
    }
  },
  "extension": {
    "name": "connect-recent-users",
    "title": "Last 10 Active Users",
    "description": "Connect Extension: List last 10 active users",
    "access_type": "logged_in"
  }
}
# requirements.txt generated by rsconnect-python on 2024-09-10 18:51:57.198908
aiofiles==23.2.1
altair==5.2.0
aniso8601==9.0.1
annotated-types==0.7.0
anyio==3.7.1
anywidget==0.9.2
appdirs==1.4.4
appnope==0.1.3
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
asgiref==3.8.1
asttokens==2.4.1
attrs==23.1.0
beautifulsoup4==4.12.2
bleach==6.1.0
blinker==1.8.2
bokeh==3.4.0
build==1.2.1
cachetools==5.3.3
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
click==8.1.7
comm==0.2.0
commonmark==0.9.1
contourpy==1.2.1
cryptography==43.0.1
debugpy==1.8.0
decorator==5.1.1
defusedxml==0.7.1
distlib==0.3.7
entrypoints==0.4
executing==2.0.1
fastapi==0.103.1
fastapitableau==1.2.0
fastjsonschema==2.19.0
filelock==3.12.2
Flask==3.0.2
flask-restx==1.3.0
fqdn==1.5.1
fsspec==2024.5.0
fuzzbucket-client==0.12.2
gitdb==4.0.11
GitPython==3.1.43
h11==0.14.0
htmltools==0.5.2
httpcore==1.0.5
httpx==0.27.0
humanize==4.9.0
idna==3.6
importlib_metadata==7.1.0
importlib_resources==6.4.0
ipykernel==6.27.1
ipython==8.18.0
ipython-genutils==0.2.0
ipywidgets==8.1.1
isoduration==20.11.0
itsdangerous==2.2.0
jedi==0.19.1
Jinja2==3.1.2
joblib==1.3.2
jsonpointer==2.4
jsonschema==4.20.0
jsonschema-specifications==2023.11.2
jupyter==1.0.0
jupyter-console==6.6.3
jupyter-events==0.9.0
jupyter_client==7.4.9
jupyter_core==5.5.0
jupyter_server==2.12.1
jupyter_server_terminals==0.5.0
jupyterlab-widgets==3.0.9
jupyterlab_pygments==0.3.0
linkify-it-py==2.0.3
markdown-it-py==3.0.0
MarkupSafe==2.1.3
matplotlib-inline==0.1.6
mdit-py-plugins==0.4.1
mdurl==0.1.2
mistune==3.0.2
nbclassic==1.0.0
nbclient==0.9.0
nbconvert==7.12.0
nbformat==5.9.2
nest-asyncio==1.5.8
notebook==6.5.6
notebook_shim==0.2.3
numpy==1.26.4
overrides==7.4.0
packaging==23.2
panda==0.3.1
pandas==2.2.1
pandocfilters==1.5.0
parso==0.8.3
pexpect==4.9.0
pillow==10.3.0
pins==0.8.4
pip-tools==7.4.1
pipenv==2023.7.23
platformdirs==3.10.0
plotly==5.22.0
posit-sdk==0.4.0
prometheus-client==0.19.0
prompt-toolkit==3.0.36
protobuf==4.25.3
psutil==5.9.6
psygnal==0.11.1
ptyprocess==0.7.0
pure-eval==0.2.2
pyarrow==16.1.0
pycparser==2.22
pydantic==2.7.2
pydantic_core==2.18.3
pydeck==0.9.1
Pygments==2.17.2
PyJWT==2.8.0
pyproject_hooks==1.1.0
python-dateutil==2.8.2
python-dotenv==1.0.1
python-json-logger==2.0.7
python-multipart==0.0.9
pytz==2023.3.post1
PyYAML==6.0.1
pyzmq==24.0.1
qtconsole==5.5.1
QtPy==2.4.1
questionary==2.0.1
referencing==0.32.0
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rich==13.7.1
rpds-py==0.13.2
scikit-learn==1.4.1.post1
scipy==1.13.1
semver==2.13.0
Send2Trash==1.8.2
shiny==1.0.0
shinyswatch==0.7.0
shinywidgets==0.3.1
six==1.16.0
smmap==5.0.1
sniffio==1.3.0
soupsieve==2.5
stack-data==0.6.3
starlette==0.27.0
streamlit==1.31.1
tabulate==0.9.0
tenacity==8.3.0
terminado==0.18.0
threadpoolctl==3.5.0
tinycss2==1.2.1
toml==0.10.2
toolz==0.12.1
tornado==6.4
traitlets==5.14.0
types-python-dateutil==2.8.19.14
typing_extensions==4.12.0
tzdata==2023.3
tzlocal==5.2
uc-micro-py==1.0.3
uri-template==1.3.0
urllib3==2.1.0
uvicorn==0.30.0
validators==0.28.3
vetiver==0.2.4
virtualenv==20.24.2
virtualenv-clone==0.5.7
watchfiles==0.22.0
wcwidth==0.2.12
webcolors==1.13
webencodings==0.5.1
websocket-client==1.7.0
websockets==12.0
Werkzeug==3.0.3
widgetsnbextension==4.0.9
xxhash==3.4.1
xyzservices==2024.4.0
zipp==3.19.1
Note

The files, requirements.txt and manifest.json, are included here to simplify the tutorial. If you are creating your own extension, you can generate these files with the commmand-line interface by calling rsconnect write-manifest or by calling rsconnect::writeManifest() in an R console.

The extension section of the manifest.json file needs to be manually written to include the name, title, description, and access_type fields. The access_type field can be set to either acl, logged_in, or all.

  1. Create a tarball of the documents:
> tar -czvf connect-recent-users.tar.gz RecentUsers.qmd manifest.json requirements.txt

Adding the content to Connect

  1. Visit the Connect home page.

  2. Navigate to the Gallery page by clicking the Connect Gallery button.

  3. Click the Deploy External Content button.

  4. In the Deploy External Content modal, select the Bundle tab.

    • If you are adding content that is publicly hosted, you can use the URL tab to add using its URL.

    Deploy External Content modal and button

  5. Drag and drop the connect-recent-users.tar.gz file into the drop zone or click it to select the file from your local file system.

  6. Click the Add button to upload the bundle to Connect.

  7. The content is uploaded and published to your Connect instance. This might take several minutes.

    Publishing the External Content

  8. Once published, you can Open the content in Connect.

    Publishing complete

Viewing the content

  • Once published, external content displays in the Content list.

    External Content in Content list

  • Clicking the content opens it in Connect.

    External Content view

  • External content is shared with all users by default. However, it can be restricted and managed just like any other content published on Connect.