Skip to content

Commit 73e8545

Browse files
feat: use v1 api in get_jobs() (#343)
Co-authored-by: Barret Schloerke <[email protected]>
1 parent 31fee40 commit 73e8545

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+523
-31
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# connectapi (development version)
22

3+
## Breaking changes
4+
5+
- `get_jobs()` now uses the public endpoint. The data returned by this function
6+
has changed. In particular, the `finalized` column is no longer present,
7+
replaced by `status`. `status == 0` corresponds to `isFALSE(finalized)`. See
8+
`?get_jobs()` for more details about the new return format. (#340)
9+
310
## New features
411

512
- `get_users()` now supports filtering users with the `account_status` and

R/connectapi.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ current_connect_version <- "2024.03.0"
2727

2828
.onLoad <- function(...) {
2929
vctrs::s3_register("dplyr::collect", "tbl_connect")
30+
vctrs::s3_register("vctrs::vec_cast", "character.integer")
3031
invisible()
3132
}

R/content.R

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,23 @@ Content <- R6::R6Class(
9595
},
9696
#' @description Return the jobs for this content.
9797
jobs = function() {
98-
warn_experimental("jobs")
99-
url <- unversioned_url("applications", self$get_content()$guid, "jobs")
100-
res <- self$get_connect()$GET(url)
98+
res <- self$connect$GET(v1_url("content", self$content$guid, "jobs"), parser = NULL)
99+
use_unversioned <- endpoint_does_not_exist(res)
100+
if (use_unversioned) {
101+
res <- self$connect$GET(unversioned_url("applications", self$content$guid, "jobs"), parser = NULL)
102+
}
103+
self$connect$raise_error(res)
104+
parsed <- httr::content(res, as = "parsed")
105+
if (use_unversioned) {
106+
# The unversioned endpoint does not contain a `status` field. Its field
107+
# `finalized` is `FALSE` corresponds to active jobs. The `finalized`
108+
# field is dropped during parsing.
109+
parsed <- purrr::modify_if(parsed, ~ isFALSE(.x$finalized), function(x) {
110+
x$status <- 0
111+
x
112+
})
113+
}
114+
parsed
101115
},
102116
#' @description Return a single job for this content.
103117
#' @param key The job key.
@@ -588,26 +602,73 @@ content_ensure <- function(
588602

589603
#' Get Jobs
590604
#'
591-
#' `r lifecycle::badge('experimental')` Retrieve details about jobs associated with a `content_item`.
592-
#' "Jobs" in Posit Connect are content executions
605+
#' Retrieve details about server processes associated with a `content_item`,
606+
#' such as a FastAPI app or a Quarto render.
607+
#'
608+
#' Note that Connect versions below 2022.10.0 use a legacy endpoint, and will
609+
#' not return the complete set of information provided by newer versions.
593610
#'
594611
#' @param content A Content object, as returned by `content_item()`
595-
#' @param key The key for a job
596612
#'
597-
#' @rdname jobs
613+
#' @return A data frame with a row for each job, with the following columns:
614+
#'
615+
#' - `id`: The job identifier.
616+
#' - `ppid`: The job's parent process identifier (see Note 1).
617+
#' - `pid`: The job's process identifier.
618+
#' - `key`: The job's unique key identifier.
619+
#' - `remote_id`: The job's identifier for off-host execution configurations
620+
#' (see Note 1).
621+
#' - `app_id`: The job's parent content identifier
622+
#' - `variant_id`: The identifier of the variant owning this job.
623+
#' - `bundle_id`: The identifier of a content bundle linked to this job.
624+
#' - `start_time`: The timestamp (RFC3339) indicating when this job started.
625+
#' - `end_time`: The timestamp (RFC3339) indicating when this job finished.
626+
#' - `last_heartbeat_time`: The timestamp (RFC3339) indicating the last time
627+
#' this job was observed to be running (see Note 1).
628+
#' - `queued_time`: The timestamp (RFC3339) indicating when this job was added
629+
#' to the queue to be processed. Only scheduled reports will present a value
630+
#' for this field (see Note 1).
631+
#' - `queue_name`: The name of the queue which processes the job. Only
632+
#' scheduled reports will present a value for this field (see Note 1).
633+
#' - `tag`: A tag to identify the nature of the job.
634+
#' - `exit_code`: The job's exit code. Present only when job is finished.
635+
#' - `status`: The current status of the job. On Connect 2022.10.0 and newer,
636+
#' one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active:
637+
#' 0, otherwise `NA`.
638+
#' - `hostname`: The name of the node which processes the job.
639+
#' - `cluster`: The location where this content runs. Content running on the
640+
#' same server as Connect will have either a null value or the string Local.
641+
#' Gives the name of the cluster when run external to the Connect host
642+
#' (see Note 1).
643+
#' - `image`: The location where this content runs. Content running on
644+
#' the same server as Connect will have either a null value or the string
645+
#' Local. References the name of the target image when content runs in
646+
#' a clustered environment such as Kubernetes (see Note 1).
647+
#' - `run_as`: The UNIX user that executed this job.
648+
#'
649+
#' @note
650+
#' 1. On Connect instances earlier than 2022.10.0, these columns will contain `NA` values.
651+
#'
652+
#' @family job functions
598653
#' @family content functions
599654
#' @export
600655
get_jobs <- function(content) {
601-
warn_experimental("get_jobs")
602-
scoped_experimental_silence()
603656
validate_R6_class(content, "Content")
604657

605658
jobs <- content$jobs()
606-
parse_connectapi_typed(jobs, connectapi_ptypes$jobs)
659+
parse_connectapi_typed(jobs, connectapi_ptypes$jobs, order_columns = TRUE)
607660
}
608661

609662
# TODO: Need to test `logged_error` on a real error
610-
#' @rdname jobs
663+
#'
664+
#' Retrieve details about a server process
665+
#' associated with a `content_item`, such as a FastAPI app or a Quarto render.
666+
#'
667+
#' @param content A Content object, as returned by `content_item()`
668+
#' @param key The key for a job
669+
#'
670+
#' @family job functions
671+
#' @family content functions
611672
#' @export
612673
get_job <- function(content, key) {
613674
warn_experimental("get_job")

R/parse.R

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,19 @@ make_timestamp <- function(input) {
2727
safe_format(input, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC", usetz = FALSE)
2828
}
2929

30-
ensure_columns <- function(.data, ptype) {
30+
ensure_columns <- function(.data, ptype, order_columns = FALSE) {
3131
# Given a prototype, ensure that all columns are present and cast to the correct type.
3232
# If a column is missing in .data, it will be created with all missing values of the correct type.
3333
# If a column is present in both, it will be cast to the correct type.
3434
# If a column is present in .data but not in ptype, it will be left as is.
3535
for (i in names(ptype)) {
3636
.data <- ensure_column(.data, ptype[[i]], i)
3737
}
38+
39+
if (order_columns) {
40+
.data <- .data[, unique(c(names(ptype), names(.data))), drop = FALSE]
41+
}
42+
3843
.data
3944
}
4045

@@ -59,14 +64,14 @@ ensure_column <- function(data, default, name) {
5964
if (inherits(default, "list") && !inherits(col, "list")) {
6065
col <- list(col)
6166
}
62-
col <- vctrs::vec_cast(col, default)
67+
col <- vctrs::vec_cast(col, default, x_arg = name)
6368
}
6469
data[[name]] <- col
6570
data
6671
}
6772

68-
parse_connectapi_typed <- function(data, ptype) {
69-
ensure_columns(parse_connectapi(data), ptype)
73+
parse_connectapi_typed <- function(data, ptype, order_columns = FALSE) {
74+
ensure_columns(parse_connectapi(data), ptype, order_columns)
7075
}
7176

7277
parse_connectapi <- function(data) {
@@ -185,6 +190,10 @@ tzone <- function(x) {
185190
attr(x, "tzone")[[1]] %||% ""
186191
}
187192

193+
vec_cast.character.integer <- function(x, to, ...) { # nolint: object_name_linter
194+
as.character(x)
195+
}
196+
188197
new_datetime <- function(x = double(), tzone = "") {
189198
tzone <- tzone %||% ""
190199
if (is.integer(x)) {

R/ptype.R

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,26 @@ connectapi_ptypes <- list(
161161
variant_key = NA_character_,
162162
),
163163
jobs = tibble::tibble(
164-
id = NA_integer_,
165-
pid = NA_integer_,
164+
id = NA_character_,
165+
ppid = NA_character_,
166+
pid = NA_character_,
166167
key = NA_character_,
167-
app_id = NA_integer_,
168-
app_guid = NA_character_,
169-
variant_id = NA_integer_,
170-
bundle_id = NA_integer_,
168+
remote_id = NA_character_,
169+
app_id = NA_character_,
170+
variant_id = NA_character_,
171+
bundle_id = NA_character_,
171172
start_time = NA_datetime_,
172173
end_time = NA_datetime_,
174+
last_heartbeat_time = NA_datetime_,
175+
queued_time = NA_datetime_,
176+
queue_name = NA_character_,
173177
tag = NA_character_,
174178
exit_code = NA_integer_,
175-
finalized = NA,
179+
status = NA_integer_,
176180
hostname = NA_character_,
177-
variant_key = NA_character_
181+
cluster = NA_character_,
182+
image = NA_character_,
183+
run_as = NA_character_,
178184
),
179185
job = tibble::tibble(
180186
pid = NA_integer_,

R/utils.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,13 @@ token_hex <- function(n) {
186186
raw <- as.raw(sample(0:255, n, replace = TRUE))
187187
paste(as.character(raw), collapse = "")
188188
}
189+
190+
# Checks to see if an http status contains a 404 error, and that that 404
191+
# response does not contain an error code indicating that an endpoint was called
192+
# but encountered a 404 for some other reason.
193+
endpoint_does_not_exist <- function(res) {
194+
return(
195+
httr::status_code(res) == "404" &&
196+
!("code" %in% names(httr::content(res, as = "parsed")))
197+
)
198+
}

man/Content.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/content_delete.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/content_item.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/content_title.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)