Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[Workshop] Building Successful Shiny Apps with ...

Colin Fay
March 06, 2020

[Workshop] Building Successful Shiny Apps with {golem}

Workshop given at satRday Johannesburg 2020

Colin Fay

March 06, 2020
Tweet

More Decks by Colin Fay

Other Decks in Technology

Transcript

  1. Building Successful Shiny Apps with {golem} Colin Fay - ThinkR

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 1 / 130
  2. 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
  3. Data Science engineering, focused on R.  Training  Software

    Engineering  R in production  Consulting ThinkR Colin FAY  @_ColinFay - https://rtask.thinkr.fr 5 / 130
  4. $ 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
  5. Introduce yourself Take 4 minutes to introduce yourself to your

    nearest neighbour 04:00 Colin FAY  @_ColinFay - https://rtask.thinkr.fr 8 / 130
  6. Schedule 9:00 10:30 - Part 00 & 01: Introduction 10:30

    11:00 - Break ☕ / 11:00 12:30 - Part 02: Developing a golem app 12:30 13:30 - Lunch 13:30 15:00 - JavaScript & CSS 13:30 15:00 - Break ☕ / 15:30 17:00 - Testing & Sending to prod Colin FAY  @_ColinFay - https://rtask.thinkr.fr 10 / 130
  7. RED - HELP NEEDED GREEN - OK Post it Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 12 / 130
  8. PART 01 - ABOUT GOLEM Colin FAY  @_ColinFay -

    https://rtask.thinkr.fr 15 / 130
  9. 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
  10. 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
  11. {golem} central philosophy Shiny App As a Package 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
  12. {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
  13. 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
  14. Understanding {golem} fs::dir_tree("golex") golex ├── DESCRIPTION ├── NAMESPACE ├── R

    │ ├── app_config.R │ ├── app_server.R │ ├── app_ui.R │ └── run_app.R ├── dev │ ├── 01_start.R │ ├── 02_dev.R │ ├── 03_deploy.R │ └── run_dev.R ├── inst │ ├── app │ │ └── www │ │ └── favicon.ico │ └── golem-config.yml └── man └── run_app.Rd Colin FAY  @_ColinFay - https://rtask.thinkr.fr 23 / 130
  15. Understanding {golem} Standard package files (i.e. not {golem}-specific): DESCRIPTION: meta-data

    NAMESPACE: exported functions + functions from other R/: functions (everything in {golem} is a function) man/: documentation Colin FAY  @_ColinFay - https://rtask.thinkr.fr 24 / 130
  16. 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
  17. 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
  18. 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
  19. 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
  20. app_config.R get_golem_config <- function( value, config = Sys.getenv("R_CONFIG_ACTIVE", "default"), use_parent

    = TRUE ){ config::get( value = value, config = config, # Modify this if your config file is somewhere else: file = app_sys("golem-config.yml"), use_parent = use_parent ) } Retrieves elements from inst/golem-config.yml Colin FAY  @_ColinFay - https://rtask.thinkr.fr 29 / 130
  21. 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
  22. run_app.R #' Run the Shiny Application #' #' @param ...

    A series of options to be used inside the app. #' #' @export #' @importFrom shiny shinyApp #' @importFrom golem with_golem_options run_app <- function( ... ) { with_golem_options( app = shinyApp( ui = app_ui, server = app_server ), golem_opts = list(...) ) } Launches the app Colin FAY  @_ColinFay - https://rtask.thinkr.fr 31 / 130
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. : R/connect.R : R/summary.R : R/plot.R : R/odbc.R Why bother?

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 46 / 130
  32. : R/connect.R : R/summary.R : R/plot.R : R/odbc.R : R/fct_connect.R

    : R/mod_summary.R : R/utils_plot.R : R/fct_odbc.R Why bother? Colin FAY  @_ColinFay - https://rtask.thinkr.fr 47 / 130
  33. 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
  34. 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
  35. 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
  36. 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
  37. One million “Validate” buttons library(shiny) ui <- function(request){ fluidPage( sliderInput("choice1",

    "choice 1", 1, 10, 5), actionButton("validate1", "Validate choice 1"), sliderInput("choice2", "choice 2", 1, 10, 5), actionButton("validate2", "Validate choice 2") ) } server <- function(input, output, session){ observeEvent( input$validate1 , { print(input$choice1) }) observeEvent( input$validate2 , { print(input$choice2) }) } shinyApp(ui, server) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 53 / 130
  38. Why Shiny Modules Functionalizing Shiny elements: name_ui <- function(id){ ns

    <- NS(id) tagList( sliderInput(ns("choice"), "Choice", 1, 10, 5), actionButton(ns("validate"), "Validate Choice") ) } name_server <- function(input, output, session){ observeEvent( input$validate , { print(input$choice) }) } Colin FAY  @_ColinFay - https://rtask.thinkr.fr 54 / 130
  39. Why Shiny Modules Functionalizing Shiny elements: library(shiny) ui <- function(request){

    fluidPage( name_ui("name_ui_1"), name_ui("name_ui_2") ) } server <- function(input, output, session){ callModule(name_server, "name_ui_1") callModule(name_server, "name_ui_2") } shinyApp(ui, server) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 55 / 130
  40. Why Shiny Modules id <- "name_ui_1" ns <- NS(id) ns("choice")

    [1] "name_ui_1-choice" name_ui <- function(id, butname){ ns <- NS(id) tagList( actionButton(ns("validate"), butname) ) } name_ui("name_ui_1", "Validate Choice") name_ui("name_ui_2", "Validate Choice, again") <button id="name_ui_1-validate" type="button" class="btn btn-default action- button">Validate Choice</button> <button id="name_ui_2-validate" type="button" class="btn btn-default action- button">Validate Choice, again</button> Colin FAY  @_ColinFay - https://rtask.thinkr.fr 56 / 130
  41. 02_dev.R golem::add_module( name = "my_first_module") Builds a skeleton for a

    Shiny Module Colin FAY  @_ColinFay - https://rtask.thinkr.fr 57 / 130
  42. 02_dev.R #' my_first_module UI Function #' #' @description A shiny

    Module. #' #' @param id,input,output,session Internal parameters for {shiny}. #' #' @noRd #' #' @importFrom shiny NS tagList mod_my_first_module_ui <- function(id){ ns <- NS(id) tagList( ) } Colin FAY  @_ColinFay - https://rtask.thinkr.fr 58 / 130
  43. 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
  44. ⚠ DON'T FORGET THE NS() IN THE UI ⚠ Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 60 / 130
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. Adding datasets ## code to prepare `dataset` dataset goes here

    library(tidyverse) my_app_dataset <- read.csv("bla/bla/bla.csv") my_app_dataset <- my_app_dataset %>% filter(this == "that") %>% arrange(on_that) %>% select( -contains("this") ) usethis::use_data(my_app_dataset) Now available as my_app_dataset inside your app. Colin FAY  @_ColinFay - https://rtask.thinkr.fr 69 / 130
  52. PART 03 - JavaScript and CSS Colin FAY  @_ColinFay

    - https://rtask.thinkr.fr 72 / 130
  53. 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
  54. 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
  55. 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
  56. CSS selectors name, id, or class <h2 id = "titleone"

    class = "standard">One</h2> 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
  57. CSS selectors Can be combined div.standard > p { color:red;

    } Selects all <p> inside a <div class = "standard"> Colin FAY  @_ColinFay - https://rtask.thinkr.fr 79 / 130
  58. CSS pseudo-class React to events a:hover { color:red; } Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 80 / 130
  59. CSS properties Declaration block: key: value; text-align: center; color: red;

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 81 / 130
  60. 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
  61. 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
  62. Why JavaScript? Improve the quality of your front-end Lower back-end

    code Integrate external librairies Colin FAY  @_ColinFay - https://rtask.thinkr.fr 85 / 130
  63. 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
  64. Query DOM elements <div id = "pouet" name="plop" class =

    "plouf">Wesh</div> 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
  65. DOM events click / dblclick focus keypress, keydown, keyup mousedown,

    mouseenter, mouseleave, mousemove, mouseout, mouseover, mouseup scroll https://developer.mozilla.org/fr/docs/Web/Events Colin FAY  @_ColinFay - https://rtask.thinkr.fr 88 / 130
  66. Event Listeners <input type="text" onKeyPress = "alert('plop')"> <input type="text" id

    = "plop"> <script> document.getElementById("plop").addEventListener("keypress", function(){ alert("pouet") }) </script> Colin FAY  @_ColinFay - https://rtask.thinkr.fr 89 / 130
  67. In Shiny Built-in tags$button( "Show", onclick = "$('#plot').show()" ) tagAppendAttributes()

    plotOutput( "plot" ) %>% tagAppendAttributes( onclick = "alert('hello world')" ) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 90 / 130
  68. In Shiny library(shiny) library(magrittr) ui <- function(request){ tagList( textInput( "txt",

    "Enter txt" ) %>% tagAppendAttributes( onKeyPress = "alert('plop')" ) ) } server <- function(input, output, session){ } shinyApp(ui, server) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 91 / 130
  69. 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
  70. 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
  71. 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
  72. 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
  73. In inst/app/www/handlers.js $( document ).ready(function() { Shiny.addCustomMessageHandler('computed', function(mess) { alert("Computed

    " + mess.what + " in " + mess.sec + " secs"); }) }); Called from R with observe({ deb <- Sys.time() Sys.sleep(5) session$sendCustomMessage( "computed", list( what = "plop", sec = round(Sys.time() - deb) ) ) }) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 98 / 130
  74. 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
  75. 02_dev.R golem::add_js_file( "script" ) golem::add_js_handler( "handlers" ) golem::add_js_handler( "handlers" )

    $( document ).ready(function() { Shiny.addCustomMessageHandler('fun', function(arg) { }) }); Colin FAY  @_ColinFay - https://rtask.thinkr.fr 100 / 130
  76. {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
  77. PART 04 TEST AND SEND Colin FAY  @_ColinFay -

    https://rtask.thinkr.fr 104 / 130
  78. Everything which is not tested will, at last, break. Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 105 / 130
  79. 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
  80. 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
  81. 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
  82. 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
  83. Writing custom tests test_that( "details series 1", { test1a test1b

    } ) test_that("details series 2", { test2a test2b } ) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 110 / 130
  84. 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
  85. {testthat} expectations library(testthat) grep("^expect", ls("package:testthat"), value = TRUE) [1] "expect"

    "expect_condition" [3] "expect_cpp_tests_pass" "expect_equal" [5] "expect_equal_to_reference" "expect_equivalent" [7] "expect_error" "expect_failure" [9] "expect_false" "expect_gt" [11] "expect_gte" "expect_identical" [13] "expect_invisible" "expect_is" [15] "expect_known_failure" "expect_known_hash" [17] "expect_known_output" "expect_known_value" [19] "expect_length" "expect_less_than" [21] "expect_lt" "expect_lte" [23] "expect_mapequal" "expect_match" [25] "expect_message" "expect_more_than" [27] "expect_named" "expect_null" [29] "expect_output" "expect_output_file" [31] "expect_reference" "expect_s3_class" [33] "expect_s4_class" "expect_setequal" [35] "expect_silent" "expect_success" [37] "expect_that" "expect_true" [39] "expect_type" "expect_vector" Colin FAY  @_ColinFay - https://rtask.thinkr.fr 112 / 130
  86. 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
  87. 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
  88. Test with rhub {rhub} is a package that allows you

    to test for several OS: library(rhub) ls("package:rhub") [1] "check" "check_for_cran" [3] "check_on_centos" "check_on_debian" [5] "check_on_fedora" "check_on_linux" [7] "check_on_macos" "check_on_solaris" [9] "check_on_ubuntu" "check_on_windows" [11] "check_with_rdevel" "check_with_roldrel" [13] "check_with_rpatched" "check_with_rrelease" [15] "check_with_sanitizers" "check_with_valgrind" [17] "get_check" "last_check" [19] "list_my_checks" "list_package_checks" [21] "list_validated_emails" "local_check_linux" [23] "local_check_linux_images" "platforms" [25] "validate_email" Colin FAY  @_ColinFay - https://rtask.thinkr.fr 116 / 130
  89. 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
  90. Test with rhub rhub::check() Which platforms are supported? rhub::platforms() debian-clang-devel:

    Debian Linux, R-devel, clang, ISO-8859-15 locale debian-gcc-devel: Debian Linux, R-devel, GCC debian-gcc-devel-nold: Debian Linux, R-devel, GCC, no long double debian-gcc-patched: Debian Linux, R-patched, GCC debian-gcc-release: Debian Linux, R-release, GCC fedora-clang-devel: Fedora Linux, R-devel, clang, gfortran fedora-gcc-devel: Fedora Linux, R-devel, GCC linux-x86_64-centos6-epel: CentOS 6, stock R from EPEL Colin FAY  @_ColinFay - https://rtask.thinkr.fr 118 / 130
  91. ## 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