[Workshop] Building Successful Shiny Apps with {golem}

[Workshop] Building Successful Shiny Apps with {golem}

Workshop given at satRday Johannesburg 2020

Db8efd836c9a09b71e3d8e1c60d6ea84?s=128

Colin Fay

March 06, 2020
Tweet

Transcript

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

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 1 / 130
  2. PART 00 - INTRODUCTION Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    2 / 130
  3. 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
  4. ThinkR Colin FAY  @_ColinFay - https://rtask.thinkr.fr 4 / 130

  5. Data Science engineering, focused on R.  Training  Software

    Engineering  R in production  Consulting ThinkR Colin FAY  @_ColinFay - https://rtask.thinkr.fr 5 / 130
  6. $ 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
  7. Now, it's your turn! Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    7 / 130
  8. Introduce yourself Take 4 minutes to introduce yourself to your

    nearest neighbour 04:00 Colin FAY  @_ColinFay - https://rtask.thinkr.fr 8 / 130
  9. Today's schedule Colin FAY  @_ColinFay - https://rtask.thinkr.fr 9 /

    130
  10. 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
  11. Logistics Colin FAY  @_ColinFay - https://rtask.thinkr.fr 11 / 130

  12. RED - HELP NEEDED GREEN - OK Post it Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 12 / 130
  13. Logistics https://gitlab.com/colin_fay/golem-joburg WiFi ?? @_ColinFay @satRday_ZAF https://joburg2020.satrdays.org/code-of-conduct-2020.html Colin FAY 

    @_ColinFay - https://rtask.thinkr.fr 13 / 130
  14. Let's go! Colin FAY  @_ColinFay - https://rtask.thinkr.fr 14 /

    130
  15. PART 01 - ABOUT GOLEM Colin FAY  @_ColinFay -

    https://rtask.thinkr.fr 15 / 130
  16. 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
  17. 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
  18. {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
  19. {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
  20. Understanding {golem} Colin FAY  @_ColinFay - https://rtask.thinkr.fr 20 /

    130
  21. 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
  22. Create a {golem} 01:00 Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    22 / 130
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. About the dev/ folder Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    34 / 130
  35. 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
  36. 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 = "AUTHOR@MAIL.COM", # Your Email repo_url = NULL # The (optional) URL of the GitHub Repo ) 02:00 Colin FAY  @_ColinFay - https://rtask.thinkr.fr 36 / 130
  37. 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
  38. 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
  39. 01_start.R golem::use_recommended_deps() golem::use_favicon() golem::use_utils_ui() & golem::use_utils_server() Colin FAY  @_ColinFay

    - https://rtask.thinkr.fr 39 / 130
  40. 01_start.R Colin FAY  @_ColinFay - https://rtask.thinkr.fr 40 / 130

  41. Your turn! https://github.com/ColinFay/golem- joburg/tree/master/exo-part-1 Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    41 / 130
  42. PART 02 - DEV Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    42 / 130
  43. STRUCTURING YOUR APPLICATION Colin FAY  @_ColinFay - https://rtask.thinkr.fr 43

    / 130
  44. 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
  45. 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
  46. : R/connect.R : R/summary.R : R/plot.R : R/odbc.R Why bother?

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 46 / 130
  47. : 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
  48. 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
  49. 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
  50. 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
  51. USING SHINY MODULES Colin FAY  @_ColinFay - https://rtask.thinkr.fr 51

    / 130
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. ⚠ DON'T FORGET THE NS() IN THE UI ⚠ Colin

    FAY  @_ColinFay - https://rtask.thinkr.fr 60 / 130
  61. UNDERSTANDING NAMESPACE & DEPENDENCIES Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    61 / 130
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. ADDING INTERNAL DATASETS Colin FAY  @_ColinFay - https://rtask.thinkr.fr 67

    / 130
  68. 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
  69. 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
  70. Demo time Colin FAY  @_ColinFay - https://rtask.thinkr.fr 70 /

    130
  71. Your turn! https://github.com/ColinFay/golem- joburg/tree/master/exo-part-2 Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    71 / 130
  72. PART 03 - JavaScript and CSS Colin FAY  @_ColinFay

    - https://rtask.thinkr.fr 72 / 130
  73. golem_add_external_resources() Colin FAY  @_ColinFay - https://rtask.thinkr.fr 73 / 130

  74. 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
  75. A GENTLE INTRODUCTION TO CSS Colin FAY  @_ColinFay -

    https://rtask.thinkr.fr 75 / 130
  76. 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
  77. 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
  78. 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
  79. 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
  80. CSS pseudo-class React to events a:hover { color:red; } Colin

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

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 81 / 130
  82. 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
  83. A GENTLE INTRODUCTION TO JAVASCRIPT Colin FAY  @_ColinFay -

    https://rtask.thinkr.fr 83 / 130
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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
  95. Add event var x = $("#pouet"); x.on("click", function(){ $(this).attr("value", parseInt($(this).attr("value"))

    + 1 ) }) Colin FAY  @_ColinFay - https://rtask.thinkr.fr 95 / 130
  96. JavaScript <-> Shiny communication Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    96 / 130
  97. 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
  98. 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
  99. 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
  100. 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
  101. {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
  102. Demo time Colin FAY  @_ColinFay - https://rtask.thinkr.fr 102 /

    130
  103. Your turn! https://github.com/ColinFay/golem- joburg/tree/master/exo-part-3 Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    103 / 130
  104. PART 04 TEST AND SEND Colin FAY  @_ColinFay -

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

    FAY  @_ColinFay - https://rtask.thinkr.fr 105 / 130
  106. 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
  107. 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. {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
  113. {golem} test helpers golem::expect_html_equal() golem::expect_shinytag() golem::expect_shinytaglist() Colin FAY  @_ColinFay

    - https://rtask.thinkr.fr 113 / 130
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. Test with rhub Colin FAY  @_ColinFay - https://rtask.thinkr.fr 119

    / 130
  120. {shinytest} golem::add_rstudioconnect_file() shinytest::recordTest(".") Colin FAY  @_ColinFay - https://rtask.thinkr.fr 120

    / 130
  121. {shinytest} Colin FAY  @_ColinFay - https://rtask.thinkr.fr 121 / 130

  122. {shinytest} Colin FAY  @_ColinFay - https://rtask.thinkr.fr 122 / 130

  123. {shinytest} Colin FAY  @_ColinFay - https://rtask.thinkr.fr 123 / 130

  124. {shinytest} Colin FAY  @_ColinFay - https://rtask.thinkr.fr 124 / 130

  125. SEND TO PRODUCTION Colin FAY  @_ColinFay - https://rtask.thinkr.fr 125

    / 130
  126. ## 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
  127. Demo time Colin FAY  @_ColinFay - https://rtask.thinkr.fr 127 /

    130
  128. Your turn! https://github.com/ColinFay/golem- joburg/tree/master/exo-part-4 Colin FAY  @_ColinFay - https://rtask.thinkr.fr

    128 / 130
  129. THANK YOU Colin FAY  @_ColinFay - https://rtask.thinkr.fr 129 /

    130
  130. More resources http://golemverse.org/ http://connect.thinkr.fr/engineering-shiny/ https://r-pkgs.org/ twitter.com/_ColinFay Colin FAY  @_ColinFay

    - https://rtask.thinkr.fr 130 / 130