knitr::opts_chunk$set(echo = TRUE)
A shiny module contains a UI function and a server function. Both functions require the input id that is a string and much match between the UI and server for the same module instance. For a nice tutorial on shiny modules see this website.
The module's UI function specifies how the shiny module will be displayed to the user. The string variable id should be an input for every model UI as it is used to create the namespace. The main aspect of a UI module is the namespace function ns <- shiny::NS(id). This is put as the first line of any module UI function. Effectively, what this function does is append the id string to the input of the ns() function. For example, if a user calls the UI module by running exampleViewer(id = 'example_name') then the namespace function ns() will concatenate the string 'example_name' to every reference in the UI, e.g., ns('serverReference') = 'example_name_serverReference'. Therefore, as long as the id input values are unique for each instance of a module, the references within the modules viewer and server will be unique and not clash across modules.
exampleViewer <- function(id) { ns <- shiny::NS(id) shiny::fluidRow( shiny::column(width = 12, shinydashboard::box( width = 12, title = "Example Title ", status = "info", solidHeader = TRUE, DT::dataTableOutput(ns('serverReference')) ) ) ) }
The module's server function is where all the data fetching and manipulation happens. The module server and UI server is linked by using the same id for example, when calling the server you would use exampleServer(id = 'example_name'). For the example above, the server function needs to specify what the serverReference dataTable is. The server automatically knows the namespace, so the DT::dataTableOutput(ns('serverReference')) can be define using the output output$serverReference (no namespace fucntion is require in the server code).
exampleServer <- function( id, extraInput ) { shiny::moduleServer( id, function(input, output, session) { output$serverReference <- data.frame(a=1:5, b=1:5) } ) }
ui <- fluidPage( exampleViewer("exampleName") ) server <- function(input, output, session) { exampleServer("exampleName") } shinyApp(ui, server)
A package's shiny app modules should be put inside the package inst/shiny folder and have the following structure:
+-- module.r +-- helpers.r +-- /www +-- /modules +-- /tests
The module.r file contains the UI function and the server function for the main module.
[Add instructions and example function to create the environment with all the functions used by the module]
Each OHDSI package shiny module should interact with a database backend and therefore the standard input into an OHDSI package shiny module server is the database connection details, result schema, string to append to the result schema plus any module specific inputs. These should be contained in a list called resultDatabaseSettings. Therefore the module server should start with the following code:
exampleServer <- function( id, resultDatabaseSettings ) { shiny::moduleServer( id, function(input, output, session) { } ) }
[talk about how to stop everything loading at launch]
The helper-
The folder www contains all the html files that are used to display help information to the user ineracting with the shiny app. This folder should contain html files that will be loaded into the shiny app by a submodule or the main server function in module.r. Include javascript/images as well.
This is a folder with shiny modules that are used by the main package module (termed sub-modules). For the PatientLevelPrediction shiny app, there were a set of tabs with different aspects of a model (discrimination, calibration, model, model design, etc.) and these tabs were independant of each other. This made is possible to create shiny modules per tab and these are the PatientLevelPrediction submodules.
Testing shiny modules is similar to testing R packages with testthat, however, there are some notable specific differences for this set up that must be followed. The reccomended structure is to include a tests directory in the module's root.
In the test directory test files should have the prefix test-*.R, so creating a test file should be named tests/test-MyModuleServerTest.R.
For resued fixtures and useful functions in tests, you can also include the files tests/setup.R and tests/helper-<name>.R which will be loaded when the tests start.
For example, database connection strings that are specific to tests should be included in setup.R.
Tests can follow the normal (testthat patterns)[https://r-pkgs.org/tests.html#test-structure], however, testing a shiny module requires loading a shiny test server that can be used to simulate user inputs and test the behaviour of shiny::reactive calls as well as outputs.
For example:
source("../module.R") source("../modules/MyModuleCode.R") # Imagine this code lives inside your module exampleModuleServer <- function(id) { ns <- shiny::NS(id) shiny::moduleServer(id, function(input, output, session) { myReactive <- shiny::reactive({ paste("Input is ", input$testString) }) output$testOutput <- shiny::renderText(myReactive()) }) } test_that("Example Test", { shiny::testServer(exampleModuleServer, args = list(id = "testModule"), { # session$setInputs allows simulation of user behaviour session$setInputs( testString = "foo" ) # after modifying input output can be verified expect_equal(myReactive(), "Input is foo") expect_equal(output$testOutput, "Input is foo") }) })
Note the use of ../ in the path for source - this is becase code must be sourced relative to the tests directory!
The modules themselves will also require test data.
For that reason it is reccomended that a sqlite database containing test data should either be created in setup.R or be stored as flat files within the test directory.
For example, creating a test sqlite database with the path tests/testDb.sqlite.
The following tests/setup.R file loads the required database connection string:
# setup.R connectionDetails <- DatabaseConnector::createConnectionDetails(dbms = 'sqlite', server = 'testDb.sqlite')
These tests can then be called with the testthat command:
testthat::test_dir('<path_to_module>/tests')
Adding the following to a github actions script should allow tests to be run, for example as part of a HADES package where shiny source code is stored in inst/shiny/MyModule:
## Assumed to be after normal R package checkes ... ## Assumes an Renv is required for the shiny code. ## If required packages are already installed skip this step - name: Set up DiagnosticsExplorer Environment run: | install.packages("renv") setwd("inst/shiny/MyModule") renv::restore() shell: Rscript {0} - name: Test My shiny module run: | renv::activate("inst/shiny/MyModule/") testthat::test_dir("inst/shiny/MyModule/tests") shell: Rscript {0}
[add any naming conventions, code style conventions, etc. here]
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.