-
-
Notifications
You must be signed in to change notification settings - Fork 8
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
ddl alternative #167
ddl alternative #167
Conversation
#' as well as a function (`on_submit`) that will create the desired data sets. | ||
#' | ||
#' One of the inputs must be an action button (or action link) called "submit". | ||
#' When clicked, the `on_submit` function will be run. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' When clicked, the `on_submit` function will be run. | |
#' When clicked, `on_submit` function will run. |
#' `on_submit` must take one argument called `inputs`, | ||
#' which will be a list of all input elements defined in the UI function except `"submit"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' `on_submit` must take one argument called `inputs`, | |
#' which will be a list of all input elements defined in the UI function except `"submit"`. | |
#' `on_submit` takes one argument called `inputs`, | |
#' which is a list of all input elements defined in the UI function, except `"submit"`. |
#' | ||
#' `on_submit` must take one argument called `inputs`, | ||
#' which will be a list of all input elements defined in the UI function except `"submit"`. | ||
#' The function body must contain all code necessary to obtain the desired data sets and nothing else. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' The function body must contain all code necessary to obtain the desired data sets and nothing else. | |
#' `on_submit` body should contain the code necessary to obtain the desired datasets and nothing else. |
#' The obtained data sets will be packed into a `tdata` object. | ||
#' The body of `on_submit` will be recorded in the resulting `tdata`. | ||
#' | ||
#' The `mask` argument can be used to mask input values used as arguments in the recorded code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' The `mask` argument can be used to mask input values used as arguments in the recorded code. | |
#' The `mask` argument can be used to mask input values used as arguments in the recorded code. So that sensitive parts of code (like password or username) are substituted with `mask`ed values. |
#' | ||
#' The `mask` argument can be used to mask input values used as arguments in the recorded code. | ||
#' | ||
#' Input elements will be put in a div of class `connector-input`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' Input elements will be put in a div of class `connector-input`. | |
#' Input elements will be put in a `connector-input` div. |
#' | ||
#' @param ... any number of `shiny.tag`s | ||
#' @param on_submit function to run after clicking the "submit" button, see `Details` | ||
#' @param mask optional list specifying how to mask the code run by `on_submit`, see `Details` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' @param mask optional list specifying how to mask the code run by `on_submit`, see `Details` | |
#' @param mask an optional list specifying how to mask the code run by `on_submit`, see `Details` |
#' @param ... any number of `shiny.tag`s | ||
#' @param on_submit function to run after clicking the "submit" button, see `Details` | ||
#' @param mask optional list specifying how to mask the code run by `on_submit`, see `Details` | ||
#' @return A`reactive` expression returning a `tdata` object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' @return A`reactive` expression returning a `tdata` object. | |
#' @return A `reactive` expression returning a `tdata` object. |
submit_class <- submit[grep("class$", names(submit))] | ||
checkmate::assert_true( | ||
grepl("action-button", submit_class), | ||
.var.name = "The \"submit\" element has class \"action-button\"." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.var.name = "The \"submit\" element has class \"action-button\"." | |
.var.name = "The \"submit\" element class: \"action-button\"." |
Hey @chlebowa I can't give any feedback to the code, as it's extremely condensed, clean and contains a lot of advanced tricks that reduces the complexity of the implementation. As always, a solid thorough work! When it comes to the interface and user experience, I prefer this approach than #161 as I do see benefits of having everything in one place ( You have my axe! A small question: is there a way to use module <- input_template(
h5('Below is a consens statement, that you need to accept before using the app. Do you accept?')
actionButton("submit", "Accept"),
on_submit = function(input) {
# before on_submit data is NULL
data <- pull(input$data) # ??
}
) |
I just pushed a major overhaul of roxygen docs, which should have been there from the beginning but slipped my mind during the multiple rewrites this file has seen ober the last two weeks. Sorry for the inconvenience and thank you @m7pr for bringing it to my attention. |
# Convert calls to strings and substitute argument references by bquote references. | ||
code_strings <- vapply(code, deparse1, character(1L)) | ||
code_strings <- gsub("(input\\$)(\\w+)", "\\.(\\2\\)", code_strings) | ||
code_strings <- gsub("(input\\[\\[\")(\\w+)(\"\\]\\])", "\\.(\\2\\)", code_strings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chlebowa I would make sure those regexes work on all platforms. Looks like system specific?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know this is just R-specific, it looks a little weird because every slash has to be escaped. \w
and backtracing are widely recognized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coolio
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There may be some edge cases where users use purrr::plucK(input, "something")
or something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, get
, for instance. I'm not sure we have to worry about it, considering people mosty use $
.
Now that I think about it, some NSE tricks would probably go unnoticed 🤔
I think that issue is about a popup on the whole app, regardless of whether the data is remote or local, so |
Update: this version of I wonder what to do with errors raised by the user-provided code. Currently they are intercepted and passed to Requires |
env <- new.env() | ||
eval(as.expression(code_input), env) | ||
# Create `tdata` with masked code. | ||
new_tdata(as.list(env), code = as.expression(code_masked), keys = join_keys) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a way to change code_masked
when creating tdata
object
new_tdata(as.list(env), code = as.expression(code_masked), keys = join_keys) | |
new_tdata(as.list(env), code = as.expression(code_masked), keys = join_keys) |
There is a case where in the interactive session one can use login()
function which opens a dialog, while for shiny session authentication is handled by the module and no login()
function is used. It means that initial code_masked
looks like this
data <- connection_package::read_data(...)
and returned should look like:
connection_package::login()
data <- connection_package::read_data(...)
server <- function(id) { | ||
moduleServer(id, function(input, output, session) { | ||
result <- eventReactive(input[["submit"]], { | ||
inputs <- sapply(setdiff(inputIds, "submit"), function(x) input[[x]], simplify = FALSE) | ||
tryCatch( | ||
do.call(tracked_request, list(inputs)), | ||
error = function(e) validate(need(FALSE, sprintf("Error: %s", e$message))) | ||
) | ||
}) | ||
result | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a possibility to specify this server
. For example to control hierarchical inputs.
server <- function(id) { | |
moduleServer(id, function(input, output, session) { | |
result <- eventReactive(input[["submit"]], { | |
inputs <- sapply(setdiff(inputIds, "submit"), function(x) input[[x]], simplify = FALSE) | |
tryCatch( | |
do.call(tracked_request, list(inputs)), | |
error = function(e) validate(need(FALSE, sprintf("Error: %s", e$message))) | |
) | |
}) | |
result | |
}) | |
server <- function(id) { | |
moduleServer(id, function(input, output, session) { | |
result <- eventReactive(input[["submit"]], { | |
inputs <- sapply(setdiff(inputIds, "submit"), function(x) input[[x]], simplify = FALSE) | |
tryCatch( | |
do.call(tracked_request, list(inputs)), | |
error = function(e) validate(need(FALSE, sprintf("Error: %s", e$message))) | |
) | |
}) | |
result | |
}) |
Having just |
companion to [this PR](insightsengineering/teal.data#167)
ddl
implementation alternative to #161 .Complemented by this PR.
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:input_template
to create the module: enumerate input widgetson_submit
, to be run when"submit"
button is clicked; function takes input values wrapped in a list calledinput
and body refers to input values withinput$<value>
orinput[["<value>"]]
tdata
objectteal
(I don't like it)teal
; defaults to emptyteal.data::join_keys()
When inputs are submitted,
on_submit
is passed to a function that extracts the body, substitutesinput
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 inmask
(if any) and uses the environment and the masked code to createtdata
.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 createtdata
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 withglue
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
MODULE DEFINITION
AN APP
A TEAL APP