how should I access shiny user's session info? - r

I'm wondering if there's any way I can access user log for my Shiny App.
Currently, I use the code below to get who logged in and when did this person log off.
However, I'd prefer to know the time the user logged in so that I'm able to know how long does the user using the app.
session$onSessionEnded(function(){
UserInfo <- data.frame(
LoginName = session$user,
Time = as.character(Sys.time())
)
Plus, I understand that Google Analytics can easily access this kind of info, but I do prefer a 'shiny' way to solve it.
I've also tried to use `session$clientData' as the document says it's used for "Getting Non-Input Data From the Client", but I don't know how could I get the login time.
Does anyone have any idea on how could I achieve this? Thanks in advance!

Per the shiny scoping rules, everything inside server <- function(input, output, session) is per-session.
server <- function(input, output, session) {
# everything in here is run once per-session, so it should run as soon as
# a user starts using the app
started <- Sys.time()
# ... reactives here ...
session$onSessionEnded(function() {
UserInfo <- data.frame(
LoginName = session$user,
Time = as.character(Sys.time())
)
# ... do something with UserInfo ...
})
In fact, the scoping rules suggest exactly this, but they named it startTime instead. (Hard things: cache invalidation and variable naming.)

Related

how to connect to R Shiny website using programming

We have a R shiny based website where we input some parameters and get some results. We can do it manually, but considering many actions are repeat, we want write some R code to automatically pull out the results. Look looks we cannot use some simple REST request, because the HTTP request used some information like "nonce" and "session". Also I am not familiar with the JavaScript. Could someone help me to understand how to do it?
There is a functionality session$registerDataObj which allows you to add an API endpoint for a specific shiny session. This will make the provided data available at http://{host}/session/{session_id}/dataobj/{name}.
The minimal app below invokes the produced endpoint via browseURL(). See ?shiny::session for more details.
shinyApp(
ui = fluidPage(),
server = function(input, output, session) {
name <- "iris"
session$registerDataObj(
name = name,
data = iris,
filterFunc = function(data, req) {
httpResponse(content = paste('nrow: ', nrow(data)))
}
)
uri <- paste0("http://", session$request$HTTP_HOST, "/session/",
session$token, "/dataobj/", name)
browseURL(print(uri))
}
)
#> Listening on http://127.0.0.1:5130
#> [1] "http://127.0.0.1:5130/session/fd47ff29025bbe4d9dbf922b935186b3/dataobj/iris"
This should open up a new browser tab that shows: "nrow: 150". The return value of session$registerDataObj() can also be used to create the uri with additional query parameters.
But I personally think it is better to use a database in 99% of all usecases. This would mean that there is an export button in your ui which causes the app server to write all relevant results into the database and the database is then accessed by another process that needs the app data.

How to fetch data from REST API periodically?

Background
I am currently building a „Data-Logger“ - App using R Shiny. I do have an REST - API, which returns a value, that changes over time. My goal is to create an Shiny App, in which an user can click on an actionbutton to start writing the values fetched from the api periodically (e.g. every 60 seconds) to a dataframe. The logging of the data also should be stopped, when the user clicks on another actionbutton.
Problem
My problem is writing a function that starts executing when a button is pressed, executes periodically after that and stops executing when another button is pressed.
Previous Ideas
I previously tried using invalidateLater(), but i could not achieve what i desire.
Can you guys help me out with a clever thought or idea?
Thanks in advance!
This should show how it works. invalidateLater() is the right choice. The start/stop buttons change a reactive expression that determines whether polling is on or off. That way, the reactive RestPoll expression gets notified every time it gets switched on/off and, of course, after 500 ms as long as Running() == TRUE.
library(shiny)
ui <- fluidPage(
actionButton("btnStart", "Start"),
actionButton("btnStop", "Stop"),
textOutput("outTime")
)
server <- function(input, output, session) {
Running <- reactiveVal(FALSE)
observeEvent(input$btnStart, {
Running(TRUE)
})
observeEvent(input$btnStop, {
Running(FALSE)
})
RestPoll <- reactive({
if (Running()) # IS called every time `Running` changes
invalidateLater(500, session)
# Add any REST calls here, process the results
return(Sys.time()) # deliver results
})
output$outTime <- renderText({
req(RestPoll())
})
}
shinyApp(ui, server)
You could also do it with a reactiveTimer but that would also poll and use resources when polling is not required.

Shiny: conditionally build UI

I'm building a Shiny dashboard to show a large amount of data. People access the dashboard through a separate login page (non-Shiny) that sits in front, at which point a JWT is generated and put in a cookie. I've managed to read in the cookie in Shiny and parse the data, saving the token in a variable called userPermissions. Now, however, I'd like to show/hide tabs in the dashboard depending on the user permissions.
For example: I have data on both managers and assistants. I want to show the manager data to any user with userPermissions == 'manager', and the assistant data to anyone with userPermissions == assistant.
I thought the best way would be to use conditionalPanel() – here's a (simplified) reproducible example:
library(shiny)
# UI file
ui <- fluidPage(
# JS to read cookie -- simplified to just return value!
tags$head(tags$script(
HTML('
Shiny.addCustomMessageHandler("goReadTheCookie", function (message) {
Shiny.onInputChange("cookie", "manager");
})
')
)
# Title
,titlePanel('Test')
# Navbar
,navbarPage(
id="navbar"
,conditionalPanel(condition = "userPermissions() == 'manager'",
mainPanel(textOutput('Manager data')))
,conditionalPanel(condition = "userPermissions() == 'assistant'",
mainPanel(textOutput('Assistant data')))
)
))
# Server file
server <- shinyServer(function(input, output,session){
## Grab permissions from Cookie
# Prepare output
userPermissions <- reactiveVal("")
# Tell JS to return cookie
session$sendCustomMessage(type="goReadTheCookie", message=list(name="cookie_name"))
# Listen for cookie
observeEvent(input$cookie,{
## -- Bunch of code omitted for sake of brevity -- ##
userPermissions("manager")
})
})
# Run app
shinyApp(ui=ui, server=server)
The problem with this code is that, in a browser console, I get the error Can't find variable: userPermissions.
I guess what's going on here is that the entire ui is executed, before JS can grab and return the cookie. What would be the best way to solve this?
Or, maybe I'm going about this the wrong way. I obviously need to verify the cookie server-side (i.e., in R) not to divulge the secret; and preferably this check, and the hiding/showing is completed at the very start of the Shiny application (userPermissions won't change during the session). Maybe there's a different (& better) solution to get to that point?
Any help would be very much appreciated!
In the end I found that the function appendTab does exactly what I was looking for. This needs to be run in server.R though, within the function to look for the cookie (otherwise userPermissions indeed doesn't exist). I could then do:
appendTab("navbar" # the id of the navigation bar created in ui.R
,tabPanel("tab name"
,mainPanel(...))
)
where tabPanel(...) could be anything you'd normally put in ui.R.
The added benefit here is that hidden tabs are also not available in the HTML source, as they're never even passed from the server to the client!

Limiting the number of users in a locally hosted R shiny app

I'd like to limit the number of users of my locally hosted r shiny app to one user at any one time.
So ideally when a second user attempted to run the app at the same time (users access the app by typing the local IP into the address field) the app would display a default message and stop any further progress. Nullifying any other user commands may not matter if the only thing shown upon entry is this denial message.
The content of the app doesn't matter so we can use this app as an example: http://shiny.rstudio.com/gallery/tabsets.html
Thanks for any help or info you can give.
I wouldn't recommend doing this, I think it's very dangerous, but there are ways you could hack this together. Here's one solution (as I said, it's hacky and I wouldn't do it myself). The basic idea is to have a global variable that keeps track of whether or not someone is using the app. If nobody is using the app, show the app and turn on the flag and make sure to turn off the flag when the user exits.
shinyBusy <- FALSE
runApp(shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
shinyjs::hidden(
h1(id = "busyMsg", "App is busy")
),
shinyjs::hidden(
div(
id = "app",
p("Hello!")
)
)
),
server = function(input, output, session) {
if (shinyBusy) {
shinyjs::show("busyMsg")
} else {
shinyBusy <<- TRUE
session$onSessionEnded(function() shinyBusy <<- FALSE)
shinyjs::show("app")
}
}
),
launch.browser = TRUE)
Note: in order to show/hide app elements, I'm using a package that I wrote shinyjs

Shiny Issue - Tracking User Settings in Multiuser Environment

I asked this question on the Shiny user group, but haven't been able to get a response, so I'm posting it here as well.
I have an app that needs to track a user's preference, with the possibility that several users may be using the app simultaneously. For simplicity, let's say I have a list to contain a user's settings stored within the shiny server function. It's a long list (1000 elements) that contains attributes based on the user's interaction with the app. The user can in effect change any index of this list to one of hundreds of possible settings. My initial solution was something like this:
shinyServer(function(input, output, session) {
settings <<- rep("A",1000)
observe({
input$changeSettingsButton
settings[input$changeIndex] <<- input$newSetting
})
}
Which works great unless you have multiple people using the app at the same time, because the <<- creates a global, shared variable across sessions. Is there a good way to do this?
You can use reactiveValues to store persistent user info. Something like
shinyServer(function(input, output, session) {
myReactives <- reactiveValues(settings = rep("A",1000))
observe({
input$changeSettingsButton
isolate(myReactives$settings[input$changeIndex] <- input$newSetting)
})
}
may work for you.

Resources