Need help with reactive expressions and Shiny Modules

shiny
shiny-modules

#1

I am working on a shiny app and I quickly realized it was going to become pretty large and complex. I began thinking about how it would be great to be able to modularize it and reuse the modules. After some research, I discovered this is possible using shiny modules! I am taking a stab and changing the first iteration of my app to use modules but I am stuck. I have read through all of the articles I can find and watched a few videos, but all of the examples I have found so far build a rather simple app, so I hope someone here is able to help. I have my ui.r, server.r, CFNavbarTab.r, and CFData.r files. I plan to have several navbarMenus with tabPanels so my goal was to modularize as much of the tabPanel as possible. There error I am getting is the

ERROR: Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)

so I know I am missing a reactive expression somewhere. I am having trouble with the logic around how to use renderUI for a dynamic selectInput as well as filtering on multiple ui inputs. Here is the code so far:

ui.r

library(shiny)
library(DT)
library(shinythemes)
source("CFNavbarTab.r")
source("CFData.r")

shinyUI(fluidPage(shinythemes::themeSelector(),
                  titlePanel("Cluster Capacity Dashboard"),
                  navbarPage("NavBar", 
                             navbarMenu("RAM",
                                        tabPanel("Availability Difference (Large)", CFNavbarTabUI("ram.lg"))
                             )
                             )
)
)

server.r

library(shiny)
library(dplyr)
library(ggplot2)
library(reshape2)
source("CFNavbarTab.r")
source("CFData.r")

shinyServer(function(input, output) {
  
  callModule(CFNavbarTab, "ram.lg", reactive(ram.lg.data))

  })

CFData.r

library(dplyr)

# Data import
  setwd("~/TDP/CPF_Analytics/CapacityForecasting/Version2")
  k8s_ram_cpu_report <- read.csv("k8s_ram_cpu_report.csv")
  df.RAMCPU <- mutate(k8s_ram_cpu_report, RAM.AVAILABLE.GB = RAM.AVAILABLE / 1000000000) %>% mutate(RAM.ACTUAL.GB = RAM.ACTUAL / 1000000000)
  
  CF.df <- reactive ({
    
    if (input$ui.usage == "All"){
      df.USAGE <- df.RAMCPU$USAGE
      df.USAGE
    }
    else {
      df.USAGE <- input$ui.usage
    }
    
    if (is.null(input$ui.environment)){
      df.ENVIRONMENT <- df.RAMCPU$ENVIRONMENT
      df.ENVIRONMENT
    }
    else {
      df.ENVIRONMENT <- input$ui.environment
    }
    
    if (is.null(input$ui.dataCenter)){
      df.GEOLOC <- df.RAMCPU$GEOLOC
      df.GEOLOC
    }
    else {
      df.GEOLOC <- input$ui.dataCenter
    }
    
    CF.df <- data %>% dplyr::filter(USAGE == df.USAGE, ENVIRONMENT == df.ENVIRONMENT, GEOLOC == df.GEOLOC) 
    return(CF.df)
    
  })
  
  ram.lg.data <- reactive({
    ram.lg.data <- select(CF.df(), CLUSTER.NAME, RAM.AVAILABLE.GB, RAM.ACTUAL.GB)
    ram.lg.data <- mutate(ram.lg.data, RAM.DIFFERENCE = RAM.AVAILABLE.GB - RAM.ACTUAL.GB) %>%
      arrange(desc(RAM.DIFFERENCE)) %>%
      head(n = 20) %>%
      select(CLUSTER.NAME, RAM.AVAILABLE.GB, RAM.ACTUAL.GB)
    ram.lg.data <- melt(ram.lg.data)
  return(ram.lg.data)
  })

CFNavbarTab.r

library(shiny)
library(dplyr)
library(ggplot2)
library(reshape2)

CFNavbarTabUI <- function(id) {
  ns <- NS(id)
  tagList(
    fluidRow(
               column(12,
                      column(3,
                             radioButtons(ns("ui.usage"), "Cluster Usage",
                                          c("All" = "All", "Dedicated"="Dedicated", "Shared"="Shared"), selected = "All"
                                         ),
                             selectInput(ns("ui.environment"), "Environment", choices = c("infratest", "infradev", "nonprod", "prod"), selectize = TRUE, multiple = TRUE),
                             selectInput(ns("ui.dataCenter"), "Data Center",  choices = c("DLLSTXCF", "ALPRGAED", "ALPSGACT", "STLSMORC"), selectize = TRUE, multiple = TRUE )
                            ),
                      column(9,
                             plotOutput(ns("CF.plot"))
                            )
                      )
    )
  )
}

CFNavbarTab <- function(input, output, session, data){
  
  data <- reactive({
    data <- ram.lg.data()
  })
  
  
  # Plot
  output$CF.plot <- renderPlot({
    
    # Plot
    CF.p <- ggplot(data(), aes(CLUSTER.NAME, value, fill = variable))
    CF.p + 
      geom_bar(stat="identity",position="dodge") +
      coord_flip() +
      labs(x = "Cluster Name", y = "RAM (in GB)")
    
    
  })

  }

Here is some data for df.RAMCPU:

structure(list(ENVIRONMENT = structure(c(1L, 3L, 3L, 3L, 3L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 2L, 3L, 
3L, 3L, 3L, 3L, 3L), .Label = c("infradev", "infratest", "nonprod", 
"prod"), class = "factor"), GEOLOC = structure(c(7L, 10L, 1L, 
1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 4L, 4L, 5L, 6L, 6L, 6L, 6L, 
6L, 6L, 7L, 7L, 7L, 7L, 7L), .Label = c("ALPRGAED", "ALPSGACT", 
"ATLDGA93", "ATLMGACX", "BOTHWAKY", "BRHMALDC", "DLLSTXCF", "FFPRGAED", 
"FRFDCA60", "HYWRCA02", "KGMTNC20", "KMPRGAED", "KSCYMOAU", "SFLDMIBB", 
"SNDGCA64", "STLSMORC"), class = "factor"), USAGE = structure(c(1L, 
2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L), .Label = c("Dedicated", 
"Shared"), class = "factor"), RAM.AVAILABLE = c(458838352, 458839084, 
5547148600, 229420884, 138120456, 529571480, 658022440, 278737460, 
183493584, 357147792, 80535840, 205155416, 178943204, 11552987512, 
229470652, 9768304124, 20461394432, 11089427244, 167490416, 131096336, 
93477940, 921215052, 639531392, 1101387288, 987033660, 1114321376
), RAM.ACTUAL = c(71694412L, 24761296L, 56935940L, 38210952L, 
25184700L, 85681808L, 77821724L, 41700840L, 36413632L, 32338584L, 
41430556L, 41664640L, 86492572L, 133578312L, 55139564L, 219483468L, 
923218644L, 741387120L, 71859472L, 56494056L, 33996208L, 401019200L, 
233304628L, 497478896L, 78335012L, 343349352L), CPU.AVAILABLE = c(112L, 
112L, 560L, 56L, 38L, 104L, 120L, 60L, 36L, 56L, 28L, 42L, 88L, 
1568L, 56L, 1176L, 2352L, 1176L, 44L, 32L, 28L, 156L, 136L, 216L, 
204L, 272L), CPU.ACTUAL = c(0.535, 0.1497, 0.0526, 0.3811, 0.9085, 
1.1026, 0.4709, 1.0248, 0.8243, 0.4044, 2.0725, 0.8144, 1.1668, 
0, 0.7195, 0.2941, 2.4276, 0.4515, 1.1699, 0.2949, 0.3424, 1.8434, 
2.5034, 2.606, 0.2734, 4.2449), RAM.AVAILABLE.GB = c(0.458838352, 
0.458839084, 5.5471486, 0.229420884, 0.138120456, 0.52957148, 
0.65802244, 0.27873746, 0.183493584, 0.357147792, 0.08053584, 
0.205155416, 0.178943204, 11.552987512, 0.229470652, 9.768304124, 
20.461394432, 11.089427244, 0.167490416, 0.131096336, 0.09347794, 
0.921215052, 0.639531392, 1.101387288, 0.98703366, 1.114321376
), RAM.FREE.. = c(84.37, 94.6, 98.97, 83.34, 81.76, 83.82, 88.17, 
85.03, 80.15, 90.94, 48.55, 79.69, 51.66, 98.84, 75.97, 97.75, 
95.48, 93.31, 57.09, 56.9, 63.63, 56.46, 63.51, 54.83, 92.06, 
69.18), CPU.FREE.. = c(99.52, 99.86, 99.99, 99.31, 97.6, 98.93, 
99.6, 98.29, 97.71, 99.27, 92.59, 98.06, 98.67, 100, 98.71, 99.97, 
99.89, 99.96, 97.34, 99.07, 98.77, 98.81, 98.15, 98.79, 99.86, 
98.43), CLUSTER.NAMES = c("a", "b", "c", "d", "e", "f", "g", 
"h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", 
"u", "v", "w", "x", "y", "z")), class = "data.frame", .Names = c("ENVIRONMENT", 
"GEOLOC", "USAGE", "RAM.AVAILABLE", "RAM.ACTUAL", "CPU.AVAILABLE", 
"CPU.ACTUAL", "RAM.AVAILABLE.GB", "RAM.FREE..", "CPU.FREE..", 
"CLUSTER.NAMES"), row.names = c(NA, -26L))

#2

I think you issue is in your CFData.R file. First, the code in this file is not in module form (i.e. in functions) so when you source the file it runs the code and the reactive code outside of your server code which is likely causing issues. Further, the ram.lg.data portion of that file needs to be in a reactive expression.

If you make those changes, you can explicitly return the reactive value that has completes the ram.lg.data operation. Then you can call that module and assign it to a variable. That variable would then be assigned to the data argument to your CFNavbarTab.R module


#3

Thank you @tbradley - that has gotten me in the right direction. Now I am having the following error:
Warning: Error in <reactive:CF.df>: object 'input' not found
I have updated the CFData.r and CFNavbarTab.r code above with the changes. I am still not grasping a concept here... Also, I am curious why the shiny module example from the webinar using the gapminder data does not seem to follow the same principles?


#4

So you are definitely headed in the right direction. The current issue I would guess has to do with the reactive expressions in the CFData.R. The issue is not in how they are written but rather where they are being executed in the shiny app. Currently, when you load the app, they are run prior to the server and ui code. So they are trying to access variables that do not exist in a context that isn't active (if that makes sense). To see if my assumption is correct, try editing your CFData.R and server.R files as follows:

CFData.R

library(dplyr)

# Data import
  setwd("~/TDP/CPF_Analytics/CapacityForecasting/Version2")
  k8s_ram_cpu_report <- read.csv("k8s_ram_cpu_report.csv")
  df.RAMCPU <- mutate(k8s_ram_cpu_report, RAM.AVAILABLE.GB = RAM.AVAILABLE / 1000000000) %>% mutate(RAM.ACTUAL.GB = RAM.ACTUAL / 1000000000)

server.R

library(shiny)
library(dplyr)
library(ggplot2)
library(reshape2)
source("CFNavbarTab.r")
source("CFData.r")

shinyServer(function(input, output) {
  
 CF.df <- reactive ({
    
    if (input$ui.usage == "All"){
      df.USAGE <- df.RAMCPU$USAGE
      df.USAGE
    }
    else {
      df.USAGE <- input$ui.usage
    }
    
    if (is.null(input$ui.environment)){
      df.ENVIRONMENT <- df.RAMCPU$ENVIRONMENT
      df.ENVIRONMENT
    }
    else {
      df.ENVIRONMENT <- input$ui.environment
    }
    
    if (is.null(input$ui.dataCenter)){
      df.GEOLOC <- df.RAMCPU$GEOLOC
      df.GEOLOC
    }
    else {
      df.GEOLOC <- input$ui.dataCenter
    }
    
    CF.df <- data %>% dplyr::filter(USAGE == df.USAGE, ENVIRONMENT == df.ENVIRONMENT, GEOLOC == df.GEOLOC) 
    return(CF.df)
    
  })
  
  ram.lg.data <- reactive({
    ram.lg.data <- select(CF.df(), CLUSTER.NAME, RAM.AVAILABLE.GB, RAM.ACTUAL.GB)
    ram.lg.data <- mutate(ram.lg.data, RAM.DIFFERENCE = RAM.AVAILABLE.GB - RAM.ACTUAL.GB) %>%
      arrange(desc(RAM.DIFFERENCE)) %>%
      head(n = 20) %>%
      select(CLUSTER.NAME, RAM.AVAILABLE.GB, RAM.ACTUAL.GB)
    ram.lg.data <- melt(ram.lg.data)
  return(ram.lg.data)
  })

  callModule(CFNavbarTab, "ram.lg", ram.lg.data())

  })

In addition, I believe you will need to change your CFNavbarTab.R file to make the call to the data argument as a reactive in your ggplot call directly from the input.

CFNavbarTab <- function(input, output, session, data){
  
  # Plot
  output$CF.plot <- renderPlot({
    
    # Plot
    CF.p <- ggplot(data(), aes(CLUSTER.NAME, value, fill = variable))
    CF.p + 
      geom_bar(stat="identity",position="dodge") +
      coord_flip() +
      labs(x = "Cluster Name", y = "RAM (in GB)")
    
    
  })

  }

#5

What's funny is, during my first iteration of this exercise, I had the logic just like you outline above. Somewhere along the way I decided to move it :smile: . I feel like this is really close... I have made all the changes you provided and now get the following:
Warning: Error in <reactive:CF.df>: object 'input' not found

I really appreciate you walking me through this. It will be extremely helpful in my future shiny app development


#6

Can you post the full error message that you are getting? It should give some hints as to where exactly the error is occurring. If I had to guess, it looks like something in the namespacing of the module


#7
Listening on http://127.0.0.1:6391
ERROR: [on_request_read] connection reset by peer
Warning: Error in if: argument is of length zero
Stack trace (innermost first):
    125: <reactive:CF.df> [C:\Users\AD0861\Documents\TDP\CPF_Analytics\CapacityForecasting\Version2/server.R#12]
    114: CF.df
    113: select
    112: <reactive:ram.lg.data> [C:\Users\AD0861\Documents\TDP\CPF_Analytics\CapacityForecasting\Version2/server.R#42]
    101: ram.lg.data
    100: ggplot
     99: renderPlot [CFNavbarTab.r#32]
     89: <reactive:plotObj>
     78: plotObj
     77: origRenderFunc
     76: output$ram.lg-CF.plot
      1: runApp

#8

Ah, as I expected, you are trying to access inputs from a module from outside of the modules server code. If you move the CF.df <- reactive({...}) and ram.lg.data <- reactive({...}) code chunks into your CFNavbarTab.R server code, and properly change the ggplot call to reference the ram.lg.data().

I realize that may not be what you want in the end, but I suspect that is your current issue. Off the top of my head, I can't think of the correct way to access ui elements in a module from outside of the corresponding module's server code (if its possible at all).


#9

Thanks for the help. I think I just need to dive further into shiny modules. Currently my goal is to take a working version of this app (only server.r and ui.r) and modularize it.