Extensions
Extensions provide a way for publishers to deploy content that adds features to Connect without requiring server upgrades. These features might be interfaces to capabilities which are only currently supported via the Connect Server API (e.g. using an application to enable bulk content deletion.) To deploy an extension, you must package your content as a tarball (*.tar.gz
or *.tgz
) and then upload it to Connect. Once deployed, extensions can be found in the Content List and will respect the same access controls as other Connect content.
Prerequisites
- You must have Publisher or Administrator permissions to add an extension to a Connect instance.
Example: A simple extension to list the last ten active users
What if you wanted to list the last ten users to log in to your Connect instance? Connect does not support this feature directly. However, creating a Quarto document that uses the Connect public API to list the last ten users is straightforward. We can create this document and add it as an extension to Connect.
Preparing the Extension Contents
- 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"
}
}
}
name = "connect-extension-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
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.
- Create a tarball of the documents:
> tar -czvf connect-extension-recent-users.tar.gz RecentUsers.qmd manifest.json connect-extension.toml requirements.txt
Adding the Extension to Connect
Visit the Connect home page
Click the Publish button and select Add an Extension from the dropdown menu.
In the Add Extension menu, select the Bundle tab.
- if you are adding an extension that is publicly hosted, you can use the URL tab to add the extension by its URL.
Drag and drop the
connect-extension-recent-users.tar.gz
file into the drop zone or click the Browse button to select the file from your local file system.Click the Add Extension button to upload the extension to Connect.
The extension will be uploaded and published to your Connect instance. This might take a few minutes.
Once the extension is published, you can Open it in Connect.
Viewing the Extension
Once published, extensions appear in the Content list with a badge indicating that they are extensions.
Clicking on the extension opens it in Connect.
The extension is shared with all users by default. However, it can be restricted and managed just like any other content published on Connect.