---
title: "Pub/Sub driven `{shiny}` dashboard"
author: "Andrea Dodet"
date: "`r Sys.Date()`"
output:
  html_document: default
  md_document: default
vignette: >
  %\VignetteIndexEntry{Using Pubsub in `{shiny}`}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

## Resource setup

In order to run the shiny app in `inst/shiny/consumer_example/app.R` you'll need to setup your 
`GCP_PROJECT` as an environment variable before running this example.  
Run the app from the root of the package:
```r
shiny::runApp("inst/shiny/consumer_example/")
```

Upon startup, the `inst/shiny/consumer_example/app.Rinit.R` file will take care of creating the 
following resources:

* A pubsub topic named `shiny-topic`
* An associated pubsub subscription from where we'll be polling and consuming messages, named `shiny-sub`

## Components

This app is composed by two main components:

**A producer**:

In this case, our producer will be an `actionButton` (on top of this readme) that will trigger 
a `PubsubMessage` to be generated and sent to the subscription, the message will contain the following fields:

```json
{
  "col_a": "int",    # A random integer
  "col_b": "int",    # A random integer
  "fired_at": "str"  # Timestamp at which the message was created
}
```

**A consumer:**

This is the bulk of where the interesting things happen. The shiny app will check whether new
messages have been published to the subscription every 2 seconds. In order not to block the user from
sending new messages (or keep interacting with sliders, inputs, etc. in a more complex case), this will be 
done in a parallel R session using the [`{promises}`](https://github.com/rstudio/promises) and [`{future}`](https://github.com/HenrikBengtsson/future) packages.

```r
# Set up a message consumer in background sessions (poll messages every 2 seconds)
observe({
  invalidateLater(20000, session)

  future_promise({
    pubsub_auth() # Authenticated session is not passed to futures' env
    get_data()
  }) %>%
    then(function(res) {
      # Notify the user if new messages have been received
      if (!is.null(res)) {
        showNotification(
          paste("Message received at", strftime(Sys.time()), sep = " "),
          duration = 3,
          type = "warning"
        )
      }

      # Append to the reactive dataframe
      out_df$df <- rbind(out_df$df, res)
    })

  # Hide the future, this is a fire and forget hack and allows avoid blocking
  # from https://stackoverflow.com/a/57922419/9046275
  return(NULL)
})
```

The `get_data()` function takes care of:

1. Pulling the messages from the Pub/Sub topic
2. Convert the base64 encoded string and binding the results to a data frame
3. Acknowledge the subscription the messages where consumed (effectively removing them from the
subscription).

---

The components described above interact according the behaviour described by the diagram below:
```
+---------------+   +---------------+     +------------------+        +--------------------+                                                                                                                                                   
| actionButton  |-->| Pub/Sub Topic |     |Observe({promise})|------> |Output dataframe    |                                                                                                                                                   
| (producer)    |   |               |     |(consumer)        |        |(reactiveValues(df) |                                                                                                                                                   
+---------------+   +---------------+     +------------------+        +--------------------+                                                                                                                                                   
                          |                     ^     |                                                                                                                                                                                        
                    +-----v---------+           |     |                                                                                                                                                                                        
                    |Pub/Sub        |-----------+     |                                                                                                                                                                                        
                    |Subscription   |<----------------+                                                                                                                                                                                        
                    +---------------+                                                                                                                                                                                                          
```

### Todo

- It would be nice to have the time to profile the whole thing (an idea could be to observe memory usage
inside a docker container as I am not sure `profviz` is capable to handle multi-session shiny apps).
