diff --git a/DESCRIPTION b/DESCRIPTION index 0f7758b..f15a279 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: shinylogs Title: Record Everything that Happens in a 'Shiny' Application -Version: 0.1.5.910 +Version: 0.1.5.920 Authors@R: c(person("Fanny", "Meyer", email = "fanny.meyer@dreamrs.fr", role = c("aut")), person("Victor", "Perrier", email = "victor.perrier@dreamrs.fr", role = c("aut", "cre")), person("Silex Technologies", comment = "https://www.silex-ip.com", role = "fnd"), diff --git a/NAMESPACE b/NAMESPACE index dcfc7d4..2c78d7f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,7 @@ export(store_null) export(store_rds) export(store_sqlite) export(track_usage) +export(use_tracking) importFrom(DBI,dbConnect) importFrom(DBI,dbDisconnect) importFrom(DBI,dbWriteTable) diff --git a/R/tracking.R b/R/tracking.R index b287748..16ef830 100644 --- a/R/tracking.R +++ b/R/tracking.R @@ -3,12 +3,15 @@ #' #' @description If used in \code{ui} of an application, #' this will create new \code{input}s available in the server. +#' Set \code{dependencies = FALSE} in \code{\link{track_usage}} +#' server-side to load dependencies only once. #' #' @param on_unload Logical, save log when user close the browser window or tab, #' if \code{TRUE} it prevent to create \code{shinylogs} #' input during normal use of the application, there will #' be created only on close, downside is that a popup will appear asking to close the page. -#' @param exclude_input Regular expression to exclude inputs from tracking. +#' @param exclude_input_regex Regular expression to exclude inputs from tracking. +#' @param exclude_input_id Vector of \code{inputId} to exclude from tracking. #' #' @note The following \code{input}s will be accessible in the server: #' @@ -22,27 +25,65 @@ #' #' - \strong{.shinylogs_browserData} : information about the browser where application is displayed. #' -#' @noRd +#' @export #' #' @importFrom htmltools attachDependencies tags singleton #' @importFrom jsonlite toJSON -#' @importFrom nanotime nanotime #' @importFrom bit64 as.integer64 +#' @importFrom nanotime nanotime #' @importFrom digest digest -tracking_ui <- function(on_unload = FALSE, exclude_input = NULL) { +#' +#' @examples +#' if (interactive()) { +#' +#' library(shiny) +#' +#' ui <- fluidPage( +#' +#' use_tracking(), +#' +#' splitLayout( +#' cellArgs = list(style = "height: 250px"), +#' radioButtons("radio", "Radio:", names(iris)), +#' checkboxGroupInput("checkbox", "Checkbox:", names(iris)), +#' selectInput("select", "Select:", names(iris)) +#' ), +#' +#' verbatimTextOutput("last") +#' ) +#' +#' server <- function(input, output, session) { +#' +#' output$last <- renderPrint({ +#' input$.shinylogs_lastInput +#' }) +#' +#' } +#' +#' shinyApp(ui, server) +#' +#' } +use_tracking <- function(on_unload = FALSE, exclude_input_regex = NULL, exclude_input_id = NULL) { + app_name <- basename(getwd()) timestamp <- Sys.time() + init_log <- data.frame( + app = app_name, + server_connected = get_timestamp(timestamp), + stringsAsFactors = FALSE + ) timestamp <- format(as.integer64(nanotime(timestamp)), scientific = FALSE) - sessionid <- digest::digest(timestamp) - tag_log <- tags$div(tags$script( + init_log$sessionid <- digest::digest(timestamp) + tag_log <- tags$script( id = "shinylogs-tracking", type = "application/json", `data-for` = "shinylogs", - toJSON(list( + toJSON(dropNulls(list( logsonunload = isTRUE(on_unload), - excludeinput = exclude_input, - sessionid = sessionid - ), auto_unbox = TRUE, json_verbatim = TRUE) - )) + exclude_input_regex = exclude_input_regex, + exclude_input_id = exclude_input_id, + sessionid = init_log$sessionid + )), auto_unbox = TRUE, json_verbatim = TRUE) + ) attachDependencies( x = singleton(tag_log), value = list( @@ -90,6 +131,7 @@ parse_lastInput <- function(x, shinysession, name) { #' @param exclude_users Character vectors of user for whom it is not necessary to save the log. #' @param get_user A \code{function} to get user name, it should #' return a character and take one argument: the Shiny session. +#' @param dependencies Load dependencies. #' @param session The shiny session. #' #' @export @@ -262,6 +304,7 @@ track_usage <- function(storage_mode, on_unload = FALSE, exclude_users = NULL, get_user = NULL, + dependencies = TRUE, session = getDefaultReactiveDomain()) { stopifnot(inherits(storage_mode, "shinylogs.storage_mode")) @@ -283,35 +326,37 @@ track_usage <- function(storage_mode, storage_mode$timestamp <- format(as.integer64(nanotime(timestamp)), scientific = FALSE) init_log$sessionid <- digest::digest(storage_mode$timestamp) - insertUI( - selector = "body", where = "afterBegin", - ui = singleton(tags$script( - id = "shinylogs-tracking", - type = "application/json", - `data-for` = "shinylogs", - toJSON(dropNulls(list( - logsonunload = isTRUE(on_unload), - exclude_input_regex = exclude_input_regex, - exclude_input_id = exclude_input_id, - sessionid = init_log$sessionid - )), auto_unbox = TRUE, json_verbatim = TRUE) - )), - immediate = TRUE, - session = session - ) - insertUI( - selector = "body", where = "afterBegin", - ui = attachDependencies( - x = tags$div(), - value = list( - localforage_dependencies(), - dayjs_dependencies(), - shinylogs_lf_dependencies() - ) - ), - immediate = FALSE, - session = session - ) + if (isTRUE(dependencies)) { + insertUI( + selector = "body", where = "afterBegin", + ui = singleton(tags$script( + id = "shinylogs-tracking", + type = "application/json", + `data-for` = "shinylogs", + toJSON(dropNulls(list( + logsonunload = isTRUE(on_unload), + exclude_input_regex = exclude_input_regex, + exclude_input_id = exclude_input_id, + sessionid = init_log$sessionid + )), auto_unbox = TRUE, json_verbatim = TRUE) + )), + immediate = TRUE, + session = session + ) + insertUI( + selector = "body", where = "afterBegin", + ui = attachDependencies( + x = tags$div(), + value = list( + localforage_dependencies(), + dayjs_dependencies(), + shinylogs_lf_dependencies() + ) + ), + immediate = FALSE, + session = session + ) + } if (isTRUE(storage_mode$console)) { @@ -331,8 +376,12 @@ track_usage <- function(storage_mode, isolate(session$input$.shinylogs_error), isolate(session$input$.shinylogs_output)) browser_data <- isolate(session$input$.shinylogs_browserData) - browser_data <- as.data.frame(browser_data) - logs$session <- cbind(init_log, browser_data) + if (!is.null(browser_data)) { + browser_data <- as.data.frame(browser_data) + logs$session <- cbind(init_log, browser_data) + } else { + logs$session <- init_log + } if (isTRUE(!user %in% exclude_users)) { write_logs(storage_mode, logs) } diff --git a/inst/assets/js/shinylogs-localForage.js b/inst/assets/js/shinylogs-localForage.js index e53321e..b71e631 100644 --- a/inst/assets/js/shinylogs-localForage.js +++ b/inst/assets/js/shinylogs-localForage.js @@ -6,9 +6,8 @@ * using Lowdb * https://github.com/dreamRs/shinylogs * - * @version 0.0.1 + * @version 0.0.2 */ -// on unload or not // config @@ -72,14 +71,16 @@ var browser_connected = dayjs().format(); // Send browser data if (logsonunload === false) { - Shiny.setInputValue(".shinylogs_browserData", { - user_agent: ua, - screen_res: screen_res, - browser_res: browser_res, - pixel_ratio: pixel_ratio, - browser_connected: browser_connected - }, { - priority: "event" + $(document).on("shiny:connected", function() { + Shiny.setInputValue(".shinylogs_browserData", { + user_agent: ua, + screen_res: screen_res, + browser_res: browser_res, + pixel_ratio: pixel_ratio, + browser_connected: browser_connected + }, { + priority: "event" + }); }); } @@ -180,3 +181,6 @@ if (logsonunload === true) { return "Are you sure?"; }; } + + + diff --git a/man/track_usage.Rd b/man/track_usage.Rd index 3b2cf18..8f298ec 100644 --- a/man/track_usage.Rd +++ b/man/track_usage.Rd @@ -6,7 +6,8 @@ \usage{ track_usage(storage_mode, exclude_input_regex = NULL, exclude_input_id = NULL, on_unload = FALSE, exclude_users = NULL, - get_user = NULL, session = getDefaultReactiveDomain()) + get_user = NULL, dependencies = TRUE, + session = getDefaultReactiveDomain()) } \arguments{ \item{storage_mode}{Storage mode to use : \code{\link{store_json}}, \code{\link{store_rds}}, @@ -26,6 +27,8 @@ be created only on close, downside is that a popup will appear asking to close t \item{get_user}{A \code{function} to get user name, it should return a character and take one argument: the Shiny session.} +\item{dependencies}{Load dependencies.} + \item{session}{The shiny session.} } \description{ diff --git a/man/use_tracking.Rd b/man/use_tracking.Rd new file mode 100644 index 0000000..0f1646c --- /dev/null +++ b/man/use_tracking.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tracking.R +\name{use_tracking} +\alias{use_tracking} +\title{Insert dependencies to track usage of a Shiny app} +\usage{ +use_tracking(on_unload = FALSE, exclude_input_regex = NULL, + exclude_input_id = NULL) +} +\arguments{ +\item{on_unload}{Logical, save log when user close the browser window or tab, +if \code{TRUE} it prevent to create \code{shinylogs} +input during normal use of the application, there will +be created only on close, downside is that a popup will appear asking to close the page.} + +\item{exclude_input_regex}{Regular expression to exclude inputs from tracking.} + +\item{exclude_input_id}{Vector of \code{inputId} to exclude from tracking.} +} +\description{ +If used in \code{ui} of an application, + this will create new \code{input}s available in the server. + Set \code{dependencies = FALSE} in \code{\link{track_usage}} + server-side to load dependencies only once. +} +\note{ +The following \code{input}s will be accessible in the server: + + - \strong{.shinylogs_lastInput} : last \code{input} used by the user + + - \strong{.shinylogs_input} : all \code{input}s send from the browser to the server + + - \strong{.shinylogs_error} : all errors generated by \code{output}s elements + + - \strong{.shinylogs_output} : all \code{output}s generated from the server + + - \strong{.shinylogs_browserData} : information about the browser where application is displayed. +} +\examples{ +if (interactive()) { + + library(shiny) + + ui <- fluidPage( + + use_tracking(), + + splitLayout( + cellArgs = list(style = "height: 250px"), + radioButtons("radio", "Radio:", names(iris)), + checkboxGroupInput("checkbox", "Checkbox:", names(iris)), + selectInput("select", "Select:", names(iris)) + ), + + verbatimTextOutput("last") + ) + + server <- function(input, output, session) { + + output$last <- renderPrint({ + input$.shinylogs_lastInput + }) + + } + + shinyApp(ui, server) + +} +}