Slide 1

Slide 1 text

Building Successful Shiny Apps with {golem} Colin Fay - ThinkR Colin FAY  @_ColinFay - https://rtask.thinkr.fr 1 / 130

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

$ 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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

{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

Slide 19

Slide 19 text

{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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

: 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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

⚠ DON'T FORGET THE NS() IN THE UI ⚠ Colin FAY  @_ColinFay - https://rtask.thinkr.fr 60 / 130

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

CSS selectors Can be combined div.standard > p { color:red; } Selects all

inside a

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

{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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

{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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

## 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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

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