http://thinkr.fr http://rtask.thinkr.fr http://twitter.com/_colinfay http://github.com/colinfay $ whoami Colin FAY Data Scientist & R-Hacker at ThinkR, a french company focused on Data Science & R. Hyperactive open source developer. Main developer on the {golem} project. Colin FAY @_ColinFay - https://rtask.thinkr.fr 3 / 130
Data Science engineering, focused on R. Training Software Engineering R in production Consulting ThinkR Colin FAY @_ColinFay - https://rtask.thinkr.fr 5 / 130
$ whoami Kirsty Lee Garson "A background in human physiology" %>% "PhD in bioinformatics/computational biology (in-progress)" %>% "Venturing into the field of data science, with the little bit I've learnt so far" Colin FAY @_ColinFay - https://rtask.thinkr.fr 6 / 130
Here comes {golem} {golem} is an R package that contains a framework for building production-ready Shiny Applications. Colin FAY @_ColinFay - https://rtask.thinkr.fr 16 / 130
Why using {golem}? Automate the boring stuff repetitive tasks Work with reliable tools Gain time developing Simplify deployment Standardize team work About {golem} at ThinkR: Internal need, used on a daily basis Need reliable tooling for deploying to our clients' environments Build and share good practices globally Promote R & Shiny in production Why {golem}? Colin FAY @_ColinFay - https://rtask.thinkr.fr 17 / 130
What's a "prod-ready" Shiny App? Has meta data (DESCRIPTION) Divided in functions (R/) Tested (tests/) With dependencies (NAMESPACE) Documented (man/ & vignettes) Colin FAY @_ColinFay - https://rtask.thinkr.fr 18 / 130
{golem} central philosophy Shiny App As a Package The plus side: everything you know about package development works with {golem}. Notably: Documentation Testing Colin FAY @_ColinFay - https://rtask.thinkr.fr 19 / 130
If not install.package("golem") If still not remotes::install_github( "thinkr-open/golem", ref = "bf9d041" ) ⚠ Warning ⚠ packageVersion("golem") [1] '0.2.1' https://github.com/ThinkR-open/golem/releases remotes::install_local("path/to/archive") 05:00 Colin FAY @_ColinFay - https://rtask.thinkr.fr 21 / 130
app_server.R #' The application server-side #' #' @param input,output,session Internal parameters for {shiny}. #' DO NOT REMOVE. #' @import shiny #' @noRd app_server <- function( input, output, session ) { # List the first level callModules here } server logic can be thought of as a drop in replacement of server.R series of callModule() (if used) Colin FAY @_ColinFay - https://rtask.thinkr.fr 25 / 130
app_ui.R #' @param request Internal parameter for `{shiny}`. #' DO NOT REMOVE. #' @import shiny #' @noRd app_ui <- function(request) { tagList( # Leave this function for adding external resources golem_add_external_resources(), # List the first level UI elements here fluidPage( h1("golex") ) ) } UI counterpart put UI content after the # List the first level UI elements here line Colin FAY @_ColinFay - https://rtask.thinkr.fr 26 / 130
app_ui.R golem_add_external_resources <- function(){ add_resource_path( 'www', app_sys('app/www') ) tags$head( favicon(), bundle_resources( path = app_sys('app/www'), app_title = 'golex' ) # Add here other external resources # for example, you can add shinyalert::useShinyalert() ) } Used to add external resources Will integrate CSS and JS in the app Colin FAY @_ColinFay - https://rtask.thinkr.fr 27 / 130
app_config.R #' Access files in the current app #' #' @param ... Character vector specifying directory and or file to #' point to inside the current package. #' #' @noRd app_sys <- function(...){ system.file(..., package = "golex") } app_sys("x") will refer to inst/x Colin FAY @_ColinFay - https://rtask.thinkr.fr 28 / 130
golem-config.yml default: golem_name: golex golem_version: 0.0.0.9000 app_prod: no production: app_prod: yes dev: golem_wd: !expr here::here() Uses the {config} format Can be safely ignored if you don't feel like you need it
Colin FAY @_ColinFay - https://rtask.thinkr.fr 30 / 130
run_app.R About with_golem_options Allows to pass arguments to run_app() will later be callable with golem::get_golem_options() Colin FAY @_ColinFay - https://rtask.thinkr.fr 32 / 130
Understanding {golem} inst/app/www/ Host external files, notably the one created with: golem::add_css_file() golem::add_js_file() golem::add_js_handler() golem::use_favicon() (More on the later...) Colin FAY @_ColinFay - https://rtask.thinkr.fr 33 / 130
About the dev/ folder fs::dir_tree("golex/dev") golex/dev ├── 01_start.R ├── 02_dev.R ├── 03_deploy.R └── run_dev.R Four files that bundle the golem workflow: 01_start.R: run once at the beginning of the project 02_dev.R: day to day development 03_deploy.R: to use before sending to prod run_dev.R: to relaunch your app during development Colin FAY @_ColinFay - https://rtask.thinkr.fr 35 / 130
01_start.R Fill the DESCRIPTION file golem::fill_desc( pkg_name = "golex", # The Name of the package containing the App pkg_title = "PKG_TITLE", # The Title of the package containing the App pkg_description = "PKG_DESC.", # The Description of the package containing the App author_first_name = "AUTHOR_FIRST", # Your First Name author_last_name = "AUTHOR_LAST", # Your Last Name author_email = "[email protected]", # Your Email repo_url = NULL # The (optional) URL of the GitHub Repo ) 02:00 Colin FAY @_ColinFay - https://rtask.thinkr.fr 36 / 130
01_start.R To be launched for setting elements: golem::set_golem_options() Fills the yaml file golem::use_recommended_tests() Sets common dependencies Colin FAY @_ColinFay - https://rtask.thinkr.fr 37 / 130
01_start.R {usethis} commonly used calls usethis::use_mit_license( name = "Golem User" ) # You can set another license here usethis::use_readme_rmd( open = FALSE ) usethis::use_code_of_conduct() usethis::use_lifecycle_badge( "Experimental" ) usethis::use_news_md( open = FALSE ) usethis::use_git() Colin FAY @_ColinFay - https://rtask.thinkr.fr 38 / 130
Application logic vs business logic Application logic: the things that make your Shiny app interactive Business logic: core algorithms and functions that make your application specific to your area of work Keep them separated! Colin FAY @_ColinFay - https://rtask.thinkr.fr 44 / 130
Structuring your app Naming convention: app_*: global infrastructure functions fct_*: business logic functions mod_*: file with ONE module (ui + server) utils_*: cross module utilitarian functions *_ui_* / *_server_*: relates to UI and SERVER Colin FAY @_ColinFay - https://rtask.thinkr.fr 45 / 130
Why bother? Separation of business & app logic is crucial: Easier to work on functions when they are not inside the app (you don't need to relaunch the app every time) You can test your business logic with classical package testing tools You can use the business logic functions outside the app Colin FAY @_ColinFay - https://rtask.thinkr.fr 48 / 130
Structuring your app With your nearest neighbor, think about where to put these functions (and how to name them): A module that contains the "About" panel of the App A function that takes a number n, a table, and return the n first rows of the table A function that takes a string, and returns this string as an html tag colored in red. A function that connects to the mongo database 05:00 Colin FAY @_ColinFay - https://rtask.thinkr.fr 49 / 130
02_dev.R golem::add_fct( "helpers" ) golem::add_utils( "data_ui" ) ● Go to R/fct_helpers.R ● Go to R/utils_data_ui.R Creates fct_* and utils_* files golem::add_module( name = "my_first_module") ✓ File created at R/mod_my_first_module.R ● Go to R/mod_my_first_module.R Builds a skeleton for a Shiny Module Colin FAY @_ColinFay - https://rtask.thinkr.fr 50 / 130
About Shiny Modules "Pieces" of Shiny Apps Always come in pair (UI + Server) Manage the unique IDs necessity of Shiny "Functionnalize" your app Colin FAY @_ColinFay - https://rtask.thinkr.fr 52 / 130
02_dev.R #' my_first_module Server Function #' #' @noRd mod_my_first_module_server <- function(input, output, session){ ns <- session$ns } ## To be copied in the UI # mod_my_first_module_ui("my_first_module_ui_1") ## To be copied in the server # callModule(mod_my_first_module_server, "my_first_module_ui_1") Colin FAY @_ColinFay - https://rtask.thinkr.fr 59 / 130
About the NAMESPACE file One of the most important files of your package ⚠ NEVER EDIT BY HAND ⚠ Describes how your package interacts with R, and with other packages Lists functions that are exported (from your app) and imported (from other packages) Colin FAY @_ColinFay - https://rtask.thinkr.fr 62 / 130
What's a dependency? Your app needs external functions (at least from {shiny}) The DESCRIPTION file contains the package dependencies Are added to the DESCRIPTION with: usethis::use_package("attempt") Colin FAY @_ColinFay - https://rtask.thinkr.fr 63 / 130
What's a dependency? You also need to add tags on top of each functions that specify what deps are imported Either with @import (a whole package) and @importFrom (a specific function). golem built modules, by default, import elements from {shiny}: #' @importFrom shiny NS tagList Colin FAY @_ColinFay - https://rtask.thinkr.fr 64 / 130
Do this for EACH function #' @import magrittr #' @importFrom stats na.omit mean_no_na <- function(x){ x <- x %>% na.omit() sum(x)/length(x) } You can use import or importFrom. The better is to use importFrom, for preventing namespace conflict. Add to EACH function. It will take some time, but it's better on the long run. Colin FAY @_ColinFay - https://rtask.thinkr.fr 65 / 130
How many dependencies? There is no perfect answer. Just keep in mind that depending on another package means that : you will potentially have to recode your package if some breaking change happen in your dependencies If one of your dependencies is removed from CRAN, you will be removed too The more you have deps, the longer it takes to deploy the app Colin FAY @_ColinFay - https://rtask.thinkr.fr 66 / 130
Adding datasets You might need internal data inside your app Call usethis::use_data_raw from dev/02_dev.R ## Add internal datasets ---- ## If you have data in your package usethis::use_data_raw( name = "dataset" ) Creates data-raw/dataset.R Inside this file: ## code to prepare `dataset` dataset goes here usethis::use_data("dataset") Colin FAY @_ColinFay - https://rtask.thinkr.fr 68 / 130
golem_add_external_resources() In app_ui.R, the golem_add_external_resources() functions add to the app every .css and .js file contained in inst/app/www golem_add_external_resources <- function(){ add_resource_path( 'www', app_sys('app/www') ) tags$head( favicon(), bundle_resources( path = app_sys('app/www'), app_title = 'golex' ) # Add here other external resources # for example, you can add shinyalert::useShinyalert() ) } Colin FAY @_ColinFay - https://rtask.thinkr.fr 74 / 130
What is CSS? CSS, (Cascading Style Sheets), is one of main technologies that power the web today, along with HTML and JavaScript CSS handles the design, i.e. the visual rendering of the web page: the color of the header, the font, the background, and everything we see Example: try the Web Developer extension of Google Chrome, and remove the CSS from a page In Shiny, there is a default CSS: the one from Bootstrap 3 Colin FAY @_ColinFay - https://rtask.thinkr.fr 76 / 130
Getting started with CSS Written in a .css file CSS syntax is composed of two elements: a selector, and a declaration CSS selector: describes how to identify the HTML tags Declaration: how is the selected style affected h2 { color:red; } Colin FAY @_ColinFay - https://rtask.thinkr.fr 77 / 130
CSS selectors name, id, or class One name == h2, write the name as-is: h2 id == titleone, prefix the id with #: #titleone class == standard, prefix the class with .: .standard Colin FAY @_ColinFay - https://rtask.thinkr.fr 78 / 130
02_dev.R golem::add_css_file( "custom" ) ✓ File created at /Users/colin/golex/inst/app/www/custom.css ✓ File automatically linked in `golem_add_external_resources()`. ● Go to /Users/colin/golex/inst/app/www/custom.css Colin FAY @_ColinFay - https://rtask.thinkr.fr 82 / 130
What is JavaScript? Scripting language that allows interactivity on webpages At the core of Shiny: building a Shiny app is building a JavaScript app that can talk with an R session Invisible for most Shiny users Built-in in all browser Colin FAY @_ColinFay - https://rtask.thinkr.fr 84 / 130
A webpage is a DOM (Document Object Model) Nodes of a tree where the document is the root Contain parents and children Nodes contain attributes DOM Colin FAY @_ColinFay - https://rtask.thinkr.fr 86 / 130
Query DOM elements Wesh document.querySelector("#pouet") // With the ID document.querySelectorAll(".plouf") // With the class document.getElementById("pouet") // With the ID document.getElementsByName("plop") // With the name attribute document.getElementsByClassName("plouf") // With the class document.getElementsByTagName("div") // With the tag Colin FAY @_ColinFay - https://rtask.thinkr.fr 87 / 130
About jQuery & jQuery selectors The jQuery framework is natively included in Shiny jQuery makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. Very popular JavaScript library which is designed to manipulate the DOM, its events and its elements jQuery, the most popular JavaScript library ever created, is used in 85.03% of desktop pages and 83.46% of mobile pages. https://almanac.httparchive.org/en/2019/javascript#first-party- vs-third-party Colin FAY @_ColinFay - https://rtask.thinkr.fr 92 / 130
About jQuery & jQuery selectors jQuery selectors are CSS selectors, plus some custom Are used inside $() Then call a built-in function $("#plop").hide() Hides the element of class "plop" Colin FAY @_ColinFay - https://rtask.thinkr.fr 93 / 130
Examples $('#id').show(); and $('#id').hide() Show and hide $('#id').css("color", "red); Changes the CSS to red $("#id").text( "this" ); Changes the text content to 'this' $("#id").remove(); Removes element from UI Colin FAY @_ColinFay - https://rtask.thinkr.fr 94 / 130
From R to JavaScript In inst/app/www/handlers.js $( document ).ready(function() { Shiny.addCustomMessageHandler('fun', function(arg) { }) }); Called from R with session$sendCustomMessage("fun", list()) Colin FAY @_ColinFay - https://rtask.thinkr.fr 97 / 130
From JavaScript to R In inst/app/www/handlers.js Shiny.setInputValue("rand", Math.random()) Received in R with observeEvent( input$rand , { print( input$rand ) }) Colin FAY @_ColinFay - https://rtask.thinkr.fr 99 / 130
{golem} js functions {golem} comes with a series of built-in functions They can be called with golem::invoke_js() golem::invoke_js("showid", ns("plot")) See ?golem::activate_js Colin FAY @_ColinFay - https://rtask.thinkr.fr 101 / 130
Why automate code testing? To save time! Work with others Transfer the project Long term stability Colin FAY @_ColinFay - https://rtask.thinkr.fr 106 / 130
Use test in a "defensive programming" approach to prevent bugs. Don't trust yourself in 6 months. Be sure to send in production a package with minimum bugs. Good news: you're already writing test, you just didn't know that before. Write tests before it's too late Colin FAY @_ColinFay - https://rtask.thinkr.fr 107 / 130
What do we test Focus on business logic testing: it's more important to test that the algorithm is still accurate than testing the UI As we've separated businness logic from application logic, we use package development testing tools We'll use the {testthat}
Colin FAY @_ColinFay - https://rtask.thinkr.fr 108 / 130
01_start.R ## Init Testing Infrastructure ---- ## Create a template for tests golem::use_recommended_tests() Recommended tests 02_dev.R ## Tests ---- ## Add one line by test you want to create usethis::use_test( "app" ) Add custom test Colin FAY @_ColinFay - https://rtask.thinkr.fr 109 / 130
Test functions Start with expect_* Take two elements: Actual result & the expected result. If the test is not passed, the function returns an error. If the test passes, the function returns nothing. library(testthat, warn.conflicts = FALSE) expect_equal(10, 10) a <- sample(1:10, 1) b <- sample(1:10, 1) expect_equal(a+b, 200) Error: a + b not equal to 200. 1/1 mismatches [1] 5 - 200 == -195 Colin FAY @_ColinFay - https://rtask.thinkr.fr 111 / 130
Launch tests devtools::test() Launches only the tests infrastructure devtools::check() Launches the tests + a series of default tests Colin FAY @_ColinFay - https://rtask.thinkr.fr 114 / 130
R CMD check To test the code more globally, in the command line (i.e. in the terminal): R CMD check. Or simply the devtools::check() function in your R session. More tests are performed with check than with devtools::test(), which "only" performs the tests in the test folder. This command runs around 50 different tests. Is performed when you click the "Check" button on the Build tab of RStudio. Colin FAY @_ColinFay - https://rtask.thinkr.fr 115 / 130
Test with rhub remotes::install_github("r-hub/rhub") # or install.packages("rhub") # verify your email library(rhub) validate_email() Colin FAY @_ColinFay - https://rtask.thinkr.fr 117 / 130
## RStudio ---- ## If you want to deploy on RStudio related platforms golem::add_rstudioconnect_file() golem::add_shinyappsio_file() golem::add_shinyserver_file() ## Docker ---- ## If you want to deploy via a generic Dockerfile golem::add_dockerfile() ## If you want to deploy to ShinyProxy golem::add_dockerfile_shinyproxy() ## If you want to deploy to Heroku golem::add_dockerfile_heroku() Send to prod Once everything is tested, you can send to prod using {golem} Deploy on a server, or build as a tar.gz with pkgbuild::build() Colin FAY @_ColinFay - https://rtask.thinkr.fr 126 / 130