diff --git a/DESCRIPTION b/DESCRIPTION index 0c529cc..d7859b7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: dv.edish Type: Package Title: eDISH Plot Module for DILI assessment -Version: 1.0.4 +Version: 1.0.4.9000 Authors@R: c( person("Boehringer-Ingelheim Pharma GmbH & Co.KG", role = c("cph", "fnd")), @@ -30,7 +30,7 @@ Imports: shiny (>= 1.8.0), stats (>= 4.2.2), tidyr (>= 1.3.0), - dv.manager (>= 2.0.0-17), + dv.manager (>= 2.1.4), rmarkdown (>= 2.25) Depends: R (>= 4.0) Remotes: boehringer-ingelheim/dv.manager@main diff --git a/R/CM.R b/R/CM.R index 03b606c..2efa4e9 100644 --- a/R/CM.R +++ b/R/CM.R @@ -408,7 +408,7 @@ CM <- local({ # _C_hecked _M_odule return(res) } - # TODO: Extend to all checker functions + # TODO: use check_flags instead and remove optional_and_empty <- function(flags, value) { return(isTRUE(flags[["optional"]]) && length(value) == 0) } @@ -510,9 +510,6 @@ CM <- local({ # _C_hecked _M_odule } check_choice_from_col_contents <- function(name, value, flags, dataset_name, dataset_value, column, warn, err) { - if (optional_and_empty(flags, value)) { - return(TRUE) - } ok <- check_flags(name, value, flags, warn, err) && assert( err, all(value %in% dataset_value[[column]]), diff --git a/R/DR.R b/R/DR.R new file mode 100644 index 0000000..5e6790c --- /dev/null +++ b/R/DR.R @@ -0,0 +1,1150 @@ +# YT#VH2511139c3a21a7e842ec31e495d1d528#VHb37a04c06b0d1e8cb246df00cdd8945f# +DR <- local({ # _D_ressing _R_oom + inline_shiny_input <- function(elem, label = NULL, name_selector = NULL, label_elem = NULL) { + if (is.character(label) && length(label) == 1 && nchar(label) > 0) { + label_elem <- shiny::tags$label(`for` = NULL, label) + } + + res <- shiny::tags[["div"]]( + style = "display: flex; align-items: baseline; place-content: space-between; column-gap:1rem", + label_elem, name_selector, elem + ) + return(res) + } + + enable_nicer_unnamed_multicolumn_selection <- TRUE + enable_nicer_multichoice_selection <- TRUE + + color_picker_input <- function(inputId, value = NULL) { + # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/color + + # https://shiny.posit.co/r/articles/build/building-inputs/ + + # https://forum.posit.co/t/bookmarking-custom-inputs/162483 + + restored_value <- shiny::restoreInput(id = inputId, default = NULL) + if (!is.null(restored_value)) value <- restored_value + + list( + shiny::singleton(shiny::tags$head( + shiny::tags$script(r"--( + var color_picker_binding = new Shiny.InputBinding(); + $.extend(color_picker_binding, { + find: function(scope) { return scope.querySelectorAll('.color-picker'); }, + getValue: function(el) { return el.value; }, + subscribe: function(el, callback) { + $(el).on('change.color_picker_binding', function(e) { callback(); }); + }, + unsubscribe: function(el) { $(el).off('.color_picker_binding'); } + }); + Shiny.inputBindings.register(color_picker_binding); + )--") + )), + shiny::tags$input( + id = inputId, class = "color-picker", type = "color", style = "border:none", value = value + ) + ) + } + + # NOTE: selectInput does not preserve the order of columns on bookmark restoration + # (see https://github.com/rstudio/shiny/issues/1490, which predates trump's first presidency) + # Bonus: returns character(0) on empty selection to allow to distinguish it from NULL, which is + # the value returned when the input is not present. + T_select_input <- function(inputId, label, choices, selected = NULL, multiple = FALSE) { + res <- NULL + if (multiple) { + caller_respects_limitations <- ( + (is.null(choices) || + (is.character(choices) && all(nchar(choices)) > 0)) && + (is.character(selected) || is.null(selected)) + ) + if (!caller_respects_limitations) browser() + stopifnot(caller_respects_limitations) + + shiny::registerInputHandler( + type = "dressing_room.select_input", fun = function(msg, session, input_name) as.character(msg), force = TRUE + ) + + internal_id <- paste0(inputId, "_internal") + selected <- shiny::restoreInput(id = internal_id, default = selected) + + initial_value <- paste("[", paste(sprintf("'%s'", selected), collapse = ", "), "]") + + res <- shiny::selectizeInput( + inputId = internal_id, label = label, choices = choices, multiple = TRUE, selected = selected, + options = list( # https://selectize.dev/docs/events + onInitialize = + I(sprintf( + "function() { this.setValue(%s); Shiny.setInputValue('%s:dressing_room.select_input', %s); }", + initial_value, inputId, initial_value + )), + onChange = I(sprintf("v => Shiny.setInputValue('%s:dressing_room.select_input', v)", inputId)), + plugins = list("drag_drop", "remove_button") + ) + ) + } else { + res <- shiny::selectInput(inputId, label, choices, selected, selectize = FALSE) + } + return(res) + } + + # Module explorer app ---- + explorer_ui <- function() { + panel <- function(...) { + shiny::div( + class = "panel panel-default", + style = "background-color:#eff7ff;", + shiny::div(class = "panel-body", style = "padding-bottom:0.5rem;", ...) + ) + } + + fix_dependencies_for_shiny_widgets_pickerInput <- local({ + # https://github.com/dreamRs/shinyWidgets/issues/147#issuecomment-459004725 + + # get bootstrap dependency + bsDep <- (shiny::bootstrapLib())() + bsDep$name <- "bootstrap2" + # get pickerInput dependency + pkDep <- htmltools::findDependencies(shinyWidgets:::attachShinyWidgetsDep(htmltools::tags$div(), widget = "picker")) + pkDep[[2]]$name <- "picker2" + + res <- list( + htmltools::suppressDependencies("selectPicker"), htmltools::suppressDependencies("bootstrap"), # remove + bsDep, pkDep # inject in correct order + ) + return(res) + }) + + ui <- shiny::fluidPage( + fix_dependencies_for_shiny_widgets_pickerInput, + fontawesome::fa_html_dependency(), + shiny::tags$head(shiny::HTML("
",
+ code_to_print,
+ "
"
+ ))
+
+ return(res)
+ })
+
+ shiny::observeEvent(input[["edit_code"]], {
+ if (isTRUE(input[["edit_code"]])) {
+ shiny::updateTextAreaInput(session = session, "manual_code", value = code())
+ }
+ })
+
+
+ error_and_ui_rv <- shiny::reactiveValues(ui = list(), error = NULL)
+
+ shiny::observe({
+ code_to_eval <- NULL
+ if (isTRUE(input[["edit_code"]])) {
+ code_to_eval <- input[["manual_code"]]
+ shiny::req(is.character(code_to_eval))
+ } else {
+ code_to_eval <- trimws(code())
+ }
+
+ get_package_maintainer_name <- function() {
+ package_name <- strsplit(input[["spec"]], split = "::", fixed = TRUE)[[1]][[1]]
+ desc <- utils::packageDescription(package_name)[["Maintainer"]]
+ if (is.character(desc) && length(desc) == 1 && nchar(desc) > 0) {
+ desc <- paste0("`", desc, "`")
+ } else {
+ # NOTE: Available after installing from source, but not under devtools
+ desc <- "the package maintainer"
+ }
+ return(desc)
+ }
+
+ error_and_ui <- local({
+ ui <- NULL
+
+ spec <- input[["spec"]]
+
+ build_error <- function(title, condition, preface, ui = NULL) {
+ return(list(error = list(title = title, condition = condition, preface = preface), ui = ui))
+ }
+
+ if (!is.character(spec) || nchar(input[["spec"]]) == 0) {
+ return(build_error(
+ title = "Module selection error",
+ condition = base::simpleError("No DaVINCI module selected on the `Module` drop-down."),
+ preface = "Module selection error"
+ )) # FIXME: repeats message
+ }
+
+ if (!startsWith(code_to_eval, spec)) {
+ return(build_error(
+ title = "Module configuration error",
+ condition = base::simpleError(paste("Expected call to", spec)),
+ preface = "Module configuration error"
+ )) # FIXME: repeats message
+ }
+
+ # FIXME(miguel): We should parse and evaluate arguments separately outside of a reactive environment
+ # to see if any of them is badly constructed, but here I take a shortcut and evaluate
+ # them all inside a list()
+ list_of_args <- paste0("list", substr(code_to_eval, nchar(input[["spec"]]) + 1, nchar(code_to_eval)))
+ parsed_code <- try(parse(text = list_of_args), silent = TRUE)
+ eval_result <- try(eval(parsed_code), silent = TRUE)
+ if (inherits(eval_result, "try-error")) {
+ attr(eval_result, "condition")[["call"]][[1]] <- parse(text = spec)[[1]] # undo the spec->`list` substitution
+ return(build_error(
+ title = "Syntax error",
+ condition = attr(eval_result, "condition"),
+ preface = "Cannot parse the code provided."
+ ))
+ }
+
+ # NOTE(miguel): With that out of the way, this shouldn't fail but I keep it just in case
+ parsed_code <- try(parse(text = code_to_eval), silent = TRUE)
+ if (inherits(parsed_code, "try-error")) {
+ return(build_error(
+ title = "Syntax error",
+ condition = attr(parsed_code, "condition"),
+ preface = "Cannot parse the code provided."
+ ))
+ }
+
+ ui_server_id <- try(eval(parsed_code), silent = TRUE)
+ shiny::req(!is.null(ui_server_id))
+ if (inherits(ui_server_id, "try-error")) {
+ return(build_error(
+ title = "Module Development Error",
+ condition = attr(ui_server_id, "condition"),
+ preface = paste0("Please report the following error to ", get_package_maintainer_name(), ".")
+ ))
+ }
+
+ if (length(setdiff(c("ui", "server", "module_id"), names(ui_server_id))) > 0) {
+ return(build_error(
+ title = "Module Configuration Error",
+ condition = base::simpleError("The provided code does not return a {ui, server, module_id} triplet."),
+ preface = paste0("Are you actually calling ", input[["spec"]], "?")
+ ))
+ }
+
+ id <- ui_server_id[["module_id"]]
+ if (is.function(ui_server_id[["ui"]])) ui <- ui_server_id[["ui"]](id)
+
+ afmm <- list(
+ data = list(DS = datasets()),
+ dataset_metadata = list(name = shiny::reactive("DS")),
+ unfiltered_dataset = datasets,
+ filtered_dataset = datasets,
+ module_output = function() list()
+ )
+
+ # Executes server on a separate reactive domain and destroys its observers when reinvoked
+ server_return_val <- observer_dedup(
+ id = "unique_dedup_id",
+ ui_server_id[["server"]](afmm),
+ session = session
+ )
+
+ if (inherits(server_return_val, "try-error")) {
+ return(build_error(
+ title = "Module Development Error",
+ condition = attr(server_return_val, "condition"),
+ preface = paste0("Please report the following error to ", get_package_maintainer_name(), "."),
+ ui = ui
+ ))
+ }
+
+ return(list(ui = ui))
+ })
+
+ error_and_ui_rv[["ui"]] <- error_and_ui[["ui"]]
+ error_and_ui_rv[["error"]] <- error_and_ui[["error"]]
+ })
+
+ output[["module"]] <- shiny::renderUI({
+ ui <- error_and_ui_rv[["ui"]]
+ error <- error_and_ui_rv[["error"]]
+
+ if (!is.null(error)) {
+ error_message <- error$condition[["message"]]
+ error_context <- paste(deparse(error$condition[["call"]]), collapse = "\n")
+
+ ui <- list(
+ CM$message_well(error$title, error$preface, color = "#f4d7d7"),
+ shiny::p("Message is:"),
+ shiny::pre(error_message),
+ shiny::p("And happened in the vicinity of:"),
+ shiny::pre(error_context),
+ shiny::div(ui, style = "visibility:hidden;") # does not remove ui from layout
+ )
+ }
+
+ return(ui)
+ })
+
+ NULL
+ }
+
+ return(explorer_server)
+ }
+
+ list(
+ explorer_ui = explorer_ui,
+ explorer_server_with_datasets = explorer_server_with_datasets
+ )
+})
+
+# TODO: Export when hover help is offered
+
+# Interactive module demo/configuration tool
+#
+# Launch an experimental interactive point-and-click configuration app for `dv.explorer.parameter` modules.
+# Help is accessible by hovering over any of the provided parameters. \cr
+# To try it using demo data, run `dv.explorer.parameter::explorer_app(dv.explorer.parameter:::safety_data())` in your R prompt.
+#
+# @param datasets `[list(data.frame(n))]` (optional) Datasets available to the module. One of them should be a
+# demographic subject-level dataset and the rest should be visit-dependent datasets. If not provided, the UI offers a
+# file input selector that is functionally equivalent.
+#
+explorer_app <- function(datasets = NULL) {
+ shiny::shinyApp(
+ ui = DR$explorer_ui,
+ server = DR$explorer_server_with_datasets(caller_datasets = datasets),
+ enableBookmarking = "url"
+ )
+}
diff --git a/R/check_call_auto.R b/R/check_call_auto.R
index 3f873e4..73df9d4 100644
--- a/R/check_call_auto.R
+++ b/R/check_call_auto.R
@@ -3,26 +3,68 @@
# styler: off
# dv.edish::mod_edish
-check_mod_edish_auto <- function(afmm, datasets, module_id, dataset_names, subjectid_var, arm_var, arm_default_vals,
- visit_var, baseline_visit_val, lb_test_var, lb_test_choices, lb_test_default_x_val, lb_test_default_y_val,
- lb_result_var, ref_range_upper_lim_var, warn, err) {
+check_mod_edish_auto <- function(afmm, datasets, module_id, subject_level_dataset_name, lab_dataset_name,
+ subjectid_var, arm_var, arm_default_vals, visit_var, baseline_visit_val, lb_test_var, lb_test_choices,
+ lb_test_default_x_val, lb_test_default_y_val, lb_result_var, ref_range_upper_lim_var, warn, err) {
OK <- logical(0)
used_dataset_names <- new.env(parent = emptyenv())
OK[["module_id"]] <- CM$check_module_id("module_id", module_id, warn, err)
- flags <- list(one_or_more = TRUE)
- OK[["dataset_names"]] <- CM$check_dataset_name("dataset_names", dataset_names, flags, datasets, used_dataset_names,
+ flags <- list(subject_level_dataset_name = TRUE)
+ OK[["subject_level_dataset_name"]] <- CM$check_dataset_name("subject_level_dataset_name", subject_level_dataset_name,
+ flags, datasets, used_dataset_names, warn, err)
+ flags <- structure(list(), names = character(0))
+ OK[["lab_dataset_name"]] <- CM$check_dataset_name("lab_dataset_name", lab_dataset_name, flags, datasets,
+ used_dataset_names, warn, err)
+ subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
+ flags <- list(subjid_var = TRUE)
+ OK[["subjectid_var"]] <- OK[["subject_level_dataset_name"]] && CM$check_dataset_colum_name("subjectid_var",
+ subjectid_var, subkind, flags, subject_level_dataset_name, datasets[[subject_level_dataset_name]],
warn, err)
- "TODO: subjectid_var (group)"
- "TODO: arm_var (group)"
- "TODO: arm_default_vals (group)"
- "TODO: visit_var (group)"
- "TODO: baseline_visit_val (group)"
- "TODO: lb_test_var (group)"
- "TODO: lb_test_choices (group)"
- "TODO: lb_test_default_x_val (group)"
- "TODO: lb_test_default_y_val (group)"
- "TODO: lb_result_var (group)"
- "TODO: ref_range_upper_lim_var (group)"
+ subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
+ flags <- structure(list(), names = character(0))
+ OK[["arm_var"]] <- OK[["subject_level_dataset_name"]] && CM$check_dataset_colum_name("arm_var", arm_var,
+ subkind, flags, subject_level_dataset_name, datasets[[subject_level_dataset_name]], warn, err)
+ flags <- list(one_or_more = TRUE, optional = TRUE)
+ OK[["arm_default_vals"]] <- OK[["arm_var"]] && CM$check_choice_from_col_contents("arm_default_vals",
+ arm_default_vals, flags, "subject_level_dataset_name", datasets[[subject_level_dataset_name]],
+ arm_var, warn, err)
+ subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
+ flags <- structure(list(), names = character(0))
+ OK[["visit_var"]] <- OK[["lab_dataset_name"]] && CM$check_dataset_colum_name("visit_var", visit_var,
+ subkind, flags, lab_dataset_name, datasets[[lab_dataset_name]], warn, err)
+ flags <- structure(list(), names = character(0))
+ OK[["baseline_visit_val"]] <- OK[["visit_var"]] && CM$check_choice_from_col_contents("baseline_visit_val",
+ baseline_visit_val, flags, "lab_dataset_name", datasets[[lab_dataset_name]], visit_var, warn,
+ err)
+ subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
+ flags <- structure(list(), names = character(0))
+ OK[["lb_test_var"]] <- OK[["lab_dataset_name"]] && CM$check_dataset_colum_name("lb_test_var", lb_test_var,
+ subkind, flags, lab_dataset_name, datasets[[lab_dataset_name]], warn, err)
+ flags <- list(one_or_more = TRUE, optional = TRUE)
+ OK[["lb_test_choices"]] <- OK[["lb_test_var"]] && CM$check_choice_from_col_contents("lb_test_choices",
+ lb_test_choices, flags, "lab_dataset_name", datasets[[lab_dataset_name]], lb_test_var, warn,
+ err)
+ flags <- list(optional = TRUE)
+ OK[["lb_test_default_x_val"]] <- OK[["lb_test_var"]] && CM$check_choice_from_col_contents("lb_test_default_x_val",
+ lb_test_default_x_val, flags, "lab_dataset_name", datasets[[lab_dataset_name]], lb_test_var,
+ warn, err)
+ flags <- list(optional = TRUE)
+ OK[["lb_test_default_y_val"]] <- OK[["lb_test_var"]] && CM$check_choice_from_col_contents("lb_test_default_y_val",
+ lb_test_default_y_val, flags, "lab_dataset_name", datasets[[lab_dataset_name]], lb_test_var,
+ warn, err)
+ subkind <- list(kind = "or", options = list(list(kind = "numeric", min = NA, max = NA)))
+ flags <- structure(list(), names = character(0))
+ OK[["lb_result_var"]] <- OK[["lab_dataset_name"]] && CM$check_dataset_colum_name("lb_result_var",
+ lb_result_var, subkind, flags, lab_dataset_name, datasets[[lab_dataset_name]], warn, err)
+ subkind <- list(kind = "numeric", min = NA, max = NA)
+ flags <- list(optional = TRUE)
+ OK[["ref_range_upper_lim_var"]] <- OK[["lab_dataset_name"]] && CM$check_dataset_colum_name("ref_range_upper_lim_var",
+ ref_range_upper_lim_var, subkind, flags, lab_dataset_name, datasets[[lab_dataset_name]], warn,
+ err)
+ for (ds_name in names(used_dataset_names)) {
+ OK[["subjectid_var"]] <- OK[["subjectid_var"]] && CM$check_subjid_col(datasets, ds_name, get(ds_name),
+ "subjectid_var", subjectid_var, warn, err)
+ }
return(OK)
}
diff --git a/R/mock_edish.R b/R/mock_edish.R
index 364651a..aa3e975 100644
--- a/R/mock_edish.R
+++ b/R/mock_edish.R
@@ -37,7 +37,8 @@ mock_edish_mm <- function() {
module_list <- list(
"edish demo" = mod_edish(
module_id = "edish",
- dataset_names = c("dm", "lb"),
+ subject_level_dataset_name = "dm",
+ lab_dataset_name = "lb",
arm_default_vals = c("Xanomeline Low Dose", "Placebo"),
baseline_visit_val = "SCREENING 1"
)
diff --git a/R/mod_edish.R b/R/mod_edish.R
index 1989bde..5bcae0e 100644
--- a/R/mod_edish.R
+++ b/R/mod_edish.R
@@ -162,7 +162,6 @@ edish_server <- function(
ref_range_upper_lim_var = "LBSTNRHI") {
# Check validity of arguments
ac <- checkmate::makeAssertCollection()
- checkmate::assert_string(module_id, min.chars = 1, add = ac)
checkmate::assert_multi_class(dataset_list, c("reactive", "shinymeta_reactive"), add = ac)
checkmate::assert_string(subjectid_var, min.chars = 1, add = ac)
checkmate::assert_string(arm_var, min.chars = 1, add = ac)
@@ -292,7 +291,7 @@ edish_server <- function(
#' @param module_id `[character(1)]`
#'
#' A unique module ID.
-#' @param dataset_names `[character(1+)]`
+#' @param subject_level_dataset_name,lab_dataset_name `[character(1)]`
#'
#' Name(s) of the dataset(s) that will be displayed.
#' @param subjectid_var `[character(1)]`
@@ -347,7 +346,8 @@ edish_server <- function(
#' @export
mod_edish <- function(
module_id,
- dataset_names,
+ subject_level_dataset_name,
+ lab_dataset_name,
subjectid_var = "USUBJID",
arm_var = "ACTARM",
arm_default_vals = NULL,
@@ -371,7 +371,7 @@ mod_edish <- function(
},
server = function(afmm) {
dataset_list <- shiny::reactive({
- afmm$filtered_dataset()[dataset_names]
+ afmm$filtered_dataset()[c(subject_level_dataset_name, lab_dataset_name)]
})
edish_server(
@@ -401,7 +401,8 @@ mod_edish <- function(
mod_edish_API_docs <- list(
"Edish",
module_id = list(""),
- dataset_names = list(""),
+ subject_level_dataset_name = list(""),
+ lab_dataset_name = list(""),
subjectid_var = list(""),
arm_var = list(""),
arm_default_vals = list(""),
@@ -417,79 +418,88 @@ mod_edish_API_docs <- list(
mod_edish_API_spec <- TC$group(
module_id = TC$mod_ID(),
- dataset_names = TC$dataset_name() |> TC$flag("one_or_more"),
- # TODO: The TC API functions do not allow to talk about a column belonging to more than one dataset yet
- subjectid_var = TC$group() |> TC$flag("ignore"),
- arm_var = TC$group() |> TC$flag("ignore"),
- arm_default_vals = TC$group() |> TC$flag("ignore"),
- visit_var = TC$group() |> TC$flag("ignore"),
- baseline_visit_val = TC$group() |> TC$flag("ignore"),
- lb_test_var = TC$group() |> TC$flag("ignore"),
- lb_test_choices = TC$group() |> TC$flag("ignore"),
- lb_test_default_x_val = TC$group() |> TC$flag("ignore"),
- lb_test_default_y_val = TC$group() |> TC$flag("ignore"),
- lb_result_var = TC$group() |> TC$flag("ignore"),
- ref_range_upper_lim_var = TC$group() |> TC$flag("ignore")
+ subject_level_dataset_name = TC$dataset_name() |> TC$flag("subject_level_dataset_name"),
+ lab_dataset_name = TC$dataset_name(),
+ subjectid_var = TC$col("subject_level_dataset_name", TC$or(TC$character(), TC$factor())) |> TC$flag("subjid_var"),
+ arm_var = TC$col("subject_level_dataset_name", TC$or(TC$character(), TC$factor())),
+ arm_default_vals = TC$choice_from_col_contents("arm_var") |> TC$flag("one_or_more", "optional"),
+ visit_var = TC$col("lab_dataset_name", TC$or(TC$character(), TC$factor())),
+ baseline_visit_val = TC$choice_from_col_contents("visit_var"),
+ lb_test_var = TC$col("lab_dataset_name", TC$or(TC$character(), TC$factor())),
+ lb_test_choices = TC$choice_from_col_contents("lb_test_var") |> TC$flag("one_or_more", "optional"),
+ lb_test_default_x_val = TC$choice_from_col_contents("lb_test_var") |> TC$flag("optional"),
+ lb_test_default_y_val = TC$choice_from_col_contents("lb_test_var") |> TC$flag("optional"),
+ lb_result_var = TC$col("lab_dataset_name", TC$or(TC$numeric())),
+ ref_range_upper_lim_var = TC$col("lab_dataset_name", TC$numeric()) |> TC$flag("optional")
) |> TC$attach_docs(mod_edish_API_docs)
check_mod_edish <- function(
- afmm, datasets, module_id, dataset_names, subjectid_var, arm_var, arm_default_vals, visit_var, baseline_visit_val,
- lb_test_var, lb_test_choices, lb_test_default_x_val, lb_test_default_y_val, lb_result_var, ref_range_upper_lim_var
+ afmm, datasets, module_id, subject_level_dataset_name, lab_dataset_name, subjectid_var, arm_var, arm_default_vals,
+ visit_var, baseline_visit_val, lb_test_var, lb_test_choices, lb_test_default_x_val, lb_test_default_y_val,
+ lb_result_var, ref_range_upper_lim_var
) {
warn <- CM$container()
err <- CM$container()
OK <- check_mod_edish_auto(
afmm, datasets,
- module_id, dataset_names, subjectid_var, arm_var, arm_default_vals, visit_var, baseline_visit_val,
- lb_test_var, lb_test_choices, lb_test_default_x_val, lb_test_default_y_val, lb_result_var, ref_range_upper_lim_var,
+ module_id, subject_level_dataset_name, lab_dataset_name, subjectid_var, arm_var, arm_default_vals,
+ visit_var, baseline_visit_val, lb_test_var, lb_test_choices, lb_test_default_x_val, lb_test_default_y_val,
+ lb_result_var, ref_range_upper_lim_var,
warn, err
)
-
- # subjectid_var
- if (OK[["dataset_names"]]) {
- subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
- flags <- structure(list(), names = character(0))
-
- datasets_missing_subjidvar <- character(0)
- tmp <- CM$container()
- for (dataset_name in dataset_names){
- ok <- CM$check_dataset_colum_name(name = "subjectid_var", value = subjectid_var, subkind = subkind, flags = flags,
- dataset_name = dataset_name, dataset_value = datasets[[dataset_name]],
- warn = tmp, err = tmp)
- if (isFALSE(ok)) {
- datasets_missing_subjidvar <- c(datasets_missing_subjidvar, dataset_name)
- }
- }
+
+ # NOTE: Prevents dplyr from exploding inside `prepare_initial_data`
+ var_parameters <- c("subjectid_var", "arm_var", "visit_var", "lb_test_var", "lb_result_var")
+ if (all(OK[var_parameters])) {
+ all_vars <- c(subjectid_var, arm_var, visit_var, lb_test_var, lb_result_var)
CM$assert(
- err, length(datasets_missing_subjidvar) == 0,
- sprintf("Subject ID Column `%s` is missing or is not of [character|factor] type in the following datasets: %s",
- subjectid_var, paste(datasets_missing_subjidvar, collapse = ", "))
+ container = err,
+ cond = !any(duplicated(all_vars)),
+ msg = sprintf(
+ "This modules expects the following variables to refer to unique columns:%s", + paste(capture.output(setNames(all_vars, var_parameters)), collapse = "\n") + ) ) } - # TODO: Checks that API spec does not (yet?) capture - - # arm_default_vals - # visit_var - # baseline_visit_val - # lb_test_var - # lb_test_choices - # lb_test_default_x_val - # lb_test_default_y_val - # lb_result_var - # ref_range_upper_lim_var + # NOTE: Ensures that `lb_test_default_{x,y}_val` are a subset of the available `lb_test_choices` + if (all(OK[c("lab_dataset_name", "lb_test_var", "lb_test_choices", "lb_test_default_x_val")])) { + if (OK["lb_test_default_x_val"]) { + CM$assert( + container = err, + cond = lb_test_default_x_val %in% lb_test_choices, + msg = sprintf( + 'The value assigned to `lb_test_default_x_val` ("%s") should be among the ones provided by `lb_test_choices` (%s).', + lb_test_default_x_val, paste(sprintf('"%s"', lb_test_choices), collapse = ", ") + ) + ) + } + if (OK["lb_test_default_y_val"]) { + CM$assert( + container = err, + cond = lb_test_default_y_val %in% lb_test_choices, + msg = sprintf( + 'The value assigned to `lb_test_default_y_val` ("%s") should be among the ones provided by `lb_test_choices` (%s).', + lb_test_default_y_val, paste(sprintf('"%s"', lb_test_choices), collapse = ", ") + ) + ) + } + } res <- list(warnings = warn[["messages"]], errors = err[["messages"]]) return(res) } -dataset_info_edish <- function(dataset_names, ...) { +dataset_info_edish <- function(subject_level_dataset_name, lab_dataset_name, ...) { # TODO: Replace this function with a generic one that builds the list based on mod_edish_API_spec. # Something along the lines of CM$dataset_info(mod_boxplot_API_spec, args = match.call()) - all <- character(0) - if (length(dataset_names)) all <- unique(dataset_names) - return(list(all = all, subject_level = character(0))) + return( + list( + all = unique(c(subject_level_dataset_name, lab_dataset_name)), + subject_level = subject_level_dataset_name + ) + ) } -mod_edish <- CM$module(mod_edish, check_mod_edish, dataset_info_edish) \ No newline at end of file +mod_edish <- CM$module(mod_edish, check_mod_edish, dataset_info_edish) diff --git a/README.md b/README.md index 4cbf272..442fb35 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ lb <- pharmaverseadam::adlb module_list <- list( "edish" = dv.edish::mod_edish( module_id = "edish", - dataset_names = c("dm", "lb"), + subject_level_dataset_name = 'dm', + lab_dataset_name = 'lb', arm_default_vals = c("Xanomeline Low Dose", "Xanomeline High Dose"), baseline_visit_val = "SCREENING 1" ) diff --git a/man/mod_edish.Rd b/man/mod_edish.Rd index cdac9c3..e1afcc8 100644 --- a/man/mod_edish.Rd +++ b/man/mod_edish.Rd @@ -6,7 +6,8 @@ \usage{ mod_edish( module_id, - dataset_names, + subject_level_dataset_name, + lab_dataset_name, subjectid_var = "USUBJID", arm_var = "ACTARM", arm_default_vals = NULL, @@ -26,7 +27,7 @@ mod_edish( A unique module ID.} -\item{dataset_names}{\verb{[character(1+)]} +\item{subject_level_dataset_name, lab_dataset_name}{\verb{[character(1)]} Name(s) of the dataset(s) that will be displayed.}