Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
  • 1 commit
  • 3 files changed
  • 0 comments
  • 1 contributor
Commits on Jan 04, 2019
Showing with 114 additions and 96 deletions.
  1. +1 −1 README.md
  2. +72 −59 app.R
  3. +41 −36 modules/detail.R
@@ -2,7 +2,7 @@


This is a Shiny example application, meant to demonstrate how to convert synchronous (traditional) Shiny apps to asynchronous ones. This is a Shiny example application, meant to demonstrate how to convert synchronous (traditional) Shiny apps to asynchronous ones.


This is the **synchronous** version. The (naive) asynchronous version is [here](https://github.com/jcheng5/cranwhales/tree/async) ([diff](https://github.com/jcheng5/cranwhales/compare/sync...async?diff=split)). The optimized asynchronous version is [here](https://github.com/rstudio/cranwhales/tree/async2) ([diff](https://github.com/jcheng5/cranwhales/compare/async...async2?diff=split)). This is the (naive) **asynchronous** version. The synchronous version is [here](https://github.com/jcheng5/cranwhales) ([diff](https://github.com/jcheng5/cranwhales/compare/sync...async?diff=split)). The optimized asynchronous version is [here](https://github.com/rstudio/cranwhales/tree/async2) ([diff](https://github.com/jcheng5/cranwhales/compare/async...async2?diff=split)).


To learn more about asynchronous programming in R and Shiny, see https://rstudio.github.io/promises/. To learn more about asynchronous programming in R and Shiny, see https://rstudio.github.io/promises/.


131 app.R
@@ -8,6 +8,11 @@ library(glue)
library(lubridate) library(lubridate)
library(gdata) # for gdata::humanReadable library(gdata) # for gdata::humanReadable


library(promises)
library(future)
# Leave one core for Shiny itself
plan(multisession(workers = availableCores() - 1))

source("random-names.R") source("random-names.R")
source("modules/detail.R") source("modules/detail.R")


@@ -59,26 +64,26 @@ server <- function(input, output, session) {
url <- glue("http://cran-logs.rstudio.com/{year}/{date}.csv.gz") url <- glue("http://cran-logs.rstudio.com/{year}/{date}.csv.gz")
path <- file.path("data_cache", paste0(date, ".csv.gz")) path <- file.path("data_cache", paste0(date, ".csv.gz"))


withProgress(value = NULL, { p <- Progress$new()

p$set(value = NULL, message = "Downloading data...")
future({
# Download to a temporary file path, then rename to the real # Download to a temporary file path, then rename to the real
# path when the download is complete. We do this so other # path when the download is complete. We do this so other
# processes/sessions don't use partially downloaded files. # processes/sessions don't use partially downloaded files.
if (!file.exists(path)) { if (!file.exists(path)) {
tmppath <- paste0(path, "-", Sys.getpid()) tmppath <- paste0(path, "-", Sys.getpid())
setProgress(message = "Downloading data...")
download.file(url, tmppath) download.file(url, tmppath)
if (!file.exists(path)) { if (!file.exists(path)) {
file.rename(tmppath, path) file.rename(tmppath, path)
} else { } else {
file.remove(tmppath) file.remove(tmppath)
} }
} }
}) %...>%
setProgress(message = "Parsing data...") { p$set(message = "Parsing data...") } %...>%
read_csv(path, col_types = "Dti---c-ci", progress = FALSE) %>% { future(read_csv(path, col_types = "Dti---c-ci", progress = FALSE) %>%
filter(!is.na(package)) filter(!is.na(package))) } %>%
}) finally(~p$close())
}) })


# Returns a data frame of just the top `input$count` downloaders of the day, # Returns a data frame of just the top `input$count` downloaders of the day,
@@ -93,21 +98,24 @@ server <- function(input, output, session) {
need(input$count > 0, "Too few downloaders"), need(input$count > 0, "Too few downloaders"),
need(input$count <= 25, "Too many downloaders; 25 or fewer please") need(input$count <= 25, "Too many downloaders; 25 or fewer please")
) )
data() %>% data() %...>%
count(ip_id, country) %>% count(ip_id, country) %...>%
arrange(desc(n)) %>% arrange(desc(n)) %...>%
head(input$count) %>% head(input$count) %...>%
mutate(ip_name = factor(ip_id, levels = ip_id, mutate(ip_name = factor(ip_id, levels = ip_id,
labels = glue("{random_name(length(ip_id), input$date)} [{country}]"))) %>% labels = glue("{random_name(length(ip_id), input$date)} [{country}]"))) %...>%
select(-country) select(-country)
}) })


# data(), filtered down to the downloads that are by the top `input$count` # data(), filtered down to the downloads that are by the top `input$count`
# downloaders # downloaders
whale_downloads <- reactive({ whale_downloads <- reactive({
data() %>% promise_all(data = data(), whales = whales()) %...>%
inner_join(whales(), "ip_id") %>% with({
select(-n) data %>%
inner_join(whales, "ip_id") %>%
select(-n)
})
}) })




@@ -116,77 +124,82 @@ server <- function(input, output, session) {
#### "All traffic" tab ---------------------------------------- #### "All traffic" tab ----------------------------------------


output$total_size <- renderValueBox({ output$total_size <- renderValueBox({
data() %>% data() %...>%
pull(size) %>% pull(size) %...>%
as.numeric() %>% # Cast from integer to numeric to avoid overflow warning as.numeric() %...>% # Cast from integer to numeric to avoid overflow warning
sum() %>% sum() %...>%
humanReadable() %>% humanReadable() %...>%
valueBox("bandwidth consumed") valueBox("bandwidth consumed")
}) })


output$total_count <- renderValueBox({ output$total_count <- renderValueBox({
data() %>% data() %...>%
nrow() %>% nrow() %...>%
format(big.mark = ",") %>% format(big.mark = ",") %...>%
valueBox("files downloaded") valueBox("files downloaded")
}) })


output$total_uniques <- renderValueBox({ output$total_uniques <- renderValueBox({
data() %>% data() %...>%
pull(package) %>% pull(package) %...>%
unique() %>% unique() %...>%
length() %>% length() %...>%
format(big.mark = ",") %>% format(big.mark = ",") %...>%
valueBox("unique packages") valueBox("unique packages")
}) })


output$total_downloaders <- renderValueBox({ output$total_downloaders <- renderValueBox({
data() %>% data() %...>%
pull(ip_id) %>% pull(ip_id) %...>%
unique() %>% unique() %...>%
length() %>% length() %...>%
format(big.mark = ",") %>% format(big.mark = ",") %...>%
valueBox("unique downloaders") valueBox("unique downloaders")
}) })


output$all_hour <- renderPlot({ output$all_hour <- renderPlot({
whale_ip <- whales()$ip_id promise_all(data = data(), whales = whales()) %...>%

with({
data() %>% whale_ip <- whales$ip_id
mutate(
time = hms::trunc_hms(time, 60*60), data %>%
is_whale = ip_id %in% whale_ip mutate(
) %>% time = hms::trunc_hms(time, 60*60),
count(time, is_whale) %>% is_whale = ip_id %in% whale_ip
ggplot(aes(time, n, fill = is_whale)) + ) %>%
geom_bar(stat = "identity") + count(time, is_whale) %>%
scale_fill_manual(values = c("#666666", "#88FF99"), ggplot(aes(time, n, fill = is_whale)) +
labels = c("no", "yes")) + geom_bar(stat = "identity") +
ylab("Downloads") + scale_fill_manual(values = c("#666666", "#88FF99"),
xlab("Hour") + labels = c("no", "yes")) +
scale_y_continuous(labels = scales::comma) ylab("Downloads") +
xlab("Hour") +
scale_y_continuous(labels = scales::comma)
})
}) })


#### "Biggest whales" tab ------------------------------------- #### "Biggest whales" tab -------------------------------------


output$downloaders <- renderPlot({ output$downloaders <- renderPlot({
whales() %>% whales() %...>% {
ggplot(aes(ip_name, n)) + ggplot(., aes(ip_name, n)) +
geom_bar(stat = "identity") + geom_bar(stat = "identity") +
ylab("Downloads on this day") ylab("Downloads on this day")
}
}) })


#### "Whales by hour" tab ------------------------------------- #### "Whales by hour" tab -------------------------------------


output$downloaders_hour <- renderPlot({ output$downloaders_hour <- renderPlot({
whale_downloads() %>% whale_downloads() %...>%
mutate(time = hms::trunc_hms(time, 60*60)) %>% mutate(time = hms::trunc_hms(time, 60*60)) %...>%
count(time, ip_name) %>% count(time, ip_name) %...>% {
ggplot(aes(time, n)) + ggplot(., aes(time, n)) +
geom_bar(stat = "identity") + geom_bar(stat = "identity") +
facet_wrap(~ip_name) + facet_wrap(~ip_name) +
ylab("Downloads") + ylab("Downloads") +
xlab("Hour") xlab("Hour")
}
}) })


#### "Detail view" tab ---------------------------------------- #### "Detail view" tab ----------------------------------------
@@ -16,61 +16,66 @@ detailViewUI <- function(id) {


detailView <- function(input, output, session, whales, whale_downloads) { detailView <- function(input, output, session, whales, whale_downloads) {


# When whales() changes, update the selectInput with their names # When whales() changes, update the selectInput with their names.
observeEvent(try(silent=TRUE, whales()), { # The gross try(... %>% catch(~{})) is necessary to swallow both sync and
choices <- tryCatch( # async errors, because either will cause the session to be immediately and
whales()$ip_name, # unceremoniously ended. Something to improve in Shiny v1.2.
error = function(err) { character(0) } observeEvent(try(whales() %>% catch(~{})), {
) whales() %...>%
updateSelectInput(session, "detail_ip_name", pull(ip_name) %...!%
choices = choices, { character(0) } %...>%
selected = if (input$detail_ip_name %in% choices) {
input$detail_ip_name updateSelectInput(session, "detail_ip_name",
else choices = .,
character(0)) selected = if (input$detail_ip_name %in% .)
freezeReactiveValue(input, "detail_ip_name") input$detail_ip_name
else
character(0))
freezeReactiveValue(input, "detail_ip_name")
}
}) })


detail_downloads <- reactive({ detail_downloads <- reactive({
req(input$detail_ip_name, nzchar(input$detail_ip_name)) req(input$detail_ip_name, nzchar(input$detail_ip_name))
whale_downloads() %>% whale_downloads() %...>%
filter(ip_name == input$detail_ip_name) %>% filter(ip_name == input$detail_ip_name) %...>%
arrange(time) %>% arrange(time) %...>%
mutate(package = factor(package, levels = rev(unique(package)), ordered = TRUE)) mutate(package = factor(package, levels = rev(unique(package)), ordered = TRUE))
}) })


output$detail_size <- renderValueBox({ output$detail_size <- renderValueBox({
detail_downloads() %>% detail_downloads() %...>%
pull(size) %>% pull(size) %...>%
as.numeric() %>% # Cast from integer to numeric to avoid overflow warning as.numeric() %...>% # Cast from integer to numeric to avoid overflow warning
sum() %>% sum() %...>%
humanReadable() %>% humanReadable() %...>%
valueBox("bandwidth consumed") valueBox("bandwidth consumed")
}) })


output$detail_count <- renderValueBox({ output$detail_count <- renderValueBox({
detail_downloads() %>% detail_downloads() %...>%
nrow() %>% nrow() %...>%
format(big.mark = ",") %>% format(big.mark = ",") %...>%
valueBox("files downloaded") valueBox("files downloaded")
}) })


output$detail_uniques <- renderValueBox({ output$detail_uniques <- renderValueBox({
detail_downloads() %>% detail_downloads() %...>%
pull(package) %>% pull(package) %...>%
unique() %>% unique() %...>%
length() %>% length() %...>%
format(big.mark = ",") %>% format(big.mark = ",") %...>%
valueBox("unique packages") valueBox("unique packages")
}) })


# Show every single download from the selected downloader # Show every single download from the selected downloader
output$detail <- renderPlot({ output$detail <- renderPlot({


validate(need(input$detail_ip_name, "Select a downloader from the list above")) validate(need(input$detail_ip_name, "Select a downloader from the list above"))
pkg <- levels(detail_downloads()$package)

detail_downloads() %...>% {
detail_downloads() %>% { pkg <- levels(.$package)

ggplot(., aes(time, package)) + ggplot(., aes(time, package)) +
geom_point() + geom_point() +
scale_x_time(breaks = seq(hms::hms(0,0,0), by = 60*60*3, length.out = 9), scale_x_time(breaks = seq(hms::hms(0,0,0), by = 60*60*3, length.out = 9),
@@ -83,12 +88,12 @@ detailView <- function(input, output, session, whales, whale_downloads) {
# Show the downloads that are brushed on output$detail # Show the downloads that are brushed on output$detail
output$detail_table <- renderDT({ output$detail_table <- renderDT({
req(input$detail_brush) req(input$detail_brush)
detail_downloads() %>% detail_downloads() %...>%
brushedPoints(input$detail_brush) %>% brushedPoints(input$detail_brush) %...>%
mutate( mutate(
time = as.character(time), time = as.character(time),
size = humanReadable(size) size = humanReadable(size)
) %>% ) %...>%
select(-ip_id, -ip_name, -country) select(-ip_id, -ip_name, -country)
}) })
} }

No commit comments for this range

You can’t perform that action at this time.