mirror of
https://github.com/rstudio/shiny.git
synced 2026-02-09 06:04:58 -05:00
385 lines
11 KiB
R
385 lines
11 KiB
R
|
|
# used to help define truely global node id's.
|
|
# should work accross session and in global namespace
|
|
.globals$reactIdCounter <- 0L
|
|
nextGlobalReactId <- function() {
|
|
.globals$reactIdCounter <- .globals$reactIdCounter + 1L
|
|
as.character(.globals$reactIdCounter)
|
|
}
|
|
|
|
|
|
|
|
writeReactLog <- function(file=stdout(), sessionToken = NULL) {
|
|
log <- rlogStack$as_list()
|
|
if (!is.null(sessionToken)) {
|
|
log <- Filter(function(x) {
|
|
is.null(x$session) || identical(x$session, sessionToken)
|
|
}, log)
|
|
}
|
|
cat(toJSON(log, pretty=TRUE), file=file)
|
|
}
|
|
|
|
#' Reactive Log Visualizer
|
|
#'
|
|
#' Provides an interactive browser-based tool for visualizing reactive
|
|
#' dependencies and execution in your application.
|
|
#'
|
|
#' To use the reactive log visualizer, start with a fresh R session and
|
|
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
|
|
#' application in the usual way (e.g. using \code{\link{runApp}}). At
|
|
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
|
#' web browser to launch the reactive log visualization.
|
|
#'
|
|
#' The reactive log visualization only includes reactive activity up
|
|
#' until the time the report was loaded. If you want to see more recent
|
|
#' activity, refresh the browser.
|
|
#'
|
|
#' Note that Shiny does not distinguish between reactive dependencies
|
|
#' that "belong" to one Shiny user session versus another, so the
|
|
#' visualization will include all reactive activity that has taken place
|
|
#' in the process, not just for a particular application or session.
|
|
#'
|
|
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
|
|
#' are using reactives outside of the context of a Shiny
|
|
#' application--you can run the \code{showReactLog} function, which will
|
|
#' generate the reactive log visualization as a static HTML file and
|
|
#' launch it in your default browser. In this case, refreshing your
|
|
#' browser will not load new activity into the report; you will need to
|
|
#' call \code{showReactLog()} explicitly.
|
|
#'
|
|
#' For security and performance reasons, do not enable
|
|
#' \code{shiny.reactlog} in production environments. When the option is
|
|
#' enabled, it's possible for any user of your app to see at least some
|
|
#' of the source code of your reactive expressions and observers.
|
|
#'
|
|
#' @param time A boolean that specifies whether or not to display the
|
|
#' time that each reactive.
|
|
#' @export
|
|
showReactLog <- function(time = TRUE) {
|
|
utils::browseURL(renderReactLog(time = as.logical(time)))
|
|
}
|
|
|
|
displayReactLogMessages <- function() {
|
|
for (msg in rLogMsg$messages) {
|
|
message(msg)
|
|
}
|
|
}
|
|
|
|
renderReactLog <- function(sessionToken = NULL, time = TRUE) {
|
|
templateFile <- system.file('www/reactive-graph.html', package='shiny')
|
|
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
|
|
tc <- textConnection(NULL, 'w')
|
|
on.exit(close(tc))
|
|
writeReactLog(tc, sessionToken)
|
|
cat('\n', file=tc)
|
|
flush(tc)
|
|
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
|
|
html <- sub('__TIME__', paste0('"', time, '"'), html, fixed=TRUE)
|
|
file <- tempfile(fileext = '.html')
|
|
writeLines(html, file)
|
|
return(file)
|
|
}
|
|
|
|
rlogAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
|
if (isTRUE(getOption('shiny.reactlog'))) {
|
|
sessionToken <- if (is.null(domain)) NULL else domain$token
|
|
rlogStack$push(c(logEntry, list(
|
|
session = sessionToken,
|
|
time = as.numeric(Sys.time())
|
|
)))
|
|
}
|
|
|
|
if (!is.null(domain)) {
|
|
domain$reactlog(logEntry)
|
|
}
|
|
}
|
|
|
|
|
|
rlogDependsOnReactiveValueKey <- function(reactId, depOnReactId, key) {
|
|
rLogMsg$log("dependsOnReactiveValueKey: ", rLogMsg$node(reactId), " ", rLogMsg$node(depOnReactId), " ", key)
|
|
rlogAppend(list(
|
|
action = "depReactiveValueKey",
|
|
reactId = reactId,
|
|
depOnReactId = depOnReactId,
|
|
key = key
|
|
))
|
|
}
|
|
rlogDependsOnReactiveValueNames <- function(reactId, depOnReactId) {
|
|
rLogMsg$log("dependsOnReactiveValueNames: ", rLogMsg$node(reactId), " ", rLogMsg$node(depOnReactId))
|
|
rlogAppend(list(
|
|
action = "depReactiveValueNames",
|
|
reactId = reactId,
|
|
depOnReactId = depOnReactId
|
|
))
|
|
}
|
|
rlogDependsOnReactiveValueToList <- function(reactId, depOnReactId) {
|
|
rLogMsg$log("dependsOnReactiveValueToList: ", rLogMsg$node(reactId), " ", rLogMsg$node(depOnReactId))
|
|
rlogAppend(list(
|
|
action = "dependsOnReactiveValuetoList",
|
|
reactId = reactId,
|
|
depOnReactId = depOnReactId
|
|
))
|
|
}
|
|
|
|
rlogDependsOn <- function(reactId, depOnReactId) {
|
|
rLogMsg$log("dependsOn: ", rLogMsg$node(reactId), " on ", rLogMsg$node(depOnReactId))
|
|
rlogAppend(list(
|
|
action = "dep",
|
|
reactId = reactId,
|
|
depOnReactId = depOnReactId
|
|
))
|
|
}
|
|
rlogDependsOnRemove <- function(reactId, depOnReactId) {
|
|
rLogMsg$log("dependsOnRemove: ", rLogMsg$node(reactId), " on ", rLogMsg$node(depOnReactId))
|
|
rlogAppend(list(
|
|
action = "depOnRemove",
|
|
reactId = reactId,
|
|
depOnReactId = depOnReactId
|
|
))
|
|
}
|
|
|
|
# init a node id with a label
|
|
rlogAddNodeDef <- function(reactId, label, type) {
|
|
if (!is.null(nodeCache[[reactId]])) {
|
|
stop("node definition for id: ", reactId, " already found!!", "Label: ", label, "Type: ", type)
|
|
}
|
|
nodeCache[[reactId]] <<- list(reactId = reactId, label = label, type = type)
|
|
rLogMsg$log("nodeDef: ", rLogMsg$node(reactId))
|
|
rlogAppend(list(
|
|
action = "nodeDef",
|
|
reactId = reactId,
|
|
label = label,
|
|
type = type
|
|
))
|
|
}
|
|
rlogUpdateNodeLabel <- function(reactId, label) {
|
|
nodeCache[[reactId]]$label <<- label
|
|
rLogMsg$log("updateNodeLabel: ", rLogMsg$node(reactId))
|
|
rlogAppend(list(
|
|
action = "updateNodeLabel",
|
|
reactId = reactId,
|
|
label = label
|
|
))
|
|
}
|
|
|
|
# rlogCreateContext <- function(id, label, type, prevId, domain) {
|
|
# message("!!createContext: create graph context is deprecated!!")
|
|
# rlogAppend(list(
|
|
# action='ctx', ctxId = id, label = paste(label, collapse='\n'),
|
|
# srcref=as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile"),
|
|
# type=type, prevId=prevId
|
|
# ), domain = domain)
|
|
# }
|
|
|
|
rlogEnter <- function(reactId, ctxId, type) {
|
|
rLogMsg$log("enter: ", rLogMsg$node(reactId), " ", ctxId, " ", type)
|
|
rLogMsg$depthIncrement()
|
|
rlogAppend(list(
|
|
action = 'enter',
|
|
reactId = reactId,
|
|
ctxId = ctxId,
|
|
type = type
|
|
))
|
|
}
|
|
|
|
rlogExit <- function(reactId, ctxId, type) {
|
|
rLogMsg$depthDecrement()
|
|
rLogMsg$log("exit: ", rLogMsg$node(reactId), " ", ctxId, " ", type)
|
|
rlogAppend(list(
|
|
action = 'exit',
|
|
reactId = reactId,
|
|
ctxId = ctxId,
|
|
type = type
|
|
))
|
|
}
|
|
|
|
# id = ctx id
|
|
# domain is like session
|
|
rlogValueChange <- function(reactId, value) {
|
|
rLogMsg$log("valueChange: ", rLogMsg$node(reactId), " '", paste(utils::capture.output(utils::str(value)), collapse='\n'), "'")
|
|
rLogMsg$depthIncrement()
|
|
rlogAppend(
|
|
list(
|
|
action = 'valueChange',
|
|
reactId = reactId,
|
|
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
|
)
|
|
)
|
|
}
|
|
rlogValueChangeEnd <- function(reactId, value) {
|
|
rLogMsg$depthDecrement()
|
|
rLogMsg$log("valueChangeEnd: ", rLogMsg$node(reactId), " '", paste(utils::capture.output(utils::str(value)), collapse='\n'), "'")
|
|
rlogAppend(
|
|
list(
|
|
action = 'valueChangeEnd',
|
|
reactId = reactId,
|
|
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
|
)
|
|
)
|
|
}
|
|
rlogReactValueNames <- function(reactId, values) {
|
|
namesStr <- paste(utils::capture.output(utils::str(ls(values, all.names=TRUE))), collapse='\n')
|
|
rLogMsg$log("valueChangeReactValueNames: ", rLogMsg$node(reactId), " ", namesStr)
|
|
rlogAppend(list(
|
|
action = 'valueChangeReactValueNames',
|
|
reactId = reactId,
|
|
value = namesStr
|
|
))
|
|
}
|
|
rlogReactValueValues <- function(reactId, values) {
|
|
valuesStr <- paste(utils::capture.output(utils::str(as.list(values))), collapse='\n')
|
|
# pm("valueChangeReactValue: ", reactId, " ", valuesStr)
|
|
rLogMsg$log("valueChangeReactValueValues: ", rLogMsg$node(reactId))
|
|
rlogAppend(list(
|
|
action = 'valueChangeReactValueValues',
|
|
reactId = reactId,
|
|
value = valuesStr
|
|
))
|
|
}
|
|
rlogReactValueKey <- function(reactId, key, value) {
|
|
valueStr <- paste(utils::capture.output(utils::str(value)), collapse='\n')
|
|
rLogMsg$log("valueChangeReactValueKey: ", rLogMsg$node(reactId), " ", key, " ", valueStr)
|
|
rlogAppend(list(
|
|
action = 'valueChangeReactValueKey',
|
|
reactId = reactId, key = key,
|
|
value = valueStr
|
|
))
|
|
}
|
|
|
|
# id = ctx id
|
|
# domain is like session
|
|
rlogInvalidateStart <- function(reactId, ctxId, type, domain) {
|
|
rLogMsg$log("invalidateStart: ", rLogMsg$node(reactId), " ", ctxId, " ", type)
|
|
rLogMsg$depthIncrement()
|
|
rlogAppend(
|
|
list(
|
|
action = 'invalidateStart',
|
|
reactId = reactId,
|
|
ctxId = ctxId,
|
|
type = type
|
|
),
|
|
domain
|
|
)
|
|
}
|
|
rlogInvalidateEnd <- function(reactId, ctxId, type, domain) {
|
|
rLogMsg$depthDecrement()
|
|
rLogMsg$log("invalidateEnd: ", rLogMsg$node(reactId), " ", ctxId, " ", type)
|
|
rlogAppend(
|
|
list(
|
|
action = 'invalidateEnd',
|
|
reactId = reactId,
|
|
ctxId = ctxId,
|
|
type = type
|
|
),
|
|
domain
|
|
)
|
|
}
|
|
|
|
MessageLogger = R6Class(
|
|
'Stack',
|
|
portable = FALSE,
|
|
class = FALSE,
|
|
public = list(
|
|
depth = 0L,
|
|
display = TRUE,
|
|
messages = c(),
|
|
nodeCache = list(),
|
|
|
|
initialize = function(display, depth) {
|
|
if (!missing(display)) self$display <- display
|
|
if (!missing(depth)) self$depth <- depth
|
|
},
|
|
depthIncrement = function() {
|
|
self$depth <- self$depth + 2
|
|
},
|
|
depthDecrement = function() {
|
|
self$depth <- self$depth - 2
|
|
},
|
|
node = function(reactId) {
|
|
nodeInfo <- nodeCache[[reactId]]
|
|
paste(
|
|
nodeInfo$reactId, nodeInfo$type, nodeInfo$label,
|
|
sep = ":"
|
|
)
|
|
},
|
|
log = function(...) {
|
|
msg <- paste0(
|
|
paste0(rep(" ", depth), collapse = ""), " - ", paste0(..., collapse = ""),
|
|
collapse = ""
|
|
)
|
|
self$messages[length(self$messages) + 1] <- msg
|
|
if (display) {
|
|
message(msg)
|
|
}
|
|
}
|
|
)
|
|
)
|
|
rLogMsg <- MessageLogger$new(TRUE, 0)
|
|
|
|
|
|
#' @include stack.R
|
|
rlogStack <- Stack$new()
|
|
|
|
|
|
|
|
#############################################################################
|
|
#############################################################################
|
|
#############################################################################
|
|
#############################################################################
|
|
|
|
|
|
|
|
|
|
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
|
if (isTRUE(getOption('shiny.reactlog'))) {
|
|
sessionToken <- if (is.null(domain)) NULL else domain$token
|
|
.graphStack$push(c(logEntry, list(
|
|
session = sessionToken,
|
|
time = as.numeric(Sys.time())
|
|
)))
|
|
}
|
|
|
|
if (!is.null(domain)) {
|
|
domain$reactlog(logEntry)
|
|
}
|
|
}
|
|
|
|
.graphDependsOn <- function(id, label) {
|
|
.graphAppend(list(action='dep', id=id, dependsOn=label))
|
|
}
|
|
|
|
.graphDependsOnId <- function(id, dependee) {
|
|
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
|
|
}
|
|
|
|
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
|
.graphAppend(list(
|
|
action='ctx', id=id, label=paste(label, collapse='\n'),
|
|
srcref=as.vector(attr(label, "srcref")), srcfile=attr(label, "srcfile"),
|
|
type=type, prevId=prevId
|
|
), domain = domain)
|
|
}
|
|
|
|
.graphEnterContext <- function(id) {
|
|
.graphAppend(list(action='enter', id=id))
|
|
}
|
|
|
|
.graphExitContext <- function(id) {
|
|
.graphAppend(list(action='exit', id=id))
|
|
}
|
|
|
|
.graphValueChange <- function(label, value) {
|
|
.graphAppend(list(
|
|
action = 'valueChange',
|
|
id = label,
|
|
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
|
))
|
|
}
|
|
|
|
.graphInvalidate <- function(id, domain) {
|
|
.graphAppend(list(action='invalidate', id=id), domain)
|
|
}
|
|
|
|
#' @include stack.R
|
|
.graphStack <- Stack$new()
|