1

My application server file looks like this :

packages <- c("shiny", "shinydashboard", "RColorBrewer", "DT", "readxl", "plotly", "shinyanimate", "tidyverse", "shinycssloaders", "gridExtra", "shinyjs", "shinymanager")

lapply(packages, library, character.only = TRUE)

credentials <- data.frame(
  user = c("A", "B", "C"),
  password = c("Admin", "User1", "User2"),
  admin = c(TRUE, FALSE, FALSE),
  permission = c("advanced", "basic", "basic"),
  job = c("CEO", "CTO", "DRH"),
  stringsAsFactors = FALSE)


server <- function(input, output, session) {
  
  res_auth <- secure_server(
    check_credentials = check_credentials(credentials)
  )
  
  
  # Create reactive values including all credentials
  creds_reactive <- reactive({
    reactiveValuesToList(res_auth)
  })
  
  observeEvent(creds_reactive()$job, {

  data <- subset(data, 
                   grepl(creds_reactive()$job,
                          ignore.case = TRUE,
                          Job))

  
output$ev <- renderUI ({
  data <- subset(data, 
                  grepl(creds_reactive()$job,
                        ignore.case = TRUE,
                        Job))
  tags$iframe(
    seamless = "seamless",
    src = "link to the second application",
    style = "overflow:hiden; overflow-x : hidden; overflow-y : hidden; height:90%; width : 125%; position : absolute; top : 50px; padding : 0;",
    height = "200%", width = "100%",#"100%", #2000, #transform = scale(10),
    #"transform-origin" = "top right",
    frameBorder = "0"
  )})
  })
}

I would like to apply a filter on my second application in the iframe.

For example if A connects, data in my second app will show only rows for CEO, if B connects, data in my second app will show only rows for CTO .....

My question is if there is a possibility to apply this filter to an external application?

I thank you in advance for your answers and for your time :).

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
ss10
  • 69
  • 6
  • You could control your "second application" by passing different query strings (iframe `src`) and listen on `getQueryString(session)` as done [here](https://stackoverflow.com/questions/70080803/uri-routing-for-shinydashboard-using-shiny-router/70093686#70093686). – ismirsehregal Feb 22 '22 at 23:02
  • Hi @ismirsehregal, thanks for your comment, I've really been looking but I still can't figure out how to do it. The filter only applies to my current application and not to my embedded applications (tags$iframe). – ss10 Feb 25 '22 at 16:19
  • I left an example below elaborating my earlier comment. – ismirsehregal Mar 02 '22 at 15:33

1 Answers1

4

The following script creates two shiny apps: The child_app is running in a seperate background R process (depending on how you deploy your app this might not be needed), which can be controlled (filtered) via query strings.

The parent_app displays the child_app in an iframe and changes the query string (iframe's src) depending on the user accessing the app (permission level):

library(shiny)
library(shinymanager)
library(callr)
library(datasets)
library(DT)

# create child_app --------------------------------------------------------
# which will be shown in an iframe of the parent_app and can be controlled by passing query strings
ui <- fluidPage(
  DT::DTOutput("filteredTable")
)

server <- function(input, output, session) {
  permission <- reactive({shiny::getQueryString(session)$permission})
  
  # req: if child_app is accessed without providing a permission query string nothing is shown
  # "virginica" is default (unknown permission level - query string other than "advanced" / "basic")
  # http://127.0.0.1:3838/?permission=unknown
  output$filteredTable <- DT::renderDT({
    permissionFilter <- switch(req(permission()),
                               "advanced" = "setosa",
                               "basic" = "versicolor",
                               "virginica")
    if(!is.null(permissionFilter) && permissionFilter %in% unique(iris$Species)){
      datasets::iris[datasets::iris$Species == permissionFilter,]
    } else {
      datasets::iris
    }
  })
}

child_app <- shinyApp(ui, server)

# run child_app in a background R process - not needed when e.g. hosted on shinyapps.io
child_app_process <- callr::r_bg(
  func = function(app) {
    shiny::runApp(
      appDir = app,
      port = 3838L,
      launch.browser = FALSE,
      host = "127.0.0.1" # child_app is accessible only locally (or via the iframe)
    )
  },
  args = list(child_app),
  supervise = TRUE
)
# child_app_process$is_alive()

# create parent app -------------------------------------------------------
credentials <- data.frame(
  user = c("admin", "user1", "user2"),
  password = c("admin", "user1", "user2"),
  admin = c(TRUE, FALSE, FALSE),
  permission = c("advanced", "basic", "basic"),
  job = c("CEO", "CTO", "DRH"),
  stringsAsFactors = FALSE)

ui <- fluidPage(
  fluidRow(tags$h2("My secure application"),
  verbatimTextOutput("auth_output"),
  uiOutput("child_app_iframe"))
)

ui <- secure_app(ui)

server <- function(input, output, session) {
  res_auth <- secure_server(
    check_credentials = check_credentials(credentials)
  )
  
  output$auth_output <- renderPrint({
    reactiveValuesToList(res_auth)
  })
  
  output$child_app_iframe <- renderUI({
    tags$iframe(
      src = sprintf("http://127.0.0.1:3838/?permission=%s", res_auth$permission),
      style = "border: none;
               overflow: hidden;
               height: 65vh;
               width : 100%;
               position: relative;
               top:15px;
               padding:0;"
      # position: absolute;
    )
    })
}

parent_app <- shinyApp(ui, server, onStart = function() {
  cat("Doing application setup\n")
  onStop(function() {
    cat("Doing application cleanup\n")
    child_app_process$kill() # kill child_app if parent_app is exited - not needed when hosted separately
  })
})

# run parent_app
runApp(appDir = parent_app,
       port = 3939L,
       launch.browser = TRUE,
       host = "0.0.0.0")

Please note the Species column:

result


Edit: Here is a clean multi-file approach avoiding nested render-functions (This needs to be adapted when used with shiny-server - please see my comments):

child_app.R:

library(shiny)
library(shinymanager)
library(datasets)
library(DT)

ui <- fluidPage(
  DT::DTOutput("filteredTable")
  )

server <- function(input, output, session) {
  permission <- reactive({shiny::getQueryString(session)$permission})
  
  table_data <- reactive({
    permissionFilter <- switch(req(permission()),
                               "advanced" = "setosa",
                               "basic" = "versicolor",
                               "virginica")
    if(!is.null(permissionFilter) && permissionFilter %in% unique(iris$Species)){
      datasets::iris[datasets::iris$Species == permissionFilter,]
    } else {
      NULL # don't show something without permission
    }
  })
  
  output$filteredTable <- DT::renderDT({
    table_data()
  })
    
}

child_app <- shinyApp(ui, server)

# run parent_app (local deployment)
runApp(
  appDir = child_app,
  port = 3838L,
  launch.browser = FALSE,
  host = "127.0.0.1" # child_app is accessible only locally (or via the iframe)
)

parent_app.R:

library(shiny)
library(shinymanager)
library(datasets)
library(DT)

credentials <- data.frame(
  user = c("admin", "user1", "user2"),
  password = c("admin", "user1", "user2"),
  permission = c("advanced", "basic", "basic"),
  stringsAsFactors = FALSE)

ui <- fluidPage(
  fluidRow(tags$h2("My secure application"),
           verbatimTextOutput("auth_output"),
           uiOutput("child_app_iframe"))
)

ui <- secure_app(ui)

server <- function(input, output, session) {
  res_auth <- secure_server(
    check_credentials = check_credentials(credentials)
  )
  
  output$auth_output <- renderPrint({
    reactiveValuesToList(res_auth)
  })
  
  output$child_app_iframe <- renderUI({
    tags$iframe(
      # src = sprintf("child_app_link/child_app/?permission=%s", res_auth$permission), # shiny-server
      src = sprintf("http://127.0.0.1:3838/?permission=%s", res_auth$permission), # local deployment
      style = "border: none;
               overflow: hidden;
               height: 500px;
               width : 95%;
               # position: relative;
               # top:15px;
               # padding:0;
      "
    )
  })
}

parent_app <- shinyApp(ui, server)

# run parent_app (local deployment)
runApp(appDir = parent_app,
       port = 3939L,
       launch.browser = TRUE,
       host = "0.0.0.0")
ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • Thank you @ismirsehregal for your time and script. I am in server mode (Shiny server with aws EC2) and each of my applications has its own url. So when I put the child_app in its own bubble as a separate application and then I call it in tags$iframe with its own url, I get the error message "Disconnected from the server. Reload" – ss10 Mar 03 '22 at 23:14
  • @ss10 It's hard to tell what's going wrong without seeing your modifications. Is the child app running at all? Are you able to access it via `http://your_server/child_app/?permission=advanced`? – ismirsehregal Mar 04 '22 at 07:30
  • @ismisehregal I just posted the changes I made and it works fine now. Thank you so much – ss10 Mar 09 '22 at 09:59
  • This is indeed your no-callr code and I totally voted for your post as this was crucial. However, I felt it necessary to post my piece of code for those who are even more of a beginner than I am or who would not have their codes (app1 and app2) in the same script hence the addition of observeEvent in my script. I'm fully aware that without your script I couldn't have made any progress, I just didn't want to keep the answer to my question or the changes I made to myself. I apologize if I inadvertently did not show enough appreciation. – ss10 Mar 09 '22 at 14:12
  • 2
    @ss10 please see my edit for a cleaned multi-file approach avoiding the nested render-function, only relying on reactives (please also see [this](https://stackoverflow.com/a/53035952/9841389)). – ismirsehregal Mar 09 '22 at 15:15