diff --git a/DESCRIPTION b/DESCRIPTION index 696e7492..1abbbf3c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: blockr Title: A block-based framework for data manipulation and visualization -Version: 0.0.2.9023 +Version: 0.0.2.9030 Authors@R: c(person(given = "Nicolas", family = "Bennett", @@ -26,10 +26,10 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 Imports: magrittr, - shiny, + shiny (>= 1.8.1), dplyr, utils, - bslib, + bslib (>= 0.7.0), methods, DT, htmltools, diff --git a/NAMESPACE b/NAMESPACE index 56a41f8a..4ed32af5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -113,6 +113,7 @@ S3method(ui_update,upload_field) S3method(ui_update,variable_field) S3method(update_field,field) S3method(update_field,hidden_field) +S3method(update_field,result_field) S3method(update_fields,data_block) S3method(update_fields,plot_block) S3method(update_fields,transform_block) @@ -123,6 +124,7 @@ S3method(validate_field,keyvalue_field) S3method(validate_field,list_field) S3method(validate_field,numeric_field) S3method(validate_field,range_field) +S3method(validate_field,result_field) S3method(validate_field,select_field) S3method(validate_field,string_field) S3method(validate_field,switch_field) @@ -130,6 +132,7 @@ S3method(validate_field,upload_field) S3method(validate_field,variable_field) S3method(value,field) S3method(value,list_field) +S3method(value,result_field) S3method(value,variable_field) export("%>%") export("value<-") diff --git a/NEWS.md b/NEWS.md index 932a5dc4..11ef3686 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# blockr 0.0.2.9023 +# blockr 0.0.2.9030 ## Feature - Improved `submit` feature for blocks. Now submit isn't added as a class but as a special block attribute. When you design a block, you can pass the `submit` parameter like so: @@ -52,6 +52,10 @@ If not passed, the block will belong to `uncategorized` blocks (default). - Fix issue in `handle_remove.block`: `vals$stack` wasn't correctly updated when the last block was removed leading to wrong state. - Loading spinner is now correctly hidden when the block visual is updated. +- Fix #366: + - We no longer need a reactive poll per result field for the data. + - The (serialized) field "value" is only the stack name the field refers to (not data). + - We no longer need a reactive poll for the stack selector drop-down - Fix [#358](https://github.com/BristolMyersSquibb/blockr/issues/358). # blockr 0.0.2 diff --git a/R/blocks.R b/R/blocks.R index 2b271abc..cd7902da 100644 --- a/R/blocks.R +++ b/R/blocks.R @@ -164,10 +164,13 @@ new_xpt_block <- function(...) { #' another one. This isn't relevant for single stack apps. #' #' @inheritParams new_block +#' @param stack Stack name +#' #' @export -new_result_block <- function(...) { +new_result_block <- function(stack = character(), ...) { + fields <- list( - stack = new_result_field(title = "Stack") + stack = new_result_field(stack, title = "Stack") ) new_block( diff --git a/R/field-core.R b/R/field-core.R index 6230b920..02bec23f 100644 --- a/R/field-core.R +++ b/R/field-core.R @@ -99,6 +99,12 @@ update_field.hidden_field <- function(x, new, env = list()) { eval_set_field_value(x, env) } +#' @rdname update_field +#' @export +update_field.result_field <- function(x, new, env = list()) { + NextMethod(new = attr(new, "result_field_stack_name")) +} + eval_set_field_value <- function(x, env) { for (cmp in names(x)[lgl_ply(x, is.function)]) { @@ -173,6 +179,35 @@ value.list_field <- function(x, name = "value") { NextMethod() } +#' @rdname value +#' @export +value.result_field <- function(x, name = "value") { + + stopifnot(identical(name, "value")) + + field <- get_field_value(x, "value") + + if (length(field) && field %in% list_workspace_stacks()) { + + res <- get_stack_result(get_workspace_stack(field)) + + if (inherits(res, "reactive")) { + + if (is.null(getDefaultReactiveDomain())) { + list() + } else { + res() + } + + } else { + res + } + + } else { + list() + } +} + #' Get all values from a field #' #' This calls \link{value} on all the field's names. diff --git a/R/fields.R b/R/fields.R index 480c9862..e326b96c 100644 --- a/R/fields.R +++ b/R/fields.R @@ -289,6 +289,24 @@ validate_field.list_field <- function(x) { #' @inheritParams new_string_field #' @rdname result_field #' @export -new_result_field <- function(value = list(), ...) { +new_result_field <- function(value = character(), ...) { new_field(value, ..., class = "result_field") } + +#' @rdname validate_field +#' @export +validate_field.result_field <- function(x) { + + field <- get_field_value(x, "value") + + validate_string(field) + + if (!field %in% list_workspace_stacks()) { + validation_failure( + "result fields have to refer to existing stack names", + class = "result_failure" + ) + } + + NextMethod() +} diff --git a/R/server.R b/R/server.R index a2bfd3b4..b90cb1b8 100644 --- a/R/server.R +++ b/R/server.R @@ -16,42 +16,41 @@ generate_server <- function(x, ...) { generate_server.result_field <- function(x, ...) { function(id, init = NULL, data = NULL) { moduleServer(id, function(input, output, session) { - get_result <- function() { - inp <- input[["select-stack"]] + get_result <- function(inp) { + res <- get_stack_result( + get_workspace_stack(inp) + ) - if (length(inp) && inp %in% list_workspace_stacks()) { - get_stack_result( - get_workspace_stack(inp) - ) - } else { - data.frame() - } - } + attr(res, "result_field_stack_name") <- inp - result_hash <- function() { - rlang::hash(get_result()) + res } - current_stack <- function() { - res <- strsplit(session$ns(NULL), "-")[[1L]] - res[length(res) - 2L] - } + workspace_stacks <- attr(get_workspace(), "reactive_stack_directory") - stack_opts <- function() { - setdiff(list_workspace_stacks(), current_stack()) - } + exportTestValues( + stacks = workspace_stacks() + ) - opts <- reactivePoll(100, session, stack_opts, stack_opts) + if (is.null(workspace_stacks)) { + workspace_stacks <- function() { + list_workspace_stacks() + } + } observeEvent( - opts(), - updateSelectInput(session, "select-stack", - choices = opts(), + workspace_stacks(), + updateSelectInput( + session, + "select-stack", + choices = result_field_stack_opts(session$ns, workspace_stacks()), selected = input[["select-stack"]] ) ) - reactivePoll(100, session, result_hash, get_result) + reactive({ + get_result(input[["select-stack"]]) + }) }) } } @@ -404,7 +403,7 @@ generate_server.stack <- function(x, id = NULL, new_block = NULL, vals$stack <- set_stack_blocks( vals$stack, get_block_vals(vals$blocks), - get_last_block_data(vals$blocks)() + get_last_block_data(vals$blocks) ) } ) @@ -701,6 +700,12 @@ generate_server.workspace <- function(x, id, ...) { removeUI(".stack-col", multiple = TRUE) }) + attr(x, "reactive_stack_directory") <- reactive({ + names(vals$stacks) + }) |> bindEvent( + chr_ply(lapply(vals$stacks, `[[`, "stack"), attr, "title") + ) + # Serialize output$serialize <- downloadHandler( filename = function() { diff --git a/R/ui.R b/R/ui.R index 416b9336..1f29a74f 100644 --- a/R/ui.R +++ b/R/ui.R @@ -682,13 +682,14 @@ ui_input.filesbrowser_field <- function(x, id, name) { #' @rdname ui_input #' @export ui_input.result_field <- function(x, id, name) { + ns <- NS(input_ids(x, id)) selectizeInput( ns("select-stack"), name, - list_workspace_stacks(), - value(x), + result_field_stack_opts(ns), + get_field_value(x, "value"), options = list( dropdownParent = "body", placeholder = "Please select an option below" @@ -696,6 +697,28 @@ ui_input.result_field <- function(x, id, name) { ) } +result_field_stack_opts <- function(ns, stacks = list_workspace_stacks()) { + + current_stack <- function(ns) { + res <- strsplit(ns(NULL), "-")[[1L]] + res[length(res) - 2L] + } + + res <- setdiff(stacks, current_stack(ns)) + + set_names(res, stack_id_to_name(res)) +} + +stack_id_to_name <- function(id) { + + do_one <- function(x) { + stk <- get_workspace_stack(x) + paste0(get_stack_title(stk), " (", get_stack_name(stk), ")") + } + + chr_ply(id, do_one) +} + #' @rdname ui_input #' @export input_ids <- function(x, ...) { diff --git a/inst/examples/result/app.R b/inst/examples/result/app.R new file mode 100644 index 00000000..27b892e5 --- /dev/null +++ b/inst/examples/result/app.R @@ -0,0 +1,8 @@ +library(blockr) + +serve_workspace( + stack1 = new_stack(new_dataset_block("iris")), + stack2 = new_stack(new_dataset_block("mtcars")), + stack3 = new_stack(new_result_block("stack1")), + force = TRUE +) diff --git a/man/new_result_block.Rd b/man/new_result_block.Rd index 5af7dfb0..afe24557 100644 --- a/man/new_result_block.Rd +++ b/man/new_result_block.Rd @@ -4,9 +4,11 @@ \alias{new_result_block} \title{Result block} \usage{ -new_result_block(...) +new_result_block(stack = character(), ...) } \arguments{ +\item{stack}{Stack name} + \item{...}{Further (metadata) attributes} } \description{ diff --git a/man/result_field.Rd b/man/result_field.Rd index a07e48a6..9a95579d 100644 --- a/man/result_field.Rd +++ b/man/result_field.Rd @@ -4,7 +4,7 @@ \alias{new_result_field} \title{Result field constructor} \usage{ -new_result_field(value = list(), ...) +new_result_field(value = character(), ...) } \arguments{ \item{value}{Default text input value.} diff --git a/man/update_field.Rd b/man/update_field.Rd index 5f6e0960..e9443904 100644 --- a/man/update_field.Rd +++ b/man/update_field.Rd @@ -4,6 +4,7 @@ \alias{update_field} \alias{update_field.field} \alias{update_field.hidden_field} +\alias{update_field.result_field} \alias{get_field_name} \title{Update field generic} \usage{ @@ -13,6 +14,8 @@ update_field(x, new, env = list()) \method{update_field}{hidden_field}(x, new, env = list()) +\method{update_field}{result_field}(x, new, env = list()) + get_field_name(field, name = "") } \arguments{ diff --git a/man/validate_field.Rd b/man/validate_field.Rd index 8b0705b5..5a4a00fe 100644 --- a/man/validate_field.Rd +++ b/man/validate_field.Rd @@ -10,6 +10,7 @@ \alias{validate_field.variable_field} \alias{validate_field.range_field} \alias{validate_field.list_field} +\alias{validate_field.result_field} \alias{validate_field.keyvalue_field} \alias{validate_field} \alias{validate_field.field} @@ -37,6 +38,8 @@ \method{validate_field}{list_field}(x) +\method{validate_field}{result_field}(x) + \method{validate_field}{keyvalue_field}(x) validate_field(x) diff --git a/man/value.Rd b/man/value.Rd index a12640af..5e86aa19 100644 --- a/man/value.Rd +++ b/man/value.Rd @@ -5,6 +5,7 @@ \alias{value.field} \alias{value.variable_field} \alias{value.list_field} +\alias{value.result_field} \alias{value<-} \alias{value<-.field} \alias{value<-.upload_field} @@ -20,6 +21,8 @@ value(x, name = "value") \method{value}{list_field}(x, name = "value") +\method{value}{result_field}(x, name = "value") + value(x, name = "value") <- value \method{value}{field}(x, name = "value") <- value diff --git a/tests/testthat/_snaps/block/block-app-001_.new.png b/tests/testthat/_snaps/block/block-app-001_.new.png index 38240a63..63396905 100644 Binary files a/tests/testthat/_snaps/block/block-app-001_.new.png and b/tests/testthat/_snaps/block/block-app-001_.new.png differ diff --git a/tests/testthat/_snaps/block/block-app-001_.png b/tests/testthat/_snaps/block/block-app-001_.png index a9472ccb..634c6e13 100644 Binary files a/tests/testthat/_snaps/block/block-app-001_.png and b/tests/testthat/_snaps/block/block-app-001_.png differ diff --git a/tests/testthat/_snaps/block/block-app-002_.png b/tests/testthat/_snaps/block/block-app-002_.png index 4bb6a65b..826b2d2b 100644 Binary files a/tests/testthat/_snaps/block/block-app-002_.png and b/tests/testthat/_snaps/block/block-app-002_.png differ diff --git a/tests/testthat/_snaps/block/block-app-003_.png b/tests/testthat/_snaps/block/block-app-003_.png index 94a82fb2..f598c006 100644 Binary files a/tests/testthat/_snaps/block/block-app-003_.png and b/tests/testthat/_snaps/block/block-app-003_.png differ diff --git a/tests/testthat/_snaps/block/block-app-004_.new.png b/tests/testthat/_snaps/block/block-app-004_.new.png index aa52392f..7cf37837 100644 Binary files a/tests/testthat/_snaps/block/block-app-004_.new.png and b/tests/testthat/_snaps/block/block-app-004_.new.png differ diff --git a/tests/testthat/_snaps/block/block-app-004_.png b/tests/testthat/_snaps/block/block-app-004_.png index ca165740..acbb1285 100644 Binary files a/tests/testthat/_snaps/block/block-app-004_.png and b/tests/testthat/_snaps/block/block-app-004_.png differ diff --git a/tests/testthat/_snaps/block/block-app-005_.png b/tests/testthat/_snaps/block/block-app-005_.png index c6e3b895..a755f04d 100644 Binary files a/tests/testthat/_snaps/block/block-app-005_.png and b/tests/testthat/_snaps/block/block-app-005_.png differ diff --git a/tests/testthat/_snaps/result/result-app-001.json b/tests/testthat/_snaps/result/result-app-001.json new file mode 100644 index 00000000..350e3673 --- /dev/null +++ b/tests/testthat/_snaps/result/result-app-001.json @@ -0,0 +1,8 @@ +{ + "input": { + + }, + "export": { + + } +} diff --git a/tests/testthat/_snaps/result/result-app-001_.new.png b/tests/testthat/_snaps/result/result-app-001_.new.png new file mode 100644 index 00000000..e9b0d0c5 Binary files /dev/null and b/tests/testthat/_snaps/result/result-app-001_.new.png differ diff --git a/tests/testthat/_snaps/result/result-app-001_.png b/tests/testthat/_snaps/result/result-app-001_.png new file mode 100644 index 00000000..b01b584e Binary files /dev/null and b/tests/testthat/_snaps/result/result-app-001_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-002_.new.png b/tests/testthat/_snaps/stack/stack-app-002_.new.png new file mode 100644 index 00000000..6b451e7f Binary files /dev/null and b/tests/testthat/_snaps/stack/stack-app-002_.new.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-002_.png b/tests/testthat/_snaps/stack/stack-app-002_.png index 6b451e7f..d28a2e6a 100644 Binary files a/tests/testthat/_snaps/stack/stack-app-002_.png and b/tests/testthat/_snaps/stack/stack-app-002_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-003.json b/tests/testthat/_snaps/stack/stack-app-003.json index af565436..8819cc6c 100644 --- a/tests/testthat/_snaps/stack/stack-app-003.json +++ b/tests/testthat/_snaps/stack/stack-app-003.json @@ -39,8 +39,8 @@ ], "choices": [ - "structure(function (data) ", - "colnames(data), result = c(\"Time\", \"demand\"))" + "function (data) ", + "colnames(data)" ], "multiple": true } diff --git a/tests/testthat/_snaps/stack/stack-app-003_.new.png b/tests/testthat/_snaps/stack/stack-app-003_.new.png new file mode 100644 index 00000000..b6a99d73 Binary files /dev/null and b/tests/testthat/_snaps/stack/stack-app-003_.new.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-003_.png b/tests/testthat/_snaps/stack/stack-app-003_.png index b6a99d73..173fce55 100644 Binary files a/tests/testthat/_snaps/stack/stack-app-003_.png and b/tests/testthat/_snaps/stack/stack-app-003_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-004.json b/tests/testthat/_snaps/stack/stack-app-004.json index af565436..8819cc6c 100644 --- a/tests/testthat/_snaps/stack/stack-app-004.json +++ b/tests/testthat/_snaps/stack/stack-app-004.json @@ -39,8 +39,8 @@ ], "choices": [ - "structure(function (data) ", - "colnames(data), result = c(\"Time\", \"demand\"))" + "function (data) ", + "colnames(data)" ], "multiple": true } diff --git a/tests/testthat/_snaps/stack/stack-app-004_.new.png b/tests/testthat/_snaps/stack/stack-app-004_.new.png new file mode 100644 index 00000000..3cd83561 Binary files /dev/null and b/tests/testthat/_snaps/stack/stack-app-004_.new.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-004_.png b/tests/testthat/_snaps/stack/stack-app-004_.png index 3cd83561..df7d30a3 100644 Binary files a/tests/testthat/_snaps/stack/stack-app-004_.png and b/tests/testthat/_snaps/stack/stack-app-004_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-005.json b/tests/testthat/_snaps/stack/stack-app-005.json index a70b29a8..7e62e68f 100644 --- a/tests/testthat/_snaps/stack/stack-app-005.json +++ b/tests/testthat/_snaps/stack/stack-app-005.json @@ -39,8 +39,8 @@ ], "choices": [ - "structure(function (data) ", - "colnames(data), result = c(\"Time\", \"demand\"))" + "function (data) ", + "colnames(data)" ], "multiple": true } diff --git a/tests/testthat/_snaps/stack/stack-app-005_.new.png b/tests/testthat/_snaps/stack/stack-app-005_.new.png new file mode 100644 index 00000000..e4785146 Binary files /dev/null and b/tests/testthat/_snaps/stack/stack-app-005_.new.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-005_.png b/tests/testthat/_snaps/stack/stack-app-005_.png index e4785146..4f1e88e2 100644 Binary files a/tests/testthat/_snaps/stack/stack-app-005_.png and b/tests/testthat/_snaps/stack/stack-app-005_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-006.json b/tests/testthat/_snaps/stack/stack-app-006.json index 2cf11793..3952aeb2 100644 --- a/tests/testthat/_snaps/stack/stack-app-006.json +++ b/tests/testthat/_snaps/stack/stack-app-006.json @@ -39,8 +39,8 @@ ], "choices": [ - "structure(function (data) ", - "colnames(data), result = c(\"Time\", \"demand\"))" + "function (data) ", + "colnames(data)" ], "multiple": true } diff --git a/tests/testthat/_snaps/stack/stack-app-006_.new.png b/tests/testthat/_snaps/stack/stack-app-006_.new.png new file mode 100644 index 00000000..f4b7011e Binary files /dev/null and b/tests/testthat/_snaps/stack/stack-app-006_.new.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-006_.png b/tests/testthat/_snaps/stack/stack-app-006_.png index f4b7011e..0c362ef4 100644 Binary files a/tests/testthat/_snaps/stack/stack-app-006_.png and b/tests/testthat/_snaps/stack/stack-app-006_.png differ diff --git a/tests/testthat/_snaps/stack/stack-app-007.json b/tests/testthat/_snaps/stack/stack-app-007.json index 2e06ed50..5d5eae82 100644 --- a/tests/testthat/_snaps/stack/stack-app-007.json +++ b/tests/testthat/_snaps/stack/stack-app-007.json @@ -39,8 +39,8 @@ ], "choices": [ - "structure(function (data) ", - "colnames(data), result = c(\"Time\", \"demand\"))" + "function (data) ", + "colnames(data)" ], "multiple": true } diff --git a/tests/testthat/_snaps/workspace/restore-workspace-app-001_.new.png b/tests/testthat/_snaps/workspace/restore-workspace-app-001_.new.png index db58c3b3..83cfc7a3 100644 Binary files a/tests/testthat/_snaps/workspace/restore-workspace-app-001_.new.png and b/tests/testthat/_snaps/workspace/restore-workspace-app-001_.new.png differ diff --git a/tests/testthat/test-block.R b/tests/testthat/test-block.R index d7a6e482..1d1a846d 100644 --- a/tests/testthat/test-block.R +++ b/tests/testthat/test-block.R @@ -138,7 +138,6 @@ test_that("group_by blocks", { test_that("join blocks", { datx <- dplyr::band_members - daty <- dplyr::band_instruments blk1 <- new_join_block() @@ -151,7 +150,12 @@ test_that("join blocks", { expect_equal(nrow(res1), nrow(datx)) expect_equal(colnames(res1), colnames(datx)) - blk2 <- new_join_block(daty, type = "inner", by = "name") + set_workspace( + daty = new_stack(new_dataset_block("band_instruments", "dplyr")), + force = TRUE + ) + + blk2 <- new_join_block("daty", type = "inner", by = "name") expect_s3_class(blk2, "transform_block") expect_s3_class(blk2, "join_block") diff --git a/tests/testthat/test-result.R b/tests/testthat/test-result.R index 47e9d674..49f9cf8e 100644 --- a/tests/testthat/test-result.R +++ b/tests/testthat/test-result.R @@ -1,5 +1,4 @@ test_that("result field", { - field <- new_result_field() expect_s3_class(field, "result_field") @@ -8,7 +7,6 @@ test_that("result field", { }) test_that("result block", { - block <- new_result_block() expect_s3_class(block, "result_block") @@ -19,8 +17,7 @@ test_that("result block", { expect_s3_class(ui, "shiny.tag") }) -test_that("result server works", { - +test_that("result field server works", { set_workspace( stack1 = new_stack(new_dataset_block), stack2 = new_stack(new_dataset_block), @@ -28,8 +25,25 @@ test_that("result server works", { ) shiny::testServer( - generate_server(new_result_field()), { - expect_setequal(opts(), c("stack1", "stack2")) + generate_server(new_result_field("stack1")), + { + expect_setequal(workspace_stacks(), c("stack1", "stack2")) } ) }) + +withr::local_package("shinytest2") + +test_that("result server works", { + skip_on_cran() + + app <- AppDriver$new( + system.file("examples/result/app.R", package = "blockr"), + name = "result-app" + ) + + app$expect_values( + input = "select-stack", + export = "stacks" + ) +}) diff --git a/tests/testthat/test-serialization.R b/tests/testthat/test-serialization.R index cf9ecdd0..b71b9b4f 100644 --- a/tests/testthat/test-serialization.R +++ b/tests/testthat/test-serialization.R @@ -35,7 +35,7 @@ test_that("json ser/deser for the workspace", { new_filter_block ) - set_workspace(stack = x) + set_workspace(stack = x, force = TRUE) set_workspace_title("foo") set_workspace_settings("{\"foo\": \"bar\"}")