Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/runtime-version-scanner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
path: |
extensions/${{ env.EXTENSION_NAME }}/app.R
extensions/${{ env.EXTENSION_NAME }}/R/connect_module.R
extensions/${{ env.EXTENSION_NAME }}/R/fetch_content.R
extensions/${{ env.EXTENSION_NAME }}/R/supported_versions.R
extensions/${{ env.EXTENSION_NAME }}/R/version_ordering.R
extensions/${{ env.EXTENSION_NAME }}/www/styles.css
Expand Down
48 changes: 48 additions & 0 deletions extensions/runtime-version-scanner/R/fetch_content.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
library(connectapi)

app_mode_groups <- function() {
list(
"API" = c("api", "python-fastapi", "python-api", "tensorflow-saved-model"),
"Application" = c(
"shiny",
"python-shiny",
"python-dash",
"python-gradio",
"python-streamlit",
"python-bokeh"
),
"Jupyter" = c("jupyter-static", "jupyter-voila"),
"Quarto" = c("quarto-shiny", "quarto-static"),
"R Markdown" = c("rmd-shiny", "rmd-static"),
"Pin" = c("pin"),
"Other" = c("unknown")
)
}

fetch_content <- function(client, content_type_filter = NULL) {
owner <- "owner:@me"
type <- type_query(content_type_filter)

parts <- Filter(nzchar, c(owner, type))
query <- paste(parts, collapse = " ")

search_content(client, q = query, include = "owner")
}

type_query <- function(content_types) {
if (length(content_types) == 0 || is.null(content_types)) {
return(character(0))
}
app_modes <- paste(unlist(app_mode_groups()[content_types]), collapse = ",")
if (!nzchar(app_modes)) {
return(character(0))
}
paste0("type:", app_modes)
}

content_list_to_data_frame <- function(content_list) {
connectapi:::parse_connectapi_typed(
content_list,
connectapi:::connectapi_ptypes$content
)
}
41 changes: 8 additions & 33 deletions extensions/runtime-version-scanner/app.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ library(tidyr)
library(shinyjs)
library(future)
library(future.mirai)
library(purrr)

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

app_mode_groups <- list(
"API" = c("api", "python-fastapi", "python-api", "tensorflow-saved-model"),
"Application" = c(
"shiny",
"python-shiny",
"python-dash",
"python-gradio",
"python-streamlit",
"python-bokeh"
),
"Jupyter" = c("jupyter-static", "jupyter-voila"),
"Quarto" = c("quarto-shiny", "quarto-static"),
"R Markdown" = c("rmd-shiny", "rmd-static"),
"Pin" = c("pin"),
"Other" = c("unknown")
)

app_mode_lookup <- with(
stack(app_mode_groups),
stack(app_mode_groups()),
setNames(as.character(ind), values)
)

Expand Down Expand Up @@ -131,7 +115,7 @@ ui <- page_sidebar(
placeholder = "All content types",
plugins = list("remove_button")
),
choices = names(app_mode_groups),
choices = names(app_mode_groups()),
multiple = TRUE
),

Expand Down Expand Up @@ -273,23 +257,23 @@ server <- function(input, output, session) {
last_deployed_time = as.POSIXct(character())
)

content_task <- ExtendedTask$new(function(...) {
content_task <- ExtendedTask$new(function(content_type_filter) {
future({
content <- get_content(client) |>
filter(app_role %in% c("owner", "editor")) |>
content_list <- fetch_content(client, content_type_filter = content_type_filter)
content_df <- content_list_to_data_frame(content_list)|>
mutate(
owner_name = paste(
map_chr(owner, "first_name"),
map_chr(owner, "last_name")
),
title = coalesce(title, ifelse(name != "", name, NA))
)
content
content_df
})
})

observe({
content_task$invoke()
content_task$invoke(input$content_type_filter)
})

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

content_table_data <- reactive({
# Filter by content type
content_type_mask <- if (length(input$content_type_filter) == 0) {
names(app_mode_groups)
} else {
input$content_type_filter
}

content() |>
mutate(content_type = app_mode_lookup[app_mode]) |>
filter(content_type %in% content_type_mask) |>
select(
title,
dashboard_url,
Expand Down Expand Up @@ -759,7 +735,6 @@ server <- function(input, output, session) {
})

observe({
print(content_display())
updateReactable("content_table", data = content_display())
})
observe({
Expand Down
46 changes: 25 additions & 21 deletions extensions/runtime-version-scanner/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -415,40 +415,41 @@
}
},
"connectapi": {
"Source": "CRAN",
"Repository": "https://packagemanager.posit.co/cran/latest",
"Source": "github",
"Repository": null,
"description": {
"Type": "Package",
"Package": "connectapi",
"Title": "Utilities for Interacting with the 'Posit Connect' Server API",
"Version": "0.8.0",
"Version": "0.8.0.9000",
"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\")))",
"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/>.",
"License": "MIT + file LICENSE",
"URL": "https://posit-dev.github.io/connectapi/,\nhttps://github.com/posit-dev/connectapi",
"URL": "https://posit-dev.github.io/connectapi/, https://github.com/posit-dev/connectapi",
"BugReports": "https://github.com/posit-dev/connectapi/issues",
"Imports": "bit64, fs, glue, httr, mime, jsonlite, lifecycle, magrittr,\npurrr, R6, rlang (>= 0.4.2), tibble, uuid, vctrs (>= 0.3.0),\nbase64enc",
"Suggests": "covr, dbplyr, dplyr, ggplot2, gridExtra, httptest, knitr,\nlubridate, progress, rmarkdown, rprojroot, rsconnect, spelling,\ntestthat, tidyr, webshot2, withr",
"Imports": "bit64,\nfs,\nglue,\nhttr,\nmime,\njsonlite,\nlifecycle,\nmagrittr,\npurrr,\nR6,\nrlang (>= 0.4.2),\ntibble,\nuuid,\nvctrs (>= 0.3.0),\nbase64enc",
"Suggests": "covr,\ndbplyr,\ndplyr,\nggplot2,\ngridExtra,\nhttptest,\nknitr,\nlubridate,\nprogress,\nrmarkdown,\nrprojroot,\nrsconnect,\nspelling,\ntestthat,\ntidyr,\nwebshot2,\nwithr",
"VignetteBuilder": "knitr",
"Encoding": "UTF-8",
"Language": "en-US",
"RoxygenNote": "7.3.2",
"Roxygen": "list(markdown = TRUE)",
"Config/testthat/edition": "3",
"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'",
"NeedsCompilation": "no",
"Packaged": "2025-07-30 21:46:47 UTC; toph",
"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'",
"Author": "Toph Allen [aut, cre],\n Neal Richardson [aut],\n Sean Lopp [aut],\n Cole Arendt [aut],\n Posit, PBC [cph, fnd]",
"Maintainer": "Toph Allen <[email protected]>",
"Repository": "RSPM",
"Date/Publication": "2025-07-30 22:00:11 UTC",
"Built": "R 4.3.0; ; 2025-07-31 10:36:47 UTC; unix",
"RemoteType": "standard",
"RemoteRef": "connectapi",
"RemotePkgRef": "connectapi",
"RemoteRepos": "https://packagemanager.posit.co/cran/latest",
"RemoteReposName": "CRAN",
"RemotePkgPlatform": "aarch64-apple-darwin20",
"RemoteSha": "0.8.0"
"Built": "R 4.3.3; ; 2025-10-08 18:39:18 UTC; unix",
"RemoteType": "github",
"RemoteHost": "api.github.com",
"RemoteUsername": "posit-dev",
"RemoteRepo": "connectapi",
"RemoteRef": "main",
"RemoteSha": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a",
"GithubHost": "api.github.com",
"GithubRepo": "connectapi",
"GithubUsername": "posit-dev",
"GithubRef": "main",
"GithubSHA1": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a"
}
},
"cpp11": {
Expand Down Expand Up @@ -2624,19 +2625,22 @@
},
"files": {
"app.R": {
"checksum": "7960398b11756a0ace37e183f84b7a9f"
"checksum": "43fd537653865993ff2fb5a3f963cdf1"
},
"R/connect_module.R": {
"checksum": "469dbae2ebdf431cad52f0ed98793366"
},
"R/fetch_content.R": {
"checksum": "1563f56cdbf699953ee2750fac9b560f"
},
"R/supported_versions.R": {
"checksum": "ca8c4181bddf00f8e7e0797671ae01c6"
},
"R/version_ordering.R": {
"checksum": "dc05d91a7b8cf813f03f519b5ecb72dc"
},
"renv.lock": {
"checksum": "4d741f52dfe275c669083e78147ea3f9"
"checksum": "d98fa015a15df805ee9ec47ea3277e7a"
},
"www/styles.css": {
"checksum": "48340b42bcc9fbed7bdac0c216bb6964"
Expand Down
13 changes: 9 additions & 4 deletions extensions/runtime-version-scanner/renv.lock
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,8 @@
},
"connectapi": {
"Package": "connectapi",
"Version": "0.8.0",
"Source": "Repository",
"Version": "0.8.0.9000",
"Source": "GitHub",
"Type": "Package",
"Title": "Utilities for Interacting with the 'Posit Connect' Server API",
"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\")))",
Expand Down Expand Up @@ -434,12 +434,17 @@
"Encoding": "UTF-8",
"Language": "en-US",
"RoxygenNote": "7.3.2",
"Roxygen": "list(markdown = TRUE)",
"Config/testthat/edition": "3",
"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'",
"NeedsCompilation": "no",
"Author": "Toph Allen [aut, cre], Neal Richardson [aut], Sean Lopp [aut], Cole Arendt [aut], Posit, PBC [cph, fnd]",
"Maintainer": "Toph Allen <[email protected]>",
"Repository": "CRAN"
"RemoteType": "github",
"RemoteHost": "api.github.com",
"RemoteUsername": "posit-dev",
"RemoteRepo": "connectapi",
"RemoteRef": "main",
"RemoteSha": "dc55d835d1b90c9dd5dba48d0951b8ae682e857a"
},
"cpp11": {
"Package": "cpp11",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "2025.10.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
with_mock_api({
client <- Connect$new("https://connect.example", api_key = "fake")
client$version # To hydrate the version property

without_internet({
test_that("fetch_content() with no type query makes expected request", {
expect_GET(
fetch_content(client),
"https://connect.example/__api__/v1/search/content?q=owner%3A%40me&page_number=1&page_size=500&include=owner"
)
})
test_that("fetch_content() with type query makes expected request", {
expect_GET(
fetch_content(client, content_type_filter = "Pin"),
"https://connect.example/__api__/v1/search/content?q=owner%3A%40me%20type%3Apin&page_number=1&page_size=500&include=owner"
)
})
})
})

test_that("type_query handles NULL, empty, and unknown inputs", {
expect_equal(type_query(NULL), character(0))
expect_equal(type_query(character(0)), character(0))
expect_equal(type_query("NonExistent"), character(0))
})

test_that("type_query returns correct queries for single content types", {
expect_equal(
type_query("API"),
"type:api,python-fastapi,python-api,tensorflow-saved-model"
)
expect_equal(
type_query("Application"),
"type:shiny,python-shiny,python-dash,python-gradio,python-streamlit,python-bokeh"
)
expect_equal(type_query("Jupyter"), "type:jupyter-static,jupyter-voila")
expect_equal(type_query("Quarto"), "type:quarto-shiny,quarto-static")
expect_equal(type_query("R Markdown"), "type:rmd-shiny,rmd-static")
expect_equal(type_query("Pin"), "type:pin")
expect_equal(type_query("Other"), "type:unknown")
})

test_that("type_query handles multiple content types", {
result <- type_query(c("API", "Jupyter"))
expect_true(grepl("^type:", result))
expect_true(grepl("api", result))
expect_true(grepl("jupyter-static", result))
})