Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #161

Closed
wants to merge 24 commits into from
Closed

Refactor #161

wants to merge 24 commits into from

Conversation

gogonzo
Copy link
Contributor

@gogonzo gogonzo commented Aug 15, 2023

Part of insightsengineering/teal#937
Other PRs insightsengineering/teal#899 insightsengineering/teal.slice#440

install

Installation code
staged.dependencies::dependency_table(
  project = "insightsengineering/teal@https://github.com",
  project_type = "repo@host",
  ref = "refactor",
  verbose = 1
) |> staged.dependencies::install_deps()

ddl class

unfold

Small class which contains ui and server. server calls run_ddl which evaluates a code using input and create teal_data object. Function ddl accepts several arguments which are used to generate proper ui and server.

tdata-DDL process (3)

Above diagram depict how class works. Please follow steps below and look at the diagram.

  1. One needs to specify ddl arguments to fit someone's needs. Take for example custom_connector. Let's assume that we need to call scda::synthetic_cdisc_data('latest') for two datasets.
data_code = paste(
    "adsl <- scda::synthetic_cdisc_data('latest')$adsl",
    "adtte<- scda::synthetic_cdisc_data('version')$adtte",
    collapse = "\n"
  )
)

Imagine that above datasets are on the remote server so we need to call open_conn function and close after pull. Imagine that open_conn and close_conn are in some external package.

code = paste(c(
  "open_conn(username = input$username, password = input$password)",
  "adsl <- scda::synthetic_cdisc_data('latest')$adsl",
  "adtte<- scda::synthetic_cdisc_data('version')$adtte",
  "close_conn()"
  ),
  collapse = "\n"
)

username and password should be specified by the app user. To do so we need to create ui which will create username and password inputs which are used during evaluation of above code.

ui = function(id) {
  ns <- NS(id)
  tagList(
    textInput(ns("username"), label = "Username"),
      passwordInput(ns("password"), label = "Password"),
      actionButton(ns("submit"), label = "Submit")
  )
}

If we have ui we need also server to utilize input. When submit button is clicked all input values are passed to ddl_run where all magic happens. ddl_run executes the code and substitutes all input$<name> with equivalent input_list element.

submit_button_server <- function(id) {
  moduleServer(id, function(input, output, session) {
    tdata <- eventReactive(input$submit, {
      req(input$pass)
      ddl_run(input_list = reactiveValuesToList(input))
    })

    # would need to make sure we handle reactivity correctly here as teal::init expects not reactive tdata...
    return(tdata)
  })
}

Because user username and password is not something we would like to include in the show R code. We need to replace them with some other code which can be run by anyone who receives the code. To replace args with some desired value or call we use offline_args to substitute code with these.

offline_args = list(
  username = quote(askpass::askpass("Please enter username")),
  password = quote(askpass::askpass("Please enter password"))
)  

At this point ddl_run evaluated "online" code, and has environment with the results of the evaluation. Also "offline" code is available after substitution. At the end teal_data_new is constructed from data and offline code

teal_data_new(env_list, code = masked_code, join_keys)

⚠️ As one can see ddl_run require only input_list. It is a simplification for potential app developer who doesn't need to handle additional arguments (code, input_mask) in the server. Consequence of this simplification is that server specified in ddl() have an enclosing environment changed to the environment where ddl_run, code, input_mask is available. Another consequence is that ddl_run theoretically doesn't exist anywhere - it is created on the fly when ddl object is created. It means that developing own ddl connector in external "pkg" can't use private functions from that "pkg".

Example connector app

R code
custom_password_connector <- function(datanames) {
  ddl(
    # code to be run when app user presses submit
    code = paste(
      c(
        "conn <- open_conn(username = input$username, password = input$password)",
        sprintf(
          "%1$s <- scda::synthetic_cdisc_data('latest')$%1$s",
          datanames
        ),
        "close_conn()"
      ),
      collapse = "\n"
    ),

    # arguments used for show R code
    mask_args = list(
      username = quote(askpass::askpass()),
      password = quote(askpass::askpass())
    ),

    # ui they wish to use for the loading data
    ui = function(id) {
      ns <- NS(id)
      tagList(
        textInput(ns("username"), label = "Username"),
        passwordInput(ns("password"), label = "Password"),
        actionButton(ns("submit"), label = "Submit")
      )
    },
    datanames = datanames
  )
}

pkgload::load_all("teal.data")
pkgload::load_all("teal")
library(shiny)

x <- custom_password_connector(c("adsl", "adtte", "adrs"))


app <- shinyApp(
  ui = fluidPage(
    fluidRow(
      column(3, h1("User Inputs"), x$ui(id = "custom_ui")),
      column(9, h1("R code"), verbatimTextOutput("output"))
    )
  ),
  server = function(input, output, session) {
    loaded_data <- x$server(id = "custom_ui", x = x)
    output$output <- renderText({
      req(loaded_data())
      teal.code::get_code(loaded_data()) |> paste(collapse = "\n")
    })
  }
)

shiny::runApp(app)

Example teal app with connector

teal app code

use teal.data, teal and teal.slice from refactor branch

# options(teal.log_level = "TRACE", teal.show_js_log = TRUE)
options("teal.bs_theme" = bslib::bs_theme(version = 3))

library(shiny)
library(scda)
library(scda.2022)
library(teal.data)
library(teal.transform)
pkgload::load_all("teal.data")
pkgload::load_all("teal.slice")
pkgload::load_all("teal")

library(teal.modules.general)

funny_module <- function(label = "Filter states", datanames = "all") {
  checkmate::assert_string(label)
  module(
    label = label,
    datanames = datanames,
    ui = function(id, ...) {
      ns <- NS(id)
      div(
        h2("The following filter calls are generated:"),
        verbatimTextOutput(ns("filter_states")),
        verbatimTextOutput(ns("filter_calls")),
        actionButton(ns("reset"), "reset_to_default")
      )
    },
    server = function(input, output, session, data, filter_panel_api) {
      checkmate::assert_class(data, "tdata")
      observeEvent(input$reset, set_filter_state(filter_panel_api, default_filters))
      output$filter_states <- renderPrint({
        logger::log_trace("rendering text1")
        filter_panel_api %>% get_filter_state()
      })
      output$filter_calls <- renderText({
        logger::log_trace("rendering text2")
        attr(data, "code")()
      })
    }
  )
}

custom_password_connector <- function(datanames) {
  ddl(
    # code to be run when app user presses submit
    code = paste(
      c(
        "conn <- open_conn(username = input$username, password = input$password)",
        sprintf(
          "%1$s <- scda::synthetic_cdisc_data('latest')$%1$s",
          datanames
        ),
        "close_conn()"
      ),
      collapse = "\n"
    ),

    # arguments used for show R code
    mask_args = list(
      username = quote(askpass::askpass()),
      password = quote(askpass::askpass())
    ),

    # ui they wish to use for the loading data
    ui = function(id) {
      ns <- NS(id)
      tagList(
        textInput(ns("username"), label = "Username"),
        passwordInput(ns("password"), label = "Password"),
        actionButton(ns("submit"), label = "Submit")
      )
    },
    datanames = datanames
  )
}
data <- custom_password_connector(c("adsl", "adtte", "adrs"))

app <- init(
  data = data,
  modules = modules(
    funny_module("funny"),
    funny_module("funny2", datanames = "adtte") # will limit datanames to ADTTE and ADSL (parent)
  )
)

runApp(app)

teal app with teal_data

App code
# options(teal.log_level = "TRACE", teal.show_js_log = TRUE)
options("teal.bs_theme" = bslib::bs_theme(version = 3))

library(shiny)
library(scda)
library(scda.2022)
library(teal.data)
library(teal.transform)
pkgload::load_all("teal.data")
pkgload::load_all("teal.slice")
pkgload::load_all("teal")
library(teal.modules.general)

funny_module <- function(label = "Filter states", datanames = "all") {
  checkmate::assert_string(label)
  module(
    label = label,
    datanames = datanames,
    ui = function(id, ...) {
      ns <- NS(id)
      div(
        h2("The following filter calls are generated:"),
        verbatimTextOutput(ns("filter_states")),
        verbatimTextOutput(ns("filter_calls")),
        actionButton(ns("reset"), "reset_to_default")
      )
    },
    server = function(input, output, session, data, filter_panel_api) {
      checkmate::assert_class(data, "tdata")
      observeEvent(input$reset, set_filter_state(filter_panel_api, default_filters))
      output$filter_states <- renderPrint({
        logger::log_trace("rendering text1")
        filter_panel_api %>% get_filter_state()
      })
      output$filter_calls <- renderText({
        logger::log_trace("rendering text2")
        attr(data, "code")()
      })
    }
  )
}

data <- cdisc_data(
  ADSL = synthetic_cdisc_data("latest")$adsl,
  ADTTE = synthetic_cdisc_data("latest")$adtte,
  ADRS = synthetic_cdisc_data("latest")$adrs,
  code = '
    ADSL <- synthetic_cdisc_data("latest")$adsl
    ADTTE <- synthetic_cdisc_data("latest")$adtte
    ADRS <- synthetic_cdisc_data("latest")$adrs
  '
)

app <- init(
  data = data,
  modules = modules(
    funny_module("funny"),
    funny_module("funny2", datanames = "ADTTE") # will limit datanames to ADTTE and ADSL (parent)
  )
)

runApp(app)

teal.gallery examples

Internal Connectors

insightsengineering/nestdevs-tasks#38

What's next

To complete insightsengineering/teal#937 we need to do following:

Match arguments across all reproducible objects/classes (ddl, qenv, teal_data):

Other:

@gogonzo gogonzo added the core label Aug 15, 2023
@gogonzo gogonzo mentioned this pull request Aug 15, 2023
@gogonzo gogonzo marked this pull request as draft August 16, 2023 06:48
@m7pr
Copy link
Contributor

m7pr commented Aug 23, 2023

Hi, I'm getting below, when I run Example connector app or Example teal app with connector

Listening on http://127.0.0.1:7985
Warning in ddl_run(x = x, online_args = reactiveValuesToList(input)) :
  DDL code returned NULL. Returning empty tdata object
Warning in (function (..., join_keys = teal.data::join_keys(), code = "",  :
  Using TealDatasetConnector and TealDataset is deprecated, please just include data directly.
Warning: Error in <-: attempt to set an attribute on NULL

Had issues installing staged.dependencies openpharma/staged.dependencies#189
but after a 5th execution of the same code I managed to install it properly.

image

Result of staged.dependencies execution
x <-
  +   staged.dependencies::dependency_table(
    +        project = "insightsengineering/teal@https://github.com",
    +        project_type = "repo@host",
    +        ref = "refactor",
    +        verbose = 1
    +      )
fetch https://github.com/insightsengineering/teal.git in cache directory...
- checkout branch origin/refactor in cache directory...
fetch https://github.com/insightsengineering/teal.widgets.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.code.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.data.git in cache directory...
- checkout branch origin/refactor in cache directory...
fetch https://github.com/insightsengineering/teal.slice.git in cache directory...
- checkout branch origin/refactor in cache directory...
fetch https://github.com/insightsengineering/teal.transform.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.logger.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.reporter.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.modules.general.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.modules.clinical.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.osprey.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.goshawk.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.modules.hermes.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.modules.helios.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/rtables.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/nestcolor.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/tern.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/tern.mmrm.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/tern.gee.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/formatters.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/osprey.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/goshawk.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/hermes.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/helios.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/tern.rbmi.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/chevron.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/citril.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/scda.test.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/biomarker-catalog.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/tlg-catalog.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/rlistings.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/dunlin.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/citril.metadata.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/scda.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/scda.2022.git in cache directory...
- checkout branch origin/main in cache directory...
fetch https://github.com/insightsengineering/teal.devel.git in cache directory...
- checkout branch origin/main in cache directory...
Current cache directory: C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache
1 
2 
3 
4 
5 
6 
7 
8 
9 
> x |> staged.dependencies::install_deps()
Processing packages in order: formatters, rtables, teal.widgets, teal.code, teal.logger, teal.data, teal.slice, teal.transform, teal.reporter, teal
* installing *source* package 'formatters' ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** inst
** byte-compile and prepare package for lazy loading
Creating a generic function for 'ncol' from package 'base' in package 'formatters'
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (formatters)
* installing *source* package 'rtables' ...
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
Creating a generic function for 'nrow' from package 'base' in package 'rtables'
Creating a generic function for 'rbind' from package 'base' in package 'rtables'
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (rtables)
* installing *source* package 'teal.widgets' ...
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (teal.widgets)
Skipping installation of C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.code_f5c7ee90093dd244b11b1acde2d5098a since same commit sha already installed
Skipping installation of C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.logger_db3be93ac2413ef16521b0cf8fe202ad since same commit sha already installed
* installing *source* package 'teal.data' ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** inst
** byte-compile and prepare package for lazy loading
Warning message:
  package 'shiny' was built under R version 4.3.1 
Warning: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data'
Warning in lazyLoadDBinsertValue(data, datafile, ascii, compress, envhook) :
  'package:stats' may not be available when loading
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
Warning: package 'shiny' was built under R version 4.3.1
Warning: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data'
** testing if installed package can be loaded from final location
Warning: package 'shiny' was built under R version 4.3.1
Warning: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data'
** testing if installed package keeps a record of temporary installation path
* DONE (teal.data)
* installing *source* package 'teal.slice' ...
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
Warning message:
  package 'shiny' was built under R version 4.3.1 
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
Warning: package 'shiny' was built under R version 4.3.1
Warning: S3 method 'init_filtered_data.TealData' was declared in NAMESPACE but not found
** testing if installed package can be loaded from final location
Warning: package 'shiny' was built under R version 4.3.1
Warning: S3 method 'init_filtered_data.TealData' was declared in NAMESPACE but not found
** testing if installed package keeps a record of temporary installation path
* DONE (teal.slice)
* installing *source* package 'teal.transform' ...
** using staged installation
** R
** data
*** moving datasets to lazyload DB
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (teal.transform)
* installing *source* package 'teal.reporter' ...
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (teal.reporter)
* installing *source* package 'teal' ...
** using staged installation
** R
** inst
** byte-compile and prepare package for lazy loading
Warning messages:
  1: package 'shiny' was built under R version 4.3.1 
2: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data' 
3: S3 method 'init_filtered_data.TealData' was declared in NAMESPACE but not found 
** help
*** installing help indices
*** copying figures
** building package indices
** installing vignettes
** testing if installed package can be loaded from temporary location
Warning: package 'shiny' was built under R version 4.3.1
Warning: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data'
Warning: S3 method 'init_filtered_data.TealData' was declared in NAMESPACE but not found
** testing if installed package can be loaded from final location
Warning: package 'shiny' was built under R version 4.3.1
Warning: replacing previous import 'teal.code::show' by 'shinyjs::show' when loading 'teal.data'
Warning: S3 method 'init_filtered_data.TealData' was declared in NAMESPACE but not found
** testing if installed package keeps a record of temporary installation path
* DONE (teal)
Processed packages in order: formatters, rtables, teal.widgets, teal.code, teal.logger, teal.data, teal.slice, teal.transform, teal.reporter, teal
package_name
1      formatters
2         rtables
3    teal.widgets
4       teal.code
5     teal.logger
6       teal.data
7      teal.slice
8  teal.transform
9   teal.reporter
10           teal
cache_dir
1      C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_formatters_2af660375fccd6c369329f665710ecae
2         C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_rtables_f0f6291eb2b1f6976bb05ff26dfa212d
3    C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.widgets_8f92a3d7b00ae5fadadf0ffc4492c7f2
4       C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.code_f5c7ee90093dd244b11b1acde2d5098a
5     C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.logger_db3be93ac2413ef16521b0cf8fe202ad
6       C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.data_39f26acbb991125a81ff5392c742cf86
7      C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.slice_69fabf5d920c35ab0e950fac1a49b372
8  C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.transform_7dbb21e492260b8f5bd20354278faa2c
9   C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal.reporter_2c5a990a10b0c9b8f7bccf13bd04db2a
10           C:/Users/kosinsm4/Documents/.staged.dependencies/packages_cache/insightsengineering_teal_e38093f352c184cf676b1dd47551848c
actions     sha installable
1  install ebd84a1        TRUE
2  install e751f69        TRUE
3  install b4aae30        TRUE
4  install 760b73f        TRUE
5  install 203c910        TRUE
6  install 212806c        TRUE
7  install 450ddc0        TRUE
8  install 64b0bb9        TRUE
9  install d514cf3        TRUE
10 install e54a865        TRUE

@gogonzo
Copy link
Contributor Author

gogonzo commented Sep 6, 2023

@m7pr

I'm getting below, when I run Example connector app or Example teal app with connector

Yes, you typed any password. Message says that password is pass ;)

image

@m7pr
Copy link
Contributor

m7pr commented Sep 6, 2023

So it looks like it did the job, but it thrown some other unrelated error that confused me?

@averissimo
Copy link
Contributor

averissimo commented Sep 6, 2023

@m7pr I needed to add pkgload::load_all("teal.slice") to the examples for them to work

R/cdisc_data.R Outdated Show resolved Hide resolved
R/teal_data.R Outdated Show resolved Hide resolved
@m7pr
Copy link
Contributor

m7pr commented Sep 7, 2023

I did checkout to refactor branch locally for teal, teal.slice and teal.data made pulls and build those packages locally. I thought staged.dependencies would do that for me. I actually think that pkgload::load_all('teal') was loading from improper branch on my local machine.

I was able to run the Example connector app
image

and Example teal app with connector
image

Comment on lines +140 to +142
if (!checkmate::test_names(names(data_objects), type = "named")) {
stop("Dot (`...`) arguments on `teal_data()` must be named.")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (!checkmate::test_names(names(data_objects), type = "named")) {
stop("Dot (`...`) arguments on `teal_data()` must be named.")
}
checkmate::assert_names(names(data_objects), .var.name = "dot arguments (`...`) in `teal_data()`")

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah great!

The function I used before is the soon-to-be-deprecated assert_named() which had a non-descriptive error message (failed: Must have Object.), but this is much cleaner :-)

@vedhav vedhav self-requested a review September 11, 2023 12:11
Copy link
Contributor

@vedhav vedhav left a comment

Choose a reason for hiding this comment

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

I know it's still in the draft and looks like there do not seem to be major changes expected to the PR.

Can we update the _pkgdown.yml with the new exported functions and add some vignettes to explain the new functions?

@chlebowa chlebowa mentioned this pull request Sep 18, 2023
Copy link
Contributor

Choose a reason for hiding this comment

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

The only use of this function that I found is in teal::init, which on this branch looks like this:

  if (!inherits(data, c("TealData", "tdata", "ddl"))) {
    data <- teal.data::to_relational_data(data = data)
  }

Methods for TealData and tdata are defined but not called.

Furthermore, the conversion is followed by an assertion:

checkmate::assert_multi_class(data, c("TealData", "tdata", "ddl"))

I don't know why a method for ddl is not defined, but assuming this is an oversight, I think it would be better to define to_relational_data.default that will raise an error on the lines of "don't know how to handle data of class <class>" and remove the assertion from init.

Since we are trying to simplify things, I would be partial to init only accepting tdata, actually.

R/tdata.R Outdated
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like the argument being named env it it only accepts a list.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like teal_data returns tdata now, not TealData. Is the first part (if (checkmate::test_list(data_objects, types = c("TealDataConnector", "TealDataset", "TealDatasetConnector")))) is only for deprecation purposes? If so, there should be a warning there.

R/ddl.R Outdated Show resolved Hide resolved
R/ddl.R Outdated Show resolved Hide resolved
R/tdata.R Outdated Show resolved Hide resolved
R/tdata.R Outdated Show resolved Hide resolved
R/is_pulled.R Outdated Show resolved Hide resolved
gogonzo and others added 10 commits September 28, 2023 06:59
`ddl` implementation alternative to #161 .
Complemented by [this
PR](insightsengineering/teal#922).

In order to simplify the user (app dev) experience, I tried to
streamline the logic.

In order to create a `ddl` connector module, one has to:
1. use `input_template` to create the module: enumerate input widgets
2. provide a function, `on_submit`, to be run when `"submit"` button is
clicked; function takes input values wrapped in a list called `input`
and body refers to input values with `input$<value>` or
`input[["<value>"]]`
3. optionally provide mask for input values that will be used in code of
resulting `tdata` object
4. specify names of data sets for compatibility with `teal` (I don't
like it)
5. optionally specify join keys as one would previously, for
compatibility with `teal`; defaults to empty `teal.data::join_keys()`

When inputs are submitted, `on_submit` is passed to a function that
extracts the body, substitutes `input` placeholders with input values
and evaluates the code to obtain data sets in a separate environment.
Then it replaces the input values in the code with ones provided in
`mask` (if any) and uses the environment and the masked code to create
`tdata`.

Much like in the solution proposed on branch `refactor`, the user
provides code to obtain data sets and replacements for input values, and
data is created in separate environment, which is then used to create
`tdata` with masked code.

Unlike that solution, the user specifies everything in one place, rather
than having to define module ui, module server that runs a
post-processing function, the post-processing function itself, etc. This
is easier to understand **for me**.
Another difference is that the user provides code as code with `input$`
references, not text with `glue` syntax (`{ value }`). This is done move
focus to masking rather than have the user think about "online" and
"offline" arguments. It also uses pure base R.


#### MOCK DATABASE CONNECTION
```
pullme <- function(username, password) {
  if (username == "user" && password == "pass") {
    message("connection established")
  } else {
    stop("invalid credentials")
  }
}
closeme <- function() {
  message("connection closed")
}
```
#### MODULE DEFINITION
```
library(shiny)

thefun <- function(input) {
  on.exit(try(closeme()))
  pullme(username = input$user, password = input$pass)
  adsl <- scda::synthetic_cdisc_data('latest')$adsl
  adtte <- scda::synthetic_cdisc_data('latest')$adtte
}
themask <- list(
  user = quote(askpass("who are you?")),
  pass = quote(askpass("password please"))
)
module <- input_template(
  on_submit = thefun,
  mask = themask,
  datanames = c("adsl", "adtte"),
  textInput("user", "username", value = "user", placeholder = "who goes there?"),
  passwordInput("pass", "password", value = "pass", placeholder = "friend or foe?"),
  actionButton("submit", "get it")
)
```
#### AN APP
```
devtools::load_all("../teal.slice")
devtools::load_all("../teal")
devtools::load_all(".")

ui <- fluidPage(
  tagList(
    module$ui("id"),
    uiOutput("val")
  )
)
server <- function(input, output, session) {
  tdata <- module$server("id")
  output[["value"]] <- renderPrint({
    tdata()
  })
  output[["code"]] <- renderPrint({
    cat(teal.code::get_code(tdata()), sep = "\n")
  })
  output[["val"]] <- renderUI({
    req(tdata())
    tagList(
      verbatimTextOutput("value"),
      verbatimTextOutput("code")
    )
  })
}
if (interactive()) shinyApp(ui, server)
```


#### A TEAL APP
```
funny_module <- function (label = "Filter states", datanames = "all") {
  checkmate::assert_string(label)
  module(
    label = label,
    datanames = datanames,
    ui = function(id, ...) {
      ns <- NS(id)
      div(
        h2("The following filter calls are generated:"),
        verbatimTextOutput(ns("filter_states")),
        verbatimTextOutput(ns("filter_calls")),
        actionButton(ns("reset"), "reset_to_default")
      )
    },
    server = function(input, output, session, data, filter_panel_api) {
      checkmate::assert_class(data, "tdata")
      observeEvent(input$reset, set_filter_state(filter_panel_api, default_filters))
      output$filter_states <-  renderPrint({
        logger::log_trace("rendering text1")
        filter_panel_api %>% get_filter_state()
      })
      output$filter_calls <- renderText({
        logger::log_trace("rendering text2")
        attr(data, "code")()
      })
    }
  )
}

devtools::load_all("../teal.slice")
devtools::load_all("../teal")
devtools::load_all(".")

app <- init(
  data = module,
  modules = modules(
    funny_module("funny1"),
    funny_module("funny2", datanames = "adtte") # will limit datanames to ADTTE and ADSL (parent)
  )
)
shinyApp(app$ui, app$server)
```
@gogonzo
Copy link
Contributor Author

gogonzo commented Oct 4, 2023

Closing this PR in favour of #171

@gogonzo gogonzo closed this Oct 4, 2023
@gogonzo gogonzo deleted the refactor branch October 4, 2023 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants