Skip to content

Commit ec4b261

Browse files
authored
Merge pull request #23 from Boehringer-Ingelheim/test
Release v0.1.0 to main
2 parents f526218 + 5dd0606 commit ec4b261

22 files changed

+906
-516
lines changed

DESCRIPTION

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
Package: dv.tables
22
Type: Package
33
Title: Table Modules
4-
Version: 0.0.3
4+
Version: 0.1.0
55
Authors@R: c(
66
person("Boehringer-Ingelheim Pharma GmbH & Co.KG", role = c("cph", "fnd")),
7-
person(given = "Luis", family = "Moris Fernandez", role = c("aut", "cre"), email = "luis.moris.fernandez@gmail.com")
7+
person(given = "Luis", family = "Moris Fernandez", role = c("aut", "cre"), email = "luis.moris.fernandez@gmail.com"),
8+
person(given = "Mark", family = "Luff", role = "aut", email = "mark.luff.ext@boehringer-ingelheim.com")
89
)
910
Description: Modules for table outputs
1011
License: Apache License (>= 2)
@@ -16,10 +17,10 @@ Suggests: dv.manager (>= 2.1.4), jsonlite, rmarkdown, testthat (>=
1617
3.0.0), shinytest2, devtools, knitr, tibble, utils
1718
Config/testthat/edition: 3
1819
Config/testthat/parallel: false
19-
Imports: shiny (>= 1.7.1),dplyr (>= 1.0.7), purrr (>= 0.3.4),
20+
Imports: shiny (>= 1.7.1), dplyr (>= 1.0.7), purrr (>= 0.3.4),
2021
tidyr (>= 1.1.4),
2122
rlang, checkmate (>= 2.0.0), htmltools,
22-
stats, pharmaverseadam, openxlsx, shinyjs
23+
stats, pharmaverseadam, openxlsx, shinyjs, gt
2324
Depends: R (>= 4.0)
2425
VignetteBuilder: knitr
2526
Remotes: boehringer-ingelheim/dv.manager@v2.1.4

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# dv.tables 0.1.0
2+
3+
* Add jumping functionality
4+
* Implement label truncation
5+
* Implement functionality to download to Word (.rtf) format
6+
* Allow subsetting of hierarchy and group variables drop-down list through module argument
7+
* Remove subject identifier variable from hierarchy and group variables drop-down list
8+
* Alert when group selection also selected in hierarchy
9+
* Allow a maximum of 2 hierarchy variable selections
10+
111
# dv.tables 0.0.3
212

313
* Add table download button

R/CM.R

Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ CM <- local({ # _C_hecked _M_odule
2222
app_creator_feedback_ui <- function(id, ui) {
2323
id <- paste(c(id, "validator"), collapse = "-")
2424
ns <- shiny::NS(id)
25-
25+
2626
hide <- function(e) shiny::tags[["div"]](e, style = "display: none")
27-
27+
2828
res <- list(
2929
shiny::uiOutput(ns("ui")),
3030
hide(shiny::checkboxInput(inputId = ns("show_ui"), label = NULL)),
@@ -56,7 +56,7 @@ CM <- local({ # _C_hecked _M_odule
5656
return(res)
5757
})
5858
shiny::outputOptions(output, "ui", suspendWhenHidden = FALSE)
59-
59+
6060
if (length(error_messages) == 0) {
6161
shiny::updateCheckboxInput(inputId = "show_ui", value = TRUE)
6262
}
@@ -154,67 +154,67 @@ CM <- local({ # _C_hecked _M_odule
154154
error_count_by_dataset[[i_dataset]] <- length(info[["errors"]])
155155
error_count <- error_count + length(info[["errors"]])
156156
}
157-
157+
158158
as_items <- function(x) htmltools::p(htmltools::HTML(paste("\u2022", x)))
159-
159+
160160
if (error_count == 0) NULL
161161
else if (dataset_count == 1) {
162162
# single dataset
163163
res <- res_by_dataset[[1]]
164164
res[["errors"]] <- Map(as_items, res[["errors"]])
165165
} else {
166166
# multiple datasets
167-
167+
168168
# FIXME(miguel): We don't do merge "warnings" here because it's a feature that goes unused and we will
169169
# remove it soon. We can't remove it _now_ because it would still require minor fixes
170170
# in at least five different packages and we want to roll a CM bugfix (unrelated to
171171
# the "multiple dataset" feature reporting) while avoiding cascading work.
172172
errors <- list()
173-
173+
174174
dataset_names <- names(afmm[["datasets"]])
175-
175+
176176
errors <- c(
177177
list(htmltools::p(htmltools::HTML(
178178
"Issues have been grouped by input dataset. Expand/collapse the elements below to inspect them:"
179179
)))
180180
)
181-
181+
182182
details_extra <- "open"
183183
for (i_dataset in seq_len(dataset_count)){
184184
if (error_count_by_dataset[[i_dataset]] == 0) next
185-
185+
186186
details_pre <- htmltools::HTML(
187187
sprintf('
188188
<details %s>
189189
<summary style="display:list-item"><b>%s</b></summary>
190190
', details_extra, names(afmm[["data"]]))[[i_dataset]]
191191
)
192192
details_extra <- ""
193-
193+
194194
details_post <- htmltools::HTML("</details>")
195-
195+
196196
errors <- c(errors, list(details_pre))
197-
197+
198198
errors <- c(
199-
errors,
199+
errors,
200200
list(htmltools::HTML("<div style='padding: 0.5rem; margin-bottom: 1rem; background-color: #FFFFFF55;
201201
border: 1px solid #AAAAAA; border-radius: 4px;'>")),
202202
Map(as_items, res_by_dataset[[i_dataset]][["errors"]]),
203203
list(htmltools::HTML("</div>"))
204204
)
205-
205+
206206
errors <- c(errors, list(details_post))
207-
207+
208208
res[["errors"]] <- errors
209209
}
210210
}
211211
}
212-
212+
213213
return(res)
214214
})
215215

216216
app_creator_feedback_server(
217-
id = module_id, warning_messages = fb[["warnings"]], error_messages = fb[["errors"]],
217+
id = module_id, warning_messages = fb[["warnings"]], error_messages = fb[["errors"]],
218218
preface = fb[["preface"]]
219219
)
220220

@@ -223,7 +223,7 @@ CM <- local({ # _C_hecked _M_odule
223223
afmm_and_args <- append(list(afmm = afmm), args)
224224
afmm <- do.call(map_afmm_fn, afmm_and_args)
225225
}
226-
226+
227227
res <- try(module_server(afmm), silent = TRUE)
228228
}
229229

@@ -387,37 +387,37 @@ CM <- local({ # _C_hecked _M_odule
387387

388388
return(NULL)
389389
}
390-
390+
391391
generate_map_afmm_function <- function(spec, module_name) {
392392
stopifnot(spec$kind == "group")
393-
393+
394394
# TODO: At the time of writing, this code generator is only used by dv.explorer.parameter and it covers its needs.
395395
# It modifies afmm[["filtered_dataset"]] based on parameters flagged as "map_character_to_factor"
396396
# so that specific columns of target datasets are transformed to factors prior to going into a module.
397397
#
398398
# In order to complete this functionality, we would have to map afmm[["unfiltered_dataset"]] as well
399-
# as afmm[["data"]]. Moreover, we would have to look for "map_character_to_factor" flags inside possibly
399+
# as afmm[["data"]]. Moreover, we would have to look for "map_character_to_factor" flags inside possibly
400400
# nested column definitions, such as those used in papo.
401-
401+
402402
res <- character(0)
403-
403+
404404
push <- function(s) res <<- c(res, s)
405405
push("function(afmm, ")
406406
param_names <- paste(names(spec$elements), collapse = ",")
407407
push(param_names)
408408
push("){\n")
409-
409+
410410
push("res <- afmm\n")
411-
411+
412412
elements_that_require_mapping <- character(0)
413413
for (elem_name in names(spec$elements))
414414
if (isTRUE(attr(spec$elements[[elem_name]], "map_character_to_factor")))
415415
elements_that_require_mapping <- c(elements_that_require_mapping, elem_name)
416-
416+
417417
if (length(elements_that_require_mapping)) {
418418
push("mapping_summary <- character(0)\n")
419419
push("for(ds_name in names(afmm[['data']])){\n")
420-
push(" ds <- afmm[['data']][[ds_name]]\n")
420+
push(" ds <- afmm[['data']][[ds_name]]\n")
421421
for (elem_name in elements_that_require_mapping){
422422
elem <- spec$elements[[elem_name]]
423423
stopifnot(elem$kind == "col")
@@ -429,9 +429,9 @@ CM <- local({ # _C_hecked _M_odule
429429
push("}\n")
430430
}
431431
push("}\n")
432-
432+
433433
push("if(length(mapping_summary)){\n")
434-
434+
435435
push(
436436
paste0(
437437
"warning_message <- paste0('[", module_name,
@@ -441,25 +441,25 @@ CM <- local({ # _C_hecked _M_odule
441441
"warning(warning_message)\n"
442442
)
443443
)
444-
444+
445445
push("res[['filtered_dataset']] <- shiny::reactive({\n")
446446
push(" res <- afmm[['filtered_dataset']]()\n")
447-
447+
448448
for (elem_name in elements_that_require_mapping){
449449
elem <- spec$elements[[elem_name]]
450450
dataset_name <- elem[["dataset_name"]]
451-
451+
452452
push(sprintf("if (is.character(res[[%s]][[%s]])) {\n", dataset_name, elem_name))
453453
push(sprintf(" res[[%s]][[%s]] <- ", dataset_name, elem_name))
454454
push(sprintf(" as.factor(res[[%s]][[%s]])\n", dataset_name, elem_name))
455455
push("}\n")
456456
}
457-
457+
458458
push(" return(res)\n")
459459
push("})\n")
460460
push("}\n")
461461
}
462-
462+
463463
push("return(res)\n")
464464
push("}\n")
465465

@@ -480,10 +480,10 @@ CM <- local({ # _C_hecked _M_odule
480480
denamespaced_spec_name <- strsplit(spec_name, "::")[[1]][[2]]
481481
map_afmm_function_name <- paste0("map_afmm_", denamespaced_spec_name, "_auto")
482482
res <- c(res, sprintf("\n\n# %s\n", spec_name))
483-
483+
484484
res <- c(
485485
res,
486-
c(map_afmm_function_name, "<-",
486+
c(map_afmm_function_name, "<-",
487487
generate_map_afmm_function(specs[[spec_name]], module_name = denamespaced_spec_name)) |> style_code()
488488
)
489489
}
@@ -570,10 +570,10 @@ CM <- local({ # _C_hecked _M_odule
570570
return(TRUE)
571571
}
572572

573-
ok <- assert(err, is.character(value),
573+
ok <- assert(err, is.character(value),
574574
paste(sprintf("The value assigned to parameter `%s` should be of type `character`", name),
575575
sprintf("and it's instead of type `%s`.", class(value)[[1]])))
576-
576+
577577
valid_column_names <- list_columns_of_kind(dataset_value, subkind)
578578
invalid_column_names <- value[!value %in% valid_column_names]
579579
wrong_subkind_column_names <- invalid_column_names[invalid_column_names %in% names(dataset_value)]
@@ -587,12 +587,12 @@ CM <- local({ # _C_hecked _M_odule
587587
paste(
588588
sprintf("Variables assigned to parameter <b>`%s`</b> should refer to columns of dataset <b>`%s`</b>",
589589
name, dataset_name),
590-
sprintf("of type `%s`, but some (<b>%s</b>) have other types (%s).",
590+
sprintf("of type `%s`, but some (<b>%s</b>) have other types (%s).",
591591
type_desc, cnames, types_found_desc)
592592
)
593593
}
594594
)
595-
595+
596596
ok <- ok && assert(
597597
err, length(invalid_column_names) == 0, {
598598
cnames <- paste(sprintf('"%s"', invalid_column_names), collapse = ", ")
@@ -609,7 +609,7 @@ CM <- local({ # _C_hecked _M_odule
609609
if (zero_or_one_or_more) {
610610
min_len <- 0
611611
if (one_or_more) min_len <- 1
612-
612+
613613
ok <- ok && assert(
614614
err,
615615
length(value) >= min_len, {
@@ -761,7 +761,7 @@ CM <- local({ # _C_hecked _M_odule
761761

762762
unique_cat_par_combinations <- unique(dataset[c(cat, par)])
763763
dup_mask <- duplicated(unique_cat_par_combinations[par])
764-
764+
765765
ok <- assert(err, !any(dup_mask), {
766766
unique_repeat_params <- unique_cat_par_combinations[[par]][dup_mask]
767767
dups <- df_to_string(
@@ -776,8 +776,8 @@ CM <- local({ # _C_hecked _M_odule
776776
)
777777
)
778778
)
779-
prefix_repeat_params_command <-
780-
sprintf('%s <- dv.explorer.parameter::prefix_repeat_parameters(%s, cat_var = "%s", par_var = "%s")',
779+
prefix_repeat_params_command <-
780+
sprintf('%s <- dv.explorer.parameter::prefix_repeat_parameters(%s, cat_var = "%s", par_var = "%s")',
781781
ds_value, ds_value, cat, par)
782782

783783
mask <- unique_cat_par_combinations[[par]] %in% unique_repeat_params
@@ -791,7 +791,7 @@ CM <- local({ # _C_hecked _M_odule
791791
})
792792

793793
paste0(
794-
sprintf('The dataset provided by %s ("%s") contains parameter names that repeat across categories.',
794+
sprintf('The dataset provided by %s ("%s") contains parameter names that repeat across categories.',
795795
format_inline_asis(ds_name), ds_value),
796796
"This module expects them to be unique. This is the list of duplicates:",
797797
paste0("<pre>", dups, "</pre>"),
@@ -817,34 +817,34 @@ CM <- local({ # _C_hecked _M_odule
817817
first_duplicates <- head(supposedly_unique[dup_mask, ], 5)
818818
names(first_duplicates) <- paste(prefixes, names(first_duplicates))
819819
dups <- df_to_string(first_duplicates)
820-
820+
821821
unique_repeats <- unique(supposedly_unique[dup_mask, ])
822822
target <- unique_repeats[1, ]
823823
target_rows <- which(supposedly_unique[[sub]] == target[[sub]] & supposedly_unique[[cat]] == target[[cat]] &
824824
supposedly_unique[[par]] == target[[par]] & supposedly_unique[[vis]] == target[[vis]])
825-
825+
826826
row_a <- dataset[target_rows[[1]], ]
827827
row_b <- dataset[target_rows[[2]], ]
828828
diff_cols <- character(0)
829829
for (col in names(row_a)) if (!identical(row_a[[col]], row_b[[col]])) diff_cols <- c(diff_cols, col)
830-
831-
col_diff_report <- "are identical."
832-
if (length(diff_cols)) {
830+
831+
col_diff_report <- "are identical."
832+
if (length(diff_cols)) {
833833
col_diff_report <- paste0(
834834
"have indeed identical subject, category, parameter and visit values, but differ in columns: ",
835835
paste(diff_cols, collapse = ", "), ".",
836-
"<pre>",
836+
"<pre>",
837837
df_to_string(dataset[target_rows[1:2], c(sub, cat, par, vis, diff_cols)]),
838838
"</pre>"
839839
)
840840
}
841-
841+
842842
paste(
843843
sprintf("The dataset provided by `%s` (%s) contains repeated rows with identical subject, category, parameter", ds_name, ds_value),
844844
sprintf("and visit values. This module expects them to be unique. There are a total of %d duplicates.", sum(dup_mask)),
845845
"Here are the first few:",
846846
paste0("<pre>", dups, "</pre>"),
847-
sprintf("These findings can be partially confirmed by examining that rows <b>%d</b> and <b>%d</b> of that dataset",
847+
sprintf("These findings can be partially confirmed by examining that rows <b>%d</b> and <b>%d</b> of that dataset",
848848
target_rows[[1]], target_rows[[2]]),
849849
col_diff_report
850850
)

R/check_call_auto.R

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
# dv.tables::mod_hierarchical_count_table
66
check_mod_hierarchical_count_table_auto <- function(afmm, datasets, module_id, table_dataset_name, pop_dataset_name,
7-
subjid_var, show_modal_on_click, default_hierarchy, default_group, intended_use_label, receiver_id,
8-
warn, err) {
7+
subjid_var, show_modal_on_click, default_hierarchy, default_group, hierarchy_choices, group_choices,
8+
intended_use_label, receiver_id, warn, err) {
99
OK <- logical(0)
1010
used_dataset_names <- new.env(parent = emptyenv())
1111
OK[["module_id"]] <- CM$check_module_id("module_id", module_id, warn, err)
@@ -31,6 +31,15 @@ check_mod_hierarchical_count_table_auto <- function(afmm, datasets, module_id, t
3131
flags <- list(optional = TRUE)
3232
OK[["default_group"]] <- OK[["pop_dataset_name"]] && CM$check_dataset_colum_name("default_group",
3333
default_group, subkind, flags, pop_dataset_name, datasets[[pop_dataset_name]], warn, err)
34+
subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
35+
flags <- list(zero_or_more = TRUE, optional = TRUE)
36+
OK[["hierarchy_choices"]] <- OK[["table_dataset_name"]] && CM$check_dataset_colum_name("hierarchy_choices",
37+
hierarchy_choices, subkind, flags, table_dataset_name, datasets[[table_dataset_name]], warn,
38+
err)
39+
subkind <- list(kind = "or", options = list(list(kind = "character"), list(kind = "factor")))
40+
flags <- list(zero_or_more = TRUE, optional = TRUE)
41+
OK[["group_choices"]] <- OK[["pop_dataset_name"]] && CM$check_dataset_colum_name("group_choices",
42+
group_choices, subkind, flags, pop_dataset_name, datasets[[pop_dataset_name]], warn, err)
3443
"NOTE: intended_use_label (character) has no associated automated checks"
3544
" The expectation is that it either does not require them or that"
3645
" the caller of this function has written manual checks near the call site."

0 commit comments

Comments
 (0)