Skip to content

Commit 6919067

Browse files
toph-allenkarawoo
andauthored
feat: runtime version scanner: adopt content search (#280)
* reorganize files * update github action * adopt search_content in runtime version scanner * format * migrate to connectapi’s built-in get_usage * adopt search_content in runtime version scanner * format * Update extensions/runtime-version-scanner/.Rbuildignore Co-authored-by: Kara Woo <[email protected]> * Update extensions/runtime-version-scanner/DESCRIPTION Co-authored-by: Kara Woo <[email protected]> * Update DESCRIPTION * update support for search_content() * Update extensions/runtime-version-scanner/R/fetch_content.R Co-authored-by: Kara Woo <[email protected]> * extract app mdoe groups into function --------- Co-authored-by: Kara Woo <[email protected]>
1 parent 0dad0d4 commit 6919067

File tree

8 files changed

+145
-58
lines changed

8 files changed

+145
-58
lines changed

.github/workflows/runtime-version-scanner.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ jobs:
5858
path: |
5959
extensions/${{ env.EXTENSION_NAME }}/app.R
6060
extensions/${{ env.EXTENSION_NAME }}/R/connect_module.R
61+
extensions/${{ env.EXTENSION_NAME }}/R/fetch_content.R
6162
extensions/${{ env.EXTENSION_NAME }}/R/supported_versions.R
6263
extensions/${{ env.EXTENSION_NAME }}/R/version_ordering.R
6364
extensions/${{ env.EXTENSION_NAME }}/www/styles.css
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
library(connectapi)
2+
3+
app_mode_groups <- function() {
4+
list(
5+
"API" = c("api", "python-fastapi", "python-api", "tensorflow-saved-model"),
6+
"Application" = c(
7+
"shiny",
8+
"python-shiny",
9+
"python-dash",
10+
"python-gradio",
11+
"python-streamlit",
12+
"python-bokeh"
13+
),
14+
"Jupyter" = c("jupyter-static", "jupyter-voila"),
15+
"Quarto" = c("quarto-shiny", "quarto-static"),
16+
"R Markdown" = c("rmd-shiny", "rmd-static"),
17+
"Pin" = c("pin"),
18+
"Other" = c("unknown")
19+
)
20+
}
21+
22+
fetch_content <- function(client, content_type_filter = NULL) {
23+
owner <- "owner:@me"
24+
type <- type_query(content_type_filter)
25+
26+
parts <- Filter(nzchar, c(owner, type))
27+
query <- paste(parts, collapse = " ")
28+
29+
search_content(client, q = query, include = "owner")
30+
}
31+
32+
type_query <- function(content_types) {
33+
if (length(content_types) == 0 || is.null(content_types)) {
34+
return(character(0))
35+
}
36+
app_modes <- paste(unlist(app_mode_groups()[content_types]), collapse = ",")
37+
if (!nzchar(app_modes)) {
38+
return(character(0))
39+
}
40+
paste0("type:", app_modes)
41+
}
42+
43+
content_list_to_data_frame <- function(content_list) {
44+
connectapi:::parse_connectapi_typed(
45+
content_list,
46+
connectapi:::connectapi_ptypes$content
47+
)
48+
}

extensions/runtime-version-scanner/app.R

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ library(tidyr)
1010
library(shinyjs)
1111
library(future)
1212
library(future.mirai)
13+
library(purrr)
1314

1415
# Special version that will be greater than any real version
1516
ANY_VERSION <- "999.99.99"
@@ -24,25 +25,8 @@ options(
2425
spinner.color = "#7494b1"
2526
)
2627

27-
app_mode_groups <- list(
28-
"API" = c("api", "python-fastapi", "python-api", "tensorflow-saved-model"),
29-
"Application" = c(
30-
"shiny",
31-
"python-shiny",
32-
"python-dash",
33-
"python-gradio",
34-
"python-streamlit",
35-
"python-bokeh"
36-
),
37-
"Jupyter" = c("jupyter-static", "jupyter-voila"),
38-
"Quarto" = c("quarto-shiny", "quarto-static"),
39-
"R Markdown" = c("rmd-shiny", "rmd-static"),
40-
"Pin" = c("pin"),
41-
"Other" = c("unknown")
42-
)
43-
4428
app_mode_lookup <- with(
45-
stack(app_mode_groups),
29+
stack(app_mode_groups()),
4630
setNames(as.character(ind), values)
4731
)
4832

@@ -131,7 +115,7 @@ ui <- page_sidebar(
131115
placeholder = "All content types",
132116
plugins = list("remove_button")
133117
),
134-
choices = names(app_mode_groups),
118+
choices = names(app_mode_groups()),
135119
multiple = TRUE
136120
),
137121

@@ -273,23 +257,23 @@ server <- function(input, output, session) {
273257
last_deployed_time = as.POSIXct(character())
274258
)
275259

276-
content_task <- ExtendedTask$new(function(...) {
260+
content_task <- ExtendedTask$new(function(content_type_filter) {
277261
future({
278-
content <- get_content(client) |>
279-
filter(app_role %in% c("owner", "editor")) |>
262+
content_list <- fetch_content(client, content_type_filter = content_type_filter)
263+
content_df <- content_list_to_data_frame(content_list)|>
280264
mutate(
281265
owner_name = paste(
282266
map_chr(owner, "first_name"),
283267
map_chr(owner, "last_name")
284268
),
285269
title = coalesce(title, ifelse(name != "", name, NA))
286270
)
287-
content
271+
content_df
288272
})
289273
})
290274

291275
observe({
292-
content_task$invoke()
276+
content_task$invoke(input$content_type_filter)
293277
})
294278

295279
content <- reactive({
@@ -427,16 +411,8 @@ server <- function(input, output, session) {
427411
outputOptions(output, "usage_task_running", suspendWhenHidden = FALSE)
428412

429413
content_table_data <- reactive({
430-
# Filter by content type
431-
content_type_mask <- if (length(input$content_type_filter) == 0) {
432-
names(app_mode_groups)
433-
} else {
434-
input$content_type_filter
435-
}
436-
437414
content() |>
438415
mutate(content_type = app_mode_lookup[app_mode]) |>
439-
filter(content_type %in% content_type_mask) |>
440416
select(
441417
title,
442418
dashboard_url,
@@ -759,7 +735,6 @@ server <- function(input, output, session) {
759735
})
760736

761737
observe({
762-
print(content_display())
763738
updateReactable("content_table", data = content_display())
764739
})
765740
observe({

extensions/runtime-version-scanner/manifest.json

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -415,40 +415,41 @@
415415
}
416416
},
417417
"connectapi": {
418-
"Source": "CRAN",
419-
"Repository": "https://packagemanager.posit.co/cran/latest",
418+
"Source": "github",
419+
"Repository": null,
420420
"description": {
421421
"Type": "Package",
422422
"Package": "connectapi",
423423
"Title": "Utilities for Interacting with the 'Posit Connect' Server API",
424-
"Version": "0.8.0",
424+
"Version": "0.8.0.9000",
425425
"Authors@R": "c(\n person(given = \"Toph\",\n family = \"Allen\",\n role = c(\"aut\", \"cre\"),\n email = \"[email protected]\"),\n person(given = \"Neal\",\n family = \"Richardson\",\n role = c(\"aut\")),\n person(given = \"Sean\",\n family = \"Lopp\",\n role = c(\"aut\")),\n person(given = \"Cole\",\n family = \"Arendt\",\n role = c(\"aut\")),\n person(given = \"Posit, PBC\",\n role = c(\"cph\", \"fnd\")))",
426426
"Description": "Provides a helpful 'R6' class and methods for interacting with\n the 'Posit Connect' Server API along with some meaningful utility functions\n for regular tasks. API documentation varies by 'Posit Connect' installation\n and version, but the latest documentation is also hosted publicly at\n <https://docs.posit.co/connect/api/>.",
427427
"License": "MIT + file LICENSE",
428-
"URL": "https://posit-dev.github.io/connectapi/,\nhttps://github.com/posit-dev/connectapi",
428+
"URL": "https://posit-dev.github.io/connectapi/, https://github.com/posit-dev/connectapi",
429429
"BugReports": "https://github.com/posit-dev/connectapi/issues",
430-
"Imports": "bit64, fs, glue, httr, mime, jsonlite, lifecycle, magrittr,\npurrr, R6, rlang (>= 0.4.2), tibble, uuid, vctrs (>= 0.3.0),\nbase64enc",
431-
"Suggests": "covr, dbplyr, dplyr, ggplot2, gridExtra, httptest, knitr,\nlubridate, progress, rmarkdown, rprojroot, rsconnect, spelling,\ntestthat, tidyr, webshot2, withr",
430+
"Imports": "bit64,\nfs,\nglue,\nhttr,\nmime,\njsonlite,\nlifecycle,\nmagrittr,\npurrr,\nR6,\nrlang (>= 0.4.2),\ntibble,\nuuid,\nvctrs (>= 0.3.0),\nbase64enc",
431+
"Suggests": "covr,\ndbplyr,\ndplyr,\nggplot2,\ngridExtra,\nhttptest,\nknitr,\nlubridate,\nprogress,\nrmarkdown,\nrprojroot,\nrsconnect,\nspelling,\ntestthat,\ntidyr,\nwebshot2,\nwithr",
432432
"VignetteBuilder": "knitr",
433433
"Encoding": "UTF-8",
434434
"Language": "en-US",
435435
"RoxygenNote": "7.3.2",
436+
"Roxygen": "list(markdown = TRUE)",
436437
"Config/testthat/edition": "3",
437-
"Collate": "'audits.R' 'browse.R' 'connect.R' 'connectapi-package.R'\n'connectapi.R' 'content.R' 'deploy.R' 'get.R' 'git.R'\n'groups.R' 'integrations.R' 'lazy.R' 'page.R' 'parse.R'\n'promote.R' 'ptype.R' 'remote.R' 'runtime-caches.R'\n'schedule.R' 'tags.R' 'utils.R' 'thumbnail.R' 'user.R'\n'utils-ci.R' 'utils-pipe.R' 'variant.R'",
438-
"NeedsCompilation": "no",
439-
"Packaged": "2025-07-30 21:46:47 UTC; toph",
438+
"Collate": "'audits.R'\n'browse.R'\n'connect.R'\n'connectapi-package.R'\n'connectapi.R'\n'content.R'\n'deploy.R'\n'get.R'\n'git.R'\n'groups.R'\n'integrations.R'\n'lazy.R'\n'page.R'\n'parse.R'\n'promote.R'\n'ptype.R'\n'remote.R'\n'runtime-caches.R'\n'schedule.R'\n'tags.R'\n'utils.R'\n'thumbnail.R'\n'user.R'\n'utils-ci.R'\n'utils-pipe.R'\n'variant.R'",
440439
"Author": "Toph Allen [aut, cre],\n Neal Richardson [aut],\n Sean Lopp [aut],\n Cole Arendt [aut],\n Posit, PBC [cph, fnd]",
441440
"Maintainer": "Toph Allen <[email protected]>",
442-
"Repository": "RSPM",
443-
"Date/Publication": "2025-07-30 22:00:11 UTC",
444-
"Built": "R 4.3.0; ; 2025-07-31 10:36:47 UTC; unix",
445-
"RemoteType": "standard",
446-
"RemoteRef": "connectapi",
447-
"RemotePkgRef": "connectapi",
448-
"RemoteRepos": "https://packagemanager.posit.co/cran/latest",
449-
"RemoteReposName": "CRAN",
450-
"RemotePkgPlatform": "aarch64-apple-darwin20",
451-
"RemoteSha": "0.8.0"
441+
"Built": "R 4.3.3; ; 2025-10-08 18:39:18 UTC; unix",
442+
"RemoteType": "github",
443+
"RemoteHost": "api.github.com",
444+
"RemoteUsername": "posit-dev",
445+
"RemoteRepo": "connectapi",
446+
"RemoteRef": "main",
447+
"RemoteSha": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a",
448+
"GithubHost": "api.github.com",
449+
"GithubRepo": "connectapi",
450+
"GithubUsername": "posit-dev",
451+
"GithubRef": "main",
452+
"GithubSHA1": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a"
452453
}
453454
},
454455
"cpp11": {
@@ -2624,19 +2625,22 @@
26242625
},
26252626
"files": {
26262627
"app.R": {
2627-
"checksum": "7960398b11756a0ace37e183f84b7a9f"
2628+
"checksum": "43fd537653865993ff2fb5a3f963cdf1"
26282629
},
26292630
"R/connect_module.R": {
26302631
"checksum": "469dbae2ebdf431cad52f0ed98793366"
26312632
},
2633+
"R/fetch_content.R": {
2634+
"checksum": "1563f56cdbf699953ee2750fac9b560f"
2635+
},
26322636
"R/supported_versions.R": {
26332637
"checksum": "ca8c4181bddf00f8e7e0797671ae01c6"
26342638
},
26352639
"R/version_ordering.R": {
26362640
"checksum": "dc05d91a7b8cf813f03f519b5ecb72dc"
26372641
},
26382642
"renv.lock": {
2639-
"checksum": "4d741f52dfe275c669083e78147ea3f9"
2643+
"checksum": "d98fa015a15df805ee9ec47ea3277e7a"
26402644
},
26412645
"www/styles.css": {
26422646
"checksum": "48340b42bcc9fbed7bdac0c216bb6964"

extensions/runtime-version-scanner/renv.lock

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@
385385
},
386386
"connectapi": {
387387
"Package": "connectapi",
388-
"Version": "0.8.0",
389-
"Source": "Repository",
388+
"Version": "0.8.0.9000",
389+
"Source": "GitHub",
390390
"Type": "Package",
391391
"Title": "Utilities for Interacting with the 'Posit Connect' Server API",
392392
"Authors@R": "c( person(given = \"Toph\", family = \"Allen\", role = c(\"aut\", \"cre\"), email = \"[email protected]\"), person(given = \"Neal\", family = \"Richardson\", role = c(\"aut\")), person(given = \"Sean\", family = \"Lopp\", role = c(\"aut\")), person(given = \"Cole\", family = \"Arendt\", role = c(\"aut\")), person(given = \"Posit, PBC\", role = c(\"cph\", \"fnd\")))",
@@ -434,12 +434,17 @@
434434
"Encoding": "UTF-8",
435435
"Language": "en-US",
436436
"RoxygenNote": "7.3.2",
437+
"Roxygen": "list(markdown = TRUE)",
437438
"Config/testthat/edition": "3",
438439
"Collate": "'audits.R' 'browse.R' 'connect.R' 'connectapi-package.R' 'connectapi.R' 'content.R' 'deploy.R' 'get.R' 'git.R' 'groups.R' 'integrations.R' 'lazy.R' 'page.R' 'parse.R' 'promote.R' 'ptype.R' 'remote.R' 'runtime-caches.R' 'schedule.R' 'tags.R' 'utils.R' 'thumbnail.R' 'user.R' 'utils-ci.R' 'utils-pipe.R' 'variant.R'",
439-
"NeedsCompilation": "no",
440440
"Author": "Toph Allen [aut, cre], Neal Richardson [aut], Sean Lopp [aut], Cole Arendt [aut], Posit, PBC [cph, fnd]",
441441
"Maintainer": "Toph Allen <[email protected]>",
442-
"Repository": "CRAN"
442+
"RemoteType": "github",
443+
"RemoteHost": "api.github.com",
444+
"RemoteUsername": "posit-dev",
445+
"RemoteRepo": "connectapi",
446+
"RemoteRef": "main",
447+
"RemoteSha": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a"
443448
},
444449
"cpp11": {
445450
"Package": "cpp11",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"version": "2025.10.0"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
3+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
with_mock_api({
2+
client <- Connect$new("https://connect.example", api_key = "fake")
3+
client$version # To hydrate the version property
4+
5+
without_internet({
6+
test_that("fetch_content() with no type query makes expected request", {
7+
expect_GET(
8+
fetch_content(client),
9+
"https://connect.example/__api__/v1/search/content?q=owner%3A%40me&page_number=1&page_size=500&include=owner"
10+
)
11+
})
12+
test_that("fetch_content() with type query makes expected request", {
13+
expect_GET(
14+
fetch_content(client, content_type_filter = "Pin"),
15+
"https://connect.example/__api__/v1/search/content?q=owner%3A%40me%20type%3Apin&page_number=1&page_size=500&include=owner"
16+
)
17+
})
18+
})
19+
})
20+
21+
test_that("type_query handles NULL, empty, and unknown inputs", {
22+
expect_equal(type_query(NULL), character(0))
23+
expect_equal(type_query(character(0)), character(0))
24+
expect_equal(type_query("NonExistent"), character(0))
25+
})
26+
27+
test_that("type_query returns correct queries for single content types", {
28+
expect_equal(
29+
type_query("API"),
30+
"type:api,python-fastapi,python-api,tensorflow-saved-model"
31+
)
32+
expect_equal(
33+
type_query("Application"),
34+
"type:shiny,python-shiny,python-dash,python-gradio,python-streamlit,python-bokeh"
35+
)
36+
expect_equal(type_query("Jupyter"), "type:jupyter-static,jupyter-voila")
37+
expect_equal(type_query("Quarto"), "type:quarto-shiny,quarto-static")
38+
expect_equal(type_query("R Markdown"), "type:rmd-shiny,rmd-static")
39+
expect_equal(type_query("Pin"), "type:pin")
40+
expect_equal(type_query("Other"), "type:unknown")
41+
})
42+
43+
test_that("type_query handles multiple content types", {
44+
result <- type_query(c("API", "Jupyter"))
45+
expect_true(grepl("^type:", result))
46+
expect_true(grepl("api", result))
47+
expect_true(grepl("jupyter-static", result))
48+
})

0 commit comments

Comments
 (0)