diff --git a/DESCRIPTION b/DESCRIPTION index 44f4314bf..aafb54e53 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: orderly Title: Lightweight Reproducible Reporting -Version: 1.2.38 +Version: 1.2.39 Description: Order, create and store reports from R. By defining a lightweight interface around the inputs and outputs of an analysis, a lot of the repetitive work for reproducible research diff --git a/NAMESPACE b/NAMESPACE index 7d6e73707..1f5e85112 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -50,5 +50,4 @@ export(orderly_use_gitignore) export(orderly_use_package) export(orderly_use_resource) export(orderly_use_source) -export(orderly_workflow) importFrom(R6,R6Class) diff --git a/NEWS.md b/NEWS.md index 8c2d84576..0001485cf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# orderly 1.2.39 + +* Remove `orderly_workflow` in favour of supporting workflows through orderly.server (mrc-2275) + # orderly 1.2.38 * Much clearer error message where dependency resolution fails due to a query dependency (vimc-4499) diff --git a/R/db2.R b/R/db2.R index 3913e4a38..e7995e7ca 100644 --- a/R/db2.R +++ b/R/db2.R @@ -8,7 +8,7 @@ ## namespace/module feature so that implementation details can be ## hidden away a bit further. -orderly_schema_version <- "1.2.34" +orderly_schema_version <- "1.2.39" orderly_schema_table <- "orderly_schema" orderly_table_list <- "orderly_schema_tables" @@ -429,16 +429,13 @@ report_data_import <- function(con, name, id, config) { if (!is.null(dat_rds$meta$workflow)) { sql_batch <- "SELECT id FROM workflow WHERE id = $1" - if (nrow(DBI::dbGetQuery(con, sql_batch, dat_rds$meta$workflow$id)) == 0L) { - workflow <- data_frame( - id = dat_rds$meta$workflow$id, - name = dat_rds$meta$workflow$name - ) + if (nrow(DBI::dbGetQuery(con, sql_batch, dat_rds$meta$workflow)) == 0L) { + workflow <- data_frame(id = dat_rds$meta$workflow) DBI::dbWriteTable(con, "workflow", workflow, append = TRUE) } report_version_workflow <- data_frame( report_version = id, - workflow_id = dat_rds$meta$workflow$id + workflow_id = dat_rds$meta$workflow ) DBI::dbWriteTable(con, "report_version_workflow", report_version_workflow, append = TRUE) diff --git a/R/main.R b/R/main.R index 442667008..4b5809cf1 100644 --- a/R/main.R +++ b/R/main.R @@ -30,10 +30,6 @@ cli_args_process <- function(args) { dat$options$parameters <- cli_args_process_batch_parameters( dat$options$parameter, dat$options$file) dat$options$name <- dat$options[[""]] # docopt bug? - } else if (dat$command == "workflow") { - dat$options$parameters <- cli_args_process_run_parameters( - dat$options$parameter) - dat$options$name <- dat$options[[""]] # docopt bug? } dat @@ -55,8 +51,7 @@ Commands: cleanup Remove drafts and dangling data rebuild Rebuild the database migrate Migrate the archive - batch Run a batch of reports - workflow Run a workflow" + batch Run a batch of reports" cli_args_preprocess <- function(args) { ## This will work ok for filtering away unwanted arguments *if* the @@ -144,14 +139,15 @@ usage_run <- "Usage: orderly run [options] [...] Options: - --instance=NAME Database instance to use (if instances are configured) - --no-commit Do not commit the report - --print-log Print the log (rather than storing it) - --id-file=FILE File to write the id into - --ref=REF Git reference (branch or sha) to use - --fetch Fetch git before updating reference - --pull Pull git before running report - --message=TEXT A message explaining why the report was run + --instance=NAME Database instance to use (if instances are configured) + --no-commit Do not commit the report + --print-log Print the log (rather than storing it) + --id-file=FILE File to write the id into + --ref=REF Git reference (branch or sha) to use + --fetch Fetch git before updating reference + --pull Pull git before running report + --message=TEXT A message explaining why the report was run + --workflow-id=TEXT The ID of the workflow this report is run as part of Parameters, if given, must be passed through in key=value pairs" @@ -176,6 +172,7 @@ main_do_run <- function(x) { fetch <- x$options$fetch pull <- x$options$pull message <- x$options$message + workflow_id <- x$options$workflow_id if (print_log) { sink(stderr(), type = "output") @@ -188,7 +185,8 @@ main_do_run <- function(x) { id <- orderly_run_internal(name, parameters, root = config, id_file = id_file, instance = instance, ref = ref, fetch = fetch, message = message, - commit = commit, capture_log = !print_log) + commit = commit, capture_log = !print_log, + workflow_id = workflow_id) message("id:", id) } @@ -387,49 +385,6 @@ main_do_batch <- function(x) { message("ids:", paste(ids, collapse = ", ")) } -## 9. workflow -usage_workflow <- "Usage: - orderly workflow [options] [...] - -Options: - --print-log Print the log (rather than storing it) - --ref=REF Git reference (branch or sha) to use - --pull Pull git before running report - --instance=NAME Database instance to use (if instances are configured) - --message=TEXT A message explaining why the workflow was run - -Parameters, if given, must be passed through in key=value pairs" - -main_do_workflow <- function(x) { - ## TODO: Get some classed errors though here and then write out - ## information about whether or not things worked and why they - ## didn't. Possible issues (in order) - ## - ## * orderly report not found - ## * error while preparing (e.g., package not found) - ## * error while running report - ## * error checking artefacts - config <- orderly_config(x$options$root, TRUE) - name <- x$options$name - instance <- x$options$instance - print_log <- x$options$print_log - ref <- x$options$ref - pull <- x$options$pull - message <- x$options$message - - if (print_log) { - sink(stderr(), type = "output") - on.exit(sink(NULL, type = "output")) - } else { - config$add_run_option("capture_log", TRUE) - } - - git_pull_ref(pull, ref, config, message = "Can't use --pull with --ref.") - - output <- orderly_workflow_internal(name, root = config, instance = instance, - message = message, ref = ref) - message("ids:", paste(output, collapse = ", ")) -} write_script <- function(path, versioned = FALSE) { if (!isTRUE(is_directory(path))) { @@ -476,10 +431,7 @@ cli_commands <- function() { target = main_do_migrate), batch = list(name = "batch run reports", usage = usage_batch, - target = main_do_batch), - workflow = list(name = "run workflow", - usage = usage_workflow, - target = main_do_workflow)) + target = main_do_batch)) } diff --git a/R/orderly_version.R b/R/orderly_version.R index e67098b0a..1b5543a89 100644 --- a/R/orderly_version.R +++ b/R/orderly_version.R @@ -8,7 +8,7 @@ orderly_version <- R6::R6Class( id = NULL, batch_id = NULL, - workflow_info = NULL, + workflow_id = NULL, workdir = NULL, envvar = NULL, @@ -354,7 +354,7 @@ orderly_version <- R6::R6Class( tags = private$tags, git = private$preflight_info$git, batch_id = private$batch_id, - workflow = private$workflow_info, + workflow = private$workflow_id, data = private$postflight_info$data_info, view = private$postflight_info$view_info) }, @@ -423,7 +423,7 @@ orderly_version <- R6::R6Class( use_draft = FALSE, remote = NULL, ## These might move around a bit id_file = NULL, batch_id = NULL, - workflow_info = NULL, ref = NULL, fetch = FALSE, + workflow_id = NULL, ref = NULL, fetch = FALSE, capture_log = FALSE) { logfile <- tempfile() capture_log <- capture_log %||% @@ -441,7 +441,7 @@ orderly_version <- R6::R6Class( path_orderly_log(private$workdir))) } private$batch_id <- batch_id - private$workflow_info <- workflow_info + private$workflow_id <- workflow_id private$fetch() self$run_execute(echo) self$run_cleanup() diff --git a/R/paths.R b/R/paths.R index f50328a3b..ff7509398 100644 --- a/R/paths.R +++ b/R/paths.R @@ -48,10 +48,6 @@ path_orderly_envir_yml <- function(path) { file.path(path, "orderly_envir.yml") } -path_orderly_workflow_dir <- function(path, workflow_name) { - file.path(path, "workflows", paste0(workflow_name, ".yml")) -} - path_orderly_log <- function(path) { file.path(path, "orderly.log") } diff --git a/R/recipe_run.R b/R/recipe_run.R index 34e1a5611..2874edea1 100644 --- a/R/recipe_run.R +++ b/R/recipe_run.R @@ -128,13 +128,13 @@ orderly_run_internal <- function(name = NULL, parameters = NULL, envir = NULL, remote = NULL, tags = NULL, # specific to run_internal id_file = NULL, batch_id = NULL, - workflow_info = NULL, fetch = FALSE, + workflow_id = NULL, fetch = FALSE, ref = NULL, capture_log = NULL, commit = FALSE) { version <- orderly_version$new(name, root, locate) id <- version$run_internal(parameters, instance, envir, message, tags, echo, use_draft, remote, id_file, batch_id, - workflow_info, ref, fetch, capture_log) + workflow_id, ref, fetch, capture_log) if (commit) { version$commit(capture_log) } diff --git a/R/workflow.R b/R/workflow.R deleted file mode 100644 index 158240352..000000000 --- a/R/workflow.R +++ /dev/null @@ -1,122 +0,0 @@ -##' Run a workflow. -##' -##' This runs & commits each of the reports configured in the workflow in turn. -##' Note that if one report fails to be run or to be commited this will -##' continue and attempt to run the remaining reports. -##' -##' @param name Name of workflow to run -##' -##' @inheritParams orderly_list -##' @inheritParams orderly_run -##' -##' @return ID of workflow run -##' @export -##' -##' @examples -##' path <- orderly::orderly_example("demo") -##' -##' # To run most reports, provide the report name (and the path if -##' # not running in the working directory, as is the case here): -##' ids <- orderly::orderly_workflow("my_workflow", root = path) -orderly_workflow <- function(name, envir = NULL, root = NULL, locate = TRUE, - message = NULL, instance = NULL, remote = NULL) { - orderly_workflow_internal(name, envir, root, locate, message, instance, - remote, ref = NULL) -} - -orderly_workflow_internal <- function(name, envir = NULL, root = NULL, - locate = TRUE, message = NULL, - instance = NULL, remote = NULL, - ref = NULL) { - config <- orderly_config(root, locate) - workflow <- workflow$new(name, config, ref) - workflow$run(envir, message, instance, remote) -} - -workflow <- R6::R6Class( - "workflow", - - public = list( - workflow_name = NULL, - steps = NULL, - workflow_id = NULL, - config = NULL, - ref = NULL, - - initialize = function(workflow_name, config, ref = NULL) { - self$workflow_name <- workflow_name - self$workflow_id <- ids::random_id() - self$config <- config - self$ref <- ref - git_restore <- private$git_checkout(self$ref) - tryCatch({ - workflow_path <- path_orderly_workflow_dir(config$root, workflow_name) - assert_file_exists(basename(workflow_path), - workdir = dirname(workflow_path), - name = "workflow configuration") - raw <- yaml_read(workflow_path) - self$steps <- validate_workflow(raw, self$config, self$workflow_name, - workflow_path) - }, finally = git_restore()) - }, - - run = function(envir = NULL, message = NULL, instance = NULL, - remote = NULL) { - orderly_log("workflow", sprintf("Running workflow '%s' with ID '%s'", - self$workflow_name, self$workflow_id)) - run_ids <- rep(NA, length(self$steps)) - for (i in seq_along(run_ids)) { - report <- self$steps[i] - orderly_log("workflow", sprintf("Running report '%s'", report)) - tryCatch({ - run_ids[i] <- orderly_run_internal( - report, envir = envir, root = self$config, message = message, - instance = instance, remote = remote, commit = TRUE, echo = FALSE, - workflow_info = list(id = self$workflow_id, - name = self$workflow_name), - ref = self$ref) - orderly_log("workflow", - sprintf("Completed running & committing report '%s'", - report)) - }, - error = function(e) { - orderly_log("error", sprintf("Running report '%s' failed", report)) - stop(e) - }) - } - orderly_log("workflow", - sprintf("Completed running workflow '%s' with ID '%s'", - self$workflow_name, self$workflow_id)) - run_ids - } - ), - - private = list( - git_checkout = function(ref) { - if (is.null(ref)) { - return(function() NULL) - } - prev <- git_detach_head_at_ref(ref, self$config$root) - function() git_checkout_branch(prev, TRUE, self$config$root) - } - ) -) - -parse_steps <- function(yml) { - unname(unlist(yml$steps)) -} - -validate_workflow <- function(raw_yml, config, workflow_name, workflow_path) { - check_fields(raw_yml, workflow_path, "steps", NULL) - for (step in raw_yml$steps) { - check_fields(step, sprintf("%s:steps", workflow_path), "name", NULL) - } - reports <- orderly_list(config) - steps <- parse_steps(raw_yml) - msg <- setdiff(steps, reports) - if (length(msg)) { - stop(sprintf("Cannot run workflow '%s' as reports missing: %s", - workflow_name, paste(squote(msg), collapse = ", "))) - } - steps -} diff --git a/inst/database/schema.yml b/inst/database/schema.yml index 7e7ee747a..a004bad73 100644 --- a/inst/database/schema.yml +++ b/inst/database/schema.yml @@ -253,7 +253,6 @@ report_version_batch: workflow: columns: - id: {type: TEXT} - - name: {type: TEXT} report_version_workflow: columns: diff --git a/man/orderly_workflow.Rd b/man/orderly_workflow.Rd deleted file mode 100644 index e796f5648..000000000 --- a/man/orderly_workflow.Rd +++ /dev/null @@ -1,69 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/workflow.R -\name{orderly_workflow} -\alias{orderly_workflow} -\title{Run a workflow.} -\usage{ -orderly_workflow( - name, - envir = NULL, - root = NULL, - locate = TRUE, - message = NULL, - instance = NULL, - remote = NULL -) -} -\arguments{ -\item{name}{Name of workflow to run} - -\item{envir}{The parent of the environment that will be used to -evaluate the report script; by default a new environment will be -made with the global environment as the parent.} - -\item{root}{The path to an orderly root directory, or \code{NULL} -(the default) to search for one from the current working -directory if \code{locate} is \code{TRUE}.} - -\item{locate}{Logical, indicating if the configuration should be -searched for. If \code{TRUE} and \code{config} is not given, -then orderly looks in the working directory and up through its -parents until it finds an \code{orderly_config.yml} file.} - -\item{message}{An optional character string containing a message -explaining why the report was run} - -\item{instance}{Select instance of the source database to be used, -where multiple instances are configured. Use a single -\emph{unnamed} character string to indicate an instance to -match. If given, then this name must be present in all -databases where instances are listed in -\code{orderly_config.yml}, and will be ignored by all database -where instances are not given. See the "orderly" vignette for -further information.} - -\item{remote}{Remote to use to resolve dependencies. Use this in -order to run a report with the same dependencies as are -available on a remote server, particularly when using \code{id = -"latest"}. Note that this is not the same as running -\code{\link{orderly_pull_dependencies}}, then \code{orderly_run} -with \code{remote = NULL}, as the pull/run approach will use the -latest report in \emph{your} archive but the \code{remote = -"remote"} approach will use the latest approach in the -\emph{remote} archive (which might be less recent).} -} -\value{ -ID of workflow run -} -\description{ -This runs & commits each of the reports configured in the workflow in turn. -Note that if one report fails to be run or to be commited this will -continue and attempt to run the remaining reports. -} -\examples{ -path <- orderly::orderly_example("demo") - -# To run most reports, provide the report name (and the path if -# not running in the working directory, as is the case here): -ids <- orderly::orderly_workflow("my_workflow", root = path) -} diff --git a/tests/testthat/test-db.R b/tests/testthat/test-db.R index 6c0b0efb1..1e6b7bbc2 100644 --- a/tests/testthat/test-db.R +++ b/tests/testthat/test-db.R @@ -436,24 +436,6 @@ test_that("add batch info to db", { data_frame(report_version = ids, report_batch = rep(batch_id, 3))) }) -test_that("add workflow info to db", { - path <- prepare_orderly_example("demo") - - mock_random_id <- mockery::mock("workflow_id", "report_id1", "report_id2") - with_mock("ids::random_id" = mock_random_id, { - ids <- orderly_workflow("my_workflow", root = path) - }) - - con <- orderly_db("destination", path) - on.exit(DBI::dbDisconnect(con)) - expect_equal( - DBI::dbReadTable(con, "workflow"), - data_frame(id = "workflow_id", - name = "my_workflow")) - expect_equal( - DBI::dbReadTable(con, "report_version_workflow"), - data_frame(report_version = ids, workflow_id = rep("workflow_id", 2))) -}) ## Regression test for vimc-3652 diff --git a/tests/testthat/test-main.R b/tests/testthat/test-main.R index 1c8877791..7a3d788d0 100644 --- a/tests/testthat/test-main.R +++ b/tests/testthat/test-main.R @@ -684,174 +684,19 @@ test_that("batch: pull before run", { expect_equal(git_ref_to_sha("HEAD", path_local), sha_origin) }) -test_that("workflow", { - testthat::skip_on_cran() - path <- unzip_git_demo() - - args <- c("--root", path, "workflow", "my_workflow") - - res <- cli_args_process(args) - expect_equal(res$command, "workflow") - expect_equal(res$options$name, "my_workflow") - expect_false(res$options$print_log) - expect_false(res$options$pull) - expect_equal(res$options$instance, NULL) - expect_equal(res$target, main_do_workflow) - - expect_message(res$target(res), "ids:[\\w\\d-]*") - - d <- orderly_list_archive(path) - expect_equal(nrow(d), 2L) - expect_equal(d$name, c("global", "minimal")) - - log_file <- file.path(path, "archive", "global", d$id[1], "orderly.log") - expect_true(file.exists(log_file)) - logs <- readLines(log_file) - expect_true(sprintf("[ id ] %s", d$id[1]) %in% - crayon::strip_style(logs)) - expect_equal(sum(grepl("\\[ id \\].*", logs)), 1) - - log_file <- file.path(path, "archive", "minimal", d$id[2], "orderly.log") - expect_true(file.exists(log_file)) - logs <- readLines(log_file) - expect_true(sprintf("[ id ] %s", d$id[2]) %in% - crayon::strip_style(logs)) - expect_equal(sum(grepl("\\[ id \\].*", logs)), 1) -}) - -test_that("workflow: pull before run", { - testthat::skip_on_cran() - path <- prepare_orderly_git_example(branch = "other") - path_local <- path[["local"]] - path_origin <- path[["origin"]] - sha_local <- git_ref_to_sha("HEAD", path_local) - sha_origin <- git_ref_to_sha("HEAD", path_origin) - - args <- c("--root", path_local, "workflow", "--pull", "my_workflow") - - res <- cli_args_process(args) - expect_equal(res$command, "workflow") - expect_equal(res$options$name, "my_workflow") - expect_equal(res$options$instance, NULL) - expect_true(res$options$pull) - expect_equal(res$target, main_do_workflow) - - expect_message(res$target(res), "ids:[\\w\\d-]*") - - id <- orderly_latest("minimal", root = path_local) - d <- readRDS(path_orderly_run_rds( - file.path(path_local, "archive", "minimal", id))) - expect_equal(d$git$sha, sha_origin) - expect_equal(git_ref_to_sha("HEAD", path_local), sha_origin) -}) - -test_that("workflow: print logs", { - testthat::skip_on_cran() - path <- unzip_git_demo() - - args <- c("--root", path, "workflow", "--print-log", "my_workflow") - - res <- cli_args_process(args) - expect_equal(res$command, "workflow") - expect_equal(res$options$name, "my_workflow") - expect_true(res$options$print_log) - expect_false(res$options$pull) - expect_equal(res$options$instance, NULL) - expect_equal(res$target, main_do_workflow) - - out <- evaluate_promise(capture.output(res$target(res), type = "message")) - - d <- orderly_list_archive(path) - expect_equal(nrow(d), 2L) - expect_equal(d$name, c("global", "minimal")) - - expect_true(sprintf("[ id ] %s\n", d$id[1]) - %in% crayon::strip_style(out$messages)) - log_file <- file.path(path, "archive", "global", d$id[1], "orderly.log") - ## Logs have not been written to file - expect_false(file.exists(log_file)) - - expect_true(sprintf("[ id ] %s\n", d$id[2]) - %in% crayon::strip_style(out$messages)) - log_file <- file.path(path, "archive", "global", d$id[2], "orderly.log") - ## Logs have not been written to file - expect_false(file.exists(log_file)) -}) - -test_that("workflow: args passed to workflow", { - path <- unzip_git_demo() - args <- c("--root", path, "workflow", "--instance", "inst", - "--message", "msg", "my_workflow") - - res <- cli_args_process(args) - expect_equal(res$command, "workflow") - expect_equal(res$options$name, "my_workflow") - expect_false(res$options$print_log) - expect_false(res$options$pull) - expect_equal(res$options$instance, "inst") - expect_equal(res$options$message, "msg") - expect_equal(res$target, main_do_workflow) - - mock_workflow <- mockery::mock("id") - with_mock("orderly:::orderly_workflow_internal" = mock_workflow, - tryCatch(res$target(res), - error = function(e) { - ## We don't care about error here so just consume it - invisible(NULL) - })) - - args <- mockery::mock_args(mock_workflow) - ## orderly_workflow called once - expect_length(args, 1) - expect_equal(args[[1]]$instance, "inst") - expect_equal(args[[1]]$message, "msg") -}) - -test_that("workflow: ref", { - testthat::skip_on_cran() - path <- unzip_git_demo() - - ## Change workflow on "other" branch - prev <- git_checkout_branch("other", root = path) - workflow_file <- file.path(path, "workflows/my_workflow.yml") - workflow <- c("steps:", - ' - name: "minimal"', - ' - name: "other"') - writeLines(workflow, workflow_file) - ## Add a default param - eventually we want to workflow to be able - ## to pass params but set some default for now - other_yml <- file.path(path, "src/other/orderly.yml") - other <- readLines(other_yml) - other <- gsub(pattern = "nmin: ~", replace = "nmin:\n default: 0.25", - x = other) - writeLines(other, other_yml) - git_run(c("add", "."), root = path, check = TRUE) - git_run(c("commit", "-m", "'initial-import'"), root = path, check = TRUE) - other_branch <- git_checkout_branch(prev, root = path) - expect_false(identical(workflow, readLines(workflow_file))) - - ## Run the workflow on --ref other - args <- c("--root", path, "workflow", "--ref", "other", "my_workflow") +test_that("run can save workflow metadata", { + path <- prepare_orderly_example("minimal") + args <- c("--root", path, "run", "--workflow-id", "123", "example") res <- cli_args_process(args) - expect_equal(res$command, "workflow") - expect_equal(res$options$ref, "other") - - ans <- capture.output(res$target(res)) - - d <- orderly_list_archive(path) - expect_equal(nrow(d), 2L) - expect_equal(d$name, c("minimal", "other")) -}) + expect_equal(res$options$workflow_id, "123") -test_that("workflow: pull & ref don't go together", { - testthat::skip_on_cran() - path <- unzip_git_demo() - args <- c("--root", path, "workflow", "--ref", "origin/master", "--pull", - "my_workflow") - res <- cli_args_process(args) - expect_error(res$target(res), - "Can't use --pull with --ref.", - fixed = TRUE, - class = "orderly_cli_error") + capture.output(res$target(res)) + expect_equal(orderly_list(path), "example") + expect_equal(nrow(orderly_list_archive(path)), 1) + id <- orderly_list_archive(path)$id + rds <- path_orderly_run_rds(file.path(path, "archive", "example", id)) + expect_true(file.exists(rds)) + dat <- readRDS(rds) + expect_equal(dat$meta$workflow, "123") }) diff --git a/tests/testthat/test-run.R b/tests/testthat/test-run.R index 2d61429f1..c498dff43 100644 --- a/tests/testthat/test-run.R +++ b/tests/testthat/test-run.R @@ -1093,3 +1093,39 @@ test_that("orderly_run_internal writes fail rds on error", { expect_match(failed_rds$error$trace[length(failed_rds$error$trace)], 'stop\\("some error"\\)') }) + +test_that("orderly_run_internal can save workflow metadata", { + path <- prepare_orderly_example("minimal") + tmp <- tempfile() + id <- orderly_run_internal("example", root = path, id_file = tmp, + commit = TRUE, echo = FALSE, + workflow_id = "123") + rds <- path_orderly_run_rds(file.path(path, "archive", "example", id)) + expect_true(file.exists(rds)) + dat <- readRDS(rds) + expect_equal(dat$meta$workflow, "123") + + con <- orderly_db("destination", root = path) + on.exit(DBI::dbDisconnect(con)) + workflow <- DBI::dbGetQuery(con, "SELECT * FROM workflow") + expect_equal(workflow, data_frame(id = "123")) + report_version_workflow <- + DBI::dbGetQuery(con, "SELECT * FROM report_version_workflow") + expect_equal(report_version_workflow, data_frame(report_version = id, + workflow_id = "123")) + + id2 <- orderly_run_internal("example", root = path, id_file = tmp, + commit = TRUE, echo = FALSE, + workflow_id = "123") + rds <- path_orderly_run_rds(file.path(path, "archive", "example", id2)) + expect_true(file.exists(rds)) + dat <- readRDS(rds) + expect_equal(dat$meta$workflow, "123") + workflow <- DBI::dbGetQuery(con, "SELECT * FROM workflow") + expect_equal(workflow, data_frame(id = "123")) + report_version_workflow <- + DBI::dbGetQuery(con, "SELECT * FROM report_version_workflow") + expect_equal(report_version_workflow, + data_frame(report_version = c(id, id2), + workflow_id = c("123", "123"))) +}) diff --git a/tests/testthat/test-workflow.R b/tests/testthat/test-workflow.R deleted file mode 100644 index f9ce97db7..000000000 --- a/tests/testthat/test-workflow.R +++ /dev/null @@ -1,200 +0,0 @@ -context("workflow") - -test_that("workflow can be run", { - path <- prepare_orderly_example("demo") - mock_new_report_id <- mockery::mock("report_id_1", "report_id_2") - mock_random_id <- mockery::mock("workflowid") - - with_mock( - "orderly:::new_report_id" = mock_new_report_id, - "ids::random_id" = mock_random_id, { - output <- evaluate_promise(orderly_workflow("my_workflow", root = path)) - }) - - expect_output <- function(message) { - expect_true(any(grepl(message, output$messages))) - } - expect_output("Running workflow 'my_workflow' with ID 'workflowid'") - expect_output("Running report 'minimal'") - expect_output("Completed running & committing report 'minimal'") - expect_output("Running report 'global'") - expect_output("Completed running & committing report 'global'") - expect_output("Completed running workflow 'my_workflow' with ID 'workflowid'") - - expect_equal(output$result, c("report_id_1", "report_id_2")) - - expect_true(file.exists(path_orderly_run_rds( - file.path(path, "archive", "minimal", output$result[1])))) - expect_true(file.exists(path_orderly_run_rds( - file.path(path, "archive", "global", output$result[2])))) -}) - -test_that("workflow returns completed IDs if a report fails", { - path <- prepare_orderly_example("demo") - - ## Ensure one of the reports fails - p <- file.path(path, "src", "global", "script.R") - txt <- c(readLines(p), "stop('got an error')") - writeLines(txt, p) - - mock_new_report_id <- mockery::mock("report_id_1", "report_id_2") - mock_random_id <- mockery::mock("workflowid") - with_mock( - "orderly:::new_report_id" = mock_new_report_id, - "ids::random_id" = mock_random_id, { - expect_error(orderly_workflow("my_workflow", root = path), "got an error") - }) - - ## First report still gets run - expect_true(file.exists(path_orderly_run_rds( - file.path(path, "archive", "minimal", "report_id_1")))) - expect_false(file.exists(file.path(path, "archive", "global"))) -}) - -test_that("envir, message, instance, remote get passed to orderly_run", { - path <- prepare_orderly_example("demo") - ## Mock out calls to orderly_run so we can check interactions - mock_orderly_run <- mockery::mock("id1", "id2") - with_mock("orderly::orderly_run_internal" = mock_orderly_run, { - orderly_workflow("my_workflow", root = path, envir = "envir", - message = "message", instance = "instance", - remote = "remote") - }) - mockery::expect_called(mock_orderly_run, 2) - args <- mockery::mock_args(mock_orderly_run) - expect_equal(args[[1]]$envir, "envir") - expect_equal(args[[1]]$message, "message") - expect_equal(args[[1]]$instance, "instance") - expect_equal(args[[1]]$remote, "remote") - expect_equal(args[[2]]$envir, "envir") - expect_equal(args[[2]]$message, "message") - expect_equal(args[[2]]$instance, "instance") - expect_equal(args[[2]]$remote, "remote") -}) - -test_that("orderly_workflow throws an error if it cannot locate workflow", { - path <- prepare_orderly_example("demo") - expect_error( - orderly_workflow("missing_workflow", root = path), - "workflow configuration does not exist: 'missing_workflow.yml'", - fixed = TRUE) -}) - -test_that("orderly_workflow throws error if report does not exist", { - path <- prepare_orderly_example("workflow", testing = TRUE) - expect_error( - orderly_workflow("missing_report", root = path), - paste0("Cannot run workflow 'missing_report' as reports missing: ", - "'missing', 'missing2'")) -}) - -test_that("orderly_workflow throws error if steps do not exist", { - path <- prepare_orderly_example("workflow", testing = TRUE) - expect_error( - orderly_workflow("missing_steps", root = path), - "Fields missing from .+/workflows/missing_steps.yml: steps") -}) - -test_that("orderly_workflow throws error if steps are misconfigured", { - path <- prepare_orderly_example("workflow", testing = TRUE) - expect_error( - orderly_workflow("missing_name", root = path), - "Fields missing from .+/workflows/missing_name.yml:steps: name") - expect_error( - orderly_workflow("broken_steps", root = path), - "Unknown fields in .+/workflows/broken_steps.yml:steps: field") -}) - -test_that("steps can be parsed", { - test_steps <- list( - steps = list( - list( - name = "step1" - ), - list( - name = "step2" - ), - list( - name = "step3" - ) - ) - ) - expect_equal(parse_steps(test_steps), c("step1", "step2", "step3")) -}) - -test_that("workflow can post success to slack/teams", { - path <- prepare_orderly_example("demo") - mock_new_report_id <- mockery::mock("report_id_1", "report_id_2") - mock_random_id <- mockery::mock("workflowid") - mock_post_success <- mockery::mock(TRUE, TRUE) - - with_mock( - "orderly:::new_report_id" = mock_new_report_id, - "ids::random_id" = mock_random_id, - "orderly:::post_success" = mock_post_success, { - output <- evaluate_promise(orderly_workflow("my_workflow", root = path)) - }) - - expect_equal(output$result, c("report_id_1", "report_id_2")) - - minimal_rds <- path_orderly_run_rds( - file.path(path, "archive", "minimal", output$result[1])) - global_rds <- path_orderly_run_rds( - file.path(path, "archive", "global", output$result[2])) - expect_true(file.exists(minimal_rds)) - expect_true(file.exists(global_rds)) - - args <- mockery::mock_args(mock_post_success) - expect_length(args, 2) - expect_equal(args[[1]][[1]], readRDS(minimal_rds)) - expect_s3_class(args[[1]][[2]], "orderly_config") - expect_equal(args[[2]][[1]], readRDS(global_rds)) - expect_s3_class(args[[2]][[2]], "orderly_config") -}) - -test_that("workflow can be run with specified git ref", { - testthat::skip_on_cran() - path <- unzip_git_demo() - - ## Change workflow on "other" branch - prev <- git_checkout_branch("other", root = path) - workflow_file <- file.path(path, "workflows/my_workflow.yml") - workflow <- c("steps:", - ' - name: "minimal"', - ' - name: "other"') - writeLines(workflow, workflow_file) - ## Add a default param - eventually we want to workflow to be able - ## to pass params but set some default for now - other_yml <- file.path(path, "src/other/orderly.yml") - other <- readLines(other_yml) - other <- gsub(pattern = "nmin: ~", replace = "nmin:\n default: 0.25", - x = other) - writeLines(other, other_yml) - git_run(c("add", "."), root = path, check = TRUE) - git_run(c("commit", "-m", "'initial-import'"), root = path, check = TRUE) - other_branch <- git_checkout_branch(prev, root = path) - expect_false(identical(workflow, readLines(workflow_file))) - - mock_random_id <- mockery::mock("workflowid", cycle = TRUE) - - with_mock( - "ids::random_id" = mock_random_id, { - output <- evaluate_promise( - orderly_workflow_internal("my_workflow", root = path, ref = "other")) - }) - - expect_output <- function(message) { - expect_true(any(grepl(message, output$messages))) - } - expect_output("Running workflow 'my_workflow' with ID 'workflowid'") - expect_output("Running report 'minimal'") - expect_output("Completed running & committing report 'minimal'") - expect_output("Running report 'other'") - expect_output("Completed running & committing report 'other'") - expect_output("Completed running workflow 'my_workflow' with ID 'workflowid'") - - expect_true(file.exists(path_orderly_run_rds( - file.path(path, "archive", "minimal", output$result[1])))) - expect_true(file.exists(path_orderly_run_rds( - file.path(path, "archive", "other", output$result[2])))) -}) diff --git a/vignettes/workflows.Rmd b/vignettes/workflows.Rmd deleted file mode 100644 index 120565743..000000000 --- a/vignettes/workflows.Rmd +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: "Workflows" -date: "`r Sys.Date()`" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{Workflows} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -``` {r echo = FALSE, results = "hide"} -lang_output <- function(x, lang) { - writeLines(c(sprintf("```%s", lang), x, "```")) -} -r_output <- function(x) lang_output(x, "r") -yaml_output <- function(x) lang_output(x, "yaml") -path <- orderly:::prepare_orderly_example("demo") -## Even simpler -unlink(file.path(path, "archive"), recursive = TRUE) -unlink(file.path(path, "draft"), recursive = TRUE) -unlink(file.path(path, "data"), recursive = TRUE) -unlink(file.path(path, "data"), recursive = TRUE) -remove_files <- setdiff(list.files(file.path(path, "src"), full.names = TRUE), - file.path(path, "src", c("minimal", "global"))) -for (file in remove_files) { - unlink(file, recursive = TRUE) -} -unlink(file.path(path, "before.R")) -unlink(file.path(path, "source.R")) -unlink(file.path(path, "demo.yml")) -unlink(file.path(path, "README.md")) -unlink(file.path(path, "src", "README.md")) - -dir_tree <- function(path) { - withr::with_dir(path, fs::dir_tree(".")) -} -``` - -Suppose you have a sequence of reports that you always want to run together. This might be a set of reports which you always want to run at the same time e.g. when new data comes in. Or a set of dependent reports where you want to ensure whenever one of the reports in the stack is changed the whole set of reports are re-run to create an up to date final output. Orderly workflows can help to make it easier to define and run these sets of reports together. - -### Configure workflows - -Workflows are defined through a yaml file in a `workflows` directory as `.yml` - -``` {r comment = NA, echo = FALSE} -dir_tree(path) -``` - -The workflow yaml contains a list of steps of reports to be run in order. -``` {r results = "asis", echo = FALSE} -yaml_output(readLines(file.path(path, "workflows", "my_workflow.yml"))) -``` - -At the moment workflows can only be run with default parameters for each report. - -### Running a workflow - -To run a workflow use `orderly::orderly_workflow` - -``` {r} -out <- orderly::orderly_workflow("my_workflow", root = path) -``` - -This returns a list of the IDs of each of the reports run as part of the workflow -```{r} -out -``` - -`orderly_workflow` needs to know the root of the orderly reports directory to be used. You specify this explicitly via the `root` arg or use your working directory. If your working directory is somewhere within the reports repository then you can run `orderly::orderly_workflow("workflow_name", locate = TRUE)` which will search for the root directory from your current working directory. See `?orderly::orderly_workflow` for more details. - -### Additional arguments - -Like `orderly::orderly_run` `orderly::orderly_workflow` exposes arguments to use alternative source databases and to resolve dependencies from a remote machine. These include `instance` (to set source db instance to be used) and `remote` (to set remote machine to resolve dependencies from). See `?orderly::orderly_workflow` for details.