Public Content

Identifying public content

This recipe contains Quarto documents in Python and R that present tables showing how much content hosted by Posit Connect uses each access type and enumerates the content that does not require login.

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.

---
title: "Content Report: Public Access"
---

This document analyzes the access types granted to content items
hosted by Posit Connect. It presents a summary according to each 
type of access and enumerates all content that allows access 
without login.

```{python libraries}
#| echo: false
#| message: false

import os
import requests
import pandas as pd
from IPython.display import Markdown
from tabulate import tabulate
```

```{python configuration}
#| echo: false

# Confirm that environment variables are available.
connect_server = os.environ["CONNECT_SERVER"]
connect_api_key = os.environ["CONNECT_API_KEY"]
```

```{python fetch}
#| echo: false
#| warning: false

# Fetch all content items from Posit Connect.
resp = requests.get(
    f"{connect_server}__api__/v1/content",
    headers = {
        "Authorization": f"Key {connect_api_key}",
    },
)
payload = resp.json()
content = pd.DataFrame.from_dict(payload)
```

## Access type frequency

```{python frequency}
#| echo: false

# Summarize by access type with cleaned names and order by 
# restriction.
types = {
    "all": "All (public)",
    "logged_in": "Logged-in",
    "acl": "Access Control List",
}

content["access_type"] = pd.Categorical(
    content["access_type"], 
    ["all", "logged_in", "acl"],
)

counts = content.groupby(
    'access_type', observed = True,
)['access_type'].count().reset_index(name="N")
counts["Type"] = counts["access_type"].transform(
    lambda t: types.get(t,t),
)
counts = counts[["Type","N"]]
Markdown(tabulate(
    counts,
    headers=["Title", "N"],
    showindex=False,
))
```

## Public content

```{python report}
#| echo: false

# Select public content and present the title and link.
public = content.copy()
public["title"] = public["title"].fillna(public["name"])
public["dashboard_url"] = public["dashboard_url"].transform(
    lambda u: f"[{u}](u){{target='_blank'}}",
)
public = public.loc[content["access_type"] == "all"]
public = public[["title", "dashboard_url"]]
Markdown(tabulate(
    public,
    headers=["Title", "URL"],
    showindex = False,
))
```
---
title: "Content Report: Public Access"
---

This document analyzes the access types granted to content items
hosted by Posit Connect. It presents a summary according to each 
type of access and enumerates all content that allows access 
without login.

```{r libraries}
#| echo: false
#| message: false

library(httr)
library(jsonlite)
library(magrittr)
library(dplyr)
library(knitr)
```

```{r configuration}
#| echo: false

# Confirm that environment variables are available.
connect_server <- Sys.getenv("CONNECT_SERVER")
if (nchar(connect_server) == 0) {
  stop("Set the CONNECT_SERVER environment variable.")
}
connect_api_key <- Sys.getenv("CONNECT_API_KEY")
if (nchar(connect_api_key) == 0) {
  stop("Set the CONNECT_API_KEY environment variable.")
}
```

```{r fetch}
#| echo: false

# Fetch all content items from Posit Connect.
res <- httr::GET(
  paste0(connect_server, "/__api__/v1/content"),
  httr::add_headers(Authorization = paste("Key", connect_api_key)),
  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)
}
content <- httr::content(
  res, as = "parsed", simplifyDataFrame = TRUE
)
```

## Access type frequency

```{r frequency}
#| echo: false

# Summarize by access type with cleaned names and order by 
# restriction.
access_types <- content %>%
  mutate(Type = access_type) %>%
  select(Type) %>%
  group_by(Type) %>%
  summarise(N = n()) %>%
  arrange(match(Type, c('all', 'logged_in', 'all'))) %>%
  mutate(Type = case_when(
    Type == "all" ~ "All (public)",
    Type == "logged_in" ~ "Logged-in",
    Type == "acl" ~ "Access Control List"))

kable(access_types)
```

## Public content

```{r report}
#| echo: false

# Select public content and present the title and link.
public_content <- content %>%
  filter(access_type == "all") %>%
  mutate(
    Title = ifelse(is.na(title), name, title),
    URL = paste0("[", dashboard_url, "](", dashboard_url, "){target='_blank'}"),
  ) %>%
  select(Title, URL)

kable(public_content)
```