From 20cc7155782946d9de8a2b1e3f51098e833a123c Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Mon, 25 Nov 2024 17:37:44 -0500 Subject: [PATCH 1/9] minimal terminate_job --- R/content.R | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/R/content.R b/R/content.R index 890f25f7..14b7b9ef 100644 --- a/R/content.R +++ b/R/content.R @@ -93,25 +93,29 @@ Content <- R6::R6Class( get_dashboard_url = function(pane = "") { dashboard_url_chr(self$connect$server, self$content$guid, pane = pane) }, - #' @description Return the jobs for this content. + #' @description Return the jobs for this content jobs = function() { - warn_experimental("jobs") - url <- unversioned_url("applications", self$get_content()$guid, "jobs") - res <- self$get_connect()$GET(url) + url <- unversioned_url("applications", self$content$guid, "jobs") + res <- self$connect$GET(url) }, #' @description Return a single job for this content. #' @param key The job key. job = function(key) { - warn_experimental("job") - url <- unversioned_url("applications", self$get_content()$guid, "job", key) - res <- self$get_connect()$GET(url) + url <- unversioned_url("applications", self$content$guid, "job", key) + res <- self$connect$GET(url) - content_guid <- self$get_content()$guid + content_guid <- self$content$guid purrr::map( list(res), ~ purrr::list_modify(.x, app_guid = content_guid) )[[1]] }, + #' @description Terminate a single job for this content item. + terminate_job = function(key) { + con <- self$connect + url <- v1_url("content", self$content$guid, "jobs", key) + self$connect$DELETE(url) + }, #' @description Return the variants for this content. variants = function() { warn_experimental("variants") @@ -598,8 +602,6 @@ content_ensure <- function( #' @family content functions #' @export get_jobs <- function(content) { - warn_experimental("get_jobs") - scoped_experimental_silence() validate_R6_class(content, "Content") jobs <- content$jobs() @@ -610,8 +612,6 @@ get_jobs <- function(content) { #' @rdname jobs #' @export get_job <- function(content, key) { - warn_experimental("get_job") - scoped_experimental_silence() validate_R6_class(content, "Content") job <- content$job(key = key) @@ -623,6 +623,14 @@ get_job <- function(content, key) { parse_connectapi_typed(list(job), connectapi_ptypes$job) } +terminate_job <- function(content, key) { + validate_R6_class(content, "Content") + + res <- content$terminate_job(key) + content$connect$raise_error(res) + httr::content(res)$result +} + #' Set RunAs User #' #' Set the `RunAs` user for a piece of content. From 9f2ca9a5a49038f0c67727078c2380ece1719e24 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Tue, 26 Nov 2024 11:24:06 -0500 Subject: [PATCH 2/9] work on support for multiple endpoints --- R/content.R | 18 +++++++++++++----- R/utils.R | 7 +++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/R/content.R b/R/content.R index 14b7b9ef..95ffc04f 100644 --- a/R/content.R +++ b/R/content.R @@ -95,18 +95,26 @@ Content <- R6::R6Class( }, #' @description Return the jobs for this content jobs = function() { - url <- unversioned_url("applications", self$content$guid, "jobs") - res <- self$connect$GET(url) + res <- self$connect$GET(v1_url("content", self$content$guid, "jobs"), parser = NULL) + if (endpoint_does_not_exist(res)) { + res <- self$connect$GET(unversioned_url("applications", self$content$guid, "jobs"), parser = NULL) + } + self$connect$raise_error(res) + httr::content(res, as = "parsed") }, #' @description Return a single job for this content. #' @param key The job key. job = function(key) { - url <- unversioned_url("applications", self$content$guid, "job", key) - res <- self$connect$GET(url) + res <- self$connect$GET(v1("content", self$content$guid, "job", key), parser = NULL) + if (endpoint_does_not_exist(res)) { + res <- self$connect$GET(unversioned_url("applications", self$content$guid, "job", key), parser = NULL) + } + self$connect$raise_error(res) + parsed <- httr::content(res, as = "parsed") content_guid <- self$content$guid purrr::map( - list(res), + list(parsed), ~ purrr::list_modify(.x, app_guid = content_guid) )[[1]] }, diff --git a/R/utils.R b/R/utils.R index 444b6243..6bdce50a 100644 --- a/R/utils.R +++ b/R/utils.R @@ -186,3 +186,10 @@ token_hex <- function(n) { raw <- as.raw(sample(0:255, n, replace = TRUE)) paste(as.character(raw), collapse = "") } + +endpoint_does_not_exist <- function(res) { + return( + httr::status_code(res) == "404" && + "code" %in% names(httr::content(res)) + ) +} From 71af1d40e516973c4f3dd114ae006b22f52e0d04 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Tue, 26 Nov 2024 18:37:44 -0500 Subject: [PATCH 3/9] begin to support new type --- R/connectapi.R | 1 + R/content.R | 19 +++++++++++++++---- R/parse.R | 5 ++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/R/connectapi.R b/R/connectapi.R index 7c1e6d06..642cb390 100644 --- a/R/connectapi.R +++ b/R/connectapi.R @@ -27,5 +27,6 @@ current_connect_version <- "2024.03.0" .onLoad <- function(...) { vctrs::s3_register("dplyr::collect", "tbl_connect") + vctrs::s3_register("vctrs::vec_cast", "character.integer") invisible() } diff --git a/R/content.R b/R/content.R index 95ffc04f..d7188f0b 100644 --- a/R/content.R +++ b/R/content.R @@ -631,12 +631,23 @@ get_job <- function(content, key) { parse_connectapi_typed(list(job), connectapi_ptypes$job) } -terminate_job <- function(content, key) { +terminate_job <- function(content, keys = NULL) { validate_R6_class(content, "Content") - res <- content$terminate_job(key) - content$connect$raise_error(res) - httr::content(res)$result + if (is.null(keys)) { + all_jobs <- get_jobs(content) + active_jobs <- all_jobs[isFALSE(all_jobs$finalized), ] + keys <- active_jobs$key + } + + res <- purrr::map(keys, content$terminate_job) + tibble::tibble( + key = keys, + status = purrr::map_vec(res, httr::status_code), + content = purrr::map(res, httr::content) + ) + # content$connect$raise_error(res) + # httr::content(res)$result } #' Set RunAs User diff --git a/R/parse.R b/R/parse.R index 664ac256..df970680 100644 --- a/R/parse.R +++ b/R/parse.R @@ -59,7 +59,7 @@ ensure_column <- function(data, default, name) { if (inherits(default, "list") && !inherits(col, "list")) { col <- list(col) } - col <- vctrs::vec_cast(col, default) + col <- vctrs::vec_cast(col, default, x_arg = name) } data[[name]] <- col data @@ -185,6 +185,9 @@ tzone <- function(x) { attr(x, "tzone")[[1]] %||% "" } +vec_cast.character.integer <- function(x, to, ...) { # nolint: object_name_linter + as.character(x) +} new_datetime <- function(x = double(), tzone = "") { tzone <- tzone %||% "" From 616cf083001e2d45398aee315f2cb637d8bec855 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Wed, 4 Dec 2024 17:18:00 -0500 Subject: [PATCH 4/9] finalize get_jobs migration --- R/content.R | 103 ++++++++++++++++++++-------------- R/parse.R | 11 +++- R/ptype.R | 22 +++++--- R/utils.R | 5 +- man/Content.Rd | 2 +- man/content_delete.Rd | 1 + man/content_item.Rd | 1 + man/content_title.Rd | 1 + man/content_update.Rd | 1 + man/create_random_name.Rd | 1 + man/dashboard_url.Rd | 1 + man/dashboard_url_chr.Rd | 1 + man/delete_thumbnail.Rd | 1 + man/delete_vanity_url.Rd | 1 + man/deploy_repo.Rd | 1 + man/environment.Rd | 1 + man/get_bundles.Rd | 2 + man/get_image.Rd | 1 + man/{jobs.Rd => get_job.Rd} | 17 +++--- man/get_jobs.Rd | 82 +++++++++++++++++++++++++++ man/get_thumbnail.Rd | 1 + man/get_vanity_url.Rd | 1 + man/git.Rd | 1 + man/has_thumbnail.Rd | 1 + man/permissions.Rd | 1 + man/set_image.Rd | 1 + man/set_run_as.Rd | 1 + man/set_thumbnail.Rd | 1 + man/set_vanity_url.Rd | 1 + man/swap_vanity_url.Rd | 1 + man/verify_content_name.Rd | 1 + tests/testthat/test-content.R | 29 ++++++++++ 32 files changed, 235 insertions(+), 61 deletions(-) rename man/{jobs.Rd => get_job.Rd} (74%) create mode 100644 man/get_jobs.Rd diff --git a/R/content.R b/R/content.R index d7188f0b..cc92cba0 100644 --- a/R/content.R +++ b/R/content.R @@ -96,34 +96,36 @@ Content <- R6::R6Class( #' @description Return the jobs for this content jobs = function() { res <- self$connect$GET(v1_url("content", self$content$guid, "jobs"), parser = NULL) - if (endpoint_does_not_exist(res)) { + use_unversioned <- endpoint_does_not_exist(res) + if (use_unversioned) { res <- self$connect$GET(unversioned_url("applications", self$content$guid, "jobs"), parser = NULL) } self$connect$raise_error(res) - httr::content(res, as = "parsed") + parsed <- httr::content(res, as = "parsed") + if (use_unversioned) { + # The unversioned endpoint does not contain a `status` field. Its field + # `finalized` is `FALSE` corresponds to active jobs. The `finalized` + # field is dropped during parsing. + parsed <- purrr::modify_if(parsed, ~ isFALSE(.x$finalized), function(x) { + x$status = 0 + x + }) + } + parsed }, #' @description Return a single job for this content. #' @param key The job key. job = function(key) { - res <- self$connect$GET(v1("content", self$content$guid, "job", key), parser = NULL) - if (endpoint_does_not_exist(res)) { - res <- self$connect$GET(unversioned_url("applications", self$content$guid, "job", key), parser = NULL) - } - self$connect$raise_error(res) - parsed <- httr::content(res, as = "parsed") + warn_experimental("job") + url <- unversioned_url("applications", self$get_content()$guid, "job", key) + res <- self$get_connect()$GET(url) - content_guid <- self$content$guid + content_guid <- self$get_content()$guid purrr::map( - list(parsed), + list(res), ~ purrr::list_modify(.x, app_guid = content_guid) )[[1]] }, - #' @description Terminate a single job for this content item. - terminate_job = function(key) { - con <- self$connect - url <- v1_url("content", self$content$guid, "jobs", key) - self$connect$DELETE(url) - }, #' @description Return the variants for this content. variants = function() { warn_experimental("variants") @@ -600,26 +602,64 @@ content_ensure <- function( #' Get Jobs #' -#' `r lifecycle::badge('experimental')` Retrieve details about jobs associated with a `content_item`. -#' "Jobs" in Posit Connect are content executions +#' Retrieve details about server processes associated with a `content_item`, +#' such as a FastAPI app or a Quarto render. +#' +#' Note that Connect versions below 2022.10.0 use a legacy endpoint, and will +#' not return the complete set of information provided by newer versions. #' #' @param content A Content object, as returned by `content_item()` -#' @param key The key for a job #' -#' @rdname jobs +#' @return A data frame with a row for each job, with the following columns: +#' +#' - `id`: The job identifier. +#' - `ppid`: The job's parent process identifier (see Note 1). +#' - `pid`: The job's process identifier. +#' - `key`: The job's unique key identifier. +#' - `remote_id`: The job's identifier for off-host execution configurations (see Note 1). +#' - `app_id`: The job's parent content identifier +#' - `variant_id`: The identifier of the variant owning this job. +#' - `bundle_id`: The identifier of a content bundle linked to this job. +#' - `start_time`: The timestamp (RFC3339) indicating when this job started. +#' - `end_time`: The timestamp (RFC3339) indicating when this job finished. +#' - `last_heartbeat_time`: The timestamp (RFC3339) indicating the last time this job was observed to be running (see Note 1). +#' - `queued_time`: The timestamp (RFC3339) indicating when this job was added to the queue to be processed. Only scheduled reports will present a value for this field (see Note 1). +#' - `queue_name`: The name of the queue which processes the job. Only scheduled reports will present a value for this field (see Note 1). +#' - `tag`: A tag to identify the nature of the job. It can be one of unknown, build_report, build_site, build_jupyter, packrat_restore, python_restore, configure_report, run_app, run_api, run_tensorflow, run_python_api, run_dash_app, run_streamlit, run_bokeh_app, run_fastapi_app, run_pyshiny_app, render_shiny, run_voila_app, testing, git, val_py_ext_pkg, val_r_ext_pkg, val_r_install. +#' - `exit_code`: The job's exit code. Present only when job is finished. +#' - `status`: The current status of the job. On Connect 2022.10.0 and newer, one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: 0, otherwise `NA`. +#' - `hostname`: The name of the node which processes the job. +#' - `cluster`: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. Gives the name of the cluster when run external to the Connect host (see Note 1). +#' - `image`: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. References the name of the target image when content runs in a clustered environment such as Kubernetes (see Note 1). +#' - `run_as`: The UNIX user that executed this job. +#' +#' @note +#' 1. On Connect instances earlier than 2022.10.0, these columns will contain `NA` values. +#' +#' @family job functions #' @family content functions #' @export get_jobs <- function(content) { validate_R6_class(content, "Content") jobs <- content$jobs() - parse_connectapi_typed(jobs, connectapi_ptypes$jobs) + parse_connectapi_typed(jobs, connectapi_ptypes$jobs, order_columns = TRUE) } # TODO: Need to test `logged_error` on a real error -#' @rdname jobs +#' +#' Retrieve details about a server process +#' associated with a `content_item`, such as a FastAPI app or a Quarto render. +#' +#' @param content A Content object, as returned by `content_item()` +#' @param key The key for a job +#' +#' @family job functions +#' @family content functions #' @export get_job <- function(content, key) { + warn_experimental("get_job") + scoped_experimental_silence() validate_R6_class(content, "Content") job <- content$job(key = key) @@ -631,25 +671,6 @@ get_job <- function(content, key) { parse_connectapi_typed(list(job), connectapi_ptypes$job) } -terminate_job <- function(content, keys = NULL) { - validate_R6_class(content, "Content") - - if (is.null(keys)) { - all_jobs <- get_jobs(content) - active_jobs <- all_jobs[isFALSE(all_jobs$finalized), ] - keys <- active_jobs$key - } - - res <- purrr::map(keys, content$terminate_job) - tibble::tibble( - key = keys, - status = purrr::map_vec(res, httr::status_code), - content = purrr::map(res, httr::content) - ) - # content$connect$raise_error(res) - # httr::content(res)$result -} - #' Set RunAs User #' #' Set the `RunAs` user for a piece of content. diff --git a/R/parse.R b/R/parse.R index df970680..8548e99a 100644 --- a/R/parse.R +++ b/R/parse.R @@ -27,7 +27,7 @@ make_timestamp <- function(input) { safe_format(input, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC", usetz = FALSE) } -ensure_columns <- function(.data, ptype) { +ensure_columns <- function(.data, ptype, order_columns = FALSE) { # Given a prototype, ensure that all columns are present and cast to the correct type. # If a column is missing in .data, it will be created with all missing values of the correct type. # If a column is present in both, it will be cast to the correct type. @@ -35,6 +35,11 @@ ensure_columns <- function(.data, ptype) { for (i in names(ptype)) { .data <- ensure_column(.data, ptype[[i]], i) } + + if (order_columns) { + .data <- .data[, names(ptype), drop = FALSE] + } + .data } @@ -65,8 +70,8 @@ ensure_column <- function(data, default, name) { data } -parse_connectapi_typed <- function(data, ptype) { - ensure_columns(parse_connectapi(data), ptype) +parse_connectapi_typed <- function(data, ptype, order_columns = FALSE) { + ensure_columns(parse_connectapi(data), ptype, order_columns) } parse_connectapi <- function(data) { diff --git a/R/ptype.R b/R/ptype.R index 0049e65d..1b09d532 100644 --- a/R/ptype.R +++ b/R/ptype.R @@ -161,20 +161,26 @@ connectapi_ptypes <- list( variant_key = NA_character_, ), jobs = tibble::tibble( - id = NA_integer_, - pid = NA_integer_, + id = NA_character_, + ppid = NA_character_, + pid = NA_character_, key = NA_character_, - app_id = NA_integer_, - app_guid = NA_character_, - variant_id = NA_integer_, - bundle_id = NA_integer_, + remote_id = NA_character_, + app_id = NA_character_, + variant_id = NA_character_, + bundle_id = NA_character_, start_time = NA_datetime_, end_time = NA_datetime_, + last_heartbeat_time = NA_datetime_, + queued_time = NA_datetime_, + queue_name = NA_character_, tag = NA_character_, exit_code = NA_integer_, - finalized = NA, + status = NA_integer_, hostname = NA_character_, - variant_key = NA_character_ + cluster = NA_character_, + image = NA_character_, + run_as = NA_character_, ), job = tibble::tibble( pid = NA_integer_, diff --git a/R/utils.R b/R/utils.R index 6bdce50a..3c3947ff 100644 --- a/R/utils.R +++ b/R/utils.R @@ -187,9 +187,12 @@ token_hex <- function(n) { paste(as.character(raw), collapse = "") } +# Checks to see if an http status contains a 404 error, and that that 404 +# response does not contain an error code indicating that an endpoint was called +# but encountered a 404 for some other reason. endpoint_does_not_exist <- function(res) { return( httr::status_code(res) == "404" && - "code" %in% names(httr::content(res)) + !("code" %in% names(httr::content(res, as = "parsed"))) ) } diff --git a/man/Content.Rd b/man/Content.Rd index 6535367e..4b2a0469 100644 --- a/man/Content.Rd +++ b/man/Content.Rd @@ -248,7 +248,7 @@ Return the URL for this content in the Posit Connect dashboard. \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-Content-jobs}{}}} \subsection{Method \code{jobs()}}{ -Return the jobs for this content. +Return the jobs for this content \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Content$jobs()}\if{html}{\out{
}} } diff --git a/man/content_delete.Rd b/man/content_delete.Rd index 6bf767e5..e2f412da 100644 --- a/man/content_delete.Rd +++ b/man/content_delete.Rd @@ -32,6 +32,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_item.Rd b/man/content_item.Rd index a0d90fcc..2a5594c0 100644 --- a/man/content_item.Rd +++ b/man/content_item.Rd @@ -38,6 +38,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_title.Rd b/man/content_title.Rd index 7b6b83f4..1ae5be07 100644 --- a/man/content_title.Rd +++ b/man/content_title.Rd @@ -34,6 +34,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_update.Rd b/man/content_update.Rd index a0e27136..9bb62f68 100644 --- a/man/content_update.Rd +++ b/man/content_update.Rd @@ -54,6 +54,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/create_random_name.Rd b/man/create_random_name.Rd index e00dc799..0b02f0d6 100644 --- a/man/create_random_name.Rd +++ b/man/create_random_name.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/dashboard_url.Rd b/man/dashboard_url.Rd index e3dc08a8..c224c345 100644 --- a/man/dashboard_url.Rd +++ b/man/dashboard_url.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/dashboard_url_chr.Rd b/man/dashboard_url_chr.Rd index 166a160c..aa16dcb3 100644 --- a/man/dashboard_url_chr.Rd +++ b/man/dashboard_url_chr.Rd @@ -34,6 +34,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/delete_thumbnail.Rd b/man/delete_thumbnail.Rd index 4cfbd83f..2cb6fee1 100644 --- a/man/delete_thumbnail.Rd +++ b/man/delete_thumbnail.Rd @@ -42,6 +42,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/delete_vanity_url.Rd b/man/delete_vanity_url.Rd index 64dc15b3..14ba10a1 100644 --- a/man/delete_vanity_url.Rd +++ b/man/delete_vanity_url.Rd @@ -26,6 +26,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/deploy_repo.Rd b/man/deploy_repo.Rd index 3a986c90..a0b610ec 100644 --- a/man/deploy_repo.Rd +++ b/man/deploy_repo.Rd @@ -69,6 +69,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/environment.Rd b/man/environment.Rd index 944b2455..5b5dc91c 100644 --- a/man/environment.Rd +++ b/man/environment.Rd @@ -51,6 +51,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_bundles}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/get_bundles.Rd b/man/get_bundles.Rd index e59af114..5518b40d 100644 --- a/man/get_bundles.Rd +++ b/man/get_bundles.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, @@ -57,6 +58,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/get_image.Rd b/man/get_image.Rd index 15475780..b426c256 100644 --- a/man/get_image.Rd +++ b/man/get_image.Rd @@ -41,6 +41,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/jobs.Rd b/man/get_job.Rd similarity index 74% rename from man/jobs.Rd rename to man/get_job.Rd index c3dcc17e..044b5b04 100644 --- a/man/jobs.Rd +++ b/man/get_job.Rd @@ -1,12 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/content.R -\name{get_jobs} -\alias{get_jobs} +\name{get_job} \alias{get_job} -\title{Get Jobs} +\title{Retrieve details about a server process +associated with a \code{content_item}, such as a FastAPI app or a Quarto render.} \usage{ -get_jobs(content) - get_job(content, key) } \arguments{ @@ -15,10 +13,13 @@ get_job(content, key) \item{key}{The key for a job} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} Retrieve details about jobs associated with a \code{content_item}. -"Jobs" in Posit Connect are content executions +Retrieve details about a server process +associated with a \code{content_item}, such as a FastAPI app or a Quarto render. } \seealso{ +Other job functions: +\code{\link{get_jobs}()} + Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, @@ -33,6 +34,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, \code{\link{git}}, @@ -46,3 +48,4 @@ Other content functions: \code{\link{verify_content_name}()} } \concept{content functions} +\concept{job functions} diff --git a/man/get_jobs.Rd b/man/get_jobs.Rd new file mode 100644 index 00000000..ef76399a --- /dev/null +++ b/man/get_jobs.Rd @@ -0,0 +1,82 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/content.R +\name{get_jobs} +\alias{get_jobs} +\title{Get Jobs} +\usage{ +get_jobs(content) +} +\arguments{ +\item{content}{A Content object, as returned by \code{content_item()}} +} +\value{ +A data frame with a row for each job, with the following columns: +\itemize{ +\item \code{id}: The job identifier. +\item \code{ppid}: The job's parent process identifier (see Note 1). +\item \code{pid}: The job's process identifier. +\item \code{key}: The job's unique key identifier. +\item \code{remote_id}: The job's identifier for off-host execution configurations (see Note 1). +\item \code{app_id}: The job's parent content identifier +\item \code{variant_id}: The identifier of the variant owning this job. +\item \code{bundle_id}: The identifier of a content bundle linked to this job. +\item \code{start_time}: The timestamp (RFC3339) indicating when this job started. +\item \code{end_time}: The timestamp (RFC3339) indicating when this job finished. +\item \code{last_heartbeat_time}: The timestamp (RFC3339) indicating the last time this job was observed to be running (see Note 1). +\item \code{queued_time}: The timestamp (RFC3339) indicating when this job was added to the queue to be processed. Only scheduled reports will present a value for this field (see Note 1). +\item \code{queue_name}: The name of the queue which processes the job. Only scheduled reports will present a value for this field (see Note 1). +\item \code{tag}: A tag to identify the nature of the job. It can be one of unknown, build_report, build_site, build_jupyter, packrat_restore, python_restore, configure_report, run_app, run_api, run_tensorflow, run_python_api, run_dash_app, run_streamlit, run_bokeh_app, run_fastapi_app, run_pyshiny_app, render_shiny, run_voila_app, testing, git, val_py_ext_pkg, val_r_ext_pkg, val_r_install. +\item \code{exit_code}: The job's exit code. Present only when job is finished. +\item \code{status}: The current status of the job. On Connect 2022.10.0 and newer, one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: 0, otherwise \code{NA}. +\item \code{hostname}: The name of the node which processes the job. +\item \code{cluster}: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. Gives the name of the cluster when run external to the Connect host (see Note 1). +\item \code{image}: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. References the name of the target image when content runs in a clustered environment such as Kubernetes (see Note 1). +\item \code{run_as}: The UNIX user that executed this job. +} +} +\description{ +Retrieve details about server processes associated with a \code{content_item}, +such as a FastAPI app or a Quarto render. +} +\details{ +Note that Connect versions below 2022.10.0 use a legacy endpoint, and will +not return the complete set of information provided by newer versions. +} +\note{ +\enumerate{ +\item On Connect instances earlier than 2022.10.0, these columns will contain \code{NA} values. +} +} +\seealso{ +Other job functions: +\code{\link{get_job}()} + +Other content functions: +\code{\link{content_delete}()}, +\code{\link{content_item}()}, +\code{\link{content_title}()}, +\code{\link{content_update}()}, +\code{\link{create_random_name}()}, +\code{\link{dashboard_url}()}, +\code{\link{dashboard_url_chr}()}, +\code{\link{delete_thumbnail}()}, +\code{\link{delete_vanity_url}()}, +\code{\link{deploy_repo}()}, +\code{\link{get_bundles}()}, +\code{\link{get_environment}()}, +\code{\link{get_image}()}, +\code{\link{get_job}()}, +\code{\link{get_thumbnail}()}, +\code{\link{get_vanity_url}()}, +\code{\link{git}}, +\code{\link{has_thumbnail}()}, +\code{\link{permissions}}, +\code{\link{set_image_path}()}, +\code{\link{set_run_as}()}, +\code{\link{set_thumbnail}()}, +\code{\link{set_vanity_url}()}, +\code{\link{swap_vanity_url}()}, +\code{\link{verify_content_name}()} +} +\concept{content functions} +\concept{job functions} diff --git a/man/get_thumbnail.Rd b/man/get_thumbnail.Rd index 80ec48e3..2bcdf8f9 100644 --- a/man/get_thumbnail.Rd +++ b/man/get_thumbnail.Rd @@ -48,6 +48,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_vanity_url}()}, \code{\link{git}}, diff --git a/man/get_vanity_url.Rd b/man/get_vanity_url.Rd index b90dcbf9..252bc7c0 100644 --- a/man/get_vanity_url.Rd +++ b/man/get_vanity_url.Rd @@ -30,6 +30,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{git}}, diff --git a/man/git.Rd b/man/git.Rd index dbcf4b92..0c8a03f7 100644 --- a/man/git.Rd +++ b/man/git.Rd @@ -55,6 +55,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/has_thumbnail.Rd b/man/has_thumbnail.Rd index 69656905..8fb4b96d 100644 --- a/man/has_thumbnail.Rd +++ b/man/has_thumbnail.Rd @@ -44,6 +44,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/permissions.Rd b/man/permissions.Rd index 05835eda..519fc654 100644 --- a/man/permissions.Rd +++ b/man/permissions.Rd @@ -77,6 +77,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_image.Rd b/man/set_image.Rd index b5e644ba..3007d179 100644 --- a/man/set_image.Rd +++ b/man/set_image.Rd @@ -48,6 +48,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_run_as.Rd b/man/set_run_as.Rd index 772594a1..b1a26d70 100644 --- a/man/set_run_as.Rd +++ b/man/set_run_as.Rd @@ -50,6 +50,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_thumbnail.Rd b/man/set_thumbnail.Rd index 0d3caf60..013003c1 100644 --- a/man/set_thumbnail.Rd +++ b/man/set_thumbnail.Rd @@ -47,6 +47,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_vanity_url.Rd b/man/set_vanity_url.Rd index 85428805..f0ae9b09 100644 --- a/man/set_vanity_url.Rd +++ b/man/set_vanity_url.Rd @@ -43,6 +43,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/swap_vanity_url.Rd b/man/swap_vanity_url.Rd index 36de9bf0..98f08718 100644 --- a/man/swap_vanity_url.Rd +++ b/man/swap_vanity_url.Rd @@ -29,6 +29,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/verify_content_name.Rd b/man/verify_content_name.Rd index 03abe180..8610e981 100644 --- a/man/verify_content_name.Rd +++ b/man/verify_content_name.Rd @@ -35,6 +35,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index e26d202b..2dc90e07 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -241,3 +241,32 @@ with_mock_api({ expect_identical(v$key, "WrEKKa77") }) }) + +# jobs ----- + +test_that("get_jobs() using the old and new endpoints returns sensible results", { + with_mock_api({ + client <- Connect$new(server = "http://connect.example", api_key = "not-a-key") + item <- content_item(client, "8f37d6e0") + jobs_v1 <- get_jobs(item) + TRUE + }) + + with_mock_dir("2024.07.0", { + jobs_v0 <- get_jobs(item) + }) + + # Columns we expect to be identical + common_cols <- c( + "id", "pid", "key", "app_id", "variant_id", "bundle_id", "start_time", + "end_time", "tag", "exit_code", "hostname" + ) + expect_equal( + jobs_v1[common_cols], + jobs_v1[common_cols] + ) + + # Status columns line up as expected + expect_equal(jobs_v1$status, c(0L, 2L, 2L, 2L, 2L)) + expect_equal(jobs_v0$status, c(0L, NA, NA, NA, NA)) +}) From 69f0f8017b713b6bde74a1a3a05e56a2598a9d76 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Wed, 4 Dec 2024 17:50:43 -0500 Subject: [PATCH 5/9] fix lint failures --- R/content.R | 31 ++++++++++++++++++++++--------- R/utils.R | 2 +- man/get_jobs.Rd | 29 +++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/R/content.R b/R/content.R index cc92cba0..2683bbee 100644 --- a/R/content.R +++ b/R/content.R @@ -107,7 +107,7 @@ Content <- R6::R6Class( # `finalized` is `FALSE` corresponds to active jobs. The `finalized` # field is dropped during parsing. parsed <- purrr::modify_if(parsed, ~ isFALSE(.x$finalized), function(x) { - x$status = 0 + x$status <- 0 x }) } @@ -616,21 +616,34 @@ content_ensure <- function( #' - `ppid`: The job's parent process identifier (see Note 1). #' - `pid`: The job's process identifier. #' - `key`: The job's unique key identifier. -#' - `remote_id`: The job's identifier for off-host execution configurations (see Note 1). +#' - `remote_id`: The job's identifier for off-host execution configurations +#' (see Note 1). #' - `app_id`: The job's parent content identifier #' - `variant_id`: The identifier of the variant owning this job. #' - `bundle_id`: The identifier of a content bundle linked to this job. #' - `start_time`: The timestamp (RFC3339) indicating when this job started. #' - `end_time`: The timestamp (RFC3339) indicating when this job finished. -#' - `last_heartbeat_time`: The timestamp (RFC3339) indicating the last time this job was observed to be running (see Note 1). -#' - `queued_time`: The timestamp (RFC3339) indicating when this job was added to the queue to be processed. Only scheduled reports will present a value for this field (see Note 1). -#' - `queue_name`: The name of the queue which processes the job. Only scheduled reports will present a value for this field (see Note 1). -#' - `tag`: A tag to identify the nature of the job. It can be one of unknown, build_report, build_site, build_jupyter, packrat_restore, python_restore, configure_report, run_app, run_api, run_tensorflow, run_python_api, run_dash_app, run_streamlit, run_bokeh_app, run_fastapi_app, run_pyshiny_app, render_shiny, run_voila_app, testing, git, val_py_ext_pkg, val_r_ext_pkg, val_r_install. +#' - `last_heartbeat_time`: The timestamp (RFC3339) indicating the last time +#' this job was observed to be running (see Note 1). +#' - `queued_time`: The timestamp (RFC3339) indicating when this job was added +#' to the queue to be processed. Only scheduled reports will present a value +#' for this field (see Note 1). +#' - `queue_name`: The name of the queue which processes the job. Only +#' scheduled reports will present a value for this field (see Note 1). +#' - `tag`: A tag to identify the nature of the job. #' - `exit_code`: The job's exit code. Present only when job is finished. -#' - `status`: The current status of the job. On Connect 2022.10.0 and newer, one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: 0, otherwise `NA`. +#' - `status`: The current status of the job. On Connect 2022.10.0 and newer, +#' one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: +#' 0, otherwise `NA`. #' - `hostname`: The name of the node which processes the job. -#' - `cluster`: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. Gives the name of the cluster when run external to the Connect host (see Note 1). -#' - `image`: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. References the name of the target image when content runs in a clustered environment such as Kubernetes (see Note 1). +#' - `cluster`: The location where this content runs. Content running on the +#' same server as Connect will have either a null value or the string Local. +#' Gives the name of the cluster when run external to the Connect host +#' (see Note 1). +#' - `image`: The location where this content runs. Content running on +#' the same server as Connect will have either a null value or the string +#' Local. References the name of the target image when content runs in +#' a clustered environment such as Kubernetes (see Note 1). #' - `run_as`: The UNIX user that executed this job. #' #' @note diff --git a/R/utils.R b/R/utils.R index 3c3947ff..3a98fab2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -193,6 +193,6 @@ token_hex <- function(n) { endpoint_does_not_exist <- function(res) { return( httr::status_code(res) == "404" && - !("code" %in% names(httr::content(res, as = "parsed"))) + !("code" %in% names(httr::content(res, as = "parsed"))) ) } diff --git a/man/get_jobs.Rd b/man/get_jobs.Rd index ef76399a..d29cf1b1 100644 --- a/man/get_jobs.Rd +++ b/man/get_jobs.Rd @@ -16,21 +16,34 @@ A data frame with a row for each job, with the following columns: \item \code{ppid}: The job's parent process identifier (see Note 1). \item \code{pid}: The job's process identifier. \item \code{key}: The job's unique key identifier. -\item \code{remote_id}: The job's identifier for off-host execution configurations (see Note 1). +\item \code{remote_id}: The job's identifier for off-host execution configurations +(see Note 1). \item \code{app_id}: The job's parent content identifier \item \code{variant_id}: The identifier of the variant owning this job. \item \code{bundle_id}: The identifier of a content bundle linked to this job. \item \code{start_time}: The timestamp (RFC3339) indicating when this job started. \item \code{end_time}: The timestamp (RFC3339) indicating when this job finished. -\item \code{last_heartbeat_time}: The timestamp (RFC3339) indicating the last time this job was observed to be running (see Note 1). -\item \code{queued_time}: The timestamp (RFC3339) indicating when this job was added to the queue to be processed. Only scheduled reports will present a value for this field (see Note 1). -\item \code{queue_name}: The name of the queue which processes the job. Only scheduled reports will present a value for this field (see Note 1). -\item \code{tag}: A tag to identify the nature of the job. It can be one of unknown, build_report, build_site, build_jupyter, packrat_restore, python_restore, configure_report, run_app, run_api, run_tensorflow, run_python_api, run_dash_app, run_streamlit, run_bokeh_app, run_fastapi_app, run_pyshiny_app, render_shiny, run_voila_app, testing, git, val_py_ext_pkg, val_r_ext_pkg, val_r_install. +\item \code{last_heartbeat_time}: The timestamp (RFC3339) indicating the last time +this job was observed to be running (see Note 1). +\item \code{queued_time}: The timestamp (RFC3339) indicating when this job was added +to the queue to be processed. Only scheduled reports will present a value +for this field (see Note 1). +\item \code{queue_name}: The name of the queue which processes the job. Only +scheduled reports will present a value for this field (see Note 1). +\item \code{tag}: A tag to identify the nature of the job. \item \code{exit_code}: The job's exit code. Present only when job is finished. -\item \code{status}: The current status of the job. On Connect 2022.10.0 and newer, one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: 0, otherwise \code{NA}. +\item \code{status}: The current status of the job. On Connect 2022.10.0 and newer, +one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: +0, otherwise \code{NA}. \item \code{hostname}: The name of the node which processes the job. -\item \code{cluster}: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. Gives the name of the cluster when run external to the Connect host (see Note 1). -\item \code{image}: The location where this content runs. Content running on the same server as Connect will have either a null value or the string Local. References the name of the target image when content runs in a clustered environment such as Kubernetes (see Note 1). +\item \code{cluster}: The location where this content runs. Content running on the +same server as Connect will have either a null value or the string Local. +Gives the name of the cluster when run external to the Connect host +(see Note 1). +\item \code{image}: The location where this content runs. Content running on +the same server as Connect will have either a null value or the string +Local. References the name of the target image when content runs in +a clustered environment such as Kubernetes (see Note 1). \item \code{run_as}: The UNIX user that executed this job. } } From 1078d67e136a13d0c28e94012f5e7af68c005f15 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Thu, 5 Dec 2024 11:19:11 -0500 Subject: [PATCH 6/9] find and restore missing mocks --- tests/testthat/2024.07.0/README.md | 1 + .../8f37d6e0/job/QBZ5jkKfmf9iT9k8.json | 18 +++ .../__api__/applications/8f37d6e0/jobs.json | 77 ++++++++++++ .../__api__/v1/content/8f37d6e0/jobs.R | 8 ++ .../content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R | 8 ++ .../__api__/v1/content/8f37d6e0/jobs.json | 112 ++++++++++++++++++ .../8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json | 22 ++++ 7 files changed, 246 insertions(+) create mode 100644 tests/testthat/2024.07.0/README.md create mode 100644 tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json create mode 100644 tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json create mode 100644 tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R create mode 100644 tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R create mode 100644 tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json create mode 100644 tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json diff --git a/tests/testthat/2024.07.0/README.md b/tests/testthat/2024.07.0/README.md new file mode 100644 index 00000000..185124ac --- /dev/null +++ b/tests/testthat/2024.07.0/README.md @@ -0,0 +1 @@ +This dir for mocks that force the use of legacy, unversioned APIs. \ No newline at end of file diff --git a/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json new file mode 100644 index 00000000..cf4fbdad --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json @@ -0,0 +1,18 @@ +{ + "ppid": 2509183, + "pid": 2509199, + "key": "QBZ5jkKfmf9iT9k8", + "app_id": 52389, + "variant_id": 0, + "bundle_id": 127015, + "tag": "run_app", + "finalized": true, + "hostname": "dogfood02", + "origin": "0001-01-01T00:00:00Z", + "stdout": "2024/11/25 22:17:43.386988748 Running on host: dogfood02\n2024/11/25 22:17:43.387008093 Process ID: 2509199\n2024/11/25 22:17:43.430980380 Linux distribution: Ubuntu 22.04.2 LTS (jammy)\n2024/11/25 22:17:43.441252960 Running as user: uid=1031(rstudio-connect) gid=999(rstudio-connect) groups=999(rstudio-connect)\n2024/11/25 22:17:43.441266159 Connect version: 2024.12.0-dev+41-g32b4bddee2\n2024/11/25 22:17:43.441426656 LANG: C.UTF-8\n2024/11/25 22:17:43.441433843 Working directory: /opt/rstudio-connect/mnt/app\n2024/11/25 22:17:43.442234301 Using R 4.3.1\n2024/11/25 22:17:43.442251215 R.home(): /opt/R/4.3.1/lib/R\n2024/11/25 22:17:43.447902588 Content will use associated Packrat library\n2024/11/25 22:17:43.582826876 Adding Packrat library to R_LIBS and .libPaths: /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1\n2024/11/25 22:17:43.583062744 R_LIBS: /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1\n2024/11/25 22:17:43.583081371 .libPaths(): /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1, /opt/R/4.3.1/lib/R/library\n2024/11/25 22:17:43.614560696 shiny version: 1.7.4\n2024/11/25 22:17:43.614602630 httpuv version: 1.6.11\n2024/11/25 22:17:43.614782708 rmarkdown version: (none)\n2024/11/25 22:17:43.614785084 knitr version: (none)\n2024/11/25 22:17:43.614794794 jsonlite version: 1.8.4\n2024/11/25 22:17:43.614795565 RJSONIO version: (none)\n2024/11/25 22:17:43.614802828 htmltools version: 0.5.5\n2024/11/25 22:17:43.614803648 reticulate version: (none)\n2024/11/25 22:17:43.615021102 Using pandoc: /opt/rstudio-connect/ext/pandoc/2.16\n2024/11/25 22:17:44.634166987 Using Shiny bookmarking base directory /opt/rstudio-connect/mnt/bookmarks\n2024/11/25 22:17:44.635399206 Shiny application starting ...\n", + "stderr": "2024/11/25 22:17:43.030795136 [rsc-session] Content GUID: 8f37d6e0\n2024/11/25 22:17:43.030842219 [rsc-session] Content ID: 52389\n2024/11/25 22:17:43.030849040 [rsc-session] Bundle ID: 127015\n2024/11/25 22:17:43.030854033 [rsc-session] Job Key: QBZ5jkKfmf9iT9k8\n2024/11/25 22:17:45.068540867 \n2024/11/25 22:17:45.068555326 Listening on http://127.0.0.1:33545\n", + "logged_error": null, + "exit_code": 0, + "start_time": 1732573062, + "end_time": null +} diff --git a/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json new file mode 100644 index 00000000..d71beee2 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json @@ -0,0 +1,77 @@ +[ + { + "id": 40669829, + "pid": 1153321, + "key": "k3sHkEoWJNwQim7g", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1733268003, + "end_time": null, + "tag": "run_app", + "exit_code": null, + "finalized": false, + "hostname": "dogfood01" + }, + { + "id": 40097386, + "pid": 2516505, + "key": "mxPGVOMVk6f8dso2", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732573574, + "end_time": 1732577139, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 40096649, + "pid": 2509199, + "key": "QBZ5jkKfmf9iT9k8", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732573062, + "end_time": null, + "tag": "run_app", + "exit_code": null, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 40080413, + "pid": 2321354, + "key": "EzxM4sBYJrLSMHg9", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732553145, + "end_time": 1732556770, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 39368207, + "pid": 2434200, + "key": "HbdzgOJrMmMTq6vu", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1731690180, + "end_time": 1731690383, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + } +] diff --git a/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R new file mode 100644 index 00000000..abece498 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R @@ -0,0 +1,8 @@ +structure( + list( + url = "__api__/v1/content/8f37d6e0", + status_code = 404L, content = charToRaw("404 page not found"), + headers = structure(list(`content-type` = "text/plain"), class = "insensitive") + ), + class = "response" +) diff --git a/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R new file mode 100644 index 00000000..762c94f2 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R @@ -0,0 +1,8 @@ +structure( + list( + url = "__api__/v1/content/01234567/thumbnail", + status_code = 404L, content = charToRaw("404 page not found"), + headers = structure(list(`content-type` = "text/plain"), class = "insensitive") + ), + class = "response" +) diff --git a/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json new file mode 100644 index 00000000..eb81c29b --- /dev/null +++ b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json @@ -0,0 +1,112 @@ +[ + { + "id": "40669829", + "ppid": "1153303", + "pid": "1153321", + "key": "k3sHkEoWJNwQim7g", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-12-03T23:20:03Z", + "end_time": null, + "last_heartbeat_time": "2024-12-03T23:20:13Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 0, + "hostname": "dogfood01", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40097386", + "ppid": "2516489", + "pid": "2516505", + "key": "mxPGVOMVk6f8dso2", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:26:14Z", + "end_time": "2024-11-25T23:25:39Z", + "last_heartbeat_time": "2024-11-25T23:25:36Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40096649", + "ppid": "2509183", + "pid": "2509199", + "key": "QBZ5jkKfmf9iT9k8", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:17:42Z", + "end_time": null, + "last_heartbeat_time": "2024-11-25T22:20:23Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40080413", + "ppid": "2321337", + "pid": "2321354", + "key": "EzxM4sBYJrLSMHg9", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T16:45:45Z", + "end_time": "2024-11-25T17:46:10Z", + "last_heartbeat_time": "2024-11-25T17:46:08Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "39368207", + "ppid": "2434183", + "pid": "2434200", + "key": "HbdzgOJrMmMTq6vu", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-15T17:03:00Z", + "end_time": "2024-11-15T17:06:23Z", + "last_heartbeat_time": "2024-11-15T17:06:20Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + } +] diff --git a/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json new file mode 100644 index 00000000..b3d6bd65 --- /dev/null +++ b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json @@ -0,0 +1,22 @@ +{ + "id": "40096649", + "ppid": "2509183", + "pid": "2509199", + "key": "QBZ5jkKfmf9iT9k8", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:17:42Z", + "end_time": null, + "last_heartbeat_time": "2024-11-25T22:20:23Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" +} From 87043d8f91d3fc68c3eb20ba3a9e532da51040a7 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Thu, 5 Dec 2024 15:34:10 -0500 Subject: [PATCH 7/9] Update R/content.R Co-authored-by: Barret Schloerke --- R/content.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/content.R b/R/content.R index 2683bbee..d4e3eefd 100644 --- a/R/content.R +++ b/R/content.R @@ -93,7 +93,7 @@ Content <- R6::R6Class( get_dashboard_url = function(pane = "") { dashboard_url_chr(self$connect$server, self$content$guid, pane = pane) }, - #' @description Return the jobs for this content + #' @description Return the jobs for this content. jobs = function() { res <- self$connect$GET(v1_url("content", self$content$guid, "jobs"), parser = NULL) use_unversioned <- endpoint_does_not_exist(res) From 386b96d36036073ff2a40502f1a85ee63cedd3ce Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Thu, 5 Dec 2024 15:34:58 -0500 Subject: [PATCH 8/9] Update R/parse.R Co-authored-by: Barret Schloerke --- R/parse.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/parse.R b/R/parse.R index 8548e99a..7152125d 100644 --- a/R/parse.R +++ b/R/parse.R @@ -37,7 +37,7 @@ ensure_columns <- function(.data, ptype, order_columns = FALSE) { } if (order_columns) { - .data <- .data[, names(ptype), drop = FALSE] + .data <- .data[, unique(c(names(ptype), names(.data))), drop = FALSE] } .data From ff54b6a9671d631e5ed09aff550790392f434370 Mon Sep 17 00:00:00 2001 From: Toph Allen Date: Thu, 5 Dec 2024 15:39:05 -0500 Subject: [PATCH 9/9] update NEWS --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index ed56491a..99a3ff22 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # connectapi (development version) +## Breaking changes + +- `get_jobs()` now uses the public endpoint. The data returned by this function + has changed. In particular, the `finalized` column is no longer present, + replaced by `status`. `status == 0` corresponds to `isFALSE(finalized)`. See + `?get_jobs()` for more details about the new return format. (#340) + ## New features - `get_users()` now supports filtering users with the `account_status` and