diff --git a/.Rbuildignore b/.Rbuildignore index d69425f..9a069ab 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -18,3 +18,5 @@ rsconnect/ ^report.html$ ^README\.Rmd$ ^\.github$ +^[.]?air[.]toml$ +^\.vscode$ diff --git a/DESCRIPTION b/DESCRIPTION index 64ac020..9746077 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -38,4 +38,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 diff --git a/NEWS.md b/NEWS.md index b3a01be..38877b5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ +# audit.connect 0.7.6 _2025-07-16_ + - chore: Formatting using air + - chore: Update linting + # audit.connect 0.7.5 _2024-07-24_ - fix: Remove feature usage - experimental API no longer works + # audit.connect 0.7.5 _2024-06-10_ - fix: Use latest version of {connactapi} - chore: Fix Rd warnings due to R4.4 diff --git a/R/audit_details.R b/R/audit_details.R index dc51a7d..49f219b 100644 --- a/R/audit_details.R +++ b/R/audit_details.R @@ -1,27 +1,33 @@ - audit_details = function(server, token) { user_details = summarise_user(server, token) - c(user_details, - list(server = server, - time = Sys.time(), - version = utils::packageVersion("audit.connect")) + c( + user_details, + list( + server = server, + time = Sys.time(), + version = utils::packageVersion("audit.connect") + ) ) } # Details on the user running this audit summarise_user = function(server, token) { cli::cli_h2("User Summary") - res = httr::GET(paste0(server, "__api__/v1/user"), - httr::add_headers(Authorization = paste("Key", token))) + res = httr::GET( + paste0(server, "__api__/v1/user"), + httr::add_headers(Authorization = paste("Key", token)) + ) check_api_status_code(res) content = httr::content(res) - cli::cli_alert_info("{content$first_name} {content$last_name} <{content$email}>") + cli::cli_alert_info( + "{content$first_name} {content$last_name} <{content$email}>" + ) cli::cli_alert_info("Username: {content$username}") cli::cli_alert_info("Role: {content$user_role}") set_key("username", content$username) set_key("connect_account", content$username) - return(invisible(content)) + invisible(content) } check_api_status_code = function(res) { @@ -31,8 +37,10 @@ check_api_status_code = function(res) { cli::cli_alert_danger("Likely causes are: bad token or bad server URL") cli::cli_alert_danger("{res$url} has status code {status}") cli::cli_alert_danger("{httr::http_status(status)$message}") - cli::cli_alert_danger("You could try changing http <-> https in the server URL") + cli::cli_alert_danger( + "You could try changing http <-> https in the server URL" + ) cli::cli_abort("Status code: {status}") } - return(invisible(NULL)) + invisible(NULL) } diff --git a/R/check.R b/R/check.R index 79ae0f6..1ec9905 100644 --- a/R/check.R +++ b/R/check.R @@ -15,23 +15,27 @@ #' @importFrom rlang .data .env #' @import audit.base #' @export -check = function(server = NULL, token = NULL, - dir = ".", - debug_level = 0:2) { - +check = function(server = NULL, token = NULL, dir = ".", debug_level = 0:2) { debug_level = get_debug_level(force(debug_level)) check_list = list() check_list$setup = summarise_setup(server, token) check_list$audit_details = audit_details(get_server(), get_token()) check_list$server_headers = audit.base::check_server_headers(get_server()) - check_list$posit_version = check_posit_version(get_server(), get_token(), - debug_level = debug_level) + check_list$posit_version = check_posit_version( + get_server(), + get_token(), + debug_level = debug_level + ) check_list$sys_deps = check_sys_deps(debug_level = debug_level) check_list$versions = summarise_versions(get_server(), get_token()) - # check_list$feature_usage = summarise_feature_usage(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(get_server(), get_token(), debug_level = debug_level) + check_list$users_details = summarise_users( + get_server(), + get_token(), + debug_level = debug_level + ) register_uat_user(get_server(), get_token(), account = get_account()) check_list$results = check_deployments(dir, debug_level) @@ -53,5 +57,5 @@ init_r6_check = function(export, dir, file) { } else { obj = NULL } - return(obj) + obj } diff --git a/R/check_deployments.R b/R/check_deployments.R index 6cb9c03..d980f39 100644 --- a/R/check_deployments.R +++ b/R/check_deployments.R @@ -2,7 +2,7 @@ check_deployments = function(dir, debug_level) { cli::cli_h2("Document Deployment Checks") r6_inits = init_r6_checks(dir = dir, file = "config-uat-rsc.yml") lapply(r6_inits, function(r6) r6$check(debug_level = debug_level)) - deployments = purrr::map_dfr(r6_inits, ~.x$get_log()) + deployments = purrr::map_dfr(r6_inits, ~ .x$get_log()) deployments = dplyr::arrange(deployments, .data$group, .data$short) deployments } diff --git a/R/check_posit_version.R b/R/check_posit_version.R index dcc18d9..9901553 100644 --- a/R/check_posit_version.R +++ b/R/check_posit_version.R @@ -4,6 +4,9 @@ check_posit_version = function(server, token, debug_level) { client = suppress(connectapi::connect(server = server, api_key = token)) posit_version = client$server_settings()$version - audit.base::audit_posit_version(posit_version = posit_version, type = "connect") - return(posit_version) + audit.base::audit_posit_version( + posit_version = posit_version, + type = "connect" + ) + posit_version } diff --git a/R/check_sys_deps.R b/R/check_sys_deps.R index cb44c87..3d2db12 100644 --- a/R/check_sys_deps.R +++ b/R/check_sys_deps.R @@ -8,16 +8,24 @@ check_sys_deps = function(debug_level = 0:2) { debug_level = get_debug_level(debug_level) app_dir = file.path(tempdir(), "pkg") - pkg_dir = system.file("extdata", "check_sys_deps", - package = "audit.connect", - mustWork = TRUE) + pkg_dir = system.file( + "extdata", + "check_sys_deps", + package = "audit.connect", + mustWork = TRUE + ) fs::dir_copy(pkg_dir, app_dir, overwrite = TRUE) on.exit(cleanup_plumber(app_dir, content, debug_level)) - content = try(setup_plumber_sys_deps_endpoint(app_dir, debug_level), silent = TRUE) + content = try( + setup_plumber_sys_deps_endpoint(app_dir, debug_level), + silent = TRUE + ) if ("try-error" %in% class(content)) { cli::cli_alert_info("Can't deploy plumber end point - odd?") cli::cli_alert_info("Check the logs on {get_server()}") - cli::cli_alert_info("In the past, it was due to a missing sodium linux dependency") + cli::cli_alert_info( + "In the past, it was due to a missing sodium linux dependency" + ) return(NA) } @@ -31,21 +39,28 @@ check_sys_deps = function(debug_level = 0:2) { audit.base::check_sys_deps(rtn$os_release, clean_libs, debug_level) } -setup_plumber_sys_deps_endpoint = function(app_dir, debug_level) { #nolint +setup_plumber_sys_deps_endpoint = function(app_dir, debug_level) { suppress = get_suppress(debug_level) # Deploy plumber - client = suppress(connectapi::connect(server = get_server(), api_key = get_token())) + client = suppress(connectapi::connect( + server = get_server(), + api_key = get_token() + )) bundle = suppress(connectapi::bundle_dir(app_dir)) name = paste("UAT_check_package-", Sys.Date(), sep = "_") content = suppress(connectapi::deploy(client, bundle, name = name)) suppress(connectapi::poll_task(content)) - return(content) + content } # Extracting the URL differs on first deployment and subsequent deployments get_deploy_url = function(content) { url = content$get_url() - if (is.null(url)) url = content$get_content()$url - if (is.null(url)) stop("Missing deploy url") - return(url) + if (is.null(url)) { + url = content$get_content()$url + } + if (is.null(url)) { + stop("Missing deploy url") + } + url } diff --git a/R/create_config.R b/R/create_config.R index 372e3ba..9fab2ee 100644 --- a/R/create_config.R +++ b/R/create_config.R @@ -16,5 +16,7 @@ #' * force: overwrite existing file #' * error: if a config file exists, raise an error #' @export -create_config = audit.base::create_config(file = "config-uat-rsc.yml", - pkg_name = "audit.connect") +create_config = audit.base::create_config( + file = "config-uat-rsc.yml", + pkg_name = "audit.connect" +) diff --git a/R/deploy_app.R b/R/deploy_app.R index f6f0d3a..3fc6f68 100644 --- a/R/deploy_app.R +++ b/R/deploy_app.R @@ -1,5 +1,9 @@ # utility functions for testing Rmd | shiny deployments -deploy_app = function(rmd_dir, debug_level, appFiles = c("index.Rmd", "app.R")) { #nolint +deploy_app = function( + rmd_dir, + debug_level, + appFiles = c("index.Rmd", "app.R") #nolint +) { tmp_dir = tempdir() file.copy(rmd_dir, tmp_dir, recursive = TRUE) @@ -10,7 +14,7 @@ deploy_app = function(rmd_dir, debug_level, appFiles = c("index.Rmd", "app.R")) deploy_name = get_deploy_name(app_dir) push_to_connect(bundle_dir = app_dir, deploy_name = deploy_name, debug_level) - return(invisible(TRUE)) + invisible(TRUE) } get_deploy_name = function(rmd_path) { @@ -22,20 +26,29 @@ get_deploy_name = function(rmd_path) { push_to_connect = function(bundle_dir, deploy_name, debug_level) { suppress = get_suppress(debug_level) # Need: CONNECT_API_KEY and CONNECT_SERVER - client = suppress(connectapi::connect(server = get_server(), api_key = get_token())) + client = suppress(connectapi::connect( + server = get_server(), + api_key = get_token() + )) bundle = suppress(connectapi::bundle_dir(bundle_dir)) # If deploy not successful, content not created content = suppress(connectapi::deploy(client, bundle, title = deploy_name)) on.exit(cleanup_app(bundle_dir, content, debug_level)) suppress(connectapi::poll_task(content)) - return(invisible(NULL)) + invisible(NULL) } cleanup_app = function(bundle_dir, content, debug_level) { - if (debug_level == 2) return(invisible(NULL)) + if (debug_level == 2) { + return(invisible(NULL)) + } suppress = get_suppress(debug_level) - if (exists("content")) suppress(connectapi::content_delete(content, force = TRUE)) - if (!is.null(bundle_dir) && file.exists(bundle_dir)) fs::dir_delete(bundle_dir) - return(invisible(NULL)) + if (exists("content")) { + suppress(connectapi::content_delete(content, force = TRUE)) + } + if (!is.null(bundle_dir) && file.exists(bundle_dir)) { + fs::dir_delete(bundle_dir) + } + invisible(NULL) } diff --git a/R/deploy_flask-r6.R b/R/deploy_flask-r6.R index f26f108..837f60d 100644 --- a/R/deploy_flask-r6.R +++ b/R/deploy_flask-r6.R @@ -11,15 +11,22 @@ check_deploy_python_flask = R6::R6Class( check = function(debug_level) { if (isFALSE(.connect$rsconnect_python)) { cli::cli_alert_info("rsconnect-python missing. Skipping Python test.") - return(invisible(NULL)) + invisible(NULL) } - python_dir = system.file("extdata", private$group, private$short, - package = "audit.connect", mustWork = TRUE) - private$checker(deploy_python(python_dir, - python_files = "app.py", - rsconnect_type = "api", - debug_level = debug_level)) - return(invisible(NULL)) + python_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.connect", + mustWork = TRUE + ) + private$checker(deploy_python( + python_dir, + python_files = "app.py", + rsconnect_type = "api", + debug_level = debug_level + )) + invisible(NULL) } ), private = list( diff --git a/R/deploy_git-r6.R b/R/deploy_git-r6.R index 7d232c3..5884b03 100644 --- a/R/deploy_git-r6.R +++ b/R/deploy_git-r6.R @@ -9,10 +9,12 @@ check_deploy_github = R6::R6Class( #' @description Deploy a shiny application from github #' @param debug_level See check() for details check = function(debug_level) { - private$checker(deploy_git(repository = "https://github.com/uat-examples/old-faithful", - debug_level = debug_level, - title = paste("UAT", private$short))) - return(invisible(NULL)) + private$checker(deploy_git( + repository = "https://github.com/uat-examples/old-faithful", + debug_level = debug_level, + title = paste("UAT", private$short) + )) + invisible(NULL) } ), private = list( diff --git a/R/deploy_git.R b/R/deploy_git.R index dd3404e..1c3dad6 100644 --- a/R/deploy_git.R +++ b/R/deploy_git.R @@ -1,17 +1,21 @@ deploy_git = function(repository, debug_level, title) { suppress = get_suppress(debug_level) - client = suppress(connectapi::connect(server = get_server(), api_key = get_token())) - content = suppress(connectapi::deploy_repo(client, - repository, - branch = "main", - subdirectory = ".", - title = "UAT") - ) + client = suppress(connectapi::connect( + server = get_server(), + api_key = get_token() + )) + content = suppress(connectapi::deploy_repo( + client, + repository, + branch = "main", + subdirectory = ".", + title = "UAT" + )) # If deploy not successful, content not created on.exit(cleanup_app(bundle_dir = NULL, content, debug_level)) suppress(connectapi::poll_task(content)) - return(invisible(TRUE)) + invisible(TRUE) } diff --git a/R/deploy_pins-r6.R b/R/deploy_pins-r6.R index bdf2356..4f8051f 100644 --- a/R/deploy_pins-r6.R +++ b/R/deploy_pins-r6.R @@ -10,7 +10,7 @@ check_deploy_pins_rds = R6::R6Class( #' @param debug_level See check() for details check = function(debug_level) { private$checker(deploy_pins(debug_level = debug_level)) - return(invisible(NULL)) + invisible(NULL) } ), private = list( diff --git a/R/deploy_pins.R b/R/deploy_pins.R index 8e8abd1..63c0f14 100644 --- a/R/deploy_pins.R +++ b/R/deploy_pins.R @@ -1,8 +1,11 @@ deploy_pins = function(debug_level) { suppress = get_suppress(debug_level) - board = suppress(pins::board_connect(auth = "manual", versioned = TRUE, - server = get_server(), - key = get_token())) + board = suppress(pins::board_connect( + auth = "manual", + versioned = TRUE, + server = get_server(), + key = get_token() + )) tmp_env = new.env() utils::data("mtcars", package = "datasets", envir = tmp_env) @@ -19,8 +22,12 @@ deploy_pins = function(debug_level) { type = "rds" ) ) - if (debug_level != 2L) on.exit(pins::pin_delete(board, deployed_pin)) + if (debug_level != 2L) { + on.exit(pins::pin_delete(board, deployed_pin)) + } pulled_data = pins::pin_read(board, deployed_pin) - if (!identical(pulled_data, tmp_env$mtcars)) stop("Corrupted pins data") - return(invisible(TRUE)) + if (!identical(pulled_data, tmp_env$mtcars)) { + stop("Corrupted pins data") + } + invisible(TRUE) } diff --git a/R/deploy_plumber-r6.R b/R/deploy_plumber-r6.R index e566dbc..1446b04 100644 --- a/R/deploy_plumber-r6.R +++ b/R/deploy_plumber-r6.R @@ -9,10 +9,14 @@ check_deploy_plumber_api = R6::R6Class( #' @description Checks deployment of a Plumber API #' @param debug_level See check() for details check = function(debug_level) { - plumber_dir = system.file("extdata", private$group, - package = "audit.connect", mustWork = TRUE) + plumber_dir = system.file( + "extdata", + private$group, + package = "audit.connect", + mustWork = TRUE + ) private$checker(deploy_plumber(plumber_dir, debug_level = debug_level)) - return(invisible(NULL)) + invisible(NULL) } ), private = list( diff --git a/R/deploy_plumber.R b/R/deploy_plumber.R index 2689067..9f8cd2e 100644 --- a/R/deploy_plumber.R +++ b/R/deploy_plumber.R @@ -4,7 +4,10 @@ deploy_plumber = function(plumber_dir, debug_level) { fs::dir_copy(plumber_dir, app_dir) on.exit(cleanup_plumber(app_dir, content, debug_level)) - client = suppress(connectapi::connect(server = get_server(), api_key = get_token())) + client = suppress(connectapi::connect( + server = get_server(), + api_key = get_token() + )) bundle = suppress(connectapi::bundle_dir(app_dir)) # If deploy not successful, content not created @@ -19,12 +22,14 @@ deploy_plumber = function(plumber_dir, debug_level) { resp = httr::GET(url, httr::add_headers(Authorization = auth_key)) check_response = grepl(pattern = "testing", rawToChar(resp$content)) - return(invisible(check_response)) + invisible(check_response) } cleanup_plumber = function(bundle_dir, content, debug_level) { suppress = get_suppress(debug_level) - if (debug_level == 2) return(NULL) + if (debug_level == 2) { + return(NULL) + } if (exists("content") && "Connect" %in% class(content)) { suppress(connectapi::content_delete(content, force = TRUE)) } diff --git a/R/deploy_python.R b/R/deploy_python.R index c8783d2..0b15914 100644 --- a/R/deploy_python.R +++ b/R/deploy_python.R @@ -12,10 +12,12 @@ #' * Use the python interface to grab the guid #' * Clean up. #' @noRd -deploy_python = function(python_dir, - python_files = c("app.py", "index.qmd"), - rsconnect_type = c("api", "streamlit", "quarto"), - debug_level = debug_level) { +deploy_python = function( + python_dir, + python_files = c("app.py", "index.qmd"), + rsconnect_type = c("api", "streamlit", "quarto"), + debug_level = debug_level +) { suppress = get_suppress(debug_level) rsconnect_type = match.arg(rsconnect_type) tmp_dir = file.path(tempdir(), paste0("UAT_python-", rsconnect_type)) @@ -27,39 +29,65 @@ deploy_python = function(python_dir, file.copy(file.path(python_dir, python_files), tmp_dir) title = paste("UAT: Python", rsconnect_type, Sys.Date(), sep = "-") has_deployed = suppress( - processx::run("rsconnect", - args = c("deploy", - rsconnect_type, - ifelse("index.qmd" %in% python_files, "index.qmd", "."), - "--server", get_server(), - "--api-key", get_token(), - "--new", - "--title", title), - wd = tmp_dir)) + processx::run( + "rsconnect", + args = c( + "deploy", + rsconnect_type, + ifelse("index.qmd" %in% python_files, "index.qmd", "."), + "--server", + get_server(), + "--api-key", + get_token(), + "--new", + "--title", + title + ), + wd = tmp_dir + ) + ) - content = processx::run("rsconnect", - args = c("content", "search", - "--server", get_server(), - "--api-key", get_token(), - "--title-contains", title), - wd = tmp_dir) + content = processx::run( + "rsconnect", + args = c( + "content", + "search", + "--server", + get_server(), + "--api-key", + get_token(), + "--title-contains", + title + ), + wd = tmp_dir + ) guid = jsonlite::fromJSON(content$stdout)$guid # Redoing on.exit to clean-up content on.exit(cleanup_python(tmp_dir, debug_level, guid = guid)) - return(invisible(has_deployed$status == 0)) + invisible(has_deployed$status == 0) } cleanup_python = function(tmp_dir, debug_level, guid = NULL) { - if (debug_level == 2) return(NULL) + if (debug_level == 2) { + return(NULL) + } suppress = get_suppress(debug_level) fs::dir_delete(tmp_dir) # map in case we make multiple mistakes - con = suppress(connectapi::connect(server = get_server(), api_key = get_token())) #nolint - purrr::map(guid, ~{ - item = connectapi::content_item(con, .x) - suppress(connectapi::content_delete(item, force = TRUE)) - }) + con = suppress( + connectapi::connect( + server = get_server(), + api_key = get_token() + ) + ) + purrr::map( + guid, + ~ { + item = connectapi::content_item(con, .x) + suppress(connectapi::content_delete(item, force = TRUE)) + } + ) - return(invisible(NULL)) + invisible(NULL) } diff --git a/R/deploy_quarto-r6.R b/R/deploy_quarto-r6.R index 4b093b8..514bf79 100644 --- a/R/deploy_quarto-r6.R +++ b/R/deploy_quarto-r6.R @@ -6,10 +6,13 @@ #' @rawNamespace export(check_deploy_quarto_rsvg_convert) NULL types = c("beamer", "docx", "html", "observable", "pdf", "rsvg_convert") -longs = paste0("Checking that quarto can render a document (type: `", types, "`)") +longs = paste0( + "Checking that quarto can render a document (type: `", + types, + "`)" +) longs[6] = "Checking that quarto can render SVG image within a PDF" for (i in seq_along(types)) { - assign( paste0("check_deploy_quarto_", types[i]), R6::R6Class( @@ -17,10 +20,16 @@ for (i in seq_along(types)) { inherit = audit.base::base_check, public = list( check = function(debug_level, account = NULL) { - quarto_dir = system.file("extdata", private$group, private$short, - package = "audit.base", mustWork = TRUE) + quarto_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.base", + mustWork = TRUE + ) private$checker( - deploy_quarto(quarto_dir, account, debug_level = debug_level)) + deploy_quarto(quarto_dir, account, debug_level = debug_level) + ) return(invisible(NULL)) } @@ -54,14 +63,24 @@ check_deploy_quarto_python = R6::R6Class( #' @param account Connect username #' @param debug_level See check() for details check = function(debug_level, account = NULL) { - if (is.null(account)) account = get_username() - python_dir = system.file("extdata", private$group, private$short, - package = "audit.base", mustWork = TRUE) + if (is.null(account)) { + account = get_username() + } + python_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.base", + mustWork = TRUE + ) private$checker( - deploy_python(python_dir, - python_files = "index.qmd", - rsconnect_type = "quarto", - debug_level = debug_level)) + deploy_python( + python_dir, + python_files = "index.qmd", + rsconnect_type = "quarto", + debug_level = debug_level + ) + ) return(invisible(NULL)) } ), diff --git a/R/deploy_quarto.R b/R/deploy_quarto.R index 4051972..60b4cdf 100644 --- a/R/deploy_quarto.R +++ b/R/deploy_quarto.R @@ -1,4 +1,8 @@ -deploy_quarto = function(quarto_dir, account = NULL, debug_level = debug_level) { +deploy_quarto = function( + quarto_dir, + account = NULL, + debug_level = debug_level +) { suppress = get_suppress(debug_level) tmp_dir = file.path(tempdir(), "quarto", basename(quarto_dir)) @@ -7,27 +11,36 @@ deploy_quarto = function(quarto_dir, account = NULL, debug_level = debug_level) fs::dir_copy(quarto_dir, tmp_dir, overwrite = TRUE) title = paste("UAT: Quarto", Sys.Date()) # quarto uses rsconnect::accounts. So, looks up the server in list of accounts - if (is.null(account)) account = get_account() - has_deployed = suppress(quarto::quarto_publish_doc(file.path(tmp_dir, "index.qmd"), - render = "server", - title = title, - server = get_server(clean = TRUE), - account = account, - launch.browser = FALSE, - forceUpdate = TRUE, - logLevel = "quiet")) + if (is.null(account)) { + account = get_account() + } + has_deployed = suppress(quarto::quarto_publish_doc( + file.path(tmp_dir, "index.qmd"), + render = "server", + title = title, + server = get_server(clean = TRUE), + account = account, + launch.browser = FALSE, + forceUpdate = TRUE, + logLevel = "quiet" + )) return(invisible(has_deployed)) } cleanup_quarto = function(tmp_dir, debug_level) { - if (debug_level == 2) return(NULL) + if (debug_level == 2) { + return(NULL) + } suppress = get_suppress(debug_level) fnames = list.files(tmp_dir, recursive = TRUE, full.names = TRUE) dcf = fnames[stringr::str_detect(fnames, "(.*)\\.dcf$")] dcf_contents = read.dcf(dcf) url = dcf_contents[1, "url"] guid = stringr::str_match_all(url, "content/(.*)/")[[1]][, 2] - con = suppress(connectapi::connect(server = get_server(), api_key = get_token())) + con = suppress(connectapi::connect( + server = get_server(), + api_key = get_token() + )) item = connectapi::content_item(con, guid = guid) suppress(connectapi::content_delete(item, force = TRUE)) fs::dir_delete(tmp_dir) diff --git a/R/deploy_rmd-r6.R b/R/deploy_rmd-r6.R index 256e7dd..1a044ca 100644 --- a/R/deploy_rmd-r6.R +++ b/R/deploy_rmd-r6.R @@ -11,19 +11,29 @@ for (type in types) { inherit = audit.base::base_check, public = list( check = function(debug_level) { - rmd_dir = system.file("extdata", private$group, private$short, - package = "audit.base", mustWork = TRUE) + rmd_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.base", + mustWork = TRUE + ) private$checker( - deploy_app(rmd_dir, debug_level = debug_level)) + deploy_app(rmd_dir, debug_level = debug_level) + ) - return(invisible(NULL)) + invisible(NULL) } ), private = list( context = paste("Deploying", type), short = type, group = "render_rmd", - long = paste0("Checking that Rmarkdown can render a document (type: `", type, "`)") + long = paste0( + "Checking that Rmarkdown can render a document (type: `", + type, + "`)" + ) ) ) ) diff --git a/R/deploy_shiny-r6.R b/R/deploy_shiny-r6.R index 456b04d..6542da3 100644 --- a/R/deploy_shiny-r6.R +++ b/R/deploy_shiny-r6.R @@ -9,10 +9,15 @@ check_deploy_shiny = R6::R6Class( #' @description Checks deployment of an R Markdown document with Word Docx output #' @param debug_level See check() for details check = function(debug_level) { - shiny_dir = system.file("extdata", private$group, private$short, - package = "audit.connect", mustWork = TRUE) + shiny_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.connect", + mustWork = TRUE + ) private$checker(deploy_app(shiny_dir, debug_level, appFiles = "app.R")) - return(invisible(NULL)) + invisible(NULL) } ), private = list( diff --git a/R/deploy_streamlit-r6.R b/R/deploy_streamlit-r6.R index 0ee14fa..df5ba45 100644 --- a/R/deploy_streamlit-r6.R +++ b/R/deploy_streamlit-r6.R @@ -13,13 +13,20 @@ check_deploy_python_streamlit = R6::R6Class( cli::cli_alert_info("rsconnect-python missing. Skipping Python test.") return(invisible(NULL)) } - python_dir = system.file("extdata", private$group, private$short, - package = "audit.connect", mustWork = TRUE) - private$checker(deploy_python(python_dir, - python_files = "app.py", - rsconnect_type = "streamlit", - debug_level = debug_level)) - return(invisible(NULL)) + python_dir = system.file( + "extdata", + private$group, + private$short, + package = "audit.connect", + mustWork = TRUE + ) + private$checker(deploy_python( + python_dir, + python_files = "app.py", + rsconnect_type = "streamlit", + debug_level = debug_level + )) + invisible(NULL) } ), private = list( diff --git a/R/quarto-helpers.R b/R/quarto-helpers.R index e3b32bd..4d9f298 100644 --- a/R/quarto-helpers.R +++ b/R/quarto-helpers.R @@ -12,19 +12,30 @@ get_quarto_old_users = function(out) { old_users = dplyr::arrange(old_users, .data$domain, .data$email) old_users = old_users %>% - dplyr::mutate(last_log_on_diff = lubridate::interval(.data$active_time, lubridate::now()) / months(1), #nolint - last_log_in = dplyr::case_when( - 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+", - .default = NA)) %>% + dplyr::mutate( + last_log_on_diff = lubridate::interval( + .data$active_time, + lubridate::now() + ) / + months(1), #nolint + last_log_in = dplyr::case_when( + 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+", + .default = NA + ) + ) %>% dplyr::filter(.data$last_log_on_diff > 3) %>% 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+"), - ordered = TRUE)) %>% + dplyr::mutate( + last_log_in = factor( + .data$last_log_in, + c("12 months+", "6 months+", "3 months+"), + ordered = TRUE + ) + ) %>% dplyr::arrange(.data$last_log_in) old_users$n = stringr::str_count(old_users$email, ",") + 1 old_users @@ -45,11 +56,14 @@ get_quarto_user_roles = function(out) { #' @export get_quarto_locked_user_apps = function(out) { apps = out$users_details$apps - if (is.na(apps)) return(0L) + if (is.na(apps)) { + return(0L) + } - app_creators = purrr::map_df(apps, - ~dplyr::tibble(owner = .x[["owner_username"]], - locked = .x$owner_locked)) + app_creators = purrr::map_df( + apps, + ~ dplyr::tibble(owner = .x[["owner_username"]], locked = .x$owner_locked) + ) locked_users = dplyr::filter(app_creators, .data$locked) # nolint: object_usage_linter nrow(locked_users) } diff --git a/R/register-uat-user.R b/R/register-uat-user.R index 1452752..05ca34d 100644 --- a/R/register-uat-user.R +++ b/R/register-uat-user.R @@ -1,13 +1,18 @@ # if the server exists, everything still works register_uat_user = function(server, token, account) { clean_url = get_server(clean = TRUE) - rsconnect::addServer(paste0(server, "__api__"), name = clean_url, quiet = TRUE) + rsconnect::addServer( + paste0(server, "__api__"), + name = clean_url, + quiet = TRUE + ) rsconnect::connectApiUser( server = clean_url, account = account, apiKey = token, - quiet = TRUE) + quiet = TRUE + ) - return(invisible(NULL)) + invisible(NULL) } diff --git a/R/set_key.R b/R/set_key.R index 67a3ea2..5a70c9b 100644 --- a/R/set_key.R +++ b/R/set_key.R @@ -4,7 +4,7 @@ set_key = function(name, value) { error_msg = paste0("Missing ", name) stop(error_msg, call. = FALSE) } - return(invisible(value)) + invisible(value) } get_account = function() { @@ -18,9 +18,13 @@ get_server = function(clean = FALSE) { server = get_value("CONNECT_SERVER") set_key("connect_server", server) } - if (is.null(server)) cli::cli_abort("Missing server") - if (clean) server = stringr::str_remove_all(server, "https?|:|/") - return(server) + if (is.null(server)) { + cli::cli_abort("Missing server") + } + if (clean) { + server = stringr::str_remove_all(server, "https?|:|/") + } + server } get_token = function() { @@ -29,17 +33,23 @@ get_token = function() { token = get_value("CONNECT_API_KEY") set_key("connect_api_key", token) } - if (is.null(token)) stop("Can't find CONNECT_API_KEY") - return(token) + if (is.null(token)) { + stop("Can't find CONNECT_API_KEY") + } + token } get_username = function() { user = .connect[["username"]] - if (is.null(user)) stop("Can't find username") - return(user) + if (is.null(user)) { + stop("Can't find username") + } + user } get_value = function(env_name, value = NULL) { - if (is.null(value) || nchar(value) == 0L) value = Sys.getenv(env_name, NA) - return(value) + if (is.null(value) || nchar(value) == 0L) { + value = Sys.getenv(env_name, NA) + } + value } diff --git a/R/summarise_feat_usage.R b/R/summarise_feat_usage.R index 8461f18..ff2d503 100644 --- a/R/summarise_feat_usage.R +++ b/R/summarise_feat_usage.R @@ -3,7 +3,10 @@ # Leaving function here in case it comes back summarise_feature_usage = function(server, token) { cli::cli_h2("Checking Feature Usage") - connect = suppressMessages(connectapi::connect(server = server, api_key = token)) + connect = suppressMessages(connectapi::connect( + server = server, + api_key = token + )) client = connect$get_connect() feat_usage = client$GET("v1/experimental/feature_usage") feat_usage = dplyr::bind_rows(feat_usage) @@ -11,5 +14,5 @@ summarise_feature_usage = function(server, token) { unused = feat_usage$name[!feat_usage$used] # nolint cli::cli_alert_info("Features used: {used}") cli::cli_alert_info("Features unused: {unused}") - return(feat_usage) + feat_usage } diff --git a/R/summarise_setup.R b/R/summarise_setup.R index e30aa6b..d1c2d49 100644 --- a/R/summarise_setup.R +++ b/R/summarise_setup.R @@ -6,7 +6,7 @@ summarise_setup = function(server, token) { setup$server = check_server(server) setup$api_key = check_api_key(token) setup$rsconnect_python = check_rsconnect_python() - return(invisible(setup)) + invisible(setup) } check_server = function(server) { @@ -14,7 +14,7 @@ check_server = function(server) { server = standardise_server_url(server) server = set_key("connect_server", get_value("CONNECT_SERVER", server)) check_server_accessibility(server) - return(invisible(server)) + invisible(server) } # The server structure is really picky: we are using {rsconnect}, {connectapi}, python deployment @@ -23,9 +23,11 @@ check_server = function(server) { # But I don't have that structure to test, so ... standardise_server_url = function(server) { if (is.na(server)) { - cli::cli_abort("Can't find server. \\ + cli::cli_abort( + "Can't find server. \\ Either add CONNECT_SERVER to your .Renviron or - pass `server` as an argument") + pass `server` as an argument" + ) } end_slash = stringr::str_ends(server, pattern = "/") if (isFALSE(end_slash)) { @@ -40,7 +42,7 @@ standardise_server_url = function(server) { cli::cli_abort("Please add https (or http) to the server URL.") } cli::cli_alert_info("Server: {cli::col_green(server)}") - return(invisible(server)) + invisible(server) } check_server_accessibility = function(server) { @@ -58,17 +60,24 @@ check_api_key = function(token) { cli::cli_abort("CONNECT_API_KEY missing") } cli::cli_alert_info("API KEY: {cli::col_green(cli::symbol$tick)}") - return(invisible(TRUE)) + invisible(TRUE) } check_rsconnect_python = function() { - value = set_key("rsconnect_python", as.logical(nchar(Sys.which("rsconnect")) > 0)) - sym = ifelse(value, cli::col_green(cli::symbol$tick), cli::col_red(cli::symbol$cross)) #nolint + value = set_key( + "rsconnect_python", + as.logical(nchar(Sys.which("rsconnect")) > 0) + ) + sym = ifelse( + value, + cli::col_green(cli::symbol$tick), + cli::col_red(cli::symbol$cross) + ) #nolint cli::cli_alert_info("rsconnect-python: {sym}") if (isFALSE(value)) { cli::cli_alert_warning("rsconnect-python not installed") cli::cli_alert_warning("All python deploy tests will be skipped") cli::cli_alert_warning("Install: pip install rsconnect-python") } - return(invisible(value)) + invisible(value) } diff --git a/R/summarise_users.R b/R/summarise_users.R index 51197f4..4ddc990 100644 --- a/R/summarise_users.R +++ b/R/summarise_users.R @@ -7,7 +7,11 @@ summarise_users = function(server, token, debug_level) { user_list = list() user_list$user_account_limit = settings$license$users user_list$users = suppress(connectapi::get_users(client, limit = Inf)) - user_list$users$url = paste0(server, "connect/#/people/users/", user_list$users$guid) + user_list$users$url = paste0( + server, + "connect/#/people/users/", + user_list$users$guid + ) # XXX: I don't think this is right # Evaluation copies should have apps??? @@ -33,11 +37,20 @@ print_audit_users = function(user_list) { dplyr::filter(!.data$locked) %>% dplyr::count(.data$user_role) - user_vec = paste(stringr::str_to_title(user_summary$user_role), user_summary$n, sep = ": ") + user_vec = paste( + stringr::str_to_title(user_summary$user_role), + user_summary$n, + sep = ": " + ) user_string = paste(user_vec, collapse = ", ") # nolint: object_usage_linter - admins = dplyr::filter(user_list$users, .data$user_role == "administrator" & !.data$locked) # nolint: object_usage_linter + admins = dplyr::filter( + user_list$users, + .data$user_role == "administrator" & !.data$locked + ) # nolint: object_usage_linter - cli::cli_alert_info("Users: {sum(user_summary$n)} out of {user_list$user_account_limit}") + cli::cli_alert_info( + "Users: {sum(user_summary$n)} out of {user_list$user_account_limit}" + ) cli::cli_alert_info("User breakdown: {user_string}") cli::cli_alert_info("{nrow(admins)} Administrators: {admins$username}") } @@ -47,17 +60,24 @@ print_audit_user_apps = function(client, debug_level) { content = suppress(connectapi::get_content(client)) locked_users = suppress(connectapi::get_users(client)) |> dplyr::filter(.data$locked) - locked_content = dplyr::inner_join(content, locked_users, - by = dplyr::join_by("owner_guid" == "guid")) |> + locked_content = dplyr::inner_join( + content, + locked_users, + by = dplyr::join_by("owner_guid" == "guid") + ) |> dplyr::group_by(.data$username) |> dplyr::summarise(n = dplyr::n()) |> dplyr::arrange(dplyr::desc(.data$n)) - cli::cli_alert_info("{sum(locked_content$n)} applications owned by locked users") + cli::cli_alert_info( + "{sum(locked_content$n)} applications owned by locked users" + ) for (i in seq_len(nrow(locked_content))) { - row = locked_content[i, ] - cli::cli_alert_info("{row$n} locked application{?s} owned by {cli::style_italic(row$username)}") + row = locked_content[i, ] # nolint + cli::cli_alert_info( + "{row$n} locked application{?s} owned by {cli::style_italic(row$username)}" + ) } content } diff --git a/R/summarise_versions.R b/R/summarise_versions.R index 1c6b12c..c5aa435 100644 --- a/R/summarise_versions.R +++ b/R/summarise_versions.R @@ -2,17 +2,25 @@ summarise_versions = function(server, token) { cli::cli_h2("R/Python/Quarto Versions") software = c("r", "python", "quarto") - installed = purrr::map_dfr(software, ~get_server_settings(server, token, .x)) + installed = purrr::map_dfr(software, ~ get_server_settings(server, token, .x)) installed = audit.base::augment_installed(installed) - return(installed) + installed } -get_server_settings = function(server, token, type = c("r", "python", "quarto")) { +get_server_settings = function( + server, + token, + type = c("r", "python", "quarto") +) { type = match.arg(type) - res = httr::GET(paste0(server, "__api__/v1/server_settings/", type), - httr::add_headers(Authorization = paste("Key", token))) + res = httr::GET( + paste0(server, "__api__/v1/server_settings/", type), + httr::add_headers(Authorization = paste("Key", token)) + ) content = httr::content(res) - purrr::map_dfr(content$installations, - ~tibble::tibble(software = type, installed_version = .x$version)) + purrr::map_dfr( + content$installations, + ~ tibble::tibble(software = type, installed_version = .x$version) + ) } diff --git a/inst/extdata/check_sys_deps/plumber.R b/inst/extdata/check_sys_deps/plumber.R index e72555d..5a4b4ae 100644 --- a/inst/extdata/check_sys_deps/plumber.R +++ b/inst/extdata/check_sys_deps/plumber.R @@ -17,5 +17,5 @@ function() { } rtn = list(os_release = os_release, libs = libs) - return(rtn) + rtn } diff --git a/inst/extdata/deploy_shiny/basic_app/app.R b/inst/extdata/deploy_shiny/basic_app/app.R index ac5e36b..3592dc0 100644 --- a/inst/extdata/deploy_shiny/basic_app/app.R +++ b/inst/extdata/deploy_shiny/basic_app/app.R @@ -12,40 +12,39 @@ library(shiny) # Define UI for application that draws a histogram ui <- fluidPage( - - # Application title - titlePanel("Old Faithful Geyser Data"), - - # Sidebar with a slider input for number of bins - sidebarLayout( - sidebarPanel( - sliderInput("bins", - "Number of bins:", - min = 1, - max = 50, - value = 30) - ), - - # Show a plot of the generated distribution - mainPanel( - plotOutput("distPlot") - ) + # Application title + titlePanel("Old Faithful Geyser Data"), + + # Sidebar with a slider input for number of bins + sidebarLayout( + sidebarPanel( + sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30) + ), + + # Show a plot of the generated distribution + mainPanel( + plotOutput("distPlot") ) + ) ) # Define server logic required to draw a histogram server <- function(input, output) { - - output$distPlot <- renderPlot({ - # generate bins based on input$bins from ui.R - x <- faithful[, 2] - bins <- seq(min(x), max(x), length.out = input$bins + 1) - - # draw the histogram with the specified number of bins - hist(x, breaks = bins, col = 'darkgray', border = 'white', - xlab = 'Waiting time to next eruption (in mins)', - main = 'Histogram of waiting times') - }) + output$distPlot <- renderPlot({ + # generate bins based on input$bins from ui.R + x <- faithful[, 2] + bins <- seq(min(x), max(x), length.out = input$bins + 1) + + # draw the histogram with the specified number of bins + hist( + x, + breaks = bins, + col = 'darkgray', + border = 'white', + xlab = 'Waiting time to next eruption (in mins)', + main = 'Histogram of waiting times' + ) + }) } # Run the application diff --git a/inst/extdata/report/report.qmd b/inst/extdata/report/report.qmd index 0347608..b8f1b7f 100644 --- a/inst/extdata/report/report.qmd +++ b/inst/extdata/report/report.qmd @@ -1,6 +1,6 @@ --- title: "Posit Connect Audit" -format: +format: html: self-contained: true theme: flatly @@ -47,7 +47,7 @@ This audit was run by `r glue::glue("{audit_details$first_name} {audit_details$l This audit examines the server headings of the Posit Connect (or the server sitting in front of Connect). It is important to note, that there may be cases where you may want to deviate from setting these -headings. However, this is the exception and not the rule. +headings. However, this is the exception and not the rule. The headings listed below follow the recommendations of securityheaders.com and (to a certain extent) Mozilla. Where the heading has not been set, we provide links to documentation. @@ -55,19 +55,27 @@ Mozilla. Where the heading has not been set, we provide links to documentation. #| echo: false headers = audit.base::get_quarto_server_header(out) gt::gt(headers) %>% - gt::cols_label("header" = "Header", message = "Information") %>% + gt::cols_label("header" = "Header", message = "Information") %>% gt::cols_align("left") %>% - gt::cols_hide("status") %>% - gt::tab_row_group(label = "Looks good", rows = which(headers$status == "OK")) %>% - gt::tab_row_group(label = "To investigate", rows = which(headers$status != "OK")) %>% - gtExtras::gt_merge_stack(col1 = "message", col2 = "value", - palette = c("grey10", "grey50"), - font_weight = c("normal", "normal"), - small_cap = FALSE + gt::cols_hide("status") %>% + gt::tab_row_group( + label = "Looks good", + rows = which(headers$status == "OK") + ) %>% + gt::tab_row_group( + label = "To investigate", + rows = which(headers$status != "OK") + ) %>% + gtExtras::gt_merge_stack( + col1 = "message", + col2 = "value", + palette = c("grey10", "grey50"), + font_weight = c("normal", "normal"), + small_cap = FALSE ) ``` -## Connect Users +## Connect Users ```{r} #| echo: false @@ -86,8 +94,10 @@ old_users = audit.connect::get_quarto_old_users(out) gt::gt(old_users) %>% gt::cols_label("last_log_in" = "Last Log In", "email" = "User Email") %>% gt::cols_align("left") %>% - gt::tab_style(style = gt::cell_text(v_align = "top"), - locations = gt::cells_body(columns = last_log_in)) + gt::tab_style( + style = gt::cell_text(v_align = "top"), + locations = gt::cells_body(columns = last_log_in) + ) ``` ```{r} @@ -105,12 +115,12 @@ Out of the `r sum(n_users)` registered users, there are: ### Locked Users -There are also an additional `r locked_users` locked users. +There are also an additional `r locked_users` locked users. These users own `r get_quarto_locked_user_apps(out)` applications. ## System Dependencies -Posit Connect allows users to push dashboards and markdown documents. +Posit Connect allows users to push dashboards and markdown documents. However, these applications contain R packages that have system level dependencies. This check tries to determine which R packages would fail to install. @@ -121,42 +131,54 @@ sys_deps = audit.base::get_quarto_sys_deps(out) ### Summary -There are `r nrow(sys_deps)` R packages that can't be installed, due to +There are `r nrow(sys_deps)` R packages that can't be installed, due to `r sum(sys_deps$n)` missing system dependencies. ```{r} #| echo: false gt::gt(sys_deps) %>% - gt::cols_label("sys_libs" = "System Library", - "pkg" = "R Package") %>% + gt::cols_label("sys_libs" = "System Library", "pkg" = "R Package") %>% gt::cols_align("left") %>% gt::cols_hide("n") %>% - gt::tab_style(style = gt::cell_text(v_align = "top"), - locations = gt::cells_body(columns = sys_libs)) + gt::tab_style( + style = gt::cell_text(v_align = "top"), + locations = gt::cells_body(columns = sys_libs) + ) ``` ## Software Versions Posit Connect contains multiple versions of R, Python and Quarto. -This audit compares the installed version, to the latest version. +This audit compares the installed version, to the latest version. We've highlighted any older versions. ```{r} #| echo: false software = audit.base::get_quarto_software_versions(out) gt::gt(software) %>% - gt::cols_label("version" = "Latest version", - "installed_version" = "Installed version") %>% + gt::cols_label( + "version" = "Latest version", + "installed_version" = "Installed version" + ) %>% gt::cols_align("right") %>% gt::cols_hide(c("software", "upgrade")) %>% - gt::tab_row_group(label = "R", rows = which(.data$software == "r")) %>% - gt::tab_row_group(label = "Python", rows = which(.data$software == "python")) %>% - gt::tab_row_group(label = "Quarto", rows = which(.data$software == "quarto")) %>% - - gtExtras::gt_highlight_rows(rows = which(.data$upgrade), font_color = "white", - fill = severe) + gt::tab_row_group(label = "R", rows = which(.data$software == "r")) %>% + gt::tab_row_group( + label = "Python", + rows = which(.data$software == "python") + ) %>% + gt::tab_row_group( + label = "Quarto", + rows = which(.data$software == "quarto") + ) %>% + + gtExtras::gt_highlight_rows( + rows = which(.data$upgrade), + font_color = "white", + fill = severe + ) ``` ## Deployments @@ -167,16 +189,17 @@ The test determines if the application has been successful launched. ```{r} #| echo: false deploy = audit.base::get_quarto_deploy(out) -gt::gt(deploy) %>% - gt::cols_label("context" = "Deployment", - "time_taken" = "Time (secs)") %>% +gt::gt(deploy) %>% + gt::cols_label("context" = "Deployment", "time_taken" = "Time (secs)") %>% gt::cols_align("right", "time_taken") %>% gt::cols_hide(c("passed", "group", "short")) %>% gt::tab_row_group(label = "Failed", rows = which(!deploy$passed)) %>% gt::tab_row_group(label = "Looks good", rows = which(deploy$passed)) %>% - gtExtras::gt_merge_stack(col1 = "context", col2 = "type", - palette = c("grey10", "grey50"), - font_weight = c("normal", "normal"), - small_cap = FALSE - ) + gtExtras::gt_merge_stack( + col1 = "context", + col2 = "type", + palette = c("grey10", "grey50"), + font_weight = c("normal", "normal"), + small_cap = FALSE + ) ``` diff --git a/tests/testthat/test-sanitise.R b/tests/testthat/test-sanitise.R index 2ef08d3..8ca5d5a 100644 --- a/tests/testthat/test-sanitise.R +++ b/tests/testthat/test-sanitise.R @@ -5,9 +5,12 @@ describe("Checking sanitize function", { rtn = suppressMessages(check()) sanitize_rtn = sanitise(rtn) - it("Check users have been changed", - expect_false(identical(rtn[["users_details"]], - sanitize_rtn[["users_details"]])) + it( + "Check users have been changed", + expect_false(identical( + rtn[["users_details"]], + sanitize_rtn[["users_details"]] + )) ) it("Check everything else has stayed the same", {