Skip to content

picks - #942

Merged
averissimo merged 211 commits into
mainfrom
redesign_extraction@main
Jul 31, 2026
Merged

picks#942
averissimo merged 211 commits into
mainfrom
redesign_extraction@main

Conversation

@gogonzo

@gogonzo gogonzo commented Oct 30, 2025

Copy link
Copy Markdown
Contributor

Closes insightsengineering/NEST-roadmap#36

Check also with:

In this PR, due to the fact that data_extract_spec is not 1:1 convertible to picks, I propose to have an S3 method in each tm_ to switch between data_extract_spec/picks depending on the input type. Eventually, data_extract_spec variant will be removed completely after deprecation period.

@github-actions

Copy link
Copy Markdown
Contributor


🎉 Thank you for your contribution! Before this PR can be accepted, we require that you all read and agree to our Contributor License Agreement.
You can digitally sign the CLA by posting a comment on this Pull Request in the format shown below. This agreement will apply to this PR as well as all future contributions on this repository.


I have read the CLA Document and I hereby sign the CLA


2 out of 3 committers have signed the CLA.
✅ (gogonzo)[https://github.com/gogonzo]
✅ (vedhav)[https://github.com/vedhav]
@Dawid Kaledkowski
Dawid Kaledkowski seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

llrs-roche added a commit that referenced this pull request Jul 21, 2026
# Pull Request

<!--- Replace `#nnn` with your issue link for reference. -->

Fixes #996


<img width="868" height="722" alt="image"
src="https://github.com/user-attachments/assets/529ef615-74f7-426e-b67a-b33618321fb4"
/>

I also renamed the test file to follow the standard naming convention
test-{module_name}.R
I don't see any obvious conflict with #942 as there isn't practically no
change on the module itself.
averissimo and others added 16 commits July 22, 2026 08:07
# Pull Request

- Fixes #999

### Changes description

- [x] Creates framework to use for `gt`-type data calls
- Implements modules 
    - [x] `tm_gtsummary` with `gtsummary` backend
    - [x] `tm_tbl_roche_summary`
    - [x] `tm_tbl_listing`
- [ ] Discuss solution
- [ ] Discuss removal of `.fun` argument in favor of static
implementation only
- [ ] Documentation _(pending technical discussion)_

### Technical details

Creates internal functions called `{tm/srv/ui}_gt_template` which
implement a very flexible approach to arbitrary named arguments (also
known as dots `...`).

Name dots are dynamically generated because there are no common
parameters between target functions.

- **`data_extract_spec`** (future `teal.picks::picks()`): Print them
dynamically on encoding panel. It will use data from `teal.data` object
- **`choices_selected`** (future `teal.picsk::variables()`: Print an
optional select input on ecoding panel
- Other named dots: Considered static arguments for target function and
not rendered in UI

This assumes that any module needs at least 2 implemented functions
`tm_<module_name>` `srv_<module_name>_partial` with an optional
`ui_<module_name>_partial`

The `partial` allows for custom logic and setting custom and optional
UI.

Note: no need of partial for `crane::tbl_listing` as it has no custom
logic. `tbl_summary` and `tbl_roche_summary` need as we're using radio
buttons and custom validation.

Below is an example of a partial for `gt_summary::tbl_summary` (must
return a qenv-like object to keep with `teal` practices)

```r
srv_gtsummary_partial <- function(id,
                                  data,
                                  .fun_quo,
                                  ...,
                                  summary_args_r) {
  moduleServer(id, function(input, output, session) {
    summary_args_processed <- reactive({
      tbl_summary_args <- req(summary_args_r()) # Arguments forwarded from the main server function (template)
      tbl_summary_args$missing <- input$missing # Additional argument from custom UI
      tbl_summary_args$percent <- input$percent # Additional argument from custom UI
      tbl_summary_args
    })

    validated_q <- reactive({ # Custom validation for gtsummary
      q <- req(data())
      summary_args <- req(summary_args_processed())
      validate(
        need(
          length(summary_args$include) != 0L && all(!summary_args$include %in% summary_args$by),
          "Variables to stratify with and variables to include should be different"
        ),
      )
      q
    })

    srv_gt_template_partial(
      id = id,
      data = validated_q,
      .fun_quo = .fun_quo,
      ...,
      summary_args_r = summary_args_processed
    )
  })
}
```

### Example app

Note:

1. Last module uses internal function that works just the same as
`tm_gtsummary` (without validation)
    - Shows how flexible this implementation is
1. requires `teal.widgets@999-tm_gtsummary` branch for `tbl_listing`
module to work _(it splits the table in 2)_

```r
pkgload::load_all("../teal.widgets")
pkgload::load_all("../teal.modules.general")

add_overall_decorator <- function(col_label = "**Overall**  \nN = {style_number(N)}",
                                  statistic = NULL,
                                  digits = NULL,
                                  last = TRUE,
                                  enable = TRUE) {
  teal_transform_module(
    label = "Overall",
    ui = function(id) {
      ns <- NS(id)
      tags$div(
        bslib::input_switch(
          ns("enable"),
          label = "Enable it?",
          value = enable
        ),
        bslib::input_switch(
          ns("last"),
          label = "Show as last column",
          value = last
        )
      )
    },
    server = function(id, data) {
      moduleServer(id, function(input, output, session) {
        teal.logger::log_shiny_input_changes(input, namespace = "teal.modules.gtsummary")
        observeEvent(input$enable, {
          shinyjs::toggleState("last", input$enable)
        })
        reactive({
          if (!input$enable) {
            data()
          } else {
            q <- within(data(),
              {
                table <- gtsummary::add_overall(table,
                  col_label = col_label,
                  statistic = statistic, last = last,
                  digits = digits
                )
              },
              last = input$last,
              statistic = rlang::enexpr(statistic),
              digits = rlang::enexpr(digits),
              col_label = col_label
            )
            validate_qenv(q)
            q
          }
        })
      })
    }
  )
}

data <- within(teal.data::teal_data(), {
  ADSL <- teal.data::rADSL
  ADTTE <- teal.data::rADTTE
})
join_keys(data) <- default_cdisc_join_keys[names(data)]
app <- init(
  data = data,
  modules = modules(
    tm_tbl_roche_summary(label = "Roche Summary (no arguments)"),
    tm_tbl_summary(label = "GT Summary (no arguments)"),
    tm_tbl_roche_summary(
      label = "Roche Summary",
      by = teal.picks::picks(
        datasets(c("ADSL", "ADTTE"), "ADTTE"),
        variables(c("SEX", "COUNTRY", "SITEID", "ACTARM", "CNSR", "PARAMCD"), "SEX")
      ),
      include = teal.picks::picks(
        datasets(c("ADSL", "ADTTE"), "ADSL"),
        variables(c("SITEID", "COUNTRY", "ACTARM", "SEX"), "SITEID", multiple = TRUE)
      ),
      decorators = list(table = add_overall_decorator())
    ),
    tm_tbl_summary(
      label = "GT Summary",
      by = teal.picks::picks(
        datasets(c("ADSL", "ADTTE"), "ADSL"),
        variables(c("SEX", "COUNTRY", "SITEID", "ACTARM"), "SEX")
      ),
      include = teal.picks::picks(
        datasets(c("ADSL", "ADTTE"), "ADSL"),
        variables(c("SITEID", "COUNTRY", "ACTARM"), "SITEID", multiple = TRUE)
      )
    ),
    tm_tbl_listing(
      label = "Table Listing", 
      dataname = "ADSL",
      decorators = list(listing = add_overall_decorator())
    ),
    tm_gt_template(
      label = "GT Template (with gtsumary::tbl_summary)",
      .fun = gtsummary::tbl_summary,
      by = teal.picks::picks(
        datasets("ADSL", "ADSL"),
        variables(c("SEX", "COUNTRY", "SITEID", "ACTARM"))
      ),
      include = teal.picks::picks(
        datasets("ADSL", "ADSL"),
        variables(c("SITEID", "COUNTRY", "ACTARM"), "SITEID", multiple = TRUE)
      ),
      missing = teal.picks::values(c("no", "ifany", "always"), "ifany", multiple = FALSE),
      percent = teal.picks::values(c("column", "row", "cell"), "column", multiple = FALSE)
    )
  )
) |> shiny::runApp()
```

---------

Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dony Unardi <donyunardi@gmail.com>
Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: André Veríssimo <211358+averissimo@users.noreply.github.com>
@donyunardi donyunardi mentioned this pull request Jul 30, 2026
32 tasks

@averissimo averissimo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳 🔥

@averissimo
averissimo enabled auto-merge (squash) July 31, 2026 12:54
@averissimo
averissimo merged commit 4eea812 into main Jul 31, 2026
25 of 27 checks passed
@averissimo
averissimo deleted the redesign_extraction@main branch July 31, 2026 13:08
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design data extract and data merge

5 participants