AddingShinyModules.Rmd
A shiny module consists of two parts: the user interface (UI) function and the server function. The UI function contains the code that specifies what inputs/outputs are seen when the module is called and the server function contains the code to do any process required by the module. If an input is dynamic (e.g., it depends on some user interaction) then it may need to be specified in the server rather than the UI.
Both the UI function and server function 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)
}
)
}
If you would like to add a new module please ensure you follow the following guidelines:
We recommend following the HADES coding style details here.
Please name files in the following format: The main module should be
called <module name>-main.R
and the submodules within
that module should be called
<module name>-<submodule name>.R
for example
the main prediction module is called prediction-main.R
and
the discrimination sub module within the prediction module is called
prediction-discrimination.R
.
Please place any html or markdown files inside the package ‘inst’
folder. The helper information about the module should be inside the
subfolder ‘
system.file('<module name>-www', file, package = "OhdsiShinyModules")
See R/helpers-getHelp.R
to see any example of a function
used by the prediction module to get the helper file locations.
A new module must have >80\% test coverage before it is added to the package. 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.
Testing of module servers can be done using the
shiny::testServer()
function. For an example of how to
write shiny tests please see
/test/testthat/test-prediction-main.R
.
In the test directory test files should have the prefix
test-*.R
. Naming convention for test files is:
test-<module name>-<submodule>.R
and the start
of the file should use the context()
function.
For resued fixtures and useful functions in tests, you can also
include the files tests/helper-objects.R
(generic objects)
tests/helper-<module name>.R
(objects specific to the
module) which will be loaded when the tests start.
For example, database connection strings should be included in
tests/helper-objects.R
.
Tests can follow the normal testthat
patterns, 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:
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")
})
})
The modules themselves will also require test data. For that reason
it is recommended that an sqlite database containing test data should
either be created in helper-<module name..R
or be stored
as flat files in the tests/resources
folder. For example,
currently in tests/resources
you’ll see the file
‘databaseFile.sqlite’ that contains example prediction result data
generated using the Eunomia package. This is used to test the prediction
module. Please place similar files for different analyses types in this
folder.
The following tests/helper-objects.R
file loads the
required database connection string:
# setup.R
connectionDetails <- DatabaseConnector::createConnectionDetails(
dbms = 'sqlite',
server = 'testDb.sqlite'
)
Tip 1: the file tests/testthat/helper-objects.R
contains
R test code that gets executed before any other test. This is a useful
place to run any generic code that may be reused in multiple test
files.
These tests can then be called with the testthat command:
testthat::test_dir('<path_to_package>/tests')
The current dependencies include the standard shiny packages, plotly, ggplot and a selection of helper OHDSI packages. Ideally new modules should use the existing dependencies. If a new dependency is required please post in the GitHub issues the required dependency so there can be a discussion whether to add the dependency or ‘borrow’ the specific code required by the module.