I've been exploring (**and loving) the golem package for developing modular dashboards with R Shiny. But I'm struggling to wrap my head around how to test a modular dashboard.
For example in the repex below how would I test that if the input$n_rows in the import module is set to 15 that the output in the display module contains 15 rows?
I'd be incredibly grateful for any support on this!
library(shiny)
library(reactable)
library(dplyr)
# Import module UI
mod_import_ui <- function(id){
ns <- NS(id)
fluidRow(
# Allow the user to select the number of rows to view
numericInput(ns("n_rows"),
"Select number of observations",
value = 10)
)
}
# Import module Server
mod_import_server <- function(id){
moduleServer(
id,
function(input, output, session){
data <- reactive({
# Sample the requested number of rows from mtcars and return this to the application server
mtcars %>%
slice_sample(n = input$n_rows)
# [....] # Some complex formatting and transformations
})
return(data)
}
)}
# Display module UI
mod_display_ui <- function(id){
ns <- NS(id)
fluidRow(
reactableOutput(ns("table"))
)
}
# Display module Server
mod_display_server <- function(id, data_in){
moduleServer(
id,
function(input, output, session){
# [....] # Some more transformations and merging with data from other modules
output$table <- renderReactable(reactable(data_in()))
}
)}
app_ui <- function(request) {
tagList(
mod_import_ui("import_1"),
mod_display_ui("display_1")
)
}
app_server <- function(input, output, session) {
data_in <- mod_import_server("import_1")
mod_display_server("display_1", data_in)
}
shinyApp(ui = app_ui, server = app_server)
I would recommend separating the core of the app from the user interface.
The {golem} framework allows building your application inside a R package, which means you can use all tools from package build to document and test your code.
If you follow our guide in engineering-shiny.org/, you will see that we recommend extracting all R code from your "server" part to test it in a vignette, transform it as a regular function, so that you can test it as usual with R packages.
Hence, you ShinyApp only calls internal functions, already documented and tested. With this approach, you can test outputs of different scenarios that can happen in your application. Try different input parameters in a static script and verify outputs, whatever you change in your app in the next steps of your development.
The book gives a lot of advices. If I had to sum them up for a workflow, this would be:
Build the necessary code in an Rmd directly. This allows you to test the operation without having to go through all the necessary clicks. We call it the "Rmd first" method: https://rtask.thinkr.fr/when-development-starts-with-documentation/
Factorize this code into R functions to put as little as possible in the Shiny app itself.
Create your UI part without server (or not too much), just to see what the general appearance looks like
Include your functions in the appropriate places in the app.
Strengthen the code. Reproducible examples, unit tests, documentation, code versioning, ... (This step is better when done in parallel with the code)
As a complement to Sebastien's answer, I would like to point that starting with {shiny} v 1.5.0, you can test server functions directly using the testServer function, which might be what you are looking for.
Here is a reprex of how you can achieve that:
library(shiny)
library(magrittr)
library(dplyr)
mod_import_server <- function(id){
moduleServer( id, function(input, output, session){
data <- reactive({
mtcars %>%
slice(n = 1:input$n_rows)
})
return(data)
})
}
shiny::testServer(mod_import_server, {
for (i in 1:10){
session$setInputs(n_rows = i)
testthat::expect_equal(
data(),
slice(mtcars, n = 1:i)
)
}
})
Here, you can test that your reactive() behave the way you're expecting.
That's not perfect but a good start :)
It's hard to find a way to test for the behaviour of the second module though, as it depends on a reactive() passed as a value.
Colin
Related
I'm trying to plot the outcomes of EDA on to Shiny App, I have been using DataExplorer library for the same and i'm able to perform operations on rmarkdown notebook. I was thinking to integrate the plots to shiny app using the below code but i'm running into errors,Can you please assist me in this regard and also suggest me if there is a way possible to achieve this.
UI part
library(shiny)
library(DataExplorer)
fluidRow(width=12,
column(12,plotOutput("struct"))
)
Server block
df<-read.csv("/path/to/csv/file.csv")
output$struct<-renderPlot({
req(df)
plot_str(df)
})
Thanks for the help in advance
DataExplorer::plot_str by default prints a networkD3::diagonalNetwork, however, it returns a list.
If you want to render the diagonalNetwork object in shiny you'll need to use networkD3::renderDiagonalNetwork. Please check the following:
library(shiny)
library(DataExplorer)
library(datasets)
library(networkD3)
# DF <- read.csv("/path/to/csv/file.csv")
DF <- mtcars
ui <- fluidPage(
fluidRow(column(12, diagonalNetworkOutput("struct")))
)
server <- function(input, output, session) {
output$struct <- renderDiagonalNetwork({
req(DF)
diagonalNetwork(plot_str(DF, print_network = FALSE))
})
}
shinyApp(ui, server)
I know that using something like this code:
library(shiny)
ui <- fluidPage(
mainPanel(
textOutput("Query_String")
)
)
server <- function(input, output, session) {
observeEvent(session$clientData$url_search,{
Query <- session$clientData$url_search
output$Query_String <- renderText(Query)
# Long list of operations dependant on the parameters passed in the URL
})
}
shinyApp(ui = ui, server = server)
Can enable me to read parameters into my shiny app via URL queries. However it seems like I basically have to wrap my whole server content into one big observe() or observeEvent() to create the needed reactive context.
Is there any way to avoid this?
To avoid recalculating everything if just a single item changed, one wants to create multiple separate observes instead of just a big one. Long code should be refactored in functions e.g. process_query keeping the server function small, allowing to read the overall structure. Important intermediate results can be refactored in their own reactive values (here query). output$Query_String doesn't need to be nested in another reactive context.
process_query <- function(url) {
# many steps to process the url
return(url)
}
server <- function(input, output, session) {
query <- reactive(process_query(session$clientData$url_search))
output$Query_String <- renderText(query())
}
I'm trying to debug my Shiny app and would like to view a reactive dataframe with e.g. glimpse(df).
Initially I tried to create a breakpoint and then view the environment by my reactive df is a value not an object when used inside server.r. I also tried browser() but was not sure what it would do.
I did some searching on SO and tried various things using sink(), renderPrint() but had no success.
How can I print the contents of glimpse(some_reactive_df()) to the console when I run my app?
Calling print() from within the reactive({}) expression will do that.
library(shiny)
library(dplyr)
shinyApp(
ui <- fluidPage(
selectizeInput("cyl_select", "Choose ya mtcars$cyl ", choices = unique(mtcars$cyl)),
tableOutput("checker") # a needed output in ui.R, doesn't have to be table
),
server <- function(input, output) {
d <- reactive({
d <- dplyr::filter(mtcars, cyl %in% input$cyl_select)
print(glimpse(d)) # print from within
return(d)
})
output$checker <- renderTable({
glimpse(d()) # something that relies on the reactive, same thing here for simplicty
})
})
Assuming you provide Shiny a reason to run (and re-run) your reactive of interest, by having it be involved with a rendering in server() and linked output in ui(). This is usually the case for my debugging scenarios but it won't work unless the reactive is being used elsewhere in app.R.
I want to create a shiny application that has an input for writing some R function or Command, reads it through the ui.R then passes it to the server.R that executes that R command to display the results.
I spent hours searching about some example but couldn't find anything, I already know how to create Shiny apps using ui and server and pass the input values to server and work with them, but I have no idea if it's possible to create a shiny app like R where you can write the commands and return the results, any example or help would be appreciated.
Letting users run code in your app is bad practice, since it comes with great security risks. However, for development you might want to check this function from the shinyjs package by Dean Attali.
Example from the link:
library(shiny)
library(shinyjs)
shinyApp(
ui = fluidPage(
useShinyjs(), # Set up shinyjs
runcodeUI(code = "shinyjs::alert('Hello!')")
),
server = function(input, output) {
runcodeServer()
}
)
Some examples of why it is not such a good idea to include when deploying your app:
Try the input:
shinyjs::alert(ls(globalenv()))
or
shinyjs::alert(list.files())
I was able to find an alternative solution that doesn't require shinyjs -- wanted to restate Florian's concern that in general it is not a good thing (not secure) to let users run code in your Shiny app. Here is the alternative:
library(shiny)
library(dplyr)
ui <- fluidPage(
mainPanel(
h3("Data (mtcars): "), verbatimTextOutput("displayData"),
textInput("testcode", "Try filtering the dataset in different ways: ",
"mtcars %>% filter(cyl>6)", width="600px"),
h3("Results: "), verbatimTextOutput("codeResults"))
)
server <- function(input, output) {
shinyEnv <- environment()
output$displayData <- renderPrint({ head(mtcars) }) # prepare head(mtcars) for display on the UI
# create codeInput variable to capture what the user entered; store results to codeResults
codeInput <- reactive({ input$testcode })
output$codeResults <- renderPrint({
eval(parse(text=codeInput()), envir=shinyEnv)
})
}
shinyApp(ui, server)
I have made a shinydashboard app which has now quite an amount of lines of code, and I am wondering if there are ways to split the code into different . R files. I have seen a similar question here, but the answer does not help (especially it says nothing about the code in the server part of the app).
For the ui part, I have created functions called header, sidebar, and body, and then I merely write
ui <- dashboardPage(header(), sidebar(), body())
It works well, and it still works if the functions header, sidebar, and body need to have arguments.
For the server part, I don't think a similar strategy can be applied. I am wondering whether "local" server functions (one per menu item for instance) could be written, and then reunified into one central server function.
Do you think something like that is doable? More generally, thank you for your advice and ideas that could make my code more manageable.
I am not sure if this meets your requirement, you can create different files and do the required computations in those files and save all the objects (data frames or lists or literally anything) into .Rds file using saveRDS() in R and then load that file into server.R using loadRDS() which will have all your saved objects. You can find the documentation here.
Then simply use those objects by calling the names as they are saved earlier. Most of the complex Shiny apps use global.R file (just a general convention, you can use any name) to do the heavy computations and follow the above approach.
You can always use source to call other R files in server.R:
Use source as you usually do in regular R outside any reactive functions.
Use source("xxxxx", local=T) when you want to use it inside a reactive function so the r codes you called will run every time this piece of reactive codes are activated.
For the server side:
server.R:
library(shiny)
source('sub_server_functions.R')
function(input, output, session) {
subServerFunction1(input, output, session)
subServerFunction2(input, output, session)
subServerFunction3(input, output, session)
}
This has worked for me, it's possible you'll need to pass more variables to the subserver functions. But the scoping of the reactive output appears to allow this.
sub_server_functions.R:
subserverfunction1 <- function(input, output, session) {
output$checkboxGroupInput1 <- renderUI({
checkboxGroupInput('test1','test1',choices = c(1,2,3))
})
}
subserverfunction2 <- function(input, output, session) {
output$checkboxGroupInput2 <- renderUI({
checkboxGroupInput('test2','test2',choices = c(1,2,3))
})
}
subserverfunction3 <- function(input, output, session) {
output$checkboxGroupInput3 <- renderUI({
checkboxGroupInput('test3','test3',choices = c(1,2,3))
})
}