Skip to content

Commit 695f2ba

Browse files
authored
Merge pull request #566 from Crunch-io/dry-run-only
Dry run only
2 parents f828924 + ca45c6f commit 695f2ba

File tree

19 files changed

+1104
-53
lines changed

19 files changed

+1104
-53
lines changed

R/api.R

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
#' @param status.handlers named list of specific HTTP statuses and a response
88
#' function to call in the case where that status is returned. Passed to the
99
#' [handleAPIresponse()] function.
10+
#' @param progress.handler an optional function that resolves errors raised
11+
#' during an async request. Passed to the [`pollProgress()`] function.
1012
#' @keywords internal
11-
crunchAPI <- function(http.verb, url, config = list(), status.handlers = list(), ...) {
13+
crunchAPI <- function(http.verb, url, config = list(), status.handlers = list(), progress.handler = NULL, ...) {
1214
url ## force lazy eval of url
1315
if (isTRUE(getOption("crunch.debug"))) {
1416
## TODO: work this into httpcache.log
@@ -17,7 +19,11 @@ crunchAPI <- function(http.verb, url, config = list(), status.handlers = list(),
1719
}
1820
FUN <- get(http.verb, envir = asNamespace("httpcache"))
1921
x <- FUN(url, ..., config = c(get_crunch_config(), config, strip_token_if_outside(url)))
20-
out <- handleAPIresponse(x, special.statuses = status.handlers)
22+
out <- handleAPIresponse(
23+
x,
24+
special.statuses = status.handlers,
25+
progress.handler = progress.handler
26+
)
2127
return(out)
2228
}
2329

@@ -51,11 +57,18 @@ crDELETE <- function(...) crunchAPI("DELETE", ...)
5157
#' Do the right thing with the HTTP response
5258
#' @param response an httr response object
5359
#' @param special.statuses an optional named list of functions by status code.
60+
#' @param progress.handler an optional function to handle errors reported by
61+
#' a progress result. Default NULL prints the string `message`; other
62+
#' functions required to handle non-string messages in progress responses.
5463
#' @return The full HTTP response object, just the content, or any other
5564
#' status-specific action
5665
#' @importFrom httr content http_status
5766
#' @keywords internal
58-
handleAPIresponse <- function(response, special.statuses = list()) {
67+
handleAPIresponse <- function(
68+
response,
69+
special.statuses = list(),
70+
progress.handler = NULL
71+
) {
5972
warning <- get_header("Warning", response$headers)
6073
if (!is.null(warning)) {
6174
if (startsWith(warning, "299")) {
@@ -81,13 +94,13 @@ handleAPIresponse <- function(response, special.statuses = list()) {
8194
if (is.function(handler)) {
8295
invisible(handler(response))
8396
} else if (tolower(http_status(response)$category) == "success") {
84-
handleAPIsuccess(code, response)
97+
handleAPIsuccess(code, response, progress.handler)
8598
} else {
8699
handleAPIfailure(code, response)
87100
}
88101
}
89102

90-
handleAPIsuccess <- function(code, response) {
103+
handleAPIsuccess <- function(code, response, progress.handler) {
91104
if (code == 202) {
92105
## 202 Continue: a few cases:
93106
## 1) Legacy: POST /batches/ returns Batch entity in Location, no
@@ -106,7 +119,7 @@ handleAPIsuccess <- function(code, response) {
106119
message(paste0("Checking progress at: ", progress_url))
107120
}
108121
tryCatch(
109-
pollProgress(progress_url, getOption("crunch.poll.wait", 0.5)),
122+
pollProgress(progress_url, getOption("crunch.poll.wait", 0.5), progress.handler),
110123
error = function(e) {
111124
message(paste0(
112125
"Something went wrong during `pollProgress()` of url: ",

R/automation.R

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ setMethod("scriptSavepoint", "Script", function(x) {
102102
#' @param is_file The default guesses whether a file or string was
103103
#' used in the `script` argument, but you can override the heuristics
104104
#' by specifying `TRUE` for a file, and `FALSE` for a string.
105+
#' @param encoding Optional encoding to convert **from**, defaults to UTF-8.
106+
#' The API accepts only UTF-8, so all text will be converted to UTF-8 before
107+
#' being sent to the server.
108+
#' @param ... Additional options, such as `dry_run = TRUE` passed on
109+
#' to the API
105110
#'
106111
#' @return For `runCrunchAutomation()`: an updated dataset (invisibly),
107112
#' For `showScriptErrors()`, when run after a failure, a list with two items:
@@ -116,6 +121,9 @@ setMethod("scriptSavepoint", "Script", function(x) {
116121
#' # Or a string directly:
117122
#' ds <- runCrunchAutomation(ds, "RENAME v1 TO age;")
118123
#'
124+
#' # A "dry run" that validates the script but does not run it:
125+
#' runCrunchAutomation(ds, "RENAME V1 TO age;", dry_run = TRUE)
126+
#'
119127
#' # After a failed run, some error information prints to console,
120128
#' # But more details are available with function:
121129
#' showScriptErrors()
@@ -126,24 +134,36 @@ setMethod("scriptSavepoint", "Script", function(x) {
126134
#' }
127135
#' @export
128136
#' @seealso [`automation-undo`] & [`script-catalog`]
129-
runCrunchAutomation <- function(dataset, script, is_file = string_is_file_like(script)) {
137+
runCrunchAutomation <- function(
138+
dataset,
139+
script,
140+
is_file = string_is_file_like(script),
141+
encoding = "UTF-8",
142+
...
143+
) {
130144
reset_automation_error_env()
131145
stopifnot(is.dataset(dataset))
132146
stopifnot(is.character(script))
133147

134148
if (is_file) {
135149
automation_error_env$file <- script
136-
script <- readLines(script, encoding = "UTF-8", warn = FALSE)
150+
script <- readLines(script, encoding = encoding, warn = FALSE)
137151
} else {
138152
automation_error_env$file <- NULL
139153
}
140154
if (length(script) != 1) script <- paste(script, collapse = "\n")
141155

156+
body <- list(body = script, ...)
157+
automation_error_env$last_attempted_script <- script
158+
142159
crPOST(
143160
shojiURL(dataset, "catalogs", "scripts"),
144-
body = toJSON(wrapEntity(body = list(body = script))),
145-
status.handlers = list(`400` = crunchAutomationErrorHandler)
161+
body = toJSON(wrapEntity(body = body)),
162+
status.handlers = list(`400` = crunchAutomationErrorHandler),
163+
progress.handler = crunchAutomationErrorHandler
146164
)
165+
# Provide feedback for dry_run success so that user is confident it was succesful
166+
if (isTRUE(body$dry_run)) message("Script dry run was successful")
147167
invisible(refresh(dataset))
148168
}
149169

@@ -193,18 +213,27 @@ make_rstudio_markers <- function(errors) {
193213
rstudioapi::sourceMarkers("crunchAutomation", markers)
194214
}
195215
# nocov end
216+
196217
#' @importFrom jsonlite fromJSON
197218
#' @importFrom httr http_status content
198219
crunchAutomationErrorHandler <- function(response) {
199-
msg <- http_status(response)$message
200-
automation_messages <- try(content(response)$resolution, silent = TRUE)
220+
# Get data from appropriate place if it's a direct httr response
221+
# or if it's from pollProgress (because validation was async)
222+
# TODO: If this becomes a common pattern, should probably put
223+
# somewhere else
224+
if (inherits(response, "response")) {
225+
msg <- http_status(response)$message
226+
response_content <- content(response)
227+
} else {
228+
msg <- response$message$description
229+
response_content <- response$message
230+
}
231+
automation_messages <- try(response_content$resolution, silent = TRUE)
201232

202-
if (!is.error(automation_messages)) {
203-
# dig into the response to get the script as we sent it to the server
204-
request_body <- fromJSON(rawToChar(response$request$options$postfields))
205-
automation_error_env$script <- request_body$body$body
233+
if (!is.error(automation_messages) && !is.null(automation_messages)) {
234+
automation_error_env$script <- automation_error_env$last_attempted_script
206235

207-
# And convert the full information of the error messages into a data.frame
236+
# convert the full information of the error messages into a data.frame
208237
automation_error_cols <- c("column", "command", "line", "message")
209238
errors <- lapply(
210239
automation_messages,
@@ -236,6 +265,13 @@ crunchAutomationErrorHandler <- function(response) {
236265
}
237266

238267
msg <- paste("Crunch Automation Error\n", error_items, more_info_text, sep = "")
268+
} else {
269+
# could also have information in message property
270+
# try to use it if it's a character string
271+
other_msg <- try(content(response)[["message"]], silent = TRUE) #nocov
272+
if (is.character(other_msg)) { #nocov
273+
msg <- paste0(msg, " - ", paste0(other_msg, collapse = "\n")) #nocov
274+
}
239275
}
240276
halt(msg)
241277
}

R/progress.R

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
#' @param progress_url A Crunch progress URL
77
#' @param wait Number of seconds to wait between polling. This time is increased
88
#' 20 percent on each poll.
9+
#' @param error_handler An optional function that takes the status object
10+
#' when the progress is less than 0 (meaning the request failed)
911
#' @return The percent completed of the progress. Assuming the
1012
#' `options(crunch.timeout)` (default: 15 minutes) hasn't been reached, this
1113
#' will be 100. If the timeout is reached, it will be the last reported progress
1214
#' value.
1315
#' @export
1416
#' @importFrom httpcache uncached
15-
pollProgress <- function(progress_url, wait = .5) {
17+
pollProgress <- function(progress_url, wait = .5, error_handler = NULL) {
1618
## Configure polling interval. Will increase by rate (>1) until reaches max
1719
max.wait <- 30
1820
increase.by <- 1.2
@@ -37,7 +39,9 @@ pollProgress <- function(progress_url, wait = .5) {
3739
}
3840
close(pb)
3941

40-
if (status < 0) {
42+
if (status < 0 && !is.null(error_handler)) {
43+
return(error_handler(prog))
44+
} else if (status < 0) {
4145
email <- "There was an error on the server. Please contact [email protected]"
4246
msg <- prog$message %||% email
4347
halt(msg)

dev-misc/fixture-creation/vegetables-dataset.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,26 @@ file_copy(cube_path, here("mocks", "cubes", "numa-x-mr.json"), overwrite = TRUE)
540540

541541
dir_delete(temp_dir)
542542

543+
544+
### Generate failed async crunch automation failure
545+
httpcache::clearCache()
546+
dir_create(temp_dir)
547+
548+
start_capturing(temp_dir)
549+
failed <- try(runCrunchAutomation(ds, "NOT A COMMAND", async = TRUE), silent = TRUE)
550+
stop_capturing()
551+
552+
progress_path <- path(temp_dir, "app.crunch.io/api/progress.json")
553+
554+
file_copy(
555+
progress_path,
556+
here("mocks", "app.crunch.io", "api", "progress-failed-async-script.json"),
557+
overwrite = TRUE
558+
)
559+
560+
dir_delete(temp_dir)
561+
562+
543563
# Cleanup ----
544564
rm(deck) # Hopefully gets rid of weird message after sourcing script
545565
with_consent(delete(ds))

man/crunchAPI.Rd

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

man/handleAPIresponse.Rd

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

man/pollProgress.Rd

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

man/runCrunchAutomation.Rd

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

mocks/app.crunch.io/api/datasets/veg/cube-28013a.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"other": 0,
101101
"selected": 210
102102
}
103-
}
103+
},
104+
"is_cat_date": false
104105
},
105106
"filtered": {
106107
"unweighted_n": 210,

mocks/app.crunch.io/api/datasets/veg/cube-331178.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"other": 0,
101101
"selected": 210
102102
}
103-
}
103+
},
104+
"is_cat_date": false
104105
},
105106
"filtered": {
106107
"unweighted_n": 210,

0 commit comments

Comments
 (0)