Can we check shiny applications using testthat and usethis - r

is there a way to check the below applications. This is a sample application to display selected rows. But in general is there a way to acheive this. For example the below code is perfect. Suppose I may some errors in this and I need to check all in once. Can we do that? I have also pasted the error code down
Correct
library(shiny)
library(DT)
ui <- basicPage(
mainPanel(DT::dataTableOutput('mytable')),
textOutput("selected")
)
server <- function(input, output,session) {
mydata <- reactive({mtcars})
output$mytable = DT::renderDataTable(
datatable(mydata())
)
selectedRow <- eventReactive(input$mytable_rows_selected,{
row.names(mtcars)[c(input$mytable_rows_selected)]
})
output$selected <- renderText({
selectedRow()
})
}
runApp(list(ui = ui, server = server))
Wrong/Error code
library(shiny)
library(DT)
ui <- basicPage(
mainPanel(DT::dataTableOutput('mytable')),
textOutput("selected")
)
server <- function(input, output,session) {
mydata <- reactive({mtcars})
output$mytable = DT::renderDataTable(
datatable(mydata())
)
selectedRow <- eventReactive(input$mytable_rows_selected,{
row.names(mtcars)[c(input$[mytable_rows_selected])]
})
output$selected <- renderText({
selectedRow
})
}
runApp(list(ui = ui, server = server))

I use the package shinytest to test my shiny apps. shinytest uses phantomJS to manipulate the app (i.e. simulate button presses etc.)
An entry in testthat/test-shinyapp.R looks something like:
test_that("Shiny template works", {
app <- shinytest::ShinyDriver$new("Path_To_ShinyApp/"))
# plot changes
# first click makes plot
testthat::expect_false(length(grepl("^(data:image/png;base64)",
app$getAllValues()$output$plot1$src)) > 0)
app$setInputs(apply = "click")
testthat::expect_true(length(grepl("^(data:image/png;base64)",
app$getAllValues()$output$plot1$src)) > 0)
testthat::expect_true(grepl("^(data:image/png;base64)",
app$getAllValues()$output$plot1$src))
# are subsequent changes applied?
testthat::expect_identical(app$getAllValues()$input$xVar, "mpg")
testthat::expect_true(
grepl(" mpg cyl", app$getAllValues()$output$summary1))
app$setInputs(xVar = "drat")
app$setInputs(apply = "click")
testthat::expect_true(
grepl(" drat cyl", app$getAllValues()$output$summary1))
# # test for reactive
# shinytest::expectUpdate(app, xVar = "wt", output = "plot1" )
# shinytest::expectUpdate(app, yVar = "drat", output = "plot1" )
# shinytest::expectUpdate(app, xVar = "cyl", output = "summary1" )
# shinytest::expectUpdate(app, yVar = "mpg", output = "summary1" )
# cleanup
app$stop()
unlink(paste0(ws2us(testDir),"_ShinyApp"), recursive = TRUE)
})

Related

R shiny: save data frames from multiple panels

In the following app, I would like to add a global button, to save the tables in the 2 panels at the same time.
Ideally, they should be saved to an xlsx file, in tabs named after the corresponding tabs.
Please note that the tabs were created using a module.
Many thanks!!
library(shiny)
library(DT)
modDtUi <- function(id){ # UI module
ns = NS(id)
DT::dataTableOutput(ns('x1'))
}
modDt <- function(input, output, session, data, globalSession){ # Server module
x <- data
output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE)
proxy <- dataTableProxy('x1', session = globalSession)
}
ui <- fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Table1", modDtUi("editable")),
tabPanel("Table2", modDtUi("editable2"))
)
)
)
server <- function(input, output, session) {
callModule(modDt,"editable", data = head(iris,10), globalSession = session)
callModule(modDt,"editable2", data = tail(iris,5), globalSession = session)
}
shinyApp(ui = ui, server = server)
I believe this demo works.
I used reactiveValues v$data to store the data inside the module. The module will return v$data so it can be retrieved when you want to save the data in the shiny server.
I also added an observeEvent to detect changes in the data, and update the data table with replaceData.
The excel file is created using the writexl library, but you could substitute with others of course.
Let me know if this works for you. I imagine there are some elements of this answer that can be improved upon - and if we can identify them, would like to edit further.
library(shiny)
library(DT)
library(writexl)
modDtUi <- function(id){ # UI module
ns = NS(id)
DT::dataTableOutput(ns(id))
}
modDt <- function(input, output, session, data, id, globalSession){ # Server module
v <- reactiveValues(data = data)
output[[id]] <- DT::renderDataTable(v$data, selection = 'none', editable = TRUE)
proxy <- dataTableProxy(id, session = globalSession)
id_input = paste(id, "cell_edit", sep = "_")
# Could add observeEvent here to detect edit event
observeEvent(input[[id_input]], {
info = input[[id_input]]
if (!is.null(info)) {
v$data[info$row, info$col] <<- DT::coerceValue(info$value, v$data[info$row, info$col])
}
replaceData(proxy, v$data, resetPaging = FALSE)
})
return(data = reactive({v$data}))
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
width = 2,
actionButton("btn", "Save Both")
),
mainPanel(
tabsetPanel(
tabPanel("Table1", modDtUi("editable1")),
tabPanel("Table2", modDtUi("editable2"))
)
)
)
)
server <- function(input, output, session) {
e1 <- callModule(modDt, "editable1", data = head(iris,10), id = "editable1", globalSession = session)
e2 <- callModule(modDt, "editable2", data = tail(iris,5), id = "editable2", globalSession = session)
observeEvent(input$btn, {
print("Saving...")
sheets <- list("e1" = e1(), "e2" = e2())
write_xlsx(sheets, "test.xlsx")
})
}
shinyApp(ui = ui, server = server)

How to replaceData in DT rendered in R shiny using the datatable function

I have an R shiny app with a DT datatable that is rendered using the datatable function in order to set various options. I would like to use dataTableProxy and replaceData to update the data in the table, but all the examples I can find assume the DT is rendered directly from the data object, not using the datatable function. The reprex below shows what I would like to do, but replaceData doesn't work in this pattern. How do I do this? Thanks.
# based on
# https://community.rstudio.com/t/reorder-data-table-with-seleceted-rows-first/4254
library(shiny)
library(DT)
ui = fluidPage(
actionButton("button1", "Randomize"),
fluidRow(
column(6,
h4("Works"),
DT::dataTableOutput('table1', width="90%")),
column(6,
h4("Doesn't Work"),
DT::dataTableOutput('table2', width="90%"))
)
)
server = function(input, output, session) {
my <- reactiveValues(data = iris)
output$table1 <- DT::renderDataTable(isolate(my$data))
output$table2 <- DT::renderDataTable({
DT::datatable(isolate(my$data),
options = list(lengthChange=FALSE, ordering=FALSE, searching=FALSE,
columnDefs=list(list(className='dt-center', targets="_all")),
stateSave=TRUE, info=FALSE),
class = "nowrap cell-border hover stripe",
rownames = FALSE,
editable = FALSE
) %>%
DT::formatStyle('Sepal.Width', `text-align`="center")
})
observeEvent(input$button1, {
# calculate new row order
row_order <- sample(1:nrow(my$data))
my$data <- my$data[row_order, ]
proxy1 <- DT::dataTableProxy('table1')
DT::replaceData(proxy1, my$data)
proxy2 <- DT::dataTableProxy('table2')
DT::replaceData(proxy2, my$data)
})
}
shinyApp(ui, server)
Update: Very strangely, removing rownames = FALSE made it all possible. I'm not exactly sure why, but probably rownames might be essential for replacing Data.
# based on
# https://community.rstudio.com/t/reorder-data-table-with-seleceted-rows-first/4254
library(shiny)
library(DT)
ui = fluidPage(
actionButton("button1", "Randomize"),
fluidRow(
column(6,
h4("Works"),
DT::dataTableOutput('table1', width="90%")),
column(6,
h4("Doesn't Work"),
DT::dataTableOutput('table2', width="90%"))
)
)
server = function(input, output, session) {
my <- reactiveValues(data = iris)
output$table1 <- DT::renderDataTable(isolate(my$data))
output$table2 <- DT::renderDataTable({
DT::datatable(isolate(my$data),
options = list(lengthChange=FALSE, ordering=FALSE, searching=FALSE,
columnDefs=list(list(className='dt-center', targets="_all")),
stateSave=TRUE, info=FALSE),
class = "nowrap cell-border hover stripe",
# rownames = FALSE,
editable = FALSE
) %>%
DT::formatStyle('Sepal.Width', `text-align`="center")
})
observeEvent(input$button1, {
# calculate new row order
row_order <- sample(1:nrow(my$data))
my$data <- my$data[row_order, ]
proxy1 <- DT::dataTableProxy('table1')
DT::replaceData(proxy1, my$data)
proxy2 <- DT::dataTableProxy('table2')
DT::replaceData(proxy2, my$data)
})
}
shinyApp(ui, server)

Run Shiny Reactive after Another Finishes

I have two outputs, a print and a plot. I would like to execute the print after the run button is pressed (working) and then when the print completes the plot part executes.
The reason for this is the print part does some calculations that take a few minutes and the output from that needs to go to the plot command.
Simple example:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton('run','Run')
),
mainPanel(
verbatimTextOutput("Descriptive"),
plotOutput("plotData",width = "700px", height = "500px")
)
)
)
server <- function(input, output) {
output$Descriptive <- renderPrint({
if(input$run>0){
return(isolate({
cat('Number of rows:', nrow(mtcars))
mpg2 <<- mtcars$mpg+3
cyl2 <<- mtcars$cyl+3
}))
}else{return(invisible())}
})
#### RUN AFTER DESCRIPTIVE COMPLETES ####
output$plotData <- renderPlot({
plot(mpg2,cyl2)
})
}
shinyApp(ui = ui, server = server)
I would suggest you to store the variable as reactiveValues and make the plot dependent on them. By this you can avoid the current global assignment and also make the plot update dependent on a change in its variables.
It could look like this:
global <- reactiveValues(mpg2 = mtcars$mpg, cyl2 = mtcars$cyl, txt = "")
observe({
if(input$run > 0){
Sys.sleep(5) # simulate minutes of calculating
global$txt <- paste('Number of rows:', nrow(mtcars))
global$mpg2 <- mtcars$mpg + 3
global$cyl2 <- mtcars$cyl + 3
}
})
Your app would look like this:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton('run','Run')
),
mainPanel(
verbatimTextOutput("Descriptive"),
plotOutput("plotData",width = "700px", height = "500px")
)
)
)
server <- function(input, output) {
global <- reactiveValues(mpg2 = mtcars$mpg, cyl2 = mtcars$cyl, txt = "")
observe({
if(input$run > 0){
Sys.sleep(5) # simulate minutes of calculating
global$txt <- paste('Number of rows:', nrow(mtcars))
global$mpg2 <- mtcars$mpg + 3
global$cyl2 <- mtcars$cyl + 3
}
})
output$Descriptive <- renderPrint({
if(nchar(global$txt)) return(global$txt)
})
#### RUN AFTER DESCRIPTIVE COMPLETES ####
output$plotData <- renderPlot({
plot(global$mpg2, global$cyl2)
})
}
shinyApp(ui = ui, server = server)

R shinyapp selecting pre-stored data set from checkbox

ui <- fluidPage(
checkboxGroupInput("data", "Select data:",
c("Iris" = "iris",
"Cars" = "mtcars")),
plotOutput("myPlot")
)
server <- function(input, output) {
output$myPlot <- renderPlot({
plot(Sepal.Width ~ Sepal.Length, data = input$data)
})
}
shinyApp(ui, server)
I have a shinyApp where I want the user to select a data set. From there, I want to use that data set to make a simple plot. However, it seems that the user input into the checkbox didn't pass in successfully to the server. How can I get around this?
The typical way to do this in shiny is with switch(), which means you don't need to specify the dataset in your input, you can do it all in the server. In your context:
library(shiny)
ui <- fluidPage(
checkboxGroupInput("data", "Select data:",
c("Iris" = "iris",
"Cars" = "mtcars")),
plotOutput("myPlot")
)
server <- function(input, output) {
dat <- reactive({
switch()
})
output$myPlot <- renderPlot({
dat <- switch(input$data,
"iris" = iris,
"mtcars" = mtcars)
plot(Sepal.Width ~ Sepal.Length, data = get(input$data))
})
}
shinyApp(ui, server)
Note that you could use any strings in the checkboxGroupInput, which makes this a more flexible way to work.

Shiny - conditionalPanel - set condition as output from server

I am trying to build an app in shiny that will be able to load a dataset in the server function and then based on the user choose and then if there is a factor variable to open check box using conditionalPanel. is there a way to output variable from the server as the condition of the condtionalPanel?
Here is what I tried:
library(shiny)
library(caret)
ui <- fluidPage(
selectInput('dataset', 'Select Dataset',
list(GermanCredit = "GermanCredit",
cars = "cars")),
conditionalPanel(
condition = "output.factorflag == true",
checkboxInput("UseFactor", "Add Factor Variable")
)
)
server <- function(input, output) {
# Loading the dataset
df <- reactive({
if(input$dataset == "GermanCredit"){
data("GermanCredit")
df <- GermanCredit
}else if(input$dataset == "cars"){
data(cars)
df <- cars
}
return(df)
})
# Loading the variables list
col_type <- reactive({
col_type <- rep(NA,ncol(df()))
for(i in 1:ncol(df())){
col_type[i] <- class(df()[,i])
}
return(col_type)
})
outputOptions(output, "factorflag", suspendWhenHidden = FALSE)
output$factorflag <- reactive({
if("factor" %in% col_type()){
factor.flag <- TRUE
} else {factor.flag <- FALSE}
}
)
}
shinyApp(ui = ui, server = server)
Thank you in advance!
You were almost there, you need to put the outputOptions after the declaration of factorflag. Just reengineered a bit your code:
library(shiny)
library(caret)
ui <- fluidPage(
selectInput('dataset', 'Select Dataset',
list(GermanCredit = "GermanCredit",
cars = "cars")),
conditionalPanel(
condition = "output.factorflag == true",
checkboxInput("UseFactor", "Add Factor Variable")
)
)
server <- function(input, output) {
# Loading the dataset
df <- reactive({
if(input$dataset == "GermanCredit"){
data("GermanCredit")
GermanCredit
}else {
data("cars")
cars
}
})
output$factorflag <- reactive("factor" %in% sapply(df(),class))
outputOptions(output, "factorflag", suspendWhenHidden = FALSE)
}
shinyApp(ui = ui, server = server)

Resources