How to declare unique users in R+Shiny - r

For debugging purposes, I'd like to log in to my shiny app as user1, user2, ..., user6. I have tried 2 methods without success. Getting any method working be good, and getting all working would be great.
Preferred Method
Declare user from url inside server.R and access outside of observe
In Web-Browser
http://127.0.0.1:XXXX?username=JA1
server.R
user <- parseQueryString(session$clientData$url_search)[['username']]
## Outside of any function ->
cat( file=stderr(), "User: ", user, "\n")
2nd Method
How do turn url parameters into R variables in shiny? This is similar to https://github.com/daattali/advanced-shiny/tree/master/url-inputs ,but I would prefer to use the new variable outside of an observer (see above)
server.R
shinyServer( function( input, output, session){
observe({
query <- parseQueryString(session$clientData$url_search)
if (!is.null(query[['user']])) {
## The New User [declared in the URL]
user <- query[['user']]
} else {
## The Default User
user <- "user1"
}
})
## Some Code that executes based on `user`
if(user=="user3") cat( file=stderr(), user, " Working Now\n")
3rd Method
How to have each user log in with a sequentially assigned ID?
server.R
## Global Reactives to Count Sequential Users
Debug <- reactiveValues(UserCount=6)
shinyServer( function( input, output, session){
observe(Debug$userCount, {
cat(Debug$userCount)
user <- paste0("user", Debug$UserCount)
Debug$UserCount <<- Debug$UserCount -1
})
if(user=="user3") cat( file=stderr(), user, " Working Now\n")
Other Methods
With Authorized access for ShinyServerPro, I can just use
user <- session$user
For small numbers of users, I can just resample a userID when I refresh the page
user <- paste0("user", sample(1:2,1))

Related

How to run a scheduled cross-session database query in shiny

I have an Oracle database which is refreshed once a day. I am a bit confused on how apps work in Shiny, what gets run once on app startup - and what gets run once per session.
My naive approach was to create a database connection and run a query outside of UI and Server code to create a dataframe of around 600,000 records...which can then be filtered and sliced during the session. I am a bit concerned by doing it inside app.R in global scope, that this connection and dataframe will only be created once when the server starts the app, and will never get run again (if that makes sense).
If I create the data frame in server, then my UI code fails, as is is dependent on the results of a query to populate the select list, and I do this in app.R scope at the moment, so UI can access it.
library(shiny)
library(DBI)
library(dplyr)
library(odbc)
library(stringdist)
library(reactable)
############################################################################
# business functions #
############################################################################
get_list_of_actives_from_db <- function() {
con <- dbConnect(odbc::odbc(), Driver="oracle", Host = "server.mycompany.net", Port = "1521", SVC = "service1", UID = "user_01", PWD = "hello", timeout = 10)
ingredients_df = dbGetQuery(con,
'
select DISTINCT INGREDIENTS FROM AES
'
)
}
get_adverse_events_from_db <- function() {
con <- dbConnect(odbc::odbc(), Driver="oracle", Host = "server.mycompany.net", Port = "1521", SVC = "service1", UID = "user_01", PWD = "hello", timeout = 10)
cases_df = dbGetQuery(con,
'
select * FROM AES
'
)
return(cases_df)
}
############################################################################
# load data sets for use in dashboard #
############################################################################
cases_df = get_adverse_events_from_db() # drive select list in UI
ingredients_df = get_list_of_actives_from_db() # main data to slice and filter
############################################################################
# shiny UI #
############################################################################
ui <- fluidPage(
"Adverse Event Fuzzy Search Tool",
fluidRow(
selectInput("ingredients", label = "Select on or more Active Ingredients:", choices = ingredients_df$PRIMARY_SUSPECT_KEY_INGREDIENT, multi=TRUE),
textInput("search_term", "AE Search Term:"),
actionButton("do_search", "Perform Search")
)
,
fluidRow(
reactableOutput("search_results")
)
)
############################################################################
# shiny server #
############################################################################
server <- function(input, output, session) {
# do stuff here to filter the data frame based on the selected value and render a table
}
# Run the application
shinyApp(ui = ui, server = server)
My main concern is doing this in the root of app.R, both functions run oracle queries which never need to be re-run for the session, as the data will only change overnight via ETL.
############################################################################
# load data sets for use in dashboard #
############################################################################
cases_df = get_adverse_events_from_db()
ingredients_df = get_list_of_actives_from_db()
When and how often is this called? Once when the app is initialized so the data set is never updated and is shared across sessions by users? Or is the entire script run end to end whenever a new sessions is started?
Part of me thinks it should be in the server function, so it runs once per session. But being new to Shiny I feel like server is called constantly whenever there is a change in the UI, I dont want to be constantly loading 600,000 records from Oracle.
Ideally I would cache the results once a day and make them available to all users across all sessions, not sure how to achieve that - so for now just want to know the best way to achieve this, so each user runs the query once and has the data frame cached for the session.
Please check RStudio's article Scoping rules for Shiny apps in this context.
If I got you right, you are asking to share a dataset across shiny-sessions and update it daily (The title of the question didn't really fit your explanation of the problem - I edited it).
I'd suggest using a cross-session reactivePoll to avoid unnecessary DB queries (I once asked a similar question here - Over there I gave an example showing, that the same can be achived via reactiveValues but it's more complex).
Here is the simple pattern you can use - please note that reactivePoll is defined outside the server function so all sessions share the same data:
library(shiny)
ui <- fluidPage(textOutput("my_db_data"))
updated_db_data <- reactivePoll(
intervalMillis = 1000L*60L*5L, # check for a new day every 5 minutes
session = NULL,
checkFunc = function() {
print(paste("Running checkFunc:", Sys.time()))
Sys.Date()
},
valueFunc = function() {
# your db query goes here:
paste("Latests DB update:", Sys.time())
}
)
server <- function(input, output, session) {
output$my_db_data <- renderText(updated_db_data())
}
shinyApp(ui, server)
Here, every 5 minutes the checkFunc checks for a new day - valueFunc is executed only if the result of checkFunc changed. As a (real world) alternative for checkFunc you could implement a query to check for the number of rows of a certain DB table.
PS: There is an example given on a cross-session reactiveFileReader (which is based on reactivePoll) when viewing ?reactiveFileReader
PPS: When doing further filtering etc. on that dataset also check bindCache().
While untested, perhaps this architecture will work:
server <- function(input, output, session) {
dailydata_ <- reactiveValues(when = NULL, what = NULL)
dailydata <- reactive({
oldwhen <- dailydata_$when
if (is.null(oldwhen) ||
as.Date(oldwhen) < Sys.Date()) {
newdata <- tryCatch(
DBI::dbGetQuery(con, "..."),
error = function(e) e)
if (inherits(newdata, "error")) {
warning("error retrieving new data: ", conditionMessage(e))
warning("using stale data instead")
} else {
dailydata_$when <- Sys.time()
dailydata_$what <- newdata
}
}
dailydata_$what
})
# some consumer of the real data
output$tbl <- renderTable(dailydata())
}
The advantage to this is that it's re-query will trigger when the data was retrieved on a different day. Granted, when the new ETL is available might change how exactly this conditional is fashioned, it might be that if it is updated at (say) 2am, then you may need some more time-math to determine if the current data is before or after the most recent update.
This logic has a "data available" fail: if it could not be queried, then the current/stale data is re-used. If you prefer that it returns no data, that is easy enough to change in the code.
(One thing you might want to do is to show the user when the data was last retrieved; this can be retrieved directly with dailydata_$when, accepting that it might be NULL.)

R shiny - updateSelectizeInput does not update inputs when wrapped in observeEvent coupled with actionButton

Problem: an updateSelectizeInput call within observeEvent changes the value displayed in the browser but does not change the value I can access from code with input$
Background: In my Shiny app, I want to have an input with the following properties:
the user can type in and select from server-side list of options
a URL query string can also control it
blank means all options
an actionButton allows the user to delay expensive computation until all desired choices have been made
I currently have an observeEvent watching for the query string, and when it sees one, it calls updateSelectizeInput, but, after it does so, the input is unchanged.
Example:
library(shiny)
possibleLetters = LETTERS[1:10]
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId='letters',
label='Your letters:',
choices=NULL,
selected=NULL,
multiple=T,
width='100%'),
actionButton("recompute",
"Recompute now")
),
mainPanel(
h3("Letters: "),
textOutput('lettersDisplay'),
h3("Indices of letters: "),
textOutput('lettersIndicesDisplay')
)
)
)
server <- function(input, output, session) {
updateSelectizeInput(inputId='letters',
choices=c('',possibleLetters),
server=T)
userLetters = eventReactive(input$recompute, ignoreNULL=FALSE, {
if (length(input$letters) == 0) {
return (possibleLetters)
} else (
return (input$letters)
)
})
userLetterIndices = reactive({
return(match(userLetters(),LETTERS))
})
queryStringObserver = observeEvent(session$clientData$url_search, {
query <- parseQueryString(session$clientData$url_search)
if (!is.null(query$letters)) {
cat(file=stderr(), paste0('observeEvent running - user specified param is: ',query$letters,'\n'))
updateSelectizeInput(session,
inputId="letters",
choices = possibleLetters,
selected = query$letters,
server=T)
cat(file=stderr(), paste0('observeEvent running - ran updateSelectizeInput, input$letters is now: ',input$letters,'\n'))
}
})
output$lettersDisplay = renderText({
return(paste(userLetters(),collapse=' '))
})
output$lettersIndicesDisplay = renderText({
return(paste(userLetterIndices(), collapse=' '))
})
}
shinyApp(ui = ui, server = server, options = list(port=1111))
Steps to reproduce problem: Run the app and then navigate to http://localhost:1111/?letters=A
You will find that "A" has indeed been filled into the selectize field in the browser window, however, the input value has not been updated. In your console you will see:
observeEvent running - user specified param is: A
observeEvent running - ran updateSelectizeInput, input$letters is now:
Thus, the query string has been correctly parsed, updateSelectizeInput has been called, and yet when input$letters is accessed afterwards, its value has not changed.
I suspect this relates to some fundamental shortcoming in my understanding of reactive graphs or something, but after poring over Mastering Shiny, I still can't figure out why this call does nothing.
The value of input$letters updates, it just hasn't by the time you try to print it. I'm not sure how or if Shiny batches messages, but your observeEvent eventually triggers a message to be sent to the client to update the input, which then has to inform the server is been updated. At a minimum, I assume it would finish executing the current observer code, but through some tinkering it appears Shiny may execute all necessary reactive code before sending messages to the client.
While the value of input$letters prints nothing with your given code, if I click recompute it does update the text as expected. Basically, here's more or less the conversation I believe that happens with your code as is:
Client: Yo, server. The user added a query parameter: letters=A.
Server: Hmmm, ok I will just run this observeEvent code. Oh, the developer wants to know the current value of `input$letters`. Client, can you help a server out with that input value.
Client: No problem, friend-o. The current selected value is NULL.
Server: Well, let me just cat this NULL to the stderr.
Server (~1 ms later): Yo, client. I finished running the observeEvent code and you should really update that selectize input. It would make everyone happy.
Client: Can do.
Client (~2 ms later): Whoa, the select input updated. I gots to tell server about this jawn. Hey server, input$letters just changed to `A`. Just FYI.
Server: Hmm, good to know. Nothing for me to do about that.
When the server prints inputletters, the value is still NULL because it hasn't told the client to update it yet. Actually, I'm not sure if the server polls the client for the value or if looks it up from it's own list of current values, but either way, it still hasn't been updated when it goes to cat the value.
Move your cat statement to a separate observe statement and the conversation above changes to
Client (~2 ms later): Whoa, the select input updated. I gots to tell server about this jawn. Hey server, input$letters just changed to `A`. Just FYI.
Server: WHAT?!!! OMG! I MUST TELL STDERR ABOUT THIS!!
This all a long way to say I don't think there is actually anything wrong with your code per se. Sorry, for all the personality changes with the server and the client, but hopefully this helps.
To achieve the desired behavior of the app immediately showing the results with the given query string (without waiting for user to press Recompute now), the following changes were necessary:
Delete the original observer, and instead only call updateSelectizeInput from inside the userLetters eventReactive
Only fill in values from the query string on startup (if(!input$recompute))
To keep the query string up to date with what the user then changes in the app, add a new observer update_qs
The code:
library(shiny)
possibleLetters = LETTERS[1:10]
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId='letters',
label='Your letters:',
choices=NULL,
selected=NULL,
multiple=T,
width='100%'),
actionButton("recompute",
"Recompute now")
),
mainPanel(
h3("Letters: "),
textOutput('lettersDisplay'),
h3("Indices of letters: "),
textOutput('lettersIndicesDisplay')
)
)
)
server <- function(input, output, session) {
userLetters = eventReactive(input$recompute, ignoreNULL=FALSE, {
query <- parseQueryString(session$clientData$url_search)
if (!input$recompute & !is.null(query$letters)) {
selectedLetters = strsplit(query$letters,';')[[1]]
} else if (is.null(input$letters)) {
selectedLetters = character(0)
} else (
selectedLetters = input$letters
)
updateSelectizeInput(session,
inputId="letters",
choices = c('',possibleLetters),
selected = selectedLetters,
server=T)
if (length(selectedLetters)==0) {
return (possibleLetters)
} else {
return (selectedLetters)
}
})
update_qs = observeEvent(input$recompute, {
if (!identical(userLetters(),possibleLetters)) {
new_qs = paste0('?letters=',paste0(userLetters(),collapse=';'))
} else {
new_qs = '?'
}
updateQueryString(new_qs, mode='push')
})
userLetterIndices = reactive({
return(match(userLetters(),LETTERS))
})
output$lettersDisplay = renderText({
return(paste(userLetters(),collapse=' '))
})
output$lettersIndicesDisplay = renderText({
return(paste(userLetterIndices(), collapse=' '))
})
}
# Run the application
shinyApp(ui = ui, server = server, options = list(port=1111))

Test whether any input in a set of numbered input objects in R Shiny is empty

Let's say I have created 10 selectInput dropdowns for a multi plot export and these selectInputs are called "xaxis_1", "xaxis_2", ..... , "xaxis_10"
for a single 1 I can write:
if(!is.null(input$xaxis_1)) { .... do stuff } to stop it running export when the user hasn't entered any name, and presses submit, to avoid crashes.
A bit more general you can check this:
if(!is.null(input[[paste('xaxis', i, sep = '_')]])) { ...}
how can you write it elegantly so that 1 line of code checks whether ANY of the 1:10 input[[...]] is empty, i.e. NULL?
The nr of inputs depends on how many plots the user wants to export per file, so all is build with lapply(1:input$nrofplots, function(i) { .... } renderUI structure, and my if statement needs to have the same flexibility of 1:n
In a situation like below in the image, pressing Initiate export should give a sweetalert (got that covered) saying there is at least 1 value missing
Here a snippet I used in the UI side to validate the user's inputs.
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(), # Set up shinyjs
numericInput('axis1','Val 1',1),
numericInput('axis2','Val 2',1),
numericInput('axis3','Val 3',1),
actionButton('Go','Plot')
)
server <- function(input, output, session) {
#Try 1, space, AAA and check what shiny will return
observe(print(input$axis1))
observe({
All_Inputs <- vapply(paste0('axis',1:3),
function(x){isTruthy(input[[x]])},
logical(1))
All_InputsCP <- all(All_Inputs)
shinyjs::toggleState(id="Go", condition = All_InputsCP) #This is to make the button Go able or disable according to condition All_InputsCP #
})
}
shinyApp(ui, server)
I hope it helps.

Setting R environement valriable from a Shiny module using Sys.setenv()

I am trying to create a shiny app with a module including a username/password input fields and save them to the environment variable through Sys.setenv()
A full example of what I have done is available here
In summary, I have created a module in setCredentials.R (set/get username and password are one line function using Sys.getenv() and Sys.setenv(), not displayed here for space sake)
credentialsInput <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("username"), "Username", value = get_username()),
passwordInput(ns("password"), "Password", value = get_password()),
actionButton(ns("credentialSubmitButton"), "Submit" ))
}
setCredentials <- function(input, output, session){
eventReactive(input$credentialSubmitButton,{
set_username(input$username)
set_password(input$password)
})
observeEvent(input$credentialSubmitButton,{
print(paste(get_username(),get_password()))
})
}
Then in my app.R file I have called the module
ui <- fluidPage(
titlePanel("Set credentials"),
credentialsInput("credentials")
)
server <- function(input, output) {
callModule(setCredentials,"credentials")
}
shinyApp(ui = ui, server = server)
If variable are already set in the .Rprofile or equivalent, they are properly displayed in the field as default values. However if I modify (or enter in case of no .Rprofil) the print command return unchanged values (also if I click several times in case the print is executed before the set functions)
I guess somehow I cannot access the proper environment with Sys.setenv() from my module, but I don't understand exactly why.
Any help would be greatly appreciated.
I found a way to have this working. In my setCredentials function, I need to set the username and password in a observeEvent function call rather than a eventReactive...
setCredentials <- function(input, output, session){
observeEvent(input$credentialSubmitButton,{
set_username(input$username)
set_password(input$password)
print(paste(get_username(),get_password()))
})
}

Pattern for triggering a series of Shiny actions

I'm having trouble creating a sequence of events in a Shiny app. I know there are other ways of handling parts of this issue (with JS), and also different Shiny functions I could use to a similar end (e.g. withProgress), but I'd like to understand how to make this work with reactivity.
The flow I hope to achieve is as follows:
1) user clicks action button, which causes A) a time-consuming calculation to begin and B) a simple statement to print to the UI letting the user know the calculation has begun
2) once calculation returns a value, trigger another update to the previous text output alerting the user the calculation is complete
I've experimented with using the action button to update the text value, and setting an observer on that value to begin the calculation (so that 1B runs before 1A), to ensure that the message isn't only displayed in the UI once the calculation is complete, but haven't gotten anything to work. Here is my latest attempt:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("run", "Pull Data")
mainPanel(
textOutput("status")
)
)
)
server <- function(input, output, session) {
# slow function for demonstration purposes...
test.function <- function() {
for(i in seq(5)) {
print(i)
Sys.sleep(i)
}
data.frame(a=c(1,2,3))
}
report <- reactiveValues(
status = NULL,
data = NULL
)
observeEvent(input$run, {
report$status <- "Pulling data..."
})
observeEvent(report$status == "Pulling data...", {
report$data <- test.function()
})
observeEvent(is.data.frame(report$data), {
report$status <- "Data pull complete"
}
)
observe({
output$status <- renderText({report$status})
})
}
Eventually, I hope to build this into a longer cycle of calculation + user input, so I'm hoping to find a good pattern of observers + reactive elements to handle this kind of ongoing interaction. Any help is appreciated!

Resources