Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
060314d
feat: add S3 method dispatch for picks or teal transform variables
Mar 12, 2026
975ae0a
tests: add test for tm_g_ae_oview module
Mar 12, 2026
b6fd8f6
chore: fix spelling
Mar 12, 2026
d68f94a
chore: fix sign credentials
osenan Mar 12, 2026
3dd972e
[skip style] [skip vbump] Restyle files
github-actions[bot] Mar 12, 2026
89ecbea
Update DESCRIPTION
osenan Mar 16, 2026
630f5bf
Update R/tm_g_ae_oview_picks.R
osenan Mar 16, 2026
2869ce9
Update R/tm_g_ae_oview_picks.R
osenan Mar 16, 2026
c2f07a3
Update R/tm_g_ae_oview_picks.R
osenan Mar 16, 2026
bf70fff
chore: update docs after fix style
osenan Mar 16, 2026
88fbf7e
chore: fix typo in description
osenan Mar 16, 2026
8bd9bd3
refactor: change defaults on ae_oview generic and use dataname directly
osenan Mar 16, 2026
584c88b
refactor: add only necessary arguments to the UI
osenan Mar 17, 2026
b3f3eb0
fix: update test after refactor on method
osenan Mar 17, 2026
e00faee
[skip style] [skip vbump] Restyle files
github-actions[bot] Mar 17, 2026
440b9fe
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
github-actions[bot] Mar 17, 2026
5abc2ca
chore: fix linters and examples
osenan Mar 17, 2026
df0f269
[skip style] [skip vbump] Restyle files
github-actions[bot] Mar 17, 2026
b7219f0
chore: fix R CMD check and lintr
osenan Mar 17, 2026
640f80e
[skip style] [skip vbump] Restyle files
github-actions[bot] Mar 17, 2026
6e6e222
chore: fix styler and linter
osenan Mar 17, 2026
73c6f60
chore: revert to magrittr pipe
osenan Mar 17, 2026
8ca1bc1
chore: remove pipe consistency linter as we need magrittr pipes
osenan Mar 17, 2026
0e4d1bc
Update R/tm_g_ae_oview.R
osenan Mar 17, 2026
af68495
refactor: use as.picks to reuse picks srv and ui in choices_selected
osenan Mar 18, 2026
e5e9dad
chore: fix lintr comment to avoid automatic restyling
osenan Mar 18, 2026
5f0cfc2
Update R/tm_g_ae_oview_picks.R
osenan Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .lintr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ linters: linters_with_defaults(
line_length_linter = line_length_linter(120),
cyclocomp_linter = NULL,
object_usage_linter = NULL,
pipe_consistency_linter = NULL,
object_name_linter = object_name_linter(
styles = c("snake_case", "symbols"),
regexes = c(ANL = "^ANL_?[0-9A-Za-z_]*$", ADaM = "^r?AD[A-Z]{2,5}_?[0-9A-Za-z_]*$")
Expand Down
9 changes: 6 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Imports:
teal.code (>= 0.7.0),
teal.data (>= 0.8.0),
teal.logger (>= 0.4.0),
teal.picks (>= 0.1.0),
teal.reporter (>= 0.6.0),
teal.widgets (>= 0.5.0),
tern (>= 0.9.7),
Expand All @@ -49,10 +50,12 @@ Suggests:
testthat (>= 3.2.3),
withr (>= 3.0.0)
Remotes:
insightsengineering/osprey
insightsengineering/osprey,
insightsengineering/teal.picks
Config/Needs/verdepcheck: insightsengineering/osprey, rstudio/shiny,
insightsengineering/teal, insightsengineering/teal.slice,
insightsengineering/teal.transform, mllg/checkmate, tidyverse/dplyr,
insightsengineering/teal, insightsengineering/teal.picks,
insightsengineering/teal.slice, insightsengineering/teal.transform,
mllg/checkmate, tidyverse/dplyr,
insightsengineering/formatters, tidyverse/ggplot2, r-lib/lifecycle,
daroczig/logger, rstudio/shinyvalidate, insightsengineering/teal.code,
insightsengineering/teal.logger, insightsengineering/teal.reporter,
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method(tm_g_ae_oview,default)
S3method(tm_g_ae_oview,pick)
export(label_aevar)
export(plot_decorate_output)
export(quick_filter)
Expand Down
281 changes: 41 additions & 240 deletions R/tm_g_ae_oview.R
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
#' Teal module for the `AE` overview
#' @title Teal module for the `AE` overview
#'
#' @description
#'
#' Display the `AE` overview plot as a shiny module
#'
#' This is an S3 generic that dispatches on the class of `flag_var_anl`:
#' - [choices_selected][teal.transform::choices_selected()] dispatches to the
#' default method.
#' - [picks][teal.picks::picks()] dispatches to the picks method.
#'
#' @inheritParams teal.widgets::standard_layout
#' @inheritParams teal::module
#' @inheritParams argument_convention
#' @param flag_var_anl ([`teal.transform::choices_selected`])
#' `choices_selected` object with variables used to count adverse event
#' @param flag_var_anl Either a ([`teal.transform::choices_selected`])
#' `choices_selected` object or a (`[teal.picks::variables()]`)
#' object with variables used to count adverse event
#' sub-groups (e.g. Serious events, Related events, etc.)
#'
#' @param dataname (`character(1)`) Name of the events dataset. Required when
#' using the default method with [choices_selected][teal.transform::choices_selected()].
#' Ignored by the `.picks` method.
#' @inherit argument_convention return
#' @inheritSection teal::example_module Reporting
#'
#' @export
#'
#' @examples
#' data <- teal_data() %>%
#' within({
Expand Down Expand Up @@ -47,7 +53,7 @@
#' join_keys(data) <- default_cdisc_join_keys[names(data)]
#'
#' ADAE <- data[["ADAE"]]
#'
#' # Using default method (choices selected)
#' app <- init(
#' data = data,
#' modules = modules(
Expand All @@ -73,17 +79,33 @@
#' shinyApp(app$ui, app$server)
#' }
#'
#' @export
tm_g_ae_oview <- function(label,
dataname,
arm_var,
flag_var_anl,
fontsize = c(5, 3, 7),
plot_height = c(600L, 200L, 2000L),
plot_width = NULL,
transformators = list()) {
fontsize,
plot_height,
plot_width,
transformators) {
UseMethod("tm_g_ae_oview", arm_var)
}

#' @rdname tm_g_ae_oview
#' @export
tm_g_ae_oview.default <- function(label,
dataname,
arm_var,
flag_var_anl,
fontsize = c(5, 3, 7),
plot_height = c(600L, 200L, 2000L),
plot_width = NULL,
transformators = list()) {
message("Initializing tm_g_ae_oview")

checkmate::assert_class(arm_var, classes = "choices_selected")
checkmate::assert_class(flag_var_anl, classes = "choices_selected")

checkmate::assert(
checkmate::check_number(fontsize, finite = TRUE),
checkmate::assert(
Expand All @@ -104,235 +126,14 @@ tm_g_ae_oview <- function(label,
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width"
)

args <- as.list(environment())

module(
tm_g_ae_oview.pick(
label = label,
server = srv_g_ae_oview,
server_args = list(
label = label,
dataname = dataname,
plot_height = plot_height,
plot_width = plot_width
),
ui = ui_g_ae_oview,
ui_args = args,
transformators = transformators,
datanames = c("ADSL", dataname)
dataname = dataname,
arm_var = teal.picks::as.picks(arm_var),
flag_var_anl = teal.picks::as.picks(flag_var_anl),
fontsize,
plot_height,
plot_width,
transformators
)
}

ui_g_ae_oview <- function(id, ...) {
ns <- NS(id)
args <- list(...)
teal.widgets::standard_layout(
output = teal.widgets::white_small_well(
plot_decorate_output(id = ns(NULL))
),
encoding = tags$div(
teal.widgets::optionalSelectInput(
ns("arm_var"),
"Arm Variable",
choices = get_choices(args$arm_var$choices),
selected = args$arm_var$selected,
multiple = FALSE
),
selectInput(
ns("arm_ref"),
"Control",
choices = get_choices(args$arm_var$choices),
selected = args$arm_var$selected
),
selectInput(
ns("arm_trt"),
"Treatment",
choices = get_choices(args$arm_var$choices),
selected = args$arm_var$selected
),
selectInput(
ns("flag_var_anl"),
"Flags",
choices = get_choices(args$flag_var_anl$choices),
selected = args$flag_var_anl$selected,
multiple = TRUE
),
teal.widgets::panel_item(
"Confidence interval settings",
teal.widgets::optionalSelectInput(
ns("diff_ci_method"),
"Method for Difference of Proportions CI",
choices = ci_choices,
selected = ci_choices[1],
multiple = FALSE
),
teal.widgets::optionalSliderInput(
ns("conf_level"),
"Confidence Level",
min = 0.5,
max = 1,
value = 0.95
)
),
teal.widgets::optionalSelectInput(
ns("axis"),
"Axis Side",
choices = c("Left" = "left", "Right" = "right"),
selected = "left",
multiple = FALSE
),
ui_g_decorate(
ns(NULL),
fontsize = args$fontsize,
titles = "AE Overview",
footnotes = ""
)
)
)
}

srv_g_ae_oview <- function(id,
data,
dataname,
label,
plot_height,
plot_width) {
checkmate::assert_class(data, "reactive")
checkmate::assert_class(isolate(data()), "teal_data")

moduleServer(id, function(input, output, session) {
teal.logger::log_shiny_input_changes(input, namespace = "teal.osprey")
iv <- reactive({
ANL <- data()[[dataname]]

iv <- shinyvalidate::InputValidator$new()
iv$add_rule("arm_var", shinyvalidate::sv_required(
message = "Arm Variable is required"
))
iv$add_rule("arm_var", ~ if (!is.factor(ANL[[.]])) {
"Arm Var must be a factor variable"
})
iv$add_rule("arm_var", ~ if (nlevels(ANL[[.]]) < 2L) {
"Selected Arm Var must have at least two levels"
})
iv$add_rule("flag_var_anl", shinyvalidate::sv_required(
message = "At least one Flag is required"
))
rule_diff <- function(value, other) {
if (isTRUE(value == other)) "Control and Treatment must be different"
}
iv$add_rule("arm_trt", rule_diff, other = input$arm_ref)
iv$add_rule("arm_ref", rule_diff, other = input$arm_trt)
iv$enable()
iv
})

decorate_output <- srv_g_decorate(
id = NULL, plt = plot_r,
plot_height = plot_height, plot_width = plot_width
)
font_size <- decorate_output$font_size
pws <- decorate_output$pws

observeEvent(list(input$diff_ci_method, input$conf_level), {
req(!is.null(input$diff_ci_method) && !is.null(input$conf_level))
diff_ci_method <- input$diff_ci_method
conf_level <- input$conf_level
updateTextAreaInput(session,
"foot",
value = sprintf(
"Note: %d%% CI is calculated using %s",
round(conf_level * 100),
name_ci(diff_ci_method)
)
)
})

observeEvent(input$arm_var, ignoreNULL = TRUE, {
ANL <- data()[[dataname]]
arm_var <- input$arm_var
arm_val <- ANL[[arm_var]]
choices <- levels(arm_val)

if (length(choices) == 1) {
trt_index <- 1
} else {
trt_index <- 2
}

updateSelectInput(
session,
"arm_ref",
selected = choices[1],
choices = choices
)
updateSelectInput(
session,
"arm_trt",
selected = choices[trt_index],
choices = choices
)
})

output_q <- shiny::debounce(
millis = 200,
r = reactive({
obj <- data()
teal.reporter::teal_card(obj) <-
c(
teal.reporter::teal_card(obj),
teal.reporter::teal_card("## Module's output(s)")
)
obj <- teal.code::eval_code(obj, "library(dplyr)")

ANL <- obj[[dataname]]

teal::validate_has_data(ANL, min_nrow = 10, msg = sprintf("%s has not enough data", dataname))

teal::validate_inputs(iv())

validate(need(
input$arm_trt %in% ANL[[input$arm_var]] && input$arm_ref %in% ANL[[input$arm_var]],
"Treatment or Control not found in Arm Variable. Perhaps they have been filtered out?"
))

q1 <- obj %>%
teal.code::eval_code(
code = as.expression(c(
bquote(anl_labels <- formatters::var_labels(.(as.name(dataname)), fill = FALSE)),
bquote(
flags <- .(as.name(dataname)) %>%
select(all_of(.(input$flag_var_anl))) %>%
rename_at(vars(.(input$flag_var_anl)), function(x) paste0(x, ": ", anl_labels[x]))
)
))
)

teal.reporter::teal_card(q1) <- c(teal.reporter::teal_card(q1), "### Plot")

teal.code::eval_code(
q1,
code = as.expression(c(
bquote(
plot <- osprey::g_events_term_id(
term = flags,
id = .(as.name(dataname))[["USUBJID"]],
arm = .(as.name(dataname))[[.(input$arm_var)]],
arm_N = table(ADSL[[.(input$arm_var)]]),
ref = .(input$arm_ref),
trt = .(input$arm_trt),
diff_ci_method = .(input$diff_ci_method),
conf_level = .(input$conf_level),
axis_side = .(input$axis),
fontsize = .(font_size()),
draw = TRUE
)
)
))
)
})
)

plot_r <- reactive(output_q()[["plot"]])
set_chunk_dims(pws, output_q)
})
}
Loading
Loading