Shiny reactive inputs not updating as expected

Question originally posted on SO but with no comments or answers: https://stackoverflow.com/questions/54332016/shiny-reactivity-is-not-updating-as-expected

I have created a shiny app that pulls software components and their versions off of a list of nodes. The goal here is to make all of our nodes consistent when possible and this app helps us see which nodes are inconsistent.

Currently you can modify the version in the 'baseline' handsontable and it will reactively update the pivot table below with the change as well as the BaselineStats column within the handsontable. This works as expected. I have been asked to add the ability to upload a csv file that would overwrite the baseline table so a user does not have to change these 'baseline' versions each time they load the app.

In addition, there are some components that are 100% consistent. Currently those do not appear in the 'baseline' handsontable (since this is a tool to show inconsistency) but I have added a checkbox so that the user can still report on those components that are 100% consistent.

For some reason neither the fileUpload nor the checkboxInput are updating and no matter how much I poke and prod at my code, I cannot figure out why.

server.R

library(shiny)
library(rhandsontable)
library(rpivotTable)
library(dplyr)
library(stringr)
library(lubridate)

shinyServer(function(input, output) {

  # Create dataframe
df.consistency <- structure(list(Node = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 
                                    2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L), .Label = c("A", "B", "C", 
                                                                                    "D"), class = "factor"), Component = structure(c(3L, 4L, 1L, 2L, 3L, 
                                                                                                                                     4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L), .Label = c("docker.version", 
                                                                                                                                                                                             "kernel.version", "os.name", "os.version"), class = "factor"), 
                 Version = structure(c(10L, 3L, 1L, 6L, 10L, 3L, 1L, 7L, 10L, 
                                     5L, 1L, 8L, 10L, 4L, 2L, 9L), .Label = c("1.12.1", "1.13.1", 
                                                                              "16.04", "17.04", "18.04", "3.10.0", "3.11.0", "3.12.0", 
                                                                              "3.13.0", "RedHat"), class = "factor")), class = "data.frame", row.names = c(NA, 
                                                                                                                                                                     -16L))

# Get Date Time
Report.Date <- Sys.Date()

df.baseline <- reactive({

  inputFile <- input$uploadBaselineData

  if(!is.null(inputFile)){

    read.csv(inputFile$datapath, header = input$header)

  } else{
    if(input$showConsistent == FALSE){

      # Count the number of occurrences for Version and Component, then remove the Components that are consistent (not duplicated => nn == 1) and then remove nn column
      df.clusterCons.countComponent <- df.consistency %>%
        add_count(Version, Component) %>%
        add_count(Component) %>%
        filter(nn > 1) %>%
        select(-nn)

      # Change back to dataframe after grouping
      df.clusterCons.countComponent <- as.data.frame(df.clusterCons.countComponent)

      # Components and Versions are shown for every node/cluster. 
      # Reduce this df to get only a unique Component:Version combinations
      df.clusterCons.dist_tbl <- df.clusterCons.countComponent %>%
        distinct(Component, Version, .keep_all = TRUE)

      #Create a df that contains only duplicated rows (rows that are unique i.e. versions are consistent, are removed)
      df.clusterCons.dist_tbl.dup <- df.clusterCons.dist_tbl %>%
        filter(Component %in% unique(.[["Component"]][duplicated(.[["Component"]])]))

      #Create a baseline df to be used to filter larger dataset later 
      #(baseline = max(n) for Version -- but must retain Component since that is the parameter we will use to filter on later)
      df.clusterCons.baseline <- df.clusterCons.dist_tbl.dup[order(df.clusterCons.dist_tbl.dup$Component, df.clusterCons.dist_tbl.dup$n, decreasing = TRUE),]
      df.clusterCons.baseline <- df.clusterCons.baseline[!duplicated(df.clusterCons.baseline$Component), ]
      df.clusterCons.baseline <- df.clusterCons.baseline %>% 
        select(Component, Version)



    }
    else{
      # Count the number of occurrences for Version and Component, then remove the Components that are consistent (not duplicated => nn == 1) and then remove nn column
      df.clusterCons.countComponent <- df.consistency %>%
        add_count(Version, Component) %>%
        add_count(Component) %>%
        select(-nn)

      # Change back to dataframe after grouping
      df.clusterCons.countComponent <- as.data.frame(df.clusterCons.countComponent)

      # Components and Versions are shown for every node/cluster. 
      # Reduce this df to get only a unique Component:Version combinations
      df.clusterCons.dist_tbl <- df.clusterCons.countComponent %>%
        distinct(Component, Version, .keep_all = TRUE)

      df.clusterCons.baseline <- df.clusterCons.dist_tbl[order(df.clusterCons.dist_tbl$Component, df.clusterCons.dist_tbl$n, decreasing = TRUE),]
      df.clusterCons.baseline <- df.clusterCons.baseline[!duplicated(df.clusterCons.baseline$Component), ]
      df.clusterCons.baseline <- df.clusterCons.baseline %>% 
        select(Component, Version)
    }
  }
})


df.componentVersionCounts <- df.consistency %>%
  add_count(Component) %>%
  rename("CountComponents" = n) %>%
  add_count(Component, Version) %>%
  rename("CountComponentVersions" = n) %>%
  mutate("BaselineStats" = paste0("Baseline: ", round(CountComponentVersions / CountComponents * 100, 2), "% of Total: ", CountComponents)) %>%
  select(Component, Version, BaselineStats) %>%
  distinct(.keep_all = TRUE)

df.componentVersions_tbl <- reactive({
  df.componentVersions_tbl <- df.baseline() %>%
    distinct(Component, .keep_all = TRUE) %>%
    select(Component, Version) %>%
    left_join(df.componentVersionCounts, by = c("Component" = "Component", "Version" = "Version"))

})

# Report Date Output
output$reportDate <- renderText({
  return(paste0("Report last run: ", Report.Date))
})

# handsontable showing baseline and allowing for an updated baseline
output$baseline_table <- rhandsontable::renderRHandsontable({

  rhandsontable(df.componentVersions_tbl(), rowHeaders = NULL) %>%
    hot_col("Component", readOnly = TRUE) %>%
    hot_col("BaselineStats", readOnly = TRUE) %>%
    hot_cols(columnSorting = TRUE) %>%
    hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE, filters = TRUE)

})

observe({
  hot = isolate(input$baseline_table)
  if(!is.null(input$baseline_table)){
    handsontable <- hot_to_r(input$baseline_table)

    df.clusterCons.baseline2 <- handsontable %>%
      select(-BaselineStats)

    df.componentVersions_tbl <- df.clusterCons.baseline2  %>%
      left_join(df.componentVersionCounts, by = c("Component" = "Component", "Version" = "Version"))

    output$baseline_table <- rhandsontable::renderRHandsontable({

      rhandsontable(df.componentVersions_tbl, rowHeaders = NULL) %>%
        hot_col("Component", readOnly = TRUE) %>%
        hot_col("BaselineStats", readOnly = TRUE) %>%
        hot_cols(columnSorting = TRUE) %>%
        hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE, filters = TRUE)

    })

    df.clusterIncons <- anti_join(df.consistency, handsontable, by = c("Component" = "Component", "Version" = "Version"))
    df.clusterIncons <- df.clusterIncons

    # Pivot Table showing data with inconsistencies 
    output$pivotTable <- rpivotTable::renderRpivotTable({
      rpivotTable::rpivotTable(df.clusterIncons, rows = c("Cluster", "Node"), cols = "Component", aggregatorName = "List Unique Values", vals = "Version", 
                               rendererName = "Table", 
                               inclusions = list(Component = list("os.version", "os.name", "kernel.version", "docker.version")))


    })

    output$downloadBaselineData <- downloadHandler(
      filename = function() {
        paste('baselineData-', Sys.Date(), '.csv', sep='')
      },
      content = function(file) {
        baseline_handsontable <- handsontable %>%
          select(-BaselineStats)
        write.csv(baseline_handsontable, file, row.names = FALSE)
      }
    )


    output$downloadPivotData <- downloadHandler(
      filename = function() {
        paste('pivotData-', Sys.Date(), '.csv', sep='')
      },
      content = function(file) {
        write.csv(df.clusterIncons, file, row.names = FALSE)
      }
    )

  }
})

})

ui.R

library(shiny)
library(shinydashboard)
library(rhandsontable)
library(rpivotTable)

dashboardPage(

  dashboardHeader(title = "Test Dashboard", titleWidth = "97%"),

  dashboardSidebar(
    collapsed = TRUE,
    sidebarMenu(
      menuItem("App", tabName = "app", icon = icon("table"))
    )
  ),

  dashboardBody(

    tabItems(
      tabItem("app",
              fluidRow(
                box(width = 3, background = "light-blue",
                    "This box includes details to the user about how the application works", br(), br(), br(), 
                    verbatimTextOutput("reportDate")
                ),
                box(width = 7, status = "info", title = "Version baselines based on greatest occurance",
                    rHandsontableOutput("baseline_table", height = "350px")
                ),
                column(width = 2, 
                       fluidRow(
                         fileInput("uploadBaselineData", "Upload Other Baseline Data:", multiple = FALSE, 
                                   accept = ".csv")
                       ),
                       fluidRow(
                         downloadButton("downloadBaselineData", "Download Baseline Data")
                       ),
                       br(), 
                       fluidRow(
                         downloadButton("downloadPivotData", "Download Pivot Table Data")
                       ),
                       br(), 
                       fluidRow(
                         checkboxInput("showConsistent", "Show Consistent Components in baseline")
                       )
                )
              ),
              fluidRow(
                box(width = 12, status = "info", title = "Nodes with versions inconsistent with baseline",
                    div(style = 'overflow-x: scroll', rpivotTable::rpivotTableOutput("pivotTable", height = "500px"))
                )
              )
              )
    )
)
    )

It should be noted though that while running this app locally, if I check the checkbox and then refresh the webpage, when the app reloads (with the checkbox still checked) the data is updated as I would expect to happen reactively.

Maybe reactlog :package: could be useful here to see things more clearly. There was a recent talk at rstudio conf. You may be interested

2 Likes

Thanks! I am trying to install it and am running into some issues. As soon as I can get it installed I will see if this helps resolve the problem.

1 Like

I am still figuring out how to use reactlog but so far, all it has provided me is what I think I already know. I see the changes occurring to the 2 objects as expected but I don't understand why they are not updating the data downstream.

Here is a picture of the reactlog output before I click the check box or upload new baseline data:

And after:

I see the values of input$showConsistent and input$uploadBaselineData updating...

1 Like

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.