Skip to content

Commit b01f32c

Browse files
authored
feat: better first-run experience in Usage Metrics Dashboard (#128)
* update renv.lock to get things working * add first draft of an improved integration workflow * remove print statements * remove leftovers * improve text * change one word * more text improvements * work around connectapi package manager weirdness * add comments for planned connectapi work
1 parent 5952909 commit b01f32c

File tree

4 files changed

+226
-163
lines changed

4 files changed

+226
-163
lines changed

extensions/usage-metrics-dashboard/app.R

+71-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ shinyOptions(
1515
)
1616

1717
source("get_usage.R")
18+
source("integrations.R")
1819

1920
app_mode_groups <- list(
2021
"API" = c("api", "python-fastapi", "python-api", "tensorflow-saved-model"),
@@ -274,25 +275,84 @@ ui <- function(request) {
274275
server <- function(input, output, session) {
275276
# Set up Connect client; handle error if Visitor API Key integration isn't
276277
# present.
278+
279+
publisher_client <- connect()
280+
281+
selected_integration_guid <- reactiveVal(NULL)
282+
observeEvent(input$auto_add_integration, {
283+
auto_add_integration(publisher_client, selected_integration_guid())
284+
# Hard refresh so that the sidebar gets the up to date info
285+
runjs("window.top.location.reload(true);")
286+
})
287+
277288
client <- NULL
278289
tryCatch(
279290
client <- connect(
280291
token = session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN
281292
),
282293
error = function(e) {
283-
showModal(modalDialog(
284-
title = "Additional Setup Required",
285-
footer = NULL,
286-
HTML(paste(
287-
"In the Access panel to the right, click <strong>\"Add integration\"</strong>,",
288-
"then select a <strong>Visitor API Key</strong> integration.",
289-
"If you don't see one in the list, an administrator must enable this feature on your Connect server.",
290-
"See the <a href='https://docs.posit.co/connect/admin/integrations/oauth-integrations/connect/' target='_blank'>Admin Guide</a> for setup instructions.",
294+
eligible_integrations <- get_eligible_integrations(publisher_client)
295+
selected_integration <- slice_head(eligible_integrations, n = 1)
296+
selected_integration_guid(selected_integration$guid)
297+
298+
if (nrow(selected_integration) == 1) {
299+
message <- paste0(
300+
"This content uses a <strong>Visitor API Key</strong> ",
301+
"integration to show users the content they have access to. ",
302+
"A compatible integration is displayed below.",
303+
"<br><br>",
304+
"For more information, see ",
305+
"<a href='https://docs.posit.co/connect/user/oauth-integrations/#obtaining-a-visitor-api-key' ",
306+
"target='_blank'>documentation on Visitor API Key integrations</a>."
307+
)
308+
} else if (nrow(selected_integration) == 0) {
309+
integration_settings_url <- publisher_client$server_url(connectapi:::unversioned_url(
310+
"connect",
311+
"#",
312+
"system",
313+
"integrations"
314+
))
315+
message <- paste0(
316+
"This content needs permission to ",
317+
" show users the content they have access to.",
318+
"<br><br>",
319+
"To allow this, an Administrator must configure a ",
320+
"<strong>Connect API</strong> integration on the ",
321+
"<strong><a href='",
322+
integration_settings_url,
323+
"' target='_blank'>Integration Settings</a></strong> page. ",
291324
"<br><br>",
292-
"For guidance on using visitor-scoped permissions in your own Connect apps, see the",
293-
"<a href='https://docs.posit.co/connect/user/oauth-integrations/#obtaining-a-visitor-api-key' target='_blank'>User Guide</a>.",
294-
sep = " "
325+
"On that page, select <strong>'+ Add Integration'</strong>. ",
326+
"In the 'Select Integration' dropdown, choose <strong>'Connect API'</strong>. ",
327+
"The 'Max Role' field must be set to <strong>'Administrator'</strong> ",
328+
"or <strong>'Publisher'</strong>; 'Viewer' will not work. ",
329+
"<br><br>",
330+
"See the <a href='https://docs.posit.co/connect/admin/integrations/oauth-integrations/connect/' ",
331+
"target='_blank'>Connect API section of the Admin Guide</a> for more detailed setup instructions."
332+
)
333+
}
334+
335+
footer <- if (nrow(selected_integration) == 1) {
336+
button_label <- HTML(paste0(
337+
"Add the ",
338+
"<strong>'",
339+
selected_integration$name,
340+
"'</strong> ",
341+
"Integration"
295342
))
343+
actionButton(
344+
"auto_add_integration",
345+
button_label,
346+
icon = icon("plus")
347+
)
348+
} else if (nrow(selected_integration) == 0) {
349+
NULL
350+
}
351+
352+
showModal(modalDialog(
353+
# title = "Additional Setup Required",
354+
footer = footer,
355+
HTML(message)
296356
))
297357
}
298358
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
library(connectapi)
2+
library(purrr)
3+
library(dplyr)
4+
5+
get_eligible_integrations <- function(client) {
6+
tryCatch(
7+
{
8+
# TODO When https://github.com/posit-dev/connectapi/issues/413 is closed,
9+
# remove this and use that functionality instead.
10+
integrations <- client$GET("v1/oauth/integrations")
11+
12+
integrations_df <- map_dfr(integrations, function(record) {
13+
# Extract main fields
14+
main_fields <- discard(record, is.list) # Discard list fields like 'config'
15+
16+
# Extract and combine the config fields with field names and values
17+
config <- paste(
18+
imap_chr(record$config, ~ paste(.y, .x, sep = ": ")),
19+
collapse = ", "
20+
)
21+
22+
# Combine both into a single list
23+
c(main_fields, config = config)
24+
})
25+
26+
print(integrations_df)
27+
28+
eligible_integrations <- integrations_df |>
29+
filter(
30+
template == "connect",
31+
config %in% c("max_role: Admin", "max_role: Publisher")
32+
) |>
33+
arrange(desc(config))
34+
},
35+
error = function(e) {
36+
data.frame()
37+
}
38+
)
39+
}
40+
41+
auto_add_integration <- function(client, integration_guid) {
42+
print("About to PUT the integration!")
43+
44+
# TODO When https://github.com/posit-dev/connectapi/issues/414 is implemented,
45+
# delete this and use that instead.
46+
client$PUT(
47+
connectapi:::v1_url(
48+
"content",
49+
Sys.getenv("CONNECT_CONTENT_GUID"),
50+
"oauth",
51+
"integrations",
52+
"associations"
53+
),
54+
body = list(list(oauth_integration_guid = integration_guid))
55+
)
56+
print("Done adding the integration")
57+
}

0 commit comments

Comments
 (0)