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

shinyApp vs runApp #33

Closed
chasemc opened this issue May 14, 2019 · 13 comments

Comments

Projects
None yet
3 participants
@chasemc
Copy link

commented May 14, 2019

First, awesome work! I've been referring a lot of people to this project.

I've adopted a slightly different take on your runapp() and was wondering if this could make your workflow shorter.

Instead of using shiny::runApp() I use shiny::shinyApp, thereby bypassing the need to create the app folder and underlying files.

example:
https://github.com/chasemc/IDBacApp/blob/b0897335e5f0f9fa1c7e3d9be3353a2da3c8dc3e/R/run_app.R#L10-L24

@VincentGuyader

This comment has been minimized.

Copy link
Member

commented May 15, 2019

Thanks for your message

we had some internal discussion about using runApp or shinyApp. in some case using one instead of the other returns error and/or really tricky issue (when deploying using docker or shinyproxy like this issue on SO : https://stackoverflow.com/questions/53926718/problems-when-calling-an-external-program-from-r-in-docker ).

So I think thaht both solution are good depending the case..

Regards

@ColinFay

This comment has been minimized.

Copy link
Member

commented May 17, 2019

Hey,

Thanks for your message, and very glad to hear you like {golem} 💪

As Vincent pointed at, he came across some weird issues when deploying, and needed to use the app/ structure you're describing.

Another argument against the current runApp() implementation is that is doesn't build on Shiny Server, as Shiny Server is calling a runApp(), and:

cat datuberapp-shiny-20190424-163323-42941.log
Error in shiny::runApp(system.file("app", package = "datuberapp")) : 
  Can't call `runApp()` from within `runApp()`. If your application code contains `runApp()`, please remove it.
Calls: runApp ... eval -> ..stacktraceon.. -> <Anonymous> -> <Anonymous>
Execution halted

The workaround being:

run_app <- function() {
  shiny::shinyAppDir(system.file("app", package = "datuberapp"))
}

But on the other hand, there is the issue of the options acces with shinyApp()

Compare:

library(shiny)
ui <- fluidPage(
  verbatimTextOutput("option")
)
server <- function(input, output, session) {
  output$option <- renderText({ getOption("pouet") })
}
withr::with_options(list(pouet="pouet"), {
  shiny::shinyApp(ui, server)
})

which is empty, to:

library(shiny)
ui <- fluidPage(
  verbatimTextOutput("option")
)
server <- function(input, output, session) {
  output$option <- renderText({ getOption("pouet") })
}
withr::with_options(list(pouet="pouet"), {
  shiny::runApp(list(ui = ui, server = server))
})

Which works 🤷‍♀️

I'm not sure what the perfect way to do is there, as this seems to be a little bit deployment dependent, and the counter examples we've pointed are corner cases 🤔

Glad to hear your thoughts on that.

C.

@chasemc

This comment has been minimized.

Copy link
Author

commented May 21, 2019

Those are interesting and I definitely forgot about having this problem on shinyapps.io.

More to the point:
Is there a benefit to having golem users go through the process of setting up ~inst/app (excuse me if I missed the automation part of this) versus templating (runApp or shinyApp) for them as a function?

Like:

someFunction <- function(ui_function,
                         server_function,
                         package_name){
  
  glue::glue("shiny::runApp({package_name}::{ui_function}",
             ", ",
             "{package_name}::{server_function})")
  )
}
            

Similar to what you have already implemented here:

add_rconnect_file <- function(
pkg = "."
){
where <- file.path(pkg, "app.R")
if ( !check_file_exist(where) ) return(invisible(FALSE))
write_there <- function(..., here = where){
write(..., here, append = TRUE)
}
file.create( where )
usethis::use_build_ignore( where )
write_there("# To deploy, run: rsconnect::deployApp()")
write_there("")
write_there("pkgload::load_all()")
write_there("options( \"golem.app.prod\" = TRUE)")
write_there("shiny::shinyApp(ui = app_ui(), server = app_server)")
usethis::use_build_ignore(where)
usethis::use_package("pkgload")
cat_green_tick(glue("File created at {where}"))
cat_line("To deploy, run:")
cat_bullet(darkgrey("rsconnect::deployApp()\n"))
if (rstudioapi::isAvailable()){
rstudioapi::navigateToFile(where)
} else {
cat_red_bullet(
glue::glue("Go to {where}")
)
}
}


I'm curious about the use case of using options in a shiny app (is it just to pass variables? benefit over using an environment?)....


@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 14, 2019

Ok, let me summarise things here.

Three ways to launch a Shiny App

  1. runApp() (current implementation of {golem}), which is shiny::runApp(system.file("app", package = "aaaaaa")).

  2. shinyApp(), which is shiny::shinyApp(ui = app_ui(), server = app_server) — created by golem::add_rstudioconnect_file().

  3. shinyAppDir(), which is shinyAppDir( system.file("app", package = "aaaaaa") ) — necessary workaround for Shiny server with the current implementation of {golem}.

Before going deeply into that, let me just state that I think the cost of having an inst/app/ folder with files is not that problematic — user should not interact with them so this is not that much a cognitive load, and whatever happens we'll keep this folder for putting files in the www/ folder :)

One naive implementation

We could do:

run_app <- function(
  with = c("shinyApp", "runApp", "shinyAppDir")
) {
  with <- match.arg(with)
  if (with == "shinyApp"){
    shiny::shinyApp(ui = app_ui(), server = app_server)
  } else if (with == "runApp") {
    shiny::runApp(system.file("app", package = "aaaa"))
  } else if (with == "shinyAppDir") {
    shiny::shinyAppDir(system.file("app", package = "aaaa"))
  }
}

But it seems a little bit too far fetched.

This is the one we'll be using for our benchmark though.

Side note :

If I refer to ?shinyApp:

You generally shouldn't need to use these functions to create/run applications; they are intended for interoperability purposes

So according to documentation we should rarely call shinyApp(), and use only runApp(). But using runApp() is impossible with Connect and Server, as they print an error which looks like this:

Loading aaaa
Error in shiny::runApp(system.file("app", package = "aaaa")) :
  Can't call `runApp()` from within `runApp()`. If your application code contains `runApp()`, please remove it.
Calls: runApp ... eval -> eval -> ..stacktraceon.. -> run_app -> <Anonymous>

Looking for the best implementation

Anyway, the idea is that our implementation of run_app() should:

  • work on the max number of services (Locally + Docker + RStudio products) with minimal tweaking (one implementation to rule them all would be best).

  • Be able to read options from the global environment, so that for example we can use the golem.app.prod variable from server and UI side.

  • Be able to read options from the local environment, so we can pass arguments to run_app().

Benchmark conditions

  • New golem with current version (0.0.1.600)

  • golem::set_golem_options() has been run

app_ui :

app_ui <- function() {
  tagList(
    fluidPage(
      h1("aaaa"), 
      h2( getOption('golem.pkg.name') ), 
      verbatimTextOutput("global"),
      verbatimTextOutput("shinycall")
    )
  )
}

app_server :

app_server <- function(input, output,session) {
  output$global <- renderPrint({
    # Global options
    getOption('golem.pkg.name')
  })
  output$shinycall <- renderPrint({
    # Local options
    getOption('shinycall')
  })
}

Various run_app implementations

We'll use this function to benchmark the three functions.

run_app <- function(
  with = c("shinyApp", "runApp", "shinyAppDir")
) {
  with <- match.arg(with)
  # Set local options
  options("shinycall" = with)
  if (with == "shinyApp"){
    shiny::shinyApp(ui = app_ui(), server = app_server)
  } else if (with == "runApp") {
    shiny::runApp(system.file("app", package = "aaaa"))
  } else {
    shiny::shinyAppDir(system.file("app", package = "aaaa"))
  }
}

This is the function that will be changing during the tests :

runApp is

run_app( "runApp" )

shinyApp is

run_app( "shinyApp" )

shinyAppDir is

run_app( "shinyAppDir" )

Launch contexts

Local launch is

.rs.restartR()
options( "golem.pkg.name" = "aaa")
pkgload::load_all()
run_app( "runApp"  ) # also with shinyApp & shinyAppDir

Dockerfile for local test is

FROM rocker/tidyverse:3.6.0
RUN R -e 'install.packages("remotes")'
RUN R -e 'remotes::install_github("r-lib/remotes", ref = "97bbf81")'
RUN R -e 'remotes::install_cran("shiny")'
COPY aaaa_*.tar.gz /app.tar.gz
RUN R -e 'remotes::install_local("/app.tar.gz")'
EXPOSE 80
CMD R -e "options('shiny.port'=1234,shiny.host='0.0.0.0', 'golem.pkg.name' = 'aaa');aaaa::run_app( 'runApp' )" # also with shinyApp & shinyAppDir

Launched with:

R -e "devtools::build(path = '.')" \
    && docker build -t aaa . \
    && docker run --name aaa -p 1234:1234 -d aaa \
    && sleep 2 \
    && open http://0.0.0.0:1234

Setting a Shiny server for testing is build as such

FROM rocker/shiny:3.6.0
RUN R -e 'install.packages("remotes")'
RUN R -e 'remotes::install_github("r-lib/remotes", ref = "97bbf81")'
RUN R -e 'remotes::install_cran("shiny")'
RUN apt-get update && apt-get install libssl-dev -y
RUN R -e 'remotes::install_cran("attachment")'
COPY . /srv/shiny-server/aaaa
RUN cd /srv/shiny-server/aaaa && R -e "attachment::install_from_description()"
docker build -t plop . \
    && docker run --name plop -p 3838:3838 -d plop \
    && sleep 2 \
    && open http://0.0.0.0:3838/aaaa

File for the RStudio products

Each three versions of this file will be deployed to :

  • local Shiny Server (copied inside the Docker)
  • ThinkR internal RStudio Connect (sent with rsconnect::deployApp())
  • ThinkR's shinyapps.io account (sent with rsconnect::deployApp())
pkgload::load_all()
options( "golem.pkg.name" = "aaa")
run_app( "runApp" ) # also with shinyApp & shinyAppDir

Results

🚀: launches
💥: doesn't launch
📗 : global options are read
: global options not read
📒 : function options are read
⛔️ : function options not read

Where runApp shinyApp shinyAppDir
Locally 🚀📗📒 🚀📗⛔️ 🚀📗⛔️
Docker 🚀📗📒 🚀📗⛔️ 🚀📗⛔️
Connect 💥 ⛔️ 🚀📗⛔️ 🚀📗⛔️
shinyApps.io 💥 ⛔️ 🚀📗⛔️ 🚀📗⛔️
ShinyServer 💥 ⛔️ 🚀📗⛔️ 🚀📗⛔️

So to sum up :

  • Docker container don't get local options from the functions unless called with runApp(), just as local launch. Which you can verify with running in any terminal: R -e "options('shiny.port'=1234,shiny.host='0.0.0.0', 'golem.pkg.name' = 'aaa');aaaa::run_app( 'runApp' )"
  • runApp() fails on RStudio Product.
  • Shiny Server, doesn't get local options with any solution.

About that Blue Button

One cool thing is that we have the deploy blue button to RStudio Connect & ShinyApps.io in the app.R file.

Where run_app runApp shinyApp shinyAppDir
Has blue button
Has Run App

Screenshot 2019-06-14 at 15 47 43

Hence the reason why the app.R generated for Connect and SA.io does not contains the run_app wrapper, but the content of the function. So the mechanism is the same, the wrapper is not.

Summary

So, long story short, I think we should not rely on runApp as a function to launch the app as a default, as it doesn't launch on RStudio products.

I suggest we

  • default to, for local use:
run_app <- function() {
    shiny::runApp(system.file...
}
  • Use runApp() in Docker
CMD R -e "options('shiny.port'=1234,shiny.host='0.0.0.0', 'golem.pkg.name' = 'aaa');shiny::runApp(system.file('inst/app', package = 'aaaa'))"
  • Use shinyApp() for RStudio Product

  • Add a warning about local options for Shiny Server

  • Add a function to create server.R and ui.R files if needed.

add_app_files <- function() {
  file.create("inst/app/server.R")
  file.create("inst/app/ui.R")
  write(
    sprintf("%s:::app_ui()", getOption("golem.pkg.name")); 
    "inst/app/ui.R"
  )
  write(
    sprintf("%s:::app_server()", getOption("golem.pkg.name")), 
    "inst/app/server.R"
  )
  cat_green_tick("inst/app/server.R created")
  cat_green_tick("inst/app/ui.R created")
  cat_red_bullet("Don't forget to switch to:")
  cat_line(sprintf("shiny::shinyAppDir(system.file('app', package = '%s'))", getOption("golem.pkg.name")))
  cat_line("in R/run_app.R")
}

Let me know what you all think.

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 14, 2019

Oh, and I forgot, here are the session info.

Got them by adding :

# in UI
verbatimTextOutput("sessinfo")
# in server
output$sessinfo <- renderPrint({
    sessionInfo()
  })

Local :

R version 3.6.0 (2019-04-26)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Mojave 10.14.3

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] aaaa_0.0.0.9003 shiny_1.3.2    

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.1        compiler_3.6.0    later_0.8.0       prettyunits_1.0.2
 [5] tools_3.6.0       testthat_2.1.1    digest_0.6.19     packrat_0.5.0    
 [9] pkgbuild_1.0.3    pkgload_1.0.2     jsonlite_1.6      rlang_0.3.4      
[13] cli_1.1.0         rstudioapi_0.10   commonmark_1.7    golem_0.0.1.6000 
[17] yesno_0.1.0       withr_2.1.2       stringr_1.4.0     roxygen2_6.1.1   
[21] xml2_1.2.0        desc_1.2.0        attempt_0.3.0     rprojroot_1.3-2  
[25] glue_1.3.1        R6_2.4.0          processx_3.3.1    callr_3.2.0      
[29] magrittr_1.5      ps_1.3.0          backports_1.1.4   promises_1.0.1   
[33] htmltools_0.3.6   assertthat_0.2.1  mime_0.7          xtable_1.8-4     
[37] httpuv_1.5.1      stringi_1.4.3     crayon_1.3.4    

Docker:

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 9 (stretch)

Matrix products: default
BLAS/LAPACK: /usr/lib/libopenblasp-r0.2.19.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shiny_1.3.2

loaded via a namespace (and not attached):
 [1] compiler_3.6.0  magrittr_1.5    R6_2.4.0        promises_1.0.1 
 [5] later_0.8.0     tools_3.6.0     htmltools_0.3.6 Rcpp_1.0.1     
 [9] aaaa_0.0.0.9003 jsonlite_1.6    digest_0.6.19   xtable_1.8-4   
[13] httpuv_1.5.1    mime_0.6   

RStudio Connect

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.1 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] aaaa_0.0.0.9003 shiny_1.3.2    

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.1        magrittr_1.5      pkgload_1.0.2     xtable_1.8-4     
 [5] R6_2.4.0          rlang_0.3.4       tools_3.6.0       pkgbuild_1.0.3   
 [9] cli_1.1.0         withr_2.1.2       htmltools_0.3.6   assertthat_0.2.1 
[13] digest_0.6.19     rprojroot_1.3-2   crayon_1.3.4      processx_3.3.1   
[17] callr_3.2.0       later_0.8.0       promises_1.0.1    ps_1.3.0         
[21] mime_0.7          compiler_3.6.0    desc_1.2.0        backports_1.1.4  
[25] prettyunits_1.0.2 jsonlite_1.6      httpuv_1.5.1 

shinyApps.io

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/atlas-base/atlas/libblas.so.3.0
LAPACK: /usr/lib/atlas-base/atlas/liblapack.so.3.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] aaaa_0.0.0.9003 shiny_1.3.2    

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.1        magrittr_1.5      pkgload_1.0.2     xtable_1.8-4     
 [5] R6_2.4.0          rlang_0.3.4       tools_3.6.0       pkgbuild_1.0.3   
 [9] cli_1.1.0         withr_2.1.2       htmltools_0.3.6   assertthat_0.2.1 
[13] digest_0.6.19     rprojroot_1.3-2   crayon_1.3.4      processx_3.3.1   
[17] callr_3.2.0       later_0.8.0       promises_1.0.1    ps_1.3.0         
[21] mime_0.7          compiler_3.6.0    desc_1.2.0        backports_1.1.4  
[25] prettyunits_1.0.2 jsonlite_1.6      httpuv_1.5.1     

Shiny server :

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 9 (stretch)

Matrix products: default
BLAS/LAPACK: /usr/lib/libopenblasp-r0.2.19.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] aaaa_0.0.0.9003 shiny_1.3.2    

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.1        magrittr_1.5      pkgload_1.0.2     xtable_1.8-4     
 [5] R6_2.4.0          rlang_0.3.4       tools_3.6.0       pkgbuild_1.0.3   
 [9] cli_1.1.0         withr_2.1.2       htmltools_0.3.6   assertthat_0.2.1 
[13] digest_0.6.19     rprojroot_1.3-2   crayon_1.3.4      processx_3.3.1   
[17] callr_3.2.0       later_0.8.0       promises_1.0.1    ps_1.3.0         
[21] mime_0.7          compiler_3.6.0    desc_1.2.0        backports_1.1.4  
[25] prettyunits_1.0.2 jsonlite_1.6      httpuv_1.5.1     
@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 18, 2019

For the record, I tried to switch to shinyOptions but got the same results

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 18, 2019

Ok, finally found why the local options aren't possible with the current implementation :

  • the aaaa::run_app() function, in case of using shiny::runApp(), launches the app and returns shiny:::.globals$retval$value, which is a running server, and strictly speaking aaaa::run_app() is not exited yet as long as the shiny app is running.

  • With shiny:::shinyApp and shiny:::shinyAppDir, aaaa::run_app() returns a structure of class shiny.appobj, so the run_app() function is exited, so functions options are not kept. There the app is launched with shiny:::print.shiny.appobj.

Using shinyOptions() doesn't change anything as the assignment of options is made inside shiny:::.globals$options, but is not kept when the function stops. That's why you've got a different shiny:::.globals$options$appToken every time you launch runApp(), and why shiny:::.globals$options$appToken is NULL when called in the command line

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 18, 2019

Here's a hacky way to bypass this all :

run_app <- function(
  with = c("shinyApp", "runApp", "shinyAppDir")
) {
  with <- match.arg(with)

  opt <- shiny:::.globals
  opt$options <- append(opt$options, c(with = with))
  assignInNamespace( ".globals", opt, ns = "shiny")
  
  if (with == "shinyApp"){
    shiny::shinyApp(ui = app_ui(), server = app_server)
  } else if (with == "runApp") {
    shiny::runApp(system.file("app", package = "aaaa"))
  } else {
    shiny::shinyAppDir(system.file("app", package = "aaaa"))
  }
}

Then

output$shinycall <- renderPrint({
    shiny::getShinyOption('with')
})

I'm a little bit torn about this idea though.

  • First of all, it should be assigned in {golem}
  • This is definitely not thinkable for local use — friends don't let friends assign thing in the namespaces of local packages. So let's keep current implentation with runApp for local use.
  • Though, if we consider deployment in prod, this should be ok as we can consider that it's always the same run_app() so run_app() options will stay the same whatever the app session. And another app should be deployed in another process, so with a fresh shiny namespace :

two deployments of the same app on Connect, two application tokens :

Screenshot 2019-06-18 at 23 24 36

Same app opened in two accounts, same app token so same namespaces, but that's ok because they are both opened with the same run_app()

Screenshot 2019-06-18 at 23 26 29

@chasemc

This comment has been minimized.

Copy link
Author

commented Jun 18, 2019

That's awesome work.

I'm still a little confused about your distinction between local and global variables.

It looks like you use .Options for both; and I'm curious about the option of storing it an environment instead? This could be used for both "global" and "local"?

golemOptions <- new.env(parent = globalenv())
golemOptions$production <- TRUE

someFunction <- function(x){
  #check if golemOption set

if( exists("golemOptions", envir = globalenv() ) ) {
  if( golemOptions$prod == T ) {
      
  } else {
    
  }
} else {
  warning("golemOptions not set. Set using...")
}
}

Also, could you zip and attach the aaa app to a comment?

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 19, 2019

Thanks for your feedbacks :)

Sorry if everything is not cristal clear. FWIW I'm writting a blogpost right now that will sum up this all thing more clearly (I hope).

You can find the {aaaa} package there : https://github.com/ColinFay/golem4bench — it contains the dockerfiles and instructions to deploy.

Global options are the one defined outside of run_app(), local options are the one defined inside the run_app().

So

# Global
pkgload::load_all()
options( "golem.pkg.name" = "aaa")
run_app( "runApp" ) 
# local 
run_app <- function(
  with = c("shinyApp", "runApp", "shinyAppDir")
) {
  with <- match.arg(with)
  # Set local options
  options("shinycall" = with)
...
}

The rational behind trying to use the options() here is that it would have created less friction in the global workflow, for example by allowing to do this:

options('shiny.port'=1234,shiny.host='0.0.0.0', 'golem.pkg.name' = 'aaa'); aaaa::run_app( )

Instead of something that would look like this:

options('shiny.port'=1234,shiny.host='0.0.0.0'); set_golem_options('golem.pkg.name' = 'aaa'); aaa::run_app()

Which will also imply a different mechanism of accessing these info (getOptions() vs getGolemOptions())

Finally it's pretty standard to set options in your startup configuration file — any other mechanism works also in these file but as {golem} introduces a lot of new things, we have tried to keep things standard if we can. Even if we won't be able to for our specific case, I'm afraid :/

So yes the env might be the solution here. I wonder if we should create it inside the .onAttach(), maybe as a namespace a la {devtools} or as a an env.

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 19, 2019

Ok folks, I got this :

library(shiny)
library(attempt)

options("golem.app.name" = "aaa")

get_golem_options <- function(which = NULL){
    if (is.null(which)){
        getShinyOption("golem_options")
    } else {
        getShinyOption("golem_options")[[which]]
    }
}

with_golem_options <- function(app, golem_opts){
   app$appOptions$golem_options <- golem_opts
   app
}

app_ui <- function() {
    tagList(
        fluidPage(
            verbatimTextOutput("all"),
            verbatimTextOutput("opt"), 
            verbatimTextOutput("glob")
        )
    )
}


app_server <- function(input, output,session) {
    
    output$all <- renderPrint({ get_golem_options() })
    output$opt <- renderPrint({ get_golem_options("a") })
    output$glob <- renderPrint({ getOption("golem.app.name") })
}

run_app <- function(...) {
    with_golem_options(
        app = shinyApp(ui = app_ui(), server = app_server), 
        golem_opts = list(...)
    )
}

run_app(a = "pouet", b = "bing")

Allows to use global options and to pass arguments to run_app that can be retrieved with get_golem_options(), and works everywhere 🤘

@ColinFay

This comment has been minimized.

Copy link
Member

commented Jun 27, 2019

I'm closing this one, as it seems solved to me.

Read https://rtask.thinkr.fr/blog/shinyapp-runapp-shinyappdir-difference/ for more information.

Feel free to reopen if ever you feel like this issue is not over :)

@ColinFay ColinFay closed this Jun 27, 2019

VincentGuyader added a commit that referenced this issue Jul 11, 2019

@chasemc

This comment has been minimized.

Copy link
Author

commented Jul 11, 2019

It looks like ya'll did a ton of work on this one, just wanted to drop something here that's outside of golem but might be useful for you or someone else that comes across this.

Another method to set options is by simply passing an ellipse to a run_app function, making sure the run_app function contains options(...)


run_app <- function(port = NULL, ...) {
  options(...)
  
  if (is.null(port)) {
    shiny::shinyApp(ui = ui,
                    server = server)
  } else {
    port <- try(as.integer(port))
    if (is.integer(port) && port > 0L) {
      shiny::shinyApp(ui = ui,
                      server = server,
                      options = list(port = port))
    }
  }  
}


library(shiny)

ui <- fluidPage(

        mainPanel(
            shiny::verbatimTextOutput("mytext")
            )
    
)

server <- function(input, output) {

    output$mytext <- renderText({
        a <- names(options())
        a <- paste0(a, collapse ="\n")
     print(a)
    })
}

run_app(a = 1)


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.