Content Runtimes

Python, R, and Quarto versions in use

This recipe contains an R Markdown document that presents a table detailing the Python and R versions used by content deployed to a Posit Connect server.

The document uses the GET /v1/content endpoint to obtain information about content. This API call returns every content item visible to your API key.

To determine the full set of Python, R, and Quarto versions available on your server, use the GET /v1/server_settings/r, GET /v1/server_settings/python, and GET /v1/server_settings/quarto endpoints.

---
title: "Runtime Versions in Use"
---

This document analyzes the Python, R, and Quarto versions used to
run content on the Posit Connect server. The table presents the 
number of unique content items currently using each combination of
Python, R, and Quarto versions.


```{r setup, echo = FALSE, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

```{r libraries, echo = FALSE, message = FALSE}
library(httr)
library(jsonlite)
library(magrittr)
library(dplyr)
library(knitr)

# Ignore summarise telling us about the grouping.
options(dplyr.summarise.inform = FALSE)
```

```{r verification, echo = FALSE}
# Confirm that environment variables are available.
connectServer <- Sys.getenv("CONNECT_SERVER")
if (nchar(connectServer) == 0) {
  stop("Set the CONNECT_SERVER environment variable.")
}
connectAPIKey <- Sys.getenv("CONNECT_API_KEY")
if (nchar(connectAPIKey) == 0) {
  stop("Set the CONNECT_API_KEY environment variable.")
}
contentURL <- paste0(connectServer, "/__api__/v1/content")
```

```{r fetch, echo = FALSE}
# Fetch all content items from Posit Connect.
res <- httr::GET(
  contentURL,
  httr::add_headers(Authorization = paste("Key", connectAPIKey)),
  httr::write_memory()
)
if (httr::http_error(res)) {
  err <- sprintf(
    "%s request failed with %s",
    res$request$url,
    httr::http_status(res)$message
  )
  message(capture.output(str(httr::content(res))))
  stop(err)
}
payload <- httr::content(res, as = "text")
content <- jsonlite::fromJSON(payload, simplifyDataFrame = TRUE)
```

```{r report, echo = FALSE}
# Retain only MAJOR.MINOR version components.
major_minor <- function(versions) {
  sapply(versions, function(version) {
    if (is.na(version) || version == "") {
      NA
    } else {
      parts <- strsplit(version, ".", fixed = TRUE)[[1]]
      paste(parts[1:2], collapse = ".")
    }
  })
}

# Report the number of content items with each combination of
# Python and R MAJOR.MINOR versions.
versions <- content %>% 
  mutate(
    Python = major_minor(py_version),
    R = major_minor(r_version),
    Quarto = major_minor(quarto_version),
  ) %>% 
  select(Python, R, Quarto) %>% 
  group_by(Python, R, Quarto) %>% 
  summarise(N = n()) %>% 
  arrange(desc(Python), desc(R), desc(Quarto)) %>%
  ungroup()

kable(versions)
```