Additionally, sometimes observeEvent() function will trigger at initialization because of the authentication page. Set init = FALSE to avoid this issue:
Email DataTable as CSV/Excel within Company through SMTP
Create an email button:
Clicking the green email button reveals this dropdown:
UI
require(shinyWidgets)require(shinyAce)dropdownButton(tags$h3("Send Email with List as Attachment"),
splitLayout(radioButtons('format', 'Attach List As:', c('CSV', 'Excel'), inline =TRUE), actionButton('send', "Send", icon = icon("envelope"))),
textInput("to", "To:", value=""), textInput("cc", "Cc:", value=""), textInput("subject", "Subject:", value=NULL), shinyAce::aceEditor("message", value=" ", height = "200px"),
tags$body("Instructions: to send to multiple people, place commas in between emails"),
tags$body("For example: joe@jtv.com, bob@jtv.com"),
tags$body("Note: email is sent from the email that you logged in with"),
status ='success', up =TRUE, icon=icon('envelope'))
Server
require(mailR)observeEvent(input$send, { withProgress(message ='Emailing', value =0, { incProgress(1/3, detail ="Compiling List")if (input$format =="Excel") { path <-paste0('temp_files/filtered_customer_list_', session$token, '.xlsx') xlsx::write.xlsx(values$df, path, row.names = FALSE) } # values$df is the datatable as seen in the above picture
if (input$format =="CSV") { path <-paste0('temp_files/filtered_customer_list_', session$token, '.csv') data.table::fwrite(values$df, path) } incProgress(1/3, detail ="Writing Email") user <-reactiveValuesToList(result_auth)$user if (input$cc =="") { send.mail(from = user, to = input$to, # cc = input$cc, # bcc = input$bcc, subject = input$subject, body = input$message, smtp =list(host.name =-----, port =--), authenticate =FALSE, send =TRUE, attach.files = path) } else { send.mail(from = user, to = input$to, cc = input$cc, # bcc = input$bcc, subject = input$subject, body = input$message, smtp =list(host.name =-----, port =--), authenticate =FALSE, send =TRUE, attach.files = path) } incProgress(1/3, detail ="Finished") }) system(paste("rm -f", path)) })
# NOTE: values$df is the dataframe you want to downloadoutput$csv <-downloadHandler(# This function returns a string which tells the client browser what name to use when saving the file. filename=function() { paste("customer-filtered-", Sys.Date(), ".csv", sep="") }, # This function should write data to a file given to it by the argument 'file'. content=function(file) { # Write to a file specified by the 'file' argument write.table(values$df, file, row.names =FALSE) } )output$excel <-downloadHandler(filename=function() { paste("customer-filtered-", Sys.Date(), ".xlsx", sep="") }, content=function(file) { xlsx::write.xlsx(values$df, file, row.names =FALSE) } )
Create Report Compiler Tab
Users can compile a report in word/html using screenshots through this tab
How to build:
Create screenshot button
Create report compiling tab
Screenshot Button
Adds screenshot button to top right corner of the dashboard
require(shinyscreenshot) observeEvent(input$screenshot, { # will save the screenshots into a temp directory screenshot(filename = paste0('dashboard_screenshot_', Sys.Date())) })
output$htmlmarkdown <-reactive({ note_in_html(input$markdowninput) })observeEvent(input$preview, { src <-normalizePath('doc/documents/report_compiler_md.Rmd') owd <-setwd(tempdir()) on.exit(setwd(owd)) file.copy(src, 'report_compiler_md.Rmd', overwrite =TRUE) out <- knitr::knit('report_compiler_md.Rmd') values$src <-normalizePath(out) })output$markdown <-renderUI({ if (input$preview >0) { file <- values$src } else { file <-'doc/documents/blank_report_compiler.md' } includeMarkdown(file) })output$build_report =downloadHandler(filename<-function(){ paste("Customer_Segmentation_Report",Sys.Date(),switch(input$format_report, HTML = '.html', Word = '.docx'),sep = "")},
content=function(file) { if (input$format_report=="HTML"){ withProgress(message = 'Download in progress', detail = 'This may take a while...', value = 0, {
src <- normalizePath('doc/documents/report_compiler_html.Rmd')
# temporarily switch to the temp dir, in case you do not have write permission to the current working directory
owd <-setwd(tempdir()) on.exit(setwd(owd)) file.copy(src, 'report_compiler_html.Rmd', overwrite =TRUE) # images will already be in tmp directory so .Rmd file can reference them
library(rmarkdown) out <-render('report_compiler_html.Rmd', html_document())file.rename(out, file) }) ### below is the end of pdf content }else{ withProgress(message = 'Download in progress', detail = 'This may take a while...', value = 0, {
src <- normalizePath('doc/documents/report_compiler_word.Rmd')
owd <-setwd(tempdir()) on.exit(setwd(owd)) file.copy(src, 'report_compiler_word.Rmd', overwrite = TRUE)
library(rmarkdown) out <-render('report_compiler_word.Rmd', word_document())file.rename(out, file) }) } })
report_compiler_html.Rmd - same as above but with output: html_document
report_compiler_word.Rmd - same as above but with output: word_document
DataTable Formatting Function Examples
DataTable formatting is an absolute pain and takes forever. Below are some functions I use to make life easier:
Number Formatting
Function
# applies number formatting to DataTable (currency, rounding, percentages)## @param DataTable: a DataTable object# @param perc_columns: vector of column names that need percentage formatting# @param num_columns: vector of column names that need to be rounded# @param currency_columns: vector of column names that need dollar formatting## @return DataTable with percentages rounded to 2 decimal places and currency/numbers rounded to 0 decimal placesformatstyle_number<-function(DataTable, perc_columns, num_columns, currency_columns) { if (!FALSE%in% perc_columns) { for (i in perc_columns) { DataTable <- DataTable %>%formatPercentage(., i, 2) } } if (!FALSE%in% num_columns) { for (i in num_columns) { DataTable <- DataTable %>%formatCurrency(., i, "", digits =0) } } if (!FALSE%in% currency_columns) { for (i in currency_columns) { DataTable <- DataTable %>%formatCurrency(., i, "$", digits =0) } } DataTable}
# Adds vertical lines into the table for column separation## @param left_columns: vector of column names to place vertical borders on left of# @param right: column name of column to place vertical border at the right of## @return DataTable with vertical line bordersformatstyle_border_vertical<-function(DataTable, left_columns, right) { for (i in left_columns) { DataTable <- DataTable %>%formatStyle(., i, `border-left` ="solid 2px #000") } DataTable %>%formatStyle(., right, `border-right` ="solid 2px #000")}
Conditional Formatting
The below function is more complicated. Ask Jonathan or Tyki for an explanation if it is confusing.
Function
# applies conditional color formatting to a DataTable### @param DataTable: a DataTable object# @param columns: vector of column names to be formatted# @param is_total: TRUE formats the "total" row. FALSE for DataTables without "total" row# @param colors: vector of colors in hex format - must be odd number of colors## @return a DataTable with columns that are colored based on quantiles of numbers in columnformatstyle_color<-function(DataTable, columns, colors) { if (is_total ==TRUE) { for (i in columns) { DataTable <- DataTable %>%formatStyle(., i, backgroundColor =styleInterval(DataTable$x$data[-nrow(DataTable$x$data), ] %>%pull(i)%>%quantile(prob =seq(0, 1, by =1/length(colors))[-c(1, length(colors)+1)]), colors) ) %>%formatStyle(., 1:ncol(DataTable$x$data), valueColumns =1, backgroundColor =styleEqual("Total", "white")) } } else { for (i in columns) { DataTable <- DataTable %>%formatStyle(., i, backgroundColor =styleInterval(DataTable$x$data %>%pull(i)%>%quantile(prob =seq(0, 1, by =1/length(colors))[-c(1, length(colors)+1)],na.rm=TRUE), colors) ) } } DataTable}
Notes:
Datatable$x$data references the dataframe that was put into the datatable() function
SplitLayout() automatically places UI widgets equally spread apart, so it's easier to use
UI - sliderinput will take up half of the screen and plot output will take up the other half
fluidRow(splitLayout(sliderInput("obs", "Number of observations:", min =1, max =1000, value =500), plotOutput("distPlot")))
Column() gives you full control of UI widget location, so it gives more detailed fine-tuning
UI - slider input takes up 1/3 of the screen and plot output takes up 2/3
fluidRow(column(4, sliderInput("obs", "Number of observations:", min =1, max =1000, value =500)), column(8, plotOutput("distPlot")))# The grid width of the column (must be between 1 and 12)
IMPORTANT NOTE: use column() if selectInput() dropdown doesn't dropdown fully
Using SplitLayout()
Using column()
Adding Tool Tips
Put tool tips over/under widgets
UI
require(shinyBS)fluidRow(numericRangeInput("clv_5", "5-Year CLV:", value =c(NA,NA)),bsTooltip('clv_5', "Expected 5 year GM", placement ="top"))# IMPORTANT: be careful not to have apostrophes in the tooltip or it will not show. # For example: # bsTooltip('clv_5', "Customer's 5 year GM", placement = "top") -- this is incorrect
Useful Database Connection Functions
Connection
con <-dbConnect(odbc::odbc(), uid =----, pwd =----, "OracleODBC")
# note: dbWriteTable('temporary = TRUE') does not work with odbc package # you have to create a global temporary table in oracle and then append data onto that table using dbWriteTabledbWriteTable(con, 'GLOBAL_TEMP_TABLE_NAME', df, append=TRUE)
Lazy Load table from oracle into R - see DBPLYR section for more details
bar_chart<-function(df, variable_name_str) {hchart(df, 'bar', hcaes(x = variable_name_str, y ='Placed_Orders'))}bar_chart(df, 'gemstone')# This function doesn't work because it thinks x is 'variable_name_str' instead of 'gemstone'
Solution:
bar_chart<-function(df, variable_name_str) { column <-sym(variable_name_str)hchart(df, 'bar', hcaes(x =!!column, y ='Placed_Orders'))}
This solution should work in other scenarios outside of highchart visualizations as well
Tool Tips
Below is an example of a tooltip. The dataframe has three columns: Gemstone, Placed_Orders, and Percent_Placed_Orders
To add a tooltip, all you need is point.Variable_Name
hchart(pp_rdf(), 'column', hcaes(x = ENTRY_UNIT_PP, y = GM_PERC*100), name='% of GM Spent in this Bucket')%>%hc_title(text ="% of GM Spent Per Price Point in Last Year")%>%hc_yAxis(labels =list(format ="{value:.0f}%"))
Regression Line
hchart(pinterest_gross_up(), 'scatter', hcaes(x=WEEK, y=PERC_CLICK_ORDERS), regression =TRUE, regressionSettings =list( type ="linear", dashStyle ="ShortDash", color ="black", order =3, lineWidth =4, name ="%eq | r2: %r", hideInLegend =TRUE))%>%hc_title(text ='Estimated % of Orders from Clicks (120 Day Attribution Window)')%>%hc_add_dependency("plugins/highcharts-regression.js")
"dbplyr is the database backend for dplyr. It allows you to use remote database tables as if they are in-memory data frames by automatically converting dplyr code into SQL."
output$intro_header <-renderValueBox({valueBox(tags$p("Customer Clustering Dashboard", style = cfg$format$server$font$size), paste0("Date Last Updated: ", format(file.info("filepath.Rdata")$mtime, "%Y-%m-%d")), width =2, color ="navy")})# For icons, you can add 'icon = icon("text")' to the valuebox
Force Update/Restart of the Application
Problem: after updating data/code in the dashboard, the application doesn't update if someone has the dashboard open on one of their browser tabs - even if they are logged out/timed out.
Solution:
Create a blank text file named 'restart.txt' in the application directory. This will start a 'new R process to run the "new" (restarted) Shiny Application for this and future users'.
Old sessions will remain unchanged until old user refreshes/relogs in. This shouldn't be a problem though because dashboard can be build to time out (see table of contents)
Make a bash file that runs 'touch restart.txt' to restart the app every day at a specific time. The Shiny Server updates based on the modified time of the 'restart.txt' file.
Documentation: http://rstudio.github.io/shiny-server/os/0.4.0/#restarting-an-application
I believe this is the solution to automating executive dashboard reports. If you want to see an example, check out the executive_report folder in the executive dashboard.
To Add
DataTable Row Last Clicked/Row Selected
SQL Queries and preventing SQL Injections
Document generated by Confluence on Apr 09, 2022 16:54