$30 off During Our Annual Pro Sale. View Details »

[Workshop] Building Successful Shiny Apps with {golem}

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

    View Slide

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

    View Slide

  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

    View Slide

  4. ThinkR
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 4 / 130

    View Slide

  5. Data Science engineering,
    focused on R.
     Training
     Software Engineering
     R in production
     Consulting
    ThinkR
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 5 / 130

    View Slide

  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

    View Slide

  7. Now, it's your turn!
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 7 / 130

    View Slide

  8. Introduce yourself
    Take 4 minutes to introduce yourself to your nearest neighbour
    04:00
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 8 / 130

    View Slide

  9. Today's schedule
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 9 / 130

    View Slide

  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

    View Slide

  11. Logistics
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 11 / 130

    View Slide

  12. RED - HELP NEEDED GREEN - OK
    Post it
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 12 / 130

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  20. Understanding {golem}
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 20 / 130

    View Slide

  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

    View Slide

  22. Create a {golem}
    01:00
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 22 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  34. About the dev/ folder
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 34 / 130

    View Slide

  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

    View Slide

  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 = "[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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  51. USING SHINY MODULES
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 51 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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")
    Validate Choice
    Validate Choice, again
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 56 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  60. ⚠ DON'T FORGET THE NS() IN THE UI

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 60 / 130

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  67. ADDING INTERNAL DATASETS
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 67 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  70. Demo time

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 70 / 130

    View Slide

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

    View Slide

  72. PART 03 - JavaScript and CSS
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 72 / 130

    View Slide

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

    View Slide

  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

    View Slide

  75. A GENTLE INTRODUCTION TO CSS
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 75 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  78. 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

    View Slide

  79. CSS selectors
    Can be combined
    div.standard > p {
    color:red;
    }
    Selects all inside a
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 79 / 130

    View Slide

  80. CSS pseudo-class
    React to events
    a:hover {
    color:red;
    }
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 80 / 130

    View Slide

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

    View Slide

  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

    View Slide

  83. A GENTLE INTRODUCTION TO
    JAVASCRIPT
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 83 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  87. 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

    View Slide

  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

    View Slide

  89. Event Listeners


    <br/>document.getElementById("plop").addEventListener("keypress", function(){<br/>alert("pouet")<br/>})<br/>
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 89 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  102. Demo time

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 102 / 130

    View Slide

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

    View Slide

  104. PART 04 TEST AND SEND
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 104 / 130

    View Slide

  105. Everything which is not tested will, at last,
    break.
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 105 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  113. {golem} test helpers
    golem::expect_html_equal()
    golem::expect_shinytag()
    golem::expect_shinytaglist()
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 113 / 130

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  119. Test with rhub
    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 119 / 130

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  127. Demo time

    Colin FAY  @_ColinFay - https://rtask.thinkr.fr 127 / 130

    View Slide

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

    View Slide

  129. THANK YOU

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

    View Slide

  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

    View Slide