I am quite new to R. I tried to use reactivePoll to update my dashboard data. All my data is drawn from the database. The code shows no error. But the dashboard is not updated by day as I set it. Here is my code:
log_db <- reactivePoll(60000*60*24, session,
# Check for maximum month
checkFunc = function() {
#connect to the database
#check for maximum month in the database. If there's a change, the value function will run.
maxmonth <- paste("SQL code")
month <- dbGetQuery(maxmonth)
return(month)
},
# Pull new table if value has changed
valueFunc = function() {
#connect to db
#pull new dataframe,
return(oldnew_combined)
}
)
}
I think the format is fine since there are no error shows. I also tried to see the maximum month in the console. However, it says object not found which basically means the checkFunc didn't run. I wonder what goes wrong here. Thank you!
Steps:
1-You need to create the reactivepoll inside the server. log_db
2-
Create a rendering object inside the server (in your case: renderTable) with reactivePoll inside with parentheses: output$idforUi<- renderTable( { log_db() })
3-Create the output for your render object in the ui.
ui=fluidPage(tableOutput("idforUi"))
library(shiny) # good practices
library(RMariaDB) #good practices
server <- function(input, output,session) {
#The connection to SQL does not need to be inside the checkfunction or valuefunction,
#if you put it inside the checkfunction it will connect every milliseconds argument.
#If you place the connection inside the server but outside the reactivePoll, when you open the app it connects, and updates every milliseconds inside the reactivePoll
localuserpassword="yourpassword"
storiesDb<- dbConnect(RMariaDB::MariaDB(), user='YOUR_USER', password=localuserpassword, dbname='DBNAME', host='YOURHOST')
#your database will be checked if it changes every 60000 * 60 * 24 milliseconds (24 hours)
log_db <- reactivePoll(60000*60*24, session, #reactivePoll inside the server
# Check for maximum month
checkFunc = function() {
query2= "SELECT * FROM YOURTABLE"
rs = dbSendQuery(storiesDb,query2)
dbFetch(rs)# visualize
},
# Pull new table if value has changed
valueFunc = function() {
query2= "SELECT * FROM YOURTABLE"
rs = dbSendQuery(storiesDb,query2)
dbFetch(rs)# visualize
}
)
#log_db is a function dont forget the () inside renderTable()
output$idforUi<- renderTable( { log_db() }) # renderTable
#create a object to send the result of your reactivepoll for User Interface
}
# table output
ui=fluidPage(tableOutput("idforUi"))
# Receive the result of your reactivepoll in the User Interface
shinyApp(ui, server)
You are unable to access it from the console does not mean that checkFunc did not run,you will not be able to access the "month" object on the console because it exists only in the reactivepoll function(local variable), not in global environment. See this
Related
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.)
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))
I have an R-Shiny application, and inside the server function I fetch data to use within. Because I want to re-fetch this data after every 20 seconds or so, I have added the following function at the end of the server function as below:
server <- function(input, output, session) {
# Process data
# ...
# Reload data
counter <- reactiveVal(0)
observe({
invalidateLater(20000, session)
if (counter() > 0) {
session$reload()
} else{
counter(isolate(counter()) + 1)
}
})
}
The problem is that session reload seems to be called immediately the server function has finished execution, that is, way quicker that indicated in the by invalidateLater().
Is there a way to make this work, would be grateful for any other approach.
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))
I have a shiny App that queries data from SQL. I'm running it on an internal server and want the data to refresh automatically every hour or so.
So far this only works whenever I put the shinApp files newly on the server and run it for the first time. Afterwards, whenever I reload the link, the data is not changing.
I tried using invalidate as below, but it does not refresh the data.
shinyServer(function(input, output, session) {
sourceData <- reactive({
invalidateLater(3000000,session)
return(sourceData())
})
.
.
.
})
where sourceData() is defined
sourceData<-function(){
data1 <<- get_data1( 'query here' )
data2 <<- get_data2( 'query here' )
}
Has anyone had this issue?
I saw reactivepoll() is another option. The valueFunc would be my sourceData, but wasn't sure how to integrate the checkFunc in my context.
If you're not looking to use sourceData() to return anything as this is the way it looks for me you can do one of the following:
1
# sourceData() shouldn't return anything but it will still write into data1 and data2
sourceData <- reactive({
invalidateLater(3000000,session)
data1 <<- get_data1( 'query here' )
data2 <<- get_data2( 'query here' )
})
2
# This is the preferred option as it seems to me you don't want to use sourceData() but rather the data1 and data2
sourceData <- observe({
invalidateLater(3000000,session)
data1 <<- get_data1( 'query here' )
data2 <<- get_data2( 'query here' )
})
Also do look into reactivePoll and there are examples of how to structure it in the reactivePoll and reactiveFileReader