From d345ece55f94549688af3a27695a74f8e2c04daa Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Fri, 16 Oct 2026 17:14:34 +0100 Subject: [PATCH 1/2] fix: Small fixes from the October 2026 review - Find checks with audit.base::init_r6_checks(), so any exported check_ class runs, as the README says - Import R6 and utils; call audit_details() once - deploy_git() uses its title, so cleanup finds the content - Drop the self-referencing debug_level defaults - cleanup_python() only deletes the auditor's own content - The old-users table lists users who have never logged in, and 6 and 12 months exactly - Fix the Flask and Shiny descriptions and the plumber class name - README: use a temporary renv cache instead of deleting the user's Closes #72 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEZ4ZUv51gFQjAe1WePbJF --- DESCRIPTION | 2 ++ NAMESPACE | 1 + R/check.R | 19 +------------------ R/check_deployments.R | 6 +++++- R/deploy_flask-r6.R | 2 +- R/deploy_git.R | 2 +- R/deploy_plumber-r6.R | 2 +- R/deploy_python.R | 5 +++-- R/deploy_quarto.R | 2 +- R/deploy_shiny-r6.R | 2 +- R/quarto-helpers.R | 9 +++++---- R/summarise_setup.R | 2 +- README.Rmd | 4 ++-- README.md | 4 ++-- man/check_deploy_plumber_api.Rd | 14 +++++++------- man/check_deploy_python_flask.Rd | 2 +- man/check_deploy_shiny.Rd | 2 +- man/deploy_python.Rd | 2 +- tests/testthat/test-quarto-helpers.R | 17 +++++++++++++++++ 19 files changed, 54 insertions(+), 45 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 69db657..59e0874 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,11 +21,13 @@ Imports: pins (>= 1.1.0), processx, purrr, + R6, quarto (>= 1.3), rlang, rsconnect (>= 1.10.0), stringr, tibble, + utils, withr Suggests: glue, diff --git a/NAMESPACE b/NAMESPACE index 9bde36d..2f4b818 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,5 +24,6 @@ export(get_quarto_old_users) export(get_quarto_user_roles) export(sanitise) import(audit.base) +importFrom(R6,R6Class) importFrom(rlang,.data) importFrom(rlang,.env) diff --git a/R/check.R b/R/check.R index ee3e5af..890494f 100644 --- a/R/check.R +++ b/R/check.R @@ -13,6 +13,7 @@ #' * 1: clean-up all files, but display build steps #' * 2: No clean-up (on connect and on disk) and display build steps #' @importFrom rlang .data .env +#' @importFrom R6 R6Class #' @import audit.base #' @export check = function(server = NULL, token = NULL, dir = ".", debug_level = 0:2) { @@ -33,7 +34,6 @@ check = function(server = NULL, token = NULL, dir = ".", debug_level = 0:2) { check_list$versions = summarise_versions(get_server(), get_token()) # check_list$feature_usage = summarise_feature_usage(get_server(), get_token()) #nolint - check_list$audit_details = audit_details(get_server(), get_token()) check_list$users_details = summarise_users( server = get_server(), token = get_token(), @@ -45,20 +45,3 @@ check = function(server = NULL, token = NULL, dir = ".", debug_level = 0:2) { cli::cli_h1("All checks complete") invisible(check_list) } - -init_r6_checks = function(dir, file) { - exports = getNamespaceExports("audit.connect") - check_exports = sort(exports[stringr::str_starts(exports, "check_deploy")]) - r6_inits = lapply(check_exports, init_r6_check, dir = dir, file = file) - r6_inits -} - -init_r6_check = function(export, dir, file) { - obj = eval(parse(text = export)) - if (inherits(obj, "R6ClassGenerator")) { - obj = obj$new(dir, file) - } else { - obj = NULL - } - obj -} diff --git a/R/check_deployments.R b/R/check_deployments.R index d980f39..bec75ff 100644 --- a/R/check_deployments.R +++ b/R/check_deployments.R @@ -1,6 +1,10 @@ check_deployments = function(dir, debug_level) { cli::cli_h2("Document Deployment Checks") - r6_inits = init_r6_checks(dir = dir, file = "config-uat-rsc.yml") + r6_inits = audit.base::init_r6_checks( + dir = dir, + file = "config-uat-rsc.yml", + pkg_name = "audit.connect" + ) lapply(r6_inits, function(r6) r6$check(debug_level = debug_level)) deployments = purrr::map_dfr(r6_inits, ~ .x$get_log()) deployments = dplyr::arrange(deployments, .data$group, .data$short) diff --git a/R/deploy_flask-r6.R b/R/deploy_flask-r6.R index efc7300..5248e39 100644 --- a/R/deploy_flask-r6.R +++ b/R/deploy_flask-r6.R @@ -6,7 +6,7 @@ check_deploy_python_flask = R6::R6Class( "check_deploy_python_flask", inherit = audit.base::base_check, public = list( - #' @description Checks deployment of Quarto document with HTML output + #' @description Checks deployment of a Python Flask API #' @param debug_level See check() for details check = function(debug_level) { if (skip_without_rsconnect_python(self)) { diff --git a/R/deploy_git.R b/R/deploy_git.R index 1c3dad6..6d775d6 100644 --- a/R/deploy_git.R +++ b/R/deploy_git.R @@ -10,7 +10,7 @@ deploy_git = function(repository, debug_level, title) { repository, branch = "main", subdirectory = ".", - title = "UAT" + title = title )) # If deploy not successful, content not created diff --git a/R/deploy_plumber-r6.R b/R/deploy_plumber-r6.R index 280b4bd..2d2fbfc 100644 --- a/R/deploy_plumber-r6.R +++ b/R/deploy_plumber-r6.R @@ -3,7 +3,7 @@ #' Check classes #' @export check_deploy_plumber_api = R6::R6Class( - "check_deploy_pluumber_api", + "check_deploy_plumber_api", inherit = audit.base::base_check, public = list( #' @description Checks deployment of a Plumber API diff --git a/R/deploy_python.R b/R/deploy_python.R index d8abe3a..2f06361 100644 --- a/R/deploy_python.R +++ b/R/deploy_python.R @@ -16,7 +16,7 @@ deploy_python = function( python_dir, python_files = c("app.py", "index.qmd", "hash.ipynb"), rsconnect_type = c("api", "streamlit", "quarto", "voila"), - debug_level = debug_level + debug_level ) { suppress = get_suppress(debug_level) rsconnect_type = match.arg(rsconnect_type) @@ -69,7 +69,8 @@ cleanup_python = function(tmp_dir, debug_level, title) { api_key = get_token() ) ) - content = suppress(connectapi::get_content(con)) + # Only the auditor's content, so nobody else's item with the same title is deleted + content = suppress(connectapi::get_content(con, owner_guid = con$me()$guid)) guid = content$guid[content$title %in% title] purrr::map( guid, diff --git a/R/deploy_quarto.R b/R/deploy_quarto.R index 90cb9f6..a216ba4 100644 --- a/R/deploy_quarto.R +++ b/R/deploy_quarto.R @@ -1,7 +1,7 @@ deploy_quarto = function( quarto_dir, account = NULL, - debug_level = debug_level + debug_level ) { suppress = get_suppress(debug_level) diff --git a/R/deploy_shiny-r6.R b/R/deploy_shiny-r6.R index fd2e876..ea01875 100644 --- a/R/deploy_shiny-r6.R +++ b/R/deploy_shiny-r6.R @@ -6,7 +6,7 @@ check_deploy_shiny = R6::R6Class( "check_deploy_shiny", inherit = audit.base::base_check, public = list( - #' @description Checks deployment of an R Markdown document with Word Docx output + #' @description Checks deployment of a Shiny app #' @param debug_level See check() for details check = function(debug_level) { shiny_dir = system.file( diff --git a/R/quarto-helpers.R b/R/quarto-helpers.R index 0f81491..abcaed2 100644 --- a/R/quarto-helpers.R +++ b/R/quarto-helpers.R @@ -19,20 +19,21 @@ get_quarto_old_users = function(out) { ) / months(1), #nolint last_log_in = dplyr::case_when( + is.na(last_log_on_diff) ~ "Never logged in", last_log_on_diff > 12 ~ "12 months+", - last_log_on_diff > 6 & last_log_on_diff < 12 ~ "6 months+", - last_log_on_diff > 3 & last_log_on_diff < 6 ~ "3 months+", + last_log_on_diff > 6 ~ "6 months+", + last_log_on_diff > 3 ~ "3 months+", .default = NA ) ) |> - dplyr::filter(.data$last_log_on_diff > 3) |> + dplyr::filter(!is.na(.data$last_log_in)) |> dplyr::group_by(.data$last_log_in) |> dplyr::reframe(email = paste(.data$email, collapse = ", ")) |> dplyr::mutate( last_log_in = factor( .data$last_log_in, - c("12 months+", "6 months+", "3 months+"), + c("Never logged in", "12 months+", "6 months+", "3 months+"), ordered = TRUE ) ) |> diff --git a/R/summarise_setup.R b/R/summarise_setup.R index 4ae95ec..b15458f 100644 --- a/R/summarise_setup.R +++ b/R/summarise_setup.R @@ -1,7 +1,7 @@ # Note: storing variables in .connect as in the (near) future, we'll want to pass # keys / servers as variables summarise_setup = function(server, token) { - cli::cli_h2("Summary audit.connect v{packageVersion('audit.connect')}") + cli::cli_h2("Summary audit.connect v{utils::packageVersion('audit.connect')}") setup = list() setup$server = check_server(server) setup$api_key = check_api_key(token) diff --git a/README.Rmd b/README.Rmd index 8d3f955..a53855f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -64,8 +64,8 @@ If you aren't using binaries or the OS is the same, then you won't have any issu If you want to be sure, we can use {renv} ```{r, eval = FALSE} -# Ensure we don't use the existing renv cache -fs::dir_delete(renv::paths$cache()) +# Use a new, empty cache for this project, leaving your own renv cache alone +Sys.setenv(RENV_PATHS_CACHE = tempfile("renv-cache-")) # Use the slow installation method renv::init(repo = "https://cloud.r-project.org/") renv::install("remotes") diff --git a/README.md b/README.md index ff2b21a..72e8548 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ If you aren’t using binaries or the OS is the same, then you won’t have any issue. If you want to be sure, we can use {renv} ``` r -# Ensure we don't use the existing renv cache -fs::dir_delete(renv::paths$cache()) +# Use a new, empty cache for this project, leaving your own renv cache alone +Sys.setenv(RENV_PATHS_CACHE = tempfile("renv-cache-")) # Use the slow installation method renv::init(repo = "https://cloud.r-project.org/") renv::install("remotes") diff --git a/man/check_deploy_plumber_api.Rd b/man/check_deploy_plumber_api.Rd index ecb42aa..ea4bf81 100644 --- a/man/check_deploy_plumber_api.Rd +++ b/man/check_deploy_plumber_api.Rd @@ -12,13 +12,13 @@ R6 classes Check classes } \section{Super classes}{ -\code{\link[audit.base:logger]{audit.base::logger}} -> \code{\link[audit.base:base_check]{audit.base::base_check}} -> \code{check_deploy_pluumber_api} +\code{\link[audit.base:logger]{audit.base::logger}} -> \code{\link[audit.base:base_check]{audit.base::base_check}} -> \code{check_deploy_plumber_api} } \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-check_deploy_pluumber_api-check}{\code{check_deploy_plumber_api$check()}} -\item \href{#method-check_deploy_pluumber_api-clone}{\code{check_deploy_plumber_api$clone()}} +\item \href{#method-check_deploy_plumber_api-check}{\code{check_deploy_plumber_api$check()}} +\item \href{#method-check_deploy_plumber_api-clone}{\code{check_deploy_plumber_api$clone()}} } } \if{html}{\out{ @@ -33,8 +33,8 @@ Check classes }} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-check_deploy_pluumber_api-check}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-check_deploy_plumber_api-check}{}}} \subsection{Method \code{check()}}{ Checks deployment of a Plumber API \subsection{Usage}{ @@ -50,8 +50,8 @@ Checks deployment of a Plumber API } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-check_deploy_pluumber_api-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-check_deploy_plumber_api-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/check_deploy_python_flask.Rd b/man/check_deploy_python_flask.Rd index b003f15..57652d5 100644 --- a/man/check_deploy_python_flask.Rd +++ b/man/check_deploy_python_flask.Rd @@ -36,7 +36,7 @@ Check classes \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-check_deploy_python_flask-check}{}}} \subsection{Method \code{check()}}{ -Checks deployment of Quarto document with HTML output +Checks deployment of a Python Flask API \subsection{Usage}{ \if{html}{\out{
}}\preformatted{check_deploy_python_flask$check(debug_level)}\if{html}{\out{
}} } diff --git a/man/check_deploy_shiny.Rd b/man/check_deploy_shiny.Rd index a8dca53..e98c7cb 100644 --- a/man/check_deploy_shiny.Rd +++ b/man/check_deploy_shiny.Rd @@ -36,7 +36,7 @@ Check classes \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-check_deploy_shiny-check}{}}} \subsection{Method \code{check()}}{ -Checks deployment of an R Markdown document with Word Docx output +Checks deployment of a Shiny app \subsection{Usage}{ \if{html}{\out{
}}\preformatted{check_deploy_shiny$check(debug_level)}\if{html}{\out{
}} } diff --git a/man/deploy_python.Rd b/man/deploy_python.Rd index bd43855..a725598 100644 --- a/man/deploy_python.Rd +++ b/man/deploy_python.Rd @@ -8,7 +8,7 @@ deploy_python( python_dir, python_files = c("app.py", "index.qmd", "hash.ipynb"), rsconnect_type = c("api", "streamlit", "quarto", "voila"), - debug_level = debug_level + debug_level ) } \arguments{ diff --git a/tests/testthat/test-quarto-helpers.R b/tests/testthat/test-quarto-helpers.R index 2252f9b..1a5b0bf 100644 --- a/tests/testthat/test-quarto-helpers.R +++ b/tests/testthat/test-quarto-helpers.R @@ -7,3 +7,20 @@ test_that("get_quarto_locked_user_apps() counts content owned by locked users", # An evaluation licence skips the user audit expect_identical(get_quarto_locked_user_apps(list(users_details = list(locked_apps = NULL))), 0L) }) + +test_that("get_quarto_old_users lists users who have never logged in", { + now = lubridate::now() + users = tibble::tibble( + email = c("a@x.com", "b@x.com", "c@x.com", "d@x.com"), + locked = FALSE, + active_time = c(now - lubridate::days(400), now - lubridate::days(200), now, NA) + ) + out = list(users_details = list(user_list = list(users = users))) + + old_users = get_quarto_old_users(out) + expect_equal( + as.character(old_users$last_log_in), + c("Never logged in", "12 months+", "6 months+") + ) + expect_equal(old_users$email, c("d@x.com", "a@x.com", "b@x.com")) +}) From 4aa6d1630205a81babde629ecae3182e81498c72 Mon Sep 17 00:00:00 2001 From: Colin Gillespie Date: Fri, 16 Oct 2026 17:14:34 +0100 Subject: [PATCH 2/2] chore: Bump version to 0.8.9 --- DESCRIPTION | 2 +- NEWS.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 59e0874..c13850d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: audit.connect Title: Posit Connect Health Check -Version: 0.8.8 +Version: 0.8.9 Authors@R: person("Jumping", "Rivers", , "info@jumpingrivers.com", role = c("aut", "cre")) Description: Posit Connect Health Check. Deploys various content types to diff --git a/NEWS.md b/NEWS.md index e5c77a5..8982763 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,14 @@ +# audit.connect 0.8.9 + +## Bug fixes and minor improvements + +* Any exported class whose name starts with `check_` is run as a check, as the README says. Before, only `check_deploy*` classes were (#72). +* `check()` prints the user summary once (#72). +* The Git-backed check deploys with its own title, so its content is deleted afterwards (#72). +* Python clean-up deletes only content owned by the auditor (#72). +* The report's old-users table lists users who have never logged in. Users inactive for exactly 6 or 12 months are no longer left out (#72). +* The README's renv advice uses a temporary cache, instead of deleting the user's whole renv cache (#72). + # audit.connect 0.8.8 ## Bug fixes and minor improvements