Skip to content

Commit 23d9e64

Browse files
committed
Simplify EEF.
1 parent 4b6aff0 commit 23d9e64

4 files changed

Lines changed: 20 additions & 253 deletions

File tree

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: dv.listings
22
Type: Package
33
Title: Data listings module
4-
Version: 4.3.4-9004
4+
Version: 4.3.4-9005
55
Authors@R:
66
c(
77
person("Boehringer-Ingelheim Pharma GmbH & Co.KG", role = c("cph", "fnd")),
@@ -47,6 +47,6 @@ Imports:
4747
Depends: R (>= 4.1.0)
4848
RoxygenNote: 7.3.2
4949
Roxygen: list(markdown = TRUE)
50-
Remotes: boehringer-ingelheim/dv.manager
50+
Remotes: boehringer-ingelheim/dv.manager@372284-integrate_CM
5151
VignetteBuilder: knitr
5252
URL: https://github.com/Boehringer-Ingelheim/dv.listings, https://boehringer-ingelheim.github.io/dv.listings

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# dv.listings 4.3.4-9005
2+
- [NOT USER-FACING] Move some EEF logic into dv.manager.
3+
14
# dv.listings 4.3.4-9004
25
- Review functionality:
36
- Expand revision count limit from 1000 to 10000 entries

R/CM.R

Lines changed: 5 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -1,249 +1,16 @@
11
# YT#VH04ddb3b9e9d0bc00e6606ce5e074418b#VH20fe8acb2e57832933eb60226847381a#
22
CM <- local({ # _C_hecked _M_odule
3+
# 2026-05-07: [cleanup] Moved `CM$module` to `dv.manager` and `CM$message_well` to DR.R
34
# 2026-03-26: [cleanup] Drop hyperspecific `CM$check_unique_sub_cat_par_vis()`
45
# [cleanup] Drop unused `warning_messages` parameter and related codepaths
56
# [cleanup] Drop type mapping
67
# [feature] New `CM$format_inline_asis` helper for custom diagnostic messages
78
# [feature] Clarify that it is the app creator who is responsible for addressing errors
89
# [cleanup] Split multi-line strings to silence YT.R warning
910
# 2025-07-11: [feature] New `manual_check` flag to tell `CM$generate_check_functions()` to ignore particular elements
10-
# 2025-04-09: [fix] Make `generate_map_afmm_function` map multi-variable parameters (e.g. `visit_vars`)
11-
# 2025-03-21: [feature] report errors for all loaded datasets
12-
# [fix] dehardcode "PARAM" string and use `par` argument
13-
14-
message_well <- function(title, contents, color = "f5f5f5") {
15-
style <- sprintf(
16-
paste0(
17-
"padding: 0.5rem;",
18-
"padding-left: 1rem;",
19-
"margin-bottom: 20px;",
20-
"background-color: %s;",
21-
"border: 1px solid #e3e3e3;",
22-
"border-radius: 4px;",
23-
"-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05);",
24-
"box-shadow: inset 0 1px 1px rgba(0,0,0,.05);"
25-
),
26-
color
27-
)
28-
29-
res <- list(shiny::h3(title))
30-
if (length(contents)) res <- append(res, list(shiny::tags[["div"]](contents, style = style)))
31-
return(res)
32-
}
33-
34-
app_creator_feedback_ui <- function(id, ui) {
35-
id <- paste(c(id, "validator"), collapse = "-")
36-
ns <- shiny::NS(id)
37-
38-
hide <- function(e) shiny::tags[["div"]](e, style = "display: none")
39-
40-
res <- list(
41-
shiny::uiOutput(ns("ui")),
42-
hide(shiny::checkboxInput(inputId = ns("show_ui"), label = NULL)),
43-
shiny::conditionalPanel(condition = "input.show_ui == true", ui, ns = ns)
44-
)
45-
return(res)
46-
}
47-
48-
app_creator_feedback_server <- function(id, error_messages, preface) {
49-
id <- paste(c(id, "validator"), collapse = "-")
50-
module <- shiny::moduleServer(
51-
id,
52-
function(input, output, session) {
53-
output[["ui"]] <- shiny::renderUI({
54-
res <- list()
55-
56-
if (length(error_messages)) {
57-
app_creator_disclaimer <- htmltools::p(htmltools::HTML(
58-
paste("<i>Configuration errors prevent this module from running.",
59-
"<b>The following diagnostic information is meant for the app creator</b>.</i>")
60-
), style = "font-size: small;")
61-
62-
error_messages <- append(list(app_creator_disclaimer), error_messages)
63-
res[[length(res) + 1]] <- message_well("Module configuration errors", error_messages, color = "#f4d7d7")
64-
}
65-
66-
return(res)
67-
})
68-
shiny::outputOptions(output, "ui", suspendWhenHidden = FALSE)
69-
70-
if (length(error_messages) == 0) {
71-
shiny::updateCheckboxInput(inputId = "show_ui", value = TRUE)
72-
}
73-
}
74-
)
75-
76-
return(module)
77-
}
78-
79-
# Wrap the UI and server of a module so that, once parameterized, they:
80-
# - go through a check function prior to running
81-
# - provide `dataset_info` to module manager
82-
module <- function(module, check_mod_fn, dataset_info_fn) {
83-
local({
84-
# Make sure that the signature of `check_mod_fn` matches that of `module` except for the expected differences
85-
check_formals <- names(formals(check_mod_fn))
86-
if (!identical(utils::head(check_formals, 2), c("afmm", "datasets"))) {
87-
stop("The first two arguments of check functions passed onto `module` should be `afmm` and `datasets`")
88-
}
89-
check_formals <- check_formals[c(-1, -2)]
90-
91-
mod_formals <- names(formals(module))
92-
if (!identical(check_formals, mod_formals)) {
93-
stop(paste(
94-
"Check function arguments do not exactly match those of the module function",
95-
"(after discarding `afmm` and `datasets`)"
96-
))
97-
}
98-
})
99-
100-
mandatory_module_args <- local({
101-
args <- formals(module)
102-
names(args)[sapply(args, function(x) is.name(x) && nchar(x) == 0)]
103-
})
104-
105-
wrapper <- function(...) {
106-
# Match arguments explicitly to provide graphical error feedback
107-
# https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Argument-matching
108-
109-
module_ui <- function(...) list()
110-
module_server <- function(...) NULL
111-
module_id <- "error_id"
112-
113-
matched_args <- try(as.list(match.call(module)), silent = TRUE)
114-
error_message <- attr(matched_args, "condition")$message
115-
if (is.null(error_message)) {
116-
missing_args <- setdiff(mandatory_module_args, names(matched_args))
117-
if (length(missing_args)) {
118-
error_message <- sprintf("Missing mandatory arguments: `%s`.", paste(missing_args, collapse = ", "))
119-
}
120-
}
121-
122-
if (is.null(error_message)) {
123-
args <- list(...)
124-
evaluated_module <- do.call(module, args)
125-
module_ui <- evaluated_module[["ui"]]
126-
module_server <- evaluated_module[["server"]]
127-
module_id <- evaluated_module[["module_id"]]
128-
}
129-
130-
res <- list(
131-
ui = function(module_id) app_creator_feedback_ui(module_id, module_ui(module_id)), # `module` UI gated by app_creator_feedback_server
132-
server = function(afmm) {
133-
fb <- local({
134-
res <- NULL
135-
if (!is.null(error_message)) {
136-
res <- list(errors = error_message)
137-
} else {
138-
# NOTE: We check the call here and not inside the module server function because:
139-
# - app creators interact with the davinci module and not with the ui-server combo, so
140-
# errors reported with respect to the module signature will make sense to them.
141-
# The module server function might use a different function signature.
142-
# - Here we also have access to the original datasets, which allows us to ensure call
143-
# correctness independent of filter state or operation in a single pass.
144-
# - "catch errors early"
145-
dataset_count <- length(afmm[["data"]])
146-
147-
res_by_dataset <- list()
148-
error_count_by_dataset <- integer(0)
149-
error_count <- 0
150-
for (i_dataset in seq_len(dataset_count)) {
151-
dataset <- afmm[["data"]][[i_dataset]]
152-
if (is.function(dataset)) dataset <- dataset()
153-
check_args <- append(
154-
list(
155-
afmm = afmm, # To check receiver_ids, among others
156-
datasets = dataset # Allows data checks prior to reactive time
157-
),
158-
args
159-
)
160-
info <- do.call(check_mod_fn, check_args)
161-
res_by_dataset[[i_dataset]] <- info
162-
error_count_by_dataset[[i_dataset]] <- length(info[["errors"]])
163-
error_count <- error_count + length(info[["errors"]])
164-
}
165-
166-
as_items <- function(x) htmltools::p(htmltools::HTML(paste("\u2022", x)))
167-
168-
if (error_count == 0) NULL
169-
else if (dataset_count == 1) {
170-
# single dataset
171-
res <- res_by_dataset[[1]]
172-
res[["errors"]] <- Map(as_items, res[["errors"]])
173-
} else {
174-
# multiple datasets
175-
errors <- list()
176-
177-
dataset_names <- names(afmm[["datasets"]])
178-
179-
errors <- c(
180-
list(htmltools::p(htmltools::HTML(
181-
"Issues have been grouped by input dataset. Expand/collapse the elements below to inspect them:"
182-
)))
183-
)
184-
185-
details_extra <- "open"
186-
for (i_dataset in seq_len(dataset_count)){
187-
if (error_count_by_dataset[[i_dataset]] == 0) next
188-
189-
details_pre <- htmltools::HTML(
190-
sprintf('<details %s><summary style="display:list-item"><b>%s</b></summary>',
191-
details_extra, names(afmm[["data"]]))[[i_dataset]])
192-
details_extra <- ""
193-
194-
details_post <- htmltools::HTML("</details>")
195-
196-
errors <- c(errors, list(details_pre))
197-
198-
errors <- c(
199-
errors,
200-
list(htmltools::HTML(paste0(
201-
"<div style='padding: 0.5rem; margin-bottom: 1rem; background-color: #FFFFFF55;",
202-
"border: 1px solid #AAAAAA; border-radius: 4px;'>"
203-
))),
204-
Map(as_items, res_by_dataset[[i_dataset]][["errors"]]),
205-
list(htmltools::HTML("</div>"))
206-
)
207-
208-
errors <- c(errors, list(details_post))
209-
210-
res[["errors"]] <- errors
211-
}
212-
}
213-
}
214-
215-
return(res)
216-
})
217-
218-
app_creator_feedback_server(id = module_id, error_messages = fb[["errors"]], preface = fb[["preface"]])
219-
220-
if (length(fb[["errors"]]) == 0) {
221-
res <- try(module_server(afmm), silent = TRUE)
222-
}
223-
224-
return(res)
225-
},
226-
module_id = module_id,
227-
meta = list(
228-
dataset_info = {
229-
# extract defaults from the formals for consistency
230-
missing_args <- setdiff(names(formals(module)), names(matched_args))
231-
args <- c(args, formals(module)[missing_args])
232-
do.call(dataset_info_fn, args)
233-
}
234-
)
235-
)
236-
237-
return(res)
238-
}
239-
240-
roxygen_wrapper <- function() { # to keep parameters in the reference docs
241-
args <- (match.call() |> as.list())[c(-1)]
242-
do.call(wrapper, args, envir = parent.frame())
243-
}
244-
formals(roxygen_wrapper) <- formals(module)
245-
return(roxygen_wrapper)
246-
}
11+
# 2025-04-09: [fix] Make `generate_map_afmm_function` map multi-variable parameters (e.g. `visit_vars`)
12+
# 2025-03-21: [feature] Report errors for all loaded datasets
13+
# [fix] Dehardcode "PARAM" string and use `par` argument
24714

24815
container <- function() list2env(x = list(messages = character(0)), parent = emptyenv())
24916
assert <- function(container, cond, msg) {
@@ -645,7 +412,6 @@ CM <- local({ # _C_hecked _M_odule
645412
}
646413

647414
list(
648-
module = module,
649415
container = container,
650416
assert = assert,
651417
format_inline_asis = format_inline_asis,
@@ -657,7 +423,6 @@ CM <- local({ # _C_hecked _M_odule
657423
check_choice_from_col_contents = check_choice_from_col_contents,
658424
check_choice = check_choice,
659425
check_function = check_function,
660-
check_subjid_col = check_subjid_col,
661-
message_well = message_well
426+
check_subjid_col = check_subjid_col
662427
)
663428
})

R/mod_listings.R

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ TBL <- pack_of_constants( # nolint
3838
#'
3939
#' @export
4040
#' @family data_listings
41-
listings_UI <- function(module_id) { # nolint
41+
listings_UI <- function(module_id) {
4242

4343
# Check validity of arguments
4444
checkmate::assert_string(module_id, min.chars = 1)
@@ -883,7 +883,14 @@ mod_listings <- function(
883883
review = review
884884
)
885885
},
886-
module_id = module_id
886+
module_id = module_id,
887+
meta = list(
888+
dataset_info = list(all = unique(dataset_names), subject_level = character(0)),
889+
check_mod_fn = function(afmm, dataset) {
890+
check_mod_listings(afmm, dataset, module_id, dataset_names, default_vars, pagination,
891+
intended_use_label, subjid_var, receiver_id, review)
892+
}
893+
)
887894
)
888895
return(mod)
889896
}
@@ -924,10 +931,6 @@ mod_listings_API_spec <- TC$group(
924931
) |> TC$flag("manual_check", "optional")
925932
) |> TC$attach_docs(mod_listings_API_docs)
926933

927-
dataset_info_listings <- function(dataset_names, ...) {
928-
return(list(all = unique(dataset_names), subject_level = character(0)))
929-
}
930-
931934
check_mod_listings <- function(afmm, datasets, module_id, dataset_names,
932935
default_vars, pagination, intended_use_label,
933936
subjid_var, receiver_id, review) {
@@ -995,10 +998,6 @@ check_mod_listings <- function(afmm, datasets, module_id, dataset_names,
995998

996999
check_review_parameter(datasets, dataset_names, review, err, afmm)
9971000

998-
res <- list(errors = err[["messages"]])
1001+
res <- err[["messages"]]
9991002
return(res)
10001003
}
1001-
1002-
mod_listings <- CM$module(
1003-
mod_listings, check_mod_listings, dataset_info_listings
1004-
)

0 commit comments

Comments
 (0)