Skip to content

Commit d5dcc6f

Browse files
toph-allenjonkeane
andauthored
fix: better first-run experience for publisher-command-center extensions (#99)
* initial changes * try rendering only warning if unauthorized * move auth check to index * remove .vscode * update manifest.json * Some more words, fewer tomls --------- Co-authored-by: Jonathan Keane <[email protected]>
1 parent 7c9eca5 commit d5dcc6f

File tree

8 files changed

+112
-71
lines changed

8 files changed

+112
-71
lines changed

extensions/publisher-command-center/.vscode/extensions.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

extensions/publisher-command-center/.vscode/settings.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

extensions/publisher-command-center/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ To build the frontend for production:
5858
npm run build
5959
```
6060

61+
### Deploying to Connect
62+
63+
When deploying, include `app.py`, `requirements.txt`, and the contents of the `dist` directory created by `npm run build`.
64+
6165
### Publishing on Connect
6266

6367
This extension utilizes the [Connect API OAuth Integration](https://docs.posit.co/connect/admin/integrations/oauth-integrations/connect/index.html#create-connect-api-integration-in-posit-connect).

extensions/publisher-command-center/app.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from fastapi import FastAPI, Header
44
from fastapi.staticfiles import StaticFiles
55
from posit import connect
6+
from posit.connect.errors import ClientError
7+
import os
68

79
from cachetools import TTLCache, cached
810

@@ -13,6 +15,24 @@
1315
# Create cache with TTL=1hour and unlimited size
1416
client_cache = TTLCache(maxsize=float("inf"), ttl=3600)
1517

18+
@app.get("/api/auth-status")
19+
async def auth_status(posit_connect_user_session_token: str = Header(None)):
20+
"""
21+
If running on Connect, attempt to build a visitor client.
22+
If that raises the 212 error (no OAuth integration), return authorized=False.
23+
"""
24+
25+
if os.getenv("RSTUDIO_PRODUCT") == "CONNECT":
26+
if not posit_connect_user_session_token:
27+
return {"authorized": False}
28+
try:
29+
get_visitor_client(posit_connect_user_session_token)
30+
except ClientError as err:
31+
if err.error_code == 212:
32+
return {"authorized": False}
33+
raise # Other errors bubble up
34+
35+
return {"authorized": True}
1636

1737
@cached(client_cache)
1838
def get_visitor_client(token: str | None) -> connect.Client:

extensions/publisher-command-center/connect-extension.toml

Lines changed: 0 additions & 4 deletions
This file was deleted.

extensions/publisher-command-center/manifest.json

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,56 +18,56 @@
1818
},
1919
"packages": {},
2020
"files": {
21-
"app.py": {
22-
"checksum": "90c562e6a9f2c2e9fc44c0720bf174d8"
23-
},
24-
"dist/assets/fa-brands-400.808443ae.ttf": {
25-
"checksum": "15d54d142da2f2d6f2e90ed1d55121af"
26-
},
27-
"dist/assets/fa-brands-400.d7236a19.woff2": {
28-
"checksum": "cbcf42b2e9228a8f5bad42717d8a88db"
29-
},
30-
"dist/assets/fa-regular-400.54cf6086.ttf": {
31-
"checksum": "262525e2081311609d1fdab966c82bfc"
32-
},
33-
"dist/assets/fa-regular-400.e3456d12.woff2": {
34-
"checksum": "89672701a5874b80be27649e0494e354"
35-
},
36-
"dist/assets/fa-solid-900.aa759986.woff2": {
37-
"checksum": "4a6591ab5460ae5cbff1ecbd6e52193a"
38-
},
39-
"dist/assets/fa-solid-900.d2f05935.ttf": {
40-
"checksum": "269f971cec0d5dc864fe9ae080b19e23"
41-
},
42-
"dist/assets/fa-v4compatibility.0ce9033c.woff2": {
43-
"checksum": "a772da7bff216cd36214bc44165bba3e"
44-
},
45-
"dist/assets/fa-v4compatibility.30f6abf6.ttf": {
46-
"checksum": "4ed293ceaca9b5b2d9cd74a477963fae"
47-
},
48-
"dist/assets/index.0c2ac2eb.css": {
49-
"checksum": "96fb5a8d93644a29ece62d52354fbc73"
50-
},
51-
"dist/assets/index.13d67bec.js": {
52-
"checksum": "d2b59ba15811b99ddd6067403d3ab38b"
53-
},
54-
"dist/index.html": {
55-
"checksum": "4be76de8432e10c1b1ec25dc9e965a64"
56-
},
57-
"requirements.txt": {
58-
"checksum": "a162a98758867a701ce693948ffdfa67"
59-
}
21+
"requirements.txt": {
22+
"checksum": "a162a98758867a701ce693948ffdfa67"
23+
},
24+
"app.py": {
25+
"checksum": "0e5df52143323d9ae49cf77c51298cde"
26+
},
27+
"dist/assets/fa-brands-400.808443ae.ttf": {
28+
"checksum": "15d54d142da2f2d6f2e90ed1d55121af"
29+
},
30+
"dist/assets/fa-brands-400.d7236a19.woff2": {
31+
"checksum": "cbcf42b2e9228a8f5bad42717d8a88db"
32+
},
33+
"dist/assets/fa-regular-400.54cf6086.ttf": {
34+
"checksum": "262525e2081311609d1fdab966c82bfc"
35+
},
36+
"dist/assets/fa-regular-400.e3456d12.woff2": {
37+
"checksum": "89672701a5874b80be27649e0494e354"
38+
},
39+
"dist/assets/fa-solid-900.aa759986.woff2": {
40+
"checksum": "4a6591ab5460ae5cbff1ecbd6e52193a"
41+
},
42+
"dist/assets/fa-solid-900.d2f05935.ttf": {
43+
"checksum": "269f971cec0d5dc864fe9ae080b19e23"
44+
},
45+
"dist/assets/fa-v4compatibility.0ce9033c.woff2": {
46+
"checksum": "a772da7bff216cd36214bc44165bba3e"
47+
},
48+
"dist/assets/fa-v4compatibility.30f6abf6.ttf": {
49+
"checksum": "4ed293ceaca9b5b2d9cd74a477963fae"
50+
},
51+
"dist/assets/index.0c2ac2eb.css": {
52+
"checksum": "96fb5a8d93644a29ece62d52354fbc73"
53+
},
54+
"dist/assets/index.835b2c40.js": {
55+
"checksum": "52e8175304138c1ef6d18c808dcb4b04"
56+
},
57+
"dist/index.html": {
58+
"checksum": "a3cf0e03275e2436d932280af100438c"
59+
}
6060
},
6161
"extension": {
6262
"name": "publisher-command-center",
6363
"title": "Publisher Command Center",
64-
"description": "A dashboard for publishers to help manage and track their content",
64+
"description": "An app that helps publishers view and manage details about apps and dashboards deployed to Connect: View app metadata and history for all of the apps you are collaborating on as well as see and managing running process for individual apps.",
6565
"homepage": "https://github.com/posit-dev/connect-extensions/tree/main/extensions/publisher-command-center",
6666
"minimumConnectVersion": "2025.04.0",
6767
"requiredFeatures": [
6868
"API Publishing",
6969
"OAuth Integrations"
7070
],
71-
"version": "0.0.1"
71+
"version": "0.0.2"
7272
}
7373
}

extensions/publisher-command-center/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/publisher-command-center/src/index.js

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,48 @@ import Edit from "./views/Edit";
1111
import Layout from './views/Layout'
1212

1313
const root = document.getElementById("app");
14-
m.route(root, "/contents", {
15-
"/contents": {
16-
render: function () {
17-
return m(Layout, m(Home))
18-
},
19-
},
20-
"/contents/:id": {
21-
render: function (vnode) {
22-
return m(Layout, m(Edit, vnode.attrs));
23-
},
24-
},
25-
});
14+
15+
// First ask the server “are we authorized?”
16+
m.request({ method: "GET", url: "api/auth-status" })
17+
.then((res) => {
18+
if (!res.authorized) {
19+
// Unauthorized → just show the banner, never mount the router
20+
m.mount(root, {
21+
view: () =>
22+
m(
23+
"div.alert.alert-info",
24+
{ style: { margin: "1rem" } },
25+
[
26+
m("p", [
27+
"To finish setting up this content, you must add a Visitor API Key ",
28+
"integration with the Publisher scope."
29+
]),
30+
m("p", [
31+
'Select "+ Add integration" in the Access settings panel to the ',
32+
'right, and find an entry with "Authentication type: Visitor API Key".'
33+
]),
34+
m("p", [
35+
"If no such integration exists, an Administrator must configure one. ",
36+
"Go to Connect's System page, select the Integrations tab, then ",
37+
'click "+ Add Integration", choose "Connect API", pick Publisher or ',
38+
"Administrator under Max Role, and give it a descriptive title."
39+
])
40+
]
41+
),
42+
});
43+
} else {
44+
// Authorized → wire up routes
45+
m.route(root, "/contents", {
46+
"/contents": {
47+
render: () => m(Layout, m(Home)),
48+
},
49+
"/contents/:id": {
50+
render: (vnode) => m(Layout, m(Edit, vnode.attrs)),
51+
},
52+
});
53+
}
54+
})
55+
.catch((err) => {
56+
console.error("failed to fetch auth-status", err);
57+
// you might also render a generic “uh-oh” banner here
58+
});

0 commit comments

Comments
 (0)