├── .gitignore ├── LICENSE ├── README.Rmd ├── README.md ├── README_files └── figure-gfm │ └── analyze_data-1.png ├── connect-usage.Rproj ├── examples ├── connectAnalytics │ └── README.md ├── connectViz │ ├── README.md │ ├── analysis.Rmd │ ├── manifest.json │ └── screen-shot.png ├── disk_size │ ├── README.md │ ├── disk_size.Rmd │ └── example-disk-size.png ├── interactive_app │ ├── README.md │ ├── app.R │ ├── interactive-app-screenshot.png │ └── manifest.json ├── last_30_days │ ├── README.md │ ├── email-preview.png │ ├── manifest.json │ ├── renv.lock │ ├── renv │ │ ├── .gitignore │ │ ├── activate.R │ │ └── settings.json │ ├── report-screenshot.png │ ├── rsc-usage.Rmd │ └── usage-email.Rmd └── realtime │ ├── README.md │ ├── app.R │ ├── manifest.json │ └── screenshot.png ├── renv.lock ├── renv ├── .gitignore ├── activate.R └── settings.dcf └── scripts └── generate-manifest.R /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .Renviron* 6 | *.html 7 | .Rprofile* 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 RStudio Solutions Engineering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | ```{r setup, include=FALSE} 6 | knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE) 7 | ``` 8 | 9 | # RStudio Connect Usage Data 10 | 11 | This repository illustrates several examples for getting started with the 12 | RStudio Connect usage data. The examples: 13 | 14 | - [./examples/last_30_days](./examples/last_30_days) 15 | - [./examples/interactive_app](./examples/interactive_app) 16 | - [./examples/realtime](./examples/realtime) 17 | - [./examples/connectAnalytics](./examples/connectAnalytics) 18 | - [./examples/connectViz](./examples/connectViz) 19 | 20 | The examples are generated using the [RStudio Connect Server 21 | API](https://docs.rstudio.com/connect/api). The API and data collection are both 22 | available as of RStudio Connect 1.7.0. The API contains data to help answer 23 | questions like: 24 | 25 | - What content is most visited? 26 | - Who is visiting my content? 27 | - What reports are most common? 28 | - Has viewership increased over time? 29 | - Did my CEO actually visit this app? 30 | 31 | **A data science team's time is precious, this data will help you focus and justify your efforts.** 32 | 33 | ## Basic example 34 | 35 | The following code should work as-is if copied into your R session. NOTE that it 36 | uses the [`connectapi`](https://github.com/rstudio/connectapi) package, which 37 | can be installed from GitHub with 38 | `remotes::install_github("rstudio/connectapi")`. You just need to replace the 39 | server name, and have your API key ready. 40 | 41 | ```{r connect_creds, eval=FALSE} 42 | ## ACTION REQUIRED: Change the server URL below to your server's URL 43 | Sys.setenv("CONNECT_SERVER" = "https://connect.example.com/rsc") 44 | ## ACTION REQUIRED: Make sure to have your API key ready 45 | Sys.setenv("CONNECT_API_KEY" = rstudioapi::askForPassword("Enter Connect Token:")) 46 | ``` 47 | 48 | This will use the `get_usage_shiny()` function to pull the latest activity of 49 | the Shiny apps you are allowed to see within your server. 50 | 51 | ```{r shiny_usage} 52 | library(ggplot2) 53 | library(dplyr) 54 | library(connectapi) 55 | 56 | client <- connect() 57 | 58 | # Get and clean the Shiny usage data 59 | shiny_rsc <- get_usage_shiny( 60 | client, 61 | from = lubridate::today() - lubridate::ddays(7), 62 | limit = Inf 63 | ) %>% 64 | filter(!is.na(ended)) %>% 65 | mutate(session_duration = ended - started) 66 | 67 | glimpse(shiny_rsc) 68 | ``` 69 | 70 | The identifiers used for the content in RStudio Connect are GUIDs. We can 71 | retrieve content names using the API. The API handles only one GUID at a time, 72 | so `purrr`'s `map_dfr()` is used to iterate through all of the unique GUIDs in 73 | order to get every Shiny app's title. 74 | 75 | ```{r shiny_content_names} 76 | # Get the title of each Shiny app 77 | shiny_rsc_titles <- shiny_rsc %>% 78 | count(content_guid) %>% 79 | pull(content_guid) %>% 80 | purrr::map_dfr( 81 | ~tibble(content_guid = .x, content_name = content_title(client, .x)) 82 | ) 83 | 84 | glimpse(shiny_rsc_titles) 85 | ``` 86 | 87 | The new `shiny_rsc_titles` table, and the `shiny_rsc` can be joined to return 88 | the "user readable" title. Using standard `dplyr` and `ggplot2` functions, we 89 | can now determine things such as the top 10 apps based on how long their average 90 | sessions are. 91 | 92 | ```{r analyze_data} 93 | # Calculate the average session duration and sort 94 | app_sessions <- shiny_rsc %>% 95 | inner_join(shiny_rsc_titles, by = "content_guid") %>% 96 | group_by(content_name) %>% 97 | summarise(avg_session = mean(session_duration)) %>% 98 | ungroup() %>% 99 | arrange(desc(avg_session)) %>% 100 | head(10) 101 | 102 | # Plot the top 10 used content 103 | app_sessions %>% 104 | ggplot(aes(content_name, avg_session)) + 105 | geom_col() + 106 | scale_y_continuous() + 107 | geom_text(aes(y = (avg_session + 200), label = round(avg_session)), size = 3) + 108 | coord_flip() + 109 | theme_bw() + 110 | labs( 111 | title = "RStudio Connect - Top 10", 112 | subtitle = "Shiny Apps", 113 | x = "", 114 | y = "Average session time (seconds)" 115 | ) 116 | ``` 117 | 118 | Learn more about programmatic deployments, calling the server API, and custom emails [here](https://docs.rstudio.com/user). 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # RStudio Connect Usage Data 3 | 4 | This repository illustrates several examples for getting started with 5 | the RStudio Connect usage data. The examples: 6 | 7 | - [./examples/last_30_days](./examples/last_30_days) 8 | - [./examples/interactive_app](./examples/interactive_app) 9 | - [./examples/realtime](./examples/realtime) 10 | - [./examples/connectAnalytics](./examples/connectAnalytics) 11 | - [./examples/connectViz](./examples/connectViz) 12 | 13 | The examples are generated using the [RStudio Connect Server 14 | API](https://docs.rstudio.com/connect/api). The API and data collection 15 | are both available as of RStudio Connect 1.7.0. The API contains data to 16 | help answer questions like: 17 | 18 | - What content is most visited? 19 | - Who is visiting my content? 20 | - What reports are most common? 21 | - Has viewership increased over time? 22 | - Did my CEO actually visit this app? 23 | 24 | **A data science team’s time is precious, this data will help you focus 25 | and justify your efforts.** 26 | 27 | ## Basic example 28 | 29 | The following code should work as-is if copied into your R session. NOTE 30 | that it uses the [`connectapi`](https://github.com/rstudio/connectapi) 31 | package, which can be installed from GitHub with 32 | `remotes::install_github("rstudio/connectapi")`. You just need to 33 | replace the server name, and have your API key ready. 34 | 35 | ``` r 36 | ## ACTION REQUIRED: Change the server URL below to your server's URL 37 | Sys.setenv("CONNECT_SERVER" = "https://connect.example.com/rsc") 38 | ## ACTION REQUIRED: Make sure to have your API key ready 39 | Sys.setenv("CONNECT_API_KEY" = rstudioapi::askForPassword("Enter Connect Token:")) 40 | ``` 41 | 42 | This will use the `get_usage_shiny()` function to pull the latest 43 | activity of the Shiny apps you are allowed to see within your server. 44 | 45 | ``` r 46 | library(ggplot2) 47 | library(dplyr) 48 | library(connectapi) 49 | 50 | client <- connect() 51 | 52 | # Get and clean the Shiny usage data 53 | shiny_rsc <- get_usage_shiny( 54 | client, 55 | from = lubridate::today() - lubridate::ddays(7), 56 | limit = Inf 57 | ) %>% 58 | filter(!is.na(ended)) %>% 59 | mutate(session_duration = ended - started) 60 | 61 | glimpse(shiny_rsc) 62 | ``` 63 | 64 | ## Rows: 336 65 | ## Columns: 6 66 | ## $ content_guid "dad2ac84-2450-4275-b26e-8e0d9d741100", "6f69884b-562… 67 | ## $ user_guid NA, NA, NA, NA, NA, NA, NA, "7defcf1b-69f6-4bc5-8b52-… 68 | ## $ started 2022-02-23 01:54:12, 2022-02-23 03:17:46, 2022-02-23… 69 | ## $ ended 2022-02-23 01:54:45, 2022-02-23 03:19:25, 2022-02-23… 70 | ## $ data_version 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,… 71 | ## $ session_duration 33 secs, 99 secs, 3685 secs, 61 secs, 59 secs, 51 se… 72 | 73 | The identifiers used for the content in RStudio Connect are GUIDs. We 74 | can retrieve content names using the API. The API handles only one GUID 75 | at a time, so `purrr`’s `map_dfr()` is used to iterate through all of 76 | the unique GUIDs in order to get every Shiny app’s title. 77 | 78 | ``` r 79 | # Get the title of each Shiny app 80 | shiny_rsc_titles <- shiny_rsc %>% 81 | count(content_guid) %>% 82 | pull(content_guid) %>% 83 | purrr::map_dfr( 84 | ~tibble(content_guid = .x, content_name = content_title(client, .x)) 85 | ) 86 | 87 | glimpse(shiny_rsc_titles) 88 | ``` 89 | 90 | ## Rows: 40 91 | ## Columns: 2 92 | ## $ content_guid "0287f7d9-4d55-4813-8852-680f54beaad1", "06484fbb-f686-42… 93 | ## $ content_name "Example Palmer Penguins Shiny Dashboard", "Classroom Stu… 94 | 95 | The new `shiny_rsc_titles` table, and the `shiny_rsc` can be joined to 96 | return the “user readable” title. Using standard `dplyr` and `ggplot2` 97 | functions, we can now determine things such as the top 10 apps based on 98 | how long their average sessions are. 99 | 100 | ``` r 101 | # Calculate the average session duration and sort 102 | app_sessions <- shiny_rsc %>% 103 | inner_join(shiny_rsc_titles, by = "content_guid") %>% 104 | group_by(content_name) %>% 105 | summarise(avg_session = mean(session_duration)) %>% 106 | ungroup() %>% 107 | arrange(desc(avg_session)) %>% 108 | head(10) 109 | 110 | # Plot the top 10 used content 111 | app_sessions %>% 112 | ggplot(aes(content_name, avg_session)) + 113 | geom_col() + 114 | scale_y_continuous() + 115 | geom_text(aes(y = (avg_session + 200), label = round(avg_session)), size = 3) + 116 | coord_flip() + 117 | theme_bw() + 118 | labs( 119 | title = "RStudio Connect - Top 10", 120 | subtitle = "Shiny Apps", 121 | x = "", 122 | y = "Average session time (seconds)" 123 | ) 124 | ``` 125 | 126 | ![](README_files/figure-gfm/analyze_data-1.png) 127 | 128 | Learn more about programmatic deployments, calling the server API, and 129 | custom emails [here](https://docs.rstudio.com/user). 130 | -------------------------------------------------------------------------------- /README_files/figure-gfm/analyze_data-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/README_files/figure-gfm/analyze_data-1.png -------------------------------------------------------------------------------- /connect-usage.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | -------------------------------------------------------------------------------- /examples/connectAnalytics/README.md: -------------------------------------------------------------------------------- 1 | # connectAnalytics 2 | 3 | The [`connectAnalytics`](https://github.com/tbradley1013/connectAnalytics) Shiny 4 | application is maintained by a member of the community 5 | ([`tbradley1013`](https://github.com/tbradley1013)). 6 | -------------------------------------------------------------------------------- /examples/connectViz/README.md: -------------------------------------------------------------------------------- 1 | # connectViz 2 | 3 | [`RinteRface/connectViz`](https://github.com/RinteRface/connectViz) is a package 4 | with helper functions to present visualizations of Connect usage data, 5 | maintained by a member of the community 6 | ([`DivadNojnarg`](https://github.com/DivadNojnarg)). 7 | 8 | `analysis.Rmd` is an example report using these helpers. 9 | 10 | [Check out the README for more information!](https://github.com/RinteRface/connectViz) 11 | 12 |
13 | -------------------------------------------------------------------------------- /examples/connectViz/analysis.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RStudio Connect Data - Last `r as.numeric(Sys.getenv('DAYSBACK', 90))` Days" 3 | date: "`r Sys.Date()`" 4 | output: 5 | html_document: 6 | theme: 7 | version: 4 8 | runtime: shiny 9 | --- 10 | 11 | [Sourced from RinteRface/connectViz](https://github.com/RinteRface/connectViz) 12 | 13 | ```{r setup, include=FALSE} 14 | #remotes::install_github("RinteRface/connectViz") 15 | library(connectapi) # Tested with 0.1.0.9031 16 | library(connectViz) 17 | library(dplyr) 18 | rsc_client <- create_rsc_client() 19 | knitr::opts_chunk$set(echo = FALSE, warning=FALSE) 20 | 21 | days_back <- as.numeric(Sys.getenv("DAYSBACK", 90)) 22 | cache_location <- Sys.getenv("MEMOISE_CACHE_LOCATION", tempdir()) 23 | report_from <- lubridate::today() - lubridate::ddays(days_back) 24 | 25 | ``` 26 | 27 | ```{r database} 28 | # Get raw data from RSC database 29 | get_apps_usage <- memoise::memoise( 30 | get_usage_shiny, 31 | cache = memoise::cache_filesystem(cache_location) 32 | ) 33 | apps_usage <- get_apps_usage(rsc_client, from = report_from, limit = Inf) 34 | rsc_content <- rsc_client %>% get_content() 35 | rsc_users <- rsc_client %>% get_users(limit = Inf) 36 | publishers <- rsc_users %>% filter(user_role == "publisher") 37 | shiny_apps <- rsc_content %>% filter(app_mode == "shiny") 38 | # TO DO 39 | # rsc_static <- rsc_client %>% get_usage_static(limit = Inf) 40 | ``` 41 | 42 | ### General metrics 43 | 44 | ```{r general-metric, message=FALSE} 45 | general_metrics <- list( 46 | "Onboarded Users (n)" = nrow(rsc_users), 47 | "Publishers (n)" = nrow(publishers), 48 | "Deployments (n)" = nrow(rsc_content), 49 | "Shiny Apps (n)" = nrow(shiny_apps) 50 | ) 51 | shiny::fluidRow( 52 | align = "center", 53 | purrr::map( 54 | seq_along(general_metrics), 55 | function(i) { 56 | 57 | shinydashboard::infoBox( 58 | value = general_metrics[[i]], 59 | title = names(general_metrics)[[i]] 60 | ) 61 | } 62 | ) 63 | ) 64 | ``` 65 | 66 | ### Shiny Apps usage 67 | 68 | #### Most used apps 69 | 70 | ```{r} 71 | apps_ranking <- create_app_ranking(rsc_content, rsc_users, apps_usage) 72 | create_app_ranking_table(apps_ranking) 73 | ``` 74 | 75 | #### Daily app usage 76 | 77 | ```{r} 78 | selectInput("selected_app", "Select an application", apps_ranking[[2]]$app_name) 79 | daily_app_usage <- get_app_daily_usage(apps_ranking[[2]], reactive(input$selected_app)) 80 | create_app_daily_usage_chart(daily_app_usage) 81 | ``` 82 | 83 | #### Cumulated duration / user 84 | 85 | ```{r duration-plot} 86 | create_cumulated_duration_per_user(apps_ranking[[1]], reactive(input$selected_app)) 87 | ``` 88 | 89 | #### Number Hits / user 90 | 91 | ```{r sessions-plot} 92 | create_cumulated_hits_per_user(apps_ranking[[1]], reactive(input$selected_app)) 93 | ``` 94 | 95 | ### Consumer data 96 | 97 | 98 | #### Consumer ranking 99 | ```{r consumer-session-ranking} 100 | numericInput( 101 | "views_threshold", 102 | "N view threshold", 103 | 100, 104 | min = 0 105 | ) 106 | consumer_ranking <- create_apps_consumer_ranking(apps_usage, rsc_users, reactive(input$views_threshold)) 107 | create_apps_consumer_ranking_chart(consumer_ranking) 108 | ``` 109 | 110 | #### Daily app consumption 111 | 112 | ```{r daily-app-consumption-per-user} 113 | selectInput("selected_user", "Select an application", rsc_users$username) 114 | daily_consumption <- get_user_daily_consumption(rsc_content, rsc_users, apps_usage, reactive(input$selected_user)) 115 | create_user_daily_consumption_chart(daily_consumption) 116 | ``` 117 | 118 | 119 | ### Developers data 120 | 121 | 122 | #### Developers ranking (number of deployments: api, static, shiny, rmd, ...) 123 | 124 | ```{r developers-ranking} 125 | developers_apps_ranking <- create_dev_ranking(rsc_users, rsc_content) 126 | numericInput( 127 | "apps_threshold", 128 | "N app threshold", 129 | 5, 130 | min = 1, 131 | max = developers_apps_ranking %>% pull(n_apps) %>% max() 132 | ) 133 | create_dev_ranking_chart(developers_apps_ranking, reactive(input$apps_threshold)) 134 | ``` 135 | 136 | #### Developer projects overview 137 | 138 | ```{r} 139 | selectInput( 140 | "app_developer", 141 | "Select a developer", 142 | developers_apps_ranking$username 143 | ) 144 | create_dev_project_overview(developers_apps_ranking, rsc_client, apps_ranking[[1]], reactive(input$app_developer)) 145 | ``` 146 | 147 | 148 | ### Other data 149 | 150 | #### Users repartition (%) 151 | 152 | ```{r user-repartition} 153 | # I realized some users are not active (ie active_time is NA). 154 | # Maybe to remove from the viz in the future? 155 | sort_users_by_role(rsc_users) %>% create_pie_chart("user_role") 156 | ``` 157 | 158 | #### Content access 159 | 160 | How do people protect their content? 161 | 162 | ```{r content-access-type} 163 | sort_content_by_access(rsc_content) %>% create_pie_chart("access_type") 164 | ``` 165 | 166 | 167 | #### R versions 168 | 169 | What are the R versions used? 170 | 171 | ```{r content-r-version} 172 | sort_content_by_rversion(rsc_content) %>% create_pie_chart("r_version") 173 | ``` 174 | 175 | #### Content type 176 | 177 | ```{r content-type} 178 | sort_content_by_appmode(rsc_content) %>% create_pie_chart("app_mode") 179 | ``` 180 | -------------------------------------------------------------------------------- /examples/connectViz/screen-shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/connectViz/screen-shot.png -------------------------------------------------------------------------------- /examples/disk_size/README.md: -------------------------------------------------------------------------------- 1 | # Disk Size Reporting 2 | 3 | Inside of RStudio Connect, it is often desirable for adiministrators to review disk usage of various applications. 4 | 5 | This example report shows how this can be done today by looking at bundle sizes. We would like to eventually do 6 | similar for Jobs and saved Job Output. 7 | 8 | Some caveats: 9 | 10 | - This only works for content you are a "collaborator" on. In this example, we 11 | only retrieve size data about the author's content (because it is easier to 12 | filter on). `get_user_permission()` and `get_group_permission()` can be used to 13 | figure out what content you are a collaborator on. 14 | - As an administrator, another approach is to "add yourself to content," 15 | "retrieve bundle data", then "remove yourself again". This will allow you to 16 | retrieve all bundle data, but will take longer and will add many audit log 17 | entries. 18 | - Further, it is worth noting that this takes _a while_, because you have to iterate 19 | through each piece of content directly. 20 | - We will explore improvements to the Connect access model in the future to make such 21 | reporting easier. 22 | 23 |
24 | -------------------------------------------------------------------------------- /examples/disk_size/disk_size.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "bundle-diagnostics" 3 | output: html_document 4 | --- 5 | 6 | ```{r setup, include=FALSE} 7 | library(dplyr) 8 | library(purrr) 9 | library(connectapi) 10 | client <- connect() 11 | ``` 12 | 13 | ## Enumerate Content List 14 | 15 | ```{r} 16 | all_content <- get_content(client) 17 | 18 | unpublished_content <- all_content %>% filter(is.na(bundle_id)) 19 | ``` 20 | 21 | 22 | ## Get Bundles for a Piece of Content 23 | 24 | Beware: 25 | - This only works for content you are a "collaborator" on. In this example, we 26 | only retrieve size data about the author's content (because it is easier to 27 | filter on). `get_user_permission()` and `get_group_permission()` can be used to 28 | figure out what content you are a collaborator on. 29 | - As an administrator, another approach is to "add yourself to content," 30 | "retrieve bundle data", then "remove yourself again". This will allow you to 31 | retrieve all bundle data, but will take longer and will add many audit log 32 | entries. 33 | - Further, it is worth noting that this takes _a while_, because you have to iterate 34 | through each piece of content directly. 35 | - We will explore improvements to the Connect access model in the future to make such 36 | reporting easier. 37 | 38 | ```{r} 39 | my_guid <- client$me()$guid 40 | 41 | my_content <- all_content %>% filter(purrr::map_lgl(owner, ~ .[["guid"]] == my_guid)) 42 | item_guid <- my_content[1,"guid"][[1]] 43 | 44 | 45 | get_bundles_size <- function(client, guid, pb=NULL) { 46 | if (!is.null(pb)) pb$tick() 47 | bundles_metadata <- get_bundles(content_item(client, guid)) 48 | return(bundles_metadata[["size"]]) 49 | } 50 | 51 | pb <- progress::progress_bar$new(total = nrow(my_content)) 52 | my_content_bundles_size <- my_content %>% 53 | select(guid, name, title, description) %>% 54 | mutate(bundles = map(guid, ~ get_bundles_size(client, .x, pb))) 55 | 56 | my_bundles_tall <- my_content_bundles_size %>% 57 | select(guid, bundles) %>% 58 | tidyr::unnest(bundles) %>% 59 | group_by(guid) %>% 60 | mutate(row=row_number()) 61 | 62 | my_bundles_sum <- my_bundles_tall %>% 63 | group_by(guid) %>% 64 | summarize(total_size = sum(bundles), n = n()) 65 | 66 | ``` 67 | 68 | ## Summary of Sizes 69 | 70 | ```{r} 71 | my_bundles_sum %>% 72 | summarize( 73 | n_content = n(), n_bundles = sum(n), total_size = sum(total_size), 74 | avg_size_per_content = total_size / n_content, avg_size_per_bundle = total_size / n_bundles 75 | ) 76 | ``` 77 | 78 | ## Top Content 79 | 80 | ```{r} 81 | my_bundles_sum %>% 82 | arrange(desc(total_size)) %>% 83 | head(10) 84 | ``` 85 | -------------------------------------------------------------------------------- /examples/disk_size/example-disk-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/disk_size/example-disk-size.png -------------------------------------------------------------------------------- /examples/interactive_app/README.md: -------------------------------------------------------------------------------- 1 | # Interactive App 2 | 3 | This application is an example using the 4 | [`apexcharter`](https://dreamrs.github.io/apexcharter/) package to create 5 | interactive graphics that explore pre-downloaded and aggregated instrumentation data. 6 | 7 | For bigger servers, fetching this data real-time is costly and time-consuming, 8 | so we fetch 90 days of data at app start-up (and use a cache so that it is only 9 | fetched once per day). 10 | 11 | We also use reactivity so that selecting the timeline will filter and re-render 12 | other charts accordingly. 13 | 14 | [Example Application Deployment](https://colorado.rstudio.com/rsc/usage-interactive/) 15 | 16 |
17 | -------------------------------------------------------------------------------- /examples/interactive_app/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinydashboard) 3 | library(apexcharter) 4 | library(connectapi) 5 | library(dplyr) 6 | library(shinycssloaders) 7 | 8 | client <- connect() 9 | 10 | days_back <- as.numeric(Sys.getenv("DAYSBACK", 90)) 11 | cache_location <- Sys.getenv("MEMOISE_CACHE_LOCATION", tempdir()) 12 | message(cache_location) 13 | report_from <- lubridate::today() - lubridate::ddays(days_back) 14 | report_to <- lubridate::today() 15 | 16 | # TODO: better way to do caching...? 17 | # TODO: add connect server URL to the cache_location 18 | cached_usage_shiny <- memoise::memoise( 19 | get_usage_shiny, 20 | cache = memoise::cache_filesystem(cache_location), 21 | omit_args = c("src") # BEWARE: cache can cross connect hosts if you change connect targets 22 | ) 23 | 24 | cached_usage_static <- memoise::memoise( 25 | get_usage_static, 26 | cache = memoise::cache_filesystem(cache_location), 27 | omit_args = c("src") # BEWARE: cache can cross connect hosts if you change connect targets 28 | ) 29 | 30 | # create alternate versions that have a date to invalidate the cache 31 | 32 | local_get_users <- function(client, date, limit, ...) { 33 | connectapi::get_users(client, limit = limit, ...) 34 | } 35 | 36 | cached_get_users <- memoise::memoise( 37 | local_get_users, 38 | cache = memoise::cache_filesystem(cache_location), 39 | omit_args = "client" 40 | ) 41 | 42 | local_get_content <- function(client, date, ...) { 43 | connectapi::get_content(client, ...) 44 | } 45 | 46 | cached_get_content <- memoise::memoise( 47 | local_get_content, 48 | cache = memoise::cache_filesystem(cache_location), 49 | omit_args = "client" 50 | ) 51 | 52 | # Data Fetch ------------------------------------------------------------- 53 | 54 | # Must include "to" or the cache can get weird!! 55 | data_shiny <- cached_usage_shiny(client, from = report_from, to = report_to, limit = Inf) 56 | # data_static <- cached_usage_static(client, from = report_from, to = report_to, limit = Inf) # ~ 3 minutes on a busy server... 😱 57 | data_content <- cached_get_content(client, date = report_to) 58 | data_users <- cached_get_users(client, date = report_to, limit = Inf) 59 | 60 | ui <- dashboardPage( 61 | dashboardHeader( 62 | title = glue::glue("Shiny Usage - Last {days_back} Days"), titleWidth = "40%" 63 | ), 64 | dashboardSidebar(disable = TRUE, collapsed = TRUE), 65 | dashboardBody( 66 | fluidRow( 67 | shinycssloaders::withSpinner( 68 | apexchartOutput("shiny_time") 69 | ) 70 | ), 71 | fluidRow( 72 | box( 73 | apexchartOutput("shiny_content"), 74 | width = 4, 75 | ), 76 | box( 77 | apexchartOutput("shiny_viewer"), 78 | width = 4, 79 | ), 80 | box( 81 | apexchartOutput("shiny_owner"), 82 | width = 4, 83 | ) 84 | ), 85 | fluidRow( 86 | verbatimTextOutput("verbatim") 87 | ) 88 | ) 89 | ) 90 | 91 | safe_filter <- function(data, min_date = NULL, max_date = NULL) { 92 | data_prep <- data 93 | if (!is.null(min_date)) { 94 | data_prep <- data_prep %>% filter(started >= min_date) 95 | } 96 | if (!is.null(max_date)) { 97 | data_prep <- data_prep %>% filter(started <= max_date + 1) 98 | } 99 | 100 | return(data_prep) 101 | } 102 | 103 | server <- function(input, output, session) { 104 | 105 | 106 | # Data Prep ------------------------------------------------------------- 107 | delay_duration <- 500 108 | 109 | shiny_content <- debounce(reactive( 110 | data_shiny %>% 111 | safe_filter(min_date = minTime(), max_date = maxTime()) %>% 112 | group_by(content_guid) %>% 113 | tally() %>% 114 | left_join( 115 | data_content %>% select(guid, name, title, description), 116 | by = c(content_guid = "guid") 117 | ) %>% 118 | filter(!is.na(title)) %>% 119 | arrange(desc(n)) 120 | ), delay_duration) 121 | 122 | shiny_viewers <- debounce(reactive( 123 | data_shiny %>% 124 | safe_filter(min_date = minTime(), max_date = maxTime()) %>% 125 | group_by(user_guid) %>% 126 | tally() %>% 127 | left_join( 128 | data_users %>% select(guid, username), 129 | by = c(user_guid = "guid") 130 | ) %>% 131 | arrange(desc(n)) 132 | ), delay_duration) 133 | 134 | 135 | shiny_owners <- debounce(reactive( 136 | data_shiny %>% 137 | safe_filter(min_date = minTime(), max_date = maxTime()) %>% 138 | left_join( 139 | data_content %>% select(guid, owner_guid), 140 | by = c(content_guid = "guid") 141 | ) %>% 142 | filter(!is.na(owner_guid)) %>% # remove content that was deleted 143 | group_by(owner_guid) %>% 144 | tally() %>% 145 | left_join( 146 | data_users %>% select(guid, username), 147 | by = c(owner_guid = "guid") 148 | ) %>% 149 | arrange(desc(n)) 150 | ), delay_duration) 151 | 152 | shiny_over_time <- debounce(reactive( 153 | data_shiny %>% 154 | mutate( 155 | date = lubridate::as_date(lubridate::floor_date(started, "day")), 156 | ) %>% 157 | group_by(date) %>% 158 | tally() %>% 159 | mutate( 160 | date_disp = format(date, format="%a %b %d %Y") 161 | ) %>% 162 | arrange(date) 163 | ), delay_duration) 164 | 165 | # Observers for Selection ---------------------------------------------------------- 166 | 167 | minTime <- reactiveVal(report_from) 168 | maxTime <- reactiveVal(report_to) 169 | 170 | observeEvent(input$time, { 171 | input_min <- lubridate::as_date(input$time[[1]]$min) 172 | input_max <- lubridate::as_date(input$time[[1]]$max) 173 | # TODO: a way to "deselect" the time series 174 | if (identical(input_min, input_max)) { 175 | # treat "equals" as nothing selected 176 | minTime(report_from) 177 | maxTime(report_to) 178 | } else { 179 | minTime(input_min) 180 | maxTime(input_max) 181 | } 182 | }) 183 | 184 | # Graph output ---------------------------------------------------------- 185 | 186 | output$shiny_time <- renderApexchart( 187 | apexchart(auto_update = FALSE) %>% 188 | ax_chart(type = "line") %>% 189 | ax_title("By Date") %>% 190 | ax_plotOptions() %>% 191 | ax_series(list( 192 | name = "Count", 193 | data = purrr::map2(shiny_over_time()$date_disp, shiny_over_time()$n, ~ list(.x,.y)) 194 | )) %>% 195 | ax_xaxis( 196 | type = "datetime" 197 | ) %>% 198 | set_input_selection("time") 199 | ) 200 | 201 | output$shiny_content <- renderApexchart( 202 | apex( 203 | data = shiny_content() %>% head(20), 204 | type = "bar", 205 | mapping = aes(title, n) 206 | ) %>% 207 | ax_title("By App (Top 20)") %>% 208 | set_input_click("content") 209 | ) 210 | 211 | output$shiny_viewer <- renderApexchart( 212 | apex( 213 | data = shiny_viewers() %>% head(20), 214 | type = "bar", 215 | mapping = aes(username, n) 216 | ) %>% 217 | ax_title("By Viewer (Top 20)") %>% 218 | set_input_click("viewer") 219 | ) 220 | 221 | output$shiny_owner <- renderApexchart( 222 | apex( 223 | data = shiny_owners() %>% head(20), 224 | type = "bar", 225 | mapping = aes(username, n) 226 | ) %>% 227 | ax_title("By Owner (Top 20)") %>% 228 | set_input_click("owner") 229 | ) 230 | 231 | output$verbatim <- renderText(capture.output(str(input$time), str(minTime()), str(maxTime()), str(input$content), str(input$viewer), str(input$owner))) 232 | 233 | } 234 | 235 | shinyApp(ui = ui, server = server) 236 | -------------------------------------------------------------------------------- /examples/interactive_app/interactive-app-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/interactive_app/interactive-app-screenshot.png -------------------------------------------------------------------------------- /examples/last_30_days/README.md: -------------------------------------------------------------------------------- 1 | # Posit Connect Usage Report 2 | 3 | This R Markdown report can be published as-is to your Posit Connect server or 4 | used as a starting point for your own analysis. You can schedule the report and 5 | distribute it via email (the email will include inline graphics!) 6 | 7 | Sample Report: http://colorado.posit.co/rsc/usage 8 | 9 |
10 | 11 | The report is generated using the [Posit Connect Server 12 | API](https://docs.posit.co/connect/api). The API contains data to help answer 13 | questions like: 14 | 15 | - What content is most visited? 16 | - Who is visiting my content? 17 | - What reports are most common? 18 | - Has viewership increased over time? 19 | - Did my CEO actually visit this app? 20 | 21 | **A data science team's time is precious, this data will help you focus and justify your efforts.** 22 | 23 | The report uses the environment variables `CONNECT_SERVER` and `CONNECT_API_KEY` to collect the data. To limit the results to a single publisher, use a publisher API key. 24 | 25 | ### Common Questions 26 | 27 | - Could this be a shiny app instead of a report? Of course! Let us know what you come up with. 28 | - Can I use the API from another language besides R? Absolutely, [the API 29 | includes a spec](https://docs.posit.co/connect/api) to get you started. 30 | - Will you provide an R client for accessing the API? Yes! It is called 31 | [`connectapi`](https://github.com/rstudio/connectapi) 32 | - What is the `manifest.json` file? This file aids in programmatic or git-backed deployments 33 | - What is the funky `preflight_check` chunk at the start of the report? If the 34 | report is run without essential environment variables (`CONNECT_SERVER` and 35 | `CONNECT_API_KEY`), this chunk will return a clear error message promptly rather than 36 | waiting for the report to exit weirdly. It is there to protect you! 37 | 38 |
39 | -------------------------------------------------------------------------------- /examples/last_30_days/email-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/last_30_days/email-preview.png -------------------------------------------------------------------------------- /examples/last_30_days/renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.3", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://colorado.posit.co/rspm/all/latest" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "MASS": { 13 | "Package": "MASS", 14 | "Version": "7.3-58.2", 15 | "Source": "Repository", 16 | "Repository": "RSPM", 17 | "Requirements": [ 18 | "R", 19 | "grDevices", 20 | "graphics", 21 | "methods", 22 | "stats", 23 | "utils" 24 | ], 25 | "Hash": "e02d1a0f6122fd3e634b25b433704344" 26 | }, 27 | "Matrix": { 28 | "Package": "Matrix", 29 | "Version": "1.5-3", 30 | "Source": "Repository", 31 | "Repository": "RSPM", 32 | "Requirements": [ 33 | "R", 34 | "graphics", 35 | "grid", 36 | "lattice", 37 | "methods", 38 | "stats", 39 | "utils" 40 | ], 41 | "Hash": "4006dffe49958d2dd591c17e61e60591" 42 | }, 43 | "R6": { 44 | "Package": "R6", 45 | "Version": "2.5.1", 46 | "Source": "Repository", 47 | "Repository": "RSPM", 48 | "Requirements": [ 49 | "R" 50 | ], 51 | "Hash": "470851b6d5d0ac559e9d01bb352b4021" 52 | }, 53 | "RColorBrewer": { 54 | "Package": "RColorBrewer", 55 | "Version": "1.1-3", 56 | "Source": "Repository", 57 | "Repository": "RSPM", 58 | "Requirements": [ 59 | "R" 60 | ], 61 | "Hash": "45f0398006e83a5b10b72a90663d8d8c" 62 | }, 63 | "Rcpp": { 64 | "Package": "Rcpp", 65 | "Version": "1.0.11", 66 | "Source": "Repository", 67 | "Repository": "RSPM", 68 | "Requirements": [ 69 | "methods", 70 | "utils" 71 | ], 72 | "Hash": "ae6cbbe1492f4de79c45fce06f967ce8" 73 | }, 74 | "askpass": { 75 | "Package": "askpass", 76 | "Version": "1.1", 77 | "Source": "Repository", 78 | "Repository": "RSPM", 79 | "Requirements": [ 80 | "sys" 81 | ], 82 | "Hash": "e8a22846fff485f0be3770c2da758713" 83 | }, 84 | "base64enc": { 85 | "Package": "base64enc", 86 | "Version": "0.1-3", 87 | "Source": "Repository", 88 | "Repository": "RSPM", 89 | "Requirements": [ 90 | "R" 91 | ], 92 | "Hash": "543776ae6848fde2f48ff3816d0628bc" 93 | }, 94 | "bit": { 95 | "Package": "bit", 96 | "Version": "4.0.5", 97 | "Source": "Repository", 98 | "Repository": "RSPM", 99 | "Requirements": [ 100 | "R" 101 | ], 102 | "Hash": "d242abec29412ce988848d0294b208fd" 103 | }, 104 | "bit64": { 105 | "Package": "bit64", 106 | "Version": "4.0.5", 107 | "Source": "Repository", 108 | "Repository": "RSPM", 109 | "Requirements": [ 110 | "R", 111 | "bit", 112 | "methods", 113 | "stats", 114 | "utils" 115 | ], 116 | "Hash": "9fe98599ca456d6552421db0d6772d8f" 117 | }, 118 | "blastula": { 119 | "Package": "blastula", 120 | "Version": "0.3.4", 121 | "Source": "Repository", 122 | "Repository": "RSPM", 123 | "Requirements": [ 124 | "R", 125 | "base64enc", 126 | "commonmark", 127 | "curl", 128 | "digest", 129 | "dplyr", 130 | "fs", 131 | "getPass", 132 | "here", 133 | "htmltools", 134 | "httr", 135 | "jsonlite", 136 | "magrittr", 137 | "mime", 138 | "rlang", 139 | "rmarkdown", 140 | "stringr", 141 | "uuid" 142 | ], 143 | "Hash": "2ac6a5fdfb9e49fa746cb4c0342ba14c" 144 | }, 145 | "bslib": { 146 | "Package": "bslib", 147 | "Version": "0.5.1", 148 | "Source": "Repository", 149 | "Repository": "RSPM", 150 | "Requirements": [ 151 | "R", 152 | "base64enc", 153 | "cachem", 154 | "grDevices", 155 | "htmltools", 156 | "jquerylib", 157 | "jsonlite", 158 | "memoise", 159 | "mime", 160 | "rlang", 161 | "sass" 162 | ], 163 | "Hash": "283015ddfbb9d7bf15ea9f0b5698f0d9" 164 | }, 165 | "cachem": { 166 | "Package": "cachem", 167 | "Version": "1.0.8", 168 | "Source": "Repository", 169 | "Repository": "RSPM", 170 | "Requirements": [ 171 | "fastmap", 172 | "rlang" 173 | ], 174 | "Hash": "c35768291560ce302c0a6589f92e837d" 175 | }, 176 | "cli": { 177 | "Package": "cli", 178 | "Version": "3.6.1", 179 | "Source": "Repository", 180 | "Repository": "RSPM", 181 | "Requirements": [ 182 | "R", 183 | "utils" 184 | ], 185 | "Hash": "89e6d8219950eac806ae0c489052048a" 186 | }, 187 | "colorspace": { 188 | "Package": "colorspace", 189 | "Version": "2.1-0", 190 | "Source": "Repository", 191 | "Repository": "RSPM", 192 | "Requirements": [ 193 | "R", 194 | "grDevices", 195 | "graphics", 196 | "methods", 197 | "stats" 198 | ], 199 | "Hash": "f20c47fd52fae58b4e377c37bb8c335b" 200 | }, 201 | "commonmark": { 202 | "Package": "commonmark", 203 | "Version": "1.9.0", 204 | "Source": "Repository", 205 | "Repository": "RSPM", 206 | "Hash": "d691c61bff84bd63c383874d2d0c3307" 207 | }, 208 | "config": { 209 | "Package": "config", 210 | "Version": "0.3.1", 211 | "Source": "Repository", 212 | "Repository": "RSPM", 213 | "Requirements": [ 214 | "yaml" 215 | ], 216 | "Hash": "31d77b09f63550cee9ecb5a08bf76e8f" 217 | }, 218 | "connectapi": { 219 | "Package": "connectapi", 220 | "Version": "0.1.3.1", 221 | "Source": "Repository", 222 | "Repository": "RSPM", 223 | "Requirements": [ 224 | "R6", 225 | "bit64", 226 | "config", 227 | "dplyr", 228 | "fs", 229 | "glue", 230 | "httr", 231 | "jsonlite", 232 | "lifecycle", 233 | "magrittr", 234 | "progress", 235 | "purrr", 236 | "rlang", 237 | "tibble", 238 | "uuid", 239 | "vctrs", 240 | "yaml" 241 | ], 242 | "Hash": "3cd1efb4e829f1a2a1f1777f82e271b8" 243 | }, 244 | "cpp11": { 245 | "Package": "cpp11", 246 | "Version": "0.4.3", 247 | "Source": "Repository", 248 | "Repository": "RSPM", 249 | "Hash": "ed588261931ee3be2c700d22e94a29ab" 250 | }, 251 | "crayon": { 252 | "Package": "crayon", 253 | "Version": "1.5.2", 254 | "Source": "Repository", 255 | "Repository": "RSPM", 256 | "Requirements": [ 257 | "grDevices", 258 | "methods", 259 | "utils" 260 | ], 261 | "Hash": "e8a1e41acf02548751f45c718d55aa6a" 262 | }, 263 | "crosstalk": { 264 | "Package": "crosstalk", 265 | "Version": "1.2.0", 266 | "Source": "Repository", 267 | "Repository": "RSPM", 268 | "Requirements": [ 269 | "R6", 270 | "htmltools", 271 | "jsonlite", 272 | "lazyeval" 273 | ], 274 | "Hash": "6aa54f69598c32177e920eb3402e8293" 275 | }, 276 | "curl": { 277 | "Package": "curl", 278 | "Version": "5.0.1", 279 | "Source": "Repository", 280 | "Repository": "RSPM", 281 | "Requirements": [ 282 | "R" 283 | ], 284 | "Hash": "2118af9cb164c8d2dddc7b89eaf732d9" 285 | }, 286 | "data.table": { 287 | "Package": "data.table", 288 | "Version": "1.14.8", 289 | "Source": "Repository", 290 | "Repository": "RSPM", 291 | "Requirements": [ 292 | "R", 293 | "methods" 294 | ], 295 | "Hash": "b4c06e554f33344e044ccd7fdca750a9" 296 | }, 297 | "digest": { 298 | "Package": "digest", 299 | "Version": "0.6.33", 300 | "Source": "Repository", 301 | "Repository": "RSPM", 302 | "Requirements": [ 303 | "R", 304 | "utils" 305 | ], 306 | "Hash": "b18a9cf3c003977b0cc49d5e76ebe48d" 307 | }, 308 | "dplyr": { 309 | "Package": "dplyr", 310 | "Version": "1.1.3", 311 | "Source": "Repository", 312 | "Repository": "RSPM", 313 | "Requirements": [ 314 | "R", 315 | "R6", 316 | "cli", 317 | "generics", 318 | "glue", 319 | "lifecycle", 320 | "magrittr", 321 | "methods", 322 | "pillar", 323 | "rlang", 324 | "tibble", 325 | "tidyselect", 326 | "utils", 327 | "vctrs" 328 | ], 329 | "Hash": "e85ffbebaad5f70e1a2e2ef4302b4949" 330 | }, 331 | "ellipsis": { 332 | "Package": "ellipsis", 333 | "Version": "0.3.2", 334 | "Source": "Repository", 335 | "Repository": "RSPM", 336 | "Requirements": [ 337 | "R", 338 | "rlang" 339 | ], 340 | "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" 341 | }, 342 | "evaluate": { 343 | "Package": "evaluate", 344 | "Version": "0.21", 345 | "Source": "Repository", 346 | "Repository": "RSPM", 347 | "Requirements": [ 348 | "R", 349 | "methods" 350 | ], 351 | "Hash": "d59f3b464e8da1aef82dc04b588b8dfb" 352 | }, 353 | "fansi": { 354 | "Package": "fansi", 355 | "Version": "1.0.4", 356 | "Source": "Repository", 357 | "Repository": "RSPM", 358 | "Requirements": [ 359 | "R", 360 | "grDevices", 361 | "utils" 362 | ], 363 | "Hash": "1d9e7ad3c8312a192dea7d3db0274fde" 364 | }, 365 | "farver": { 366 | "Package": "farver", 367 | "Version": "2.1.1", 368 | "Source": "Repository", 369 | "Repository": "RSPM", 370 | "Hash": "8106d78941f34855c440ddb946b8f7a5" 371 | }, 372 | "fastmap": { 373 | "Package": "fastmap", 374 | "Version": "1.1.1", 375 | "Source": "Repository", 376 | "Repository": "RSPM", 377 | "Hash": "f7736a18de97dea803bde0a2daaafb27" 378 | }, 379 | "flexdashboard": { 380 | "Package": "flexdashboard", 381 | "Version": "0.6.2", 382 | "Source": "Repository", 383 | "Repository": "RSPM", 384 | "Requirements": [ 385 | "R", 386 | "bslib", 387 | "grDevices", 388 | "htmltools", 389 | "htmlwidgets", 390 | "jsonlite", 391 | "knitr", 392 | "rmarkdown", 393 | "sass", 394 | "scales", 395 | "shiny", 396 | "tools", 397 | "utils" 398 | ], 399 | "Hash": "c3c5a921ab91cb02845cb287efa7fcff" 400 | }, 401 | "fontawesome": { 402 | "Package": "fontawesome", 403 | "Version": "0.5.1", 404 | "Source": "Repository", 405 | "Repository": "RSPM", 406 | "Requirements": [ 407 | "R", 408 | "htmltools", 409 | "rlang" 410 | ], 411 | "Hash": "1e22b8cabbad1eae951a75e9f8b52378" 412 | }, 413 | "fs": { 414 | "Package": "fs", 415 | "Version": "1.6.2", 416 | "Source": "Repository", 417 | "Repository": "RSPM", 418 | "Requirements": [ 419 | "R", 420 | "methods" 421 | ], 422 | "Hash": "94af08e0aa9675a16fadbb3aaaa90d2a" 423 | }, 424 | "generics": { 425 | "Package": "generics", 426 | "Version": "0.1.3", 427 | "Source": "Repository", 428 | "Repository": "RSPM", 429 | "Requirements": [ 430 | "R", 431 | "methods" 432 | ], 433 | "Hash": "15e9634c0fcd294799e9b2e929ed1b86" 434 | }, 435 | "getPass": { 436 | "Package": "getPass", 437 | "Version": "0.2-2", 438 | "Source": "Repository", 439 | "Repository": "RSPM", 440 | "Requirements": [ 441 | "R", 442 | "rstudioapi", 443 | "utils" 444 | ], 445 | "Hash": "07a91f99e56951818ab911366db77700" 446 | }, 447 | "ggplot2": { 448 | "Package": "ggplot2", 449 | "Version": "3.4.3", 450 | "Source": "Repository", 451 | "Repository": "RSPM", 452 | "Requirements": [ 453 | "MASS", 454 | "R", 455 | "cli", 456 | "glue", 457 | "grDevices", 458 | "grid", 459 | "gtable", 460 | "isoband", 461 | "lifecycle", 462 | "mgcv", 463 | "rlang", 464 | "scales", 465 | "stats", 466 | "tibble", 467 | "vctrs", 468 | "withr" 469 | ], 470 | "Hash": "85846544c596e71f8f46483ab165da33" 471 | }, 472 | "glue": { 473 | "Package": "glue", 474 | "Version": "1.6.2", 475 | "Source": "Repository", 476 | "Repository": "RSPM", 477 | "Requirements": [ 478 | "R", 479 | "methods" 480 | ], 481 | "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e" 482 | }, 483 | "gtable": { 484 | "Package": "gtable", 485 | "Version": "0.3.3", 486 | "Source": "Repository", 487 | "Repository": "RSPM", 488 | "Requirements": [ 489 | "R", 490 | "cli", 491 | "glue", 492 | "grid", 493 | "lifecycle", 494 | "rlang" 495 | ], 496 | "Hash": "b44addadb528a0d227794121c00572a0" 497 | }, 498 | "here": { 499 | "Package": "here", 500 | "Version": "1.0.1", 501 | "Source": "Repository", 502 | "Repository": "RSPM", 503 | "Requirements": [ 504 | "rprojroot" 505 | ], 506 | "Hash": "24b224366f9c2e7534d2344d10d59211" 507 | }, 508 | "highr": { 509 | "Package": "highr", 510 | "Version": "0.10", 511 | "Source": "Repository", 512 | "Repository": "RSPM", 513 | "Requirements": [ 514 | "R", 515 | "xfun" 516 | ], 517 | "Hash": "06230136b2d2b9ba5805e1963fa6e890" 518 | }, 519 | "hms": { 520 | "Package": "hms", 521 | "Version": "1.1.3", 522 | "Source": "Repository", 523 | "Repository": "RSPM", 524 | "Requirements": [ 525 | "lifecycle", 526 | "methods", 527 | "pkgconfig", 528 | "rlang", 529 | "vctrs" 530 | ], 531 | "Hash": "b59377caa7ed00fa41808342002138f9" 532 | }, 533 | "htmltools": { 534 | "Package": "htmltools", 535 | "Version": "0.5.5", 536 | "Source": "Repository", 537 | "Repository": "RSPM", 538 | "Requirements": [ 539 | "R", 540 | "base64enc", 541 | "digest", 542 | "ellipsis", 543 | "fastmap", 544 | "grDevices", 545 | "rlang", 546 | "utils" 547 | ], 548 | "Hash": "ba0240784ad50a62165058a27459304a" 549 | }, 550 | "htmlwidgets": { 551 | "Package": "htmlwidgets", 552 | "Version": "1.6.2", 553 | "Source": "Repository", 554 | "Repository": "RSPM", 555 | "Requirements": [ 556 | "grDevices", 557 | "htmltools", 558 | "jsonlite", 559 | "knitr", 560 | "rmarkdown", 561 | "yaml" 562 | ], 563 | "Hash": "a865aa85bcb2697f47505bfd70422471" 564 | }, 565 | "httpuv": { 566 | "Package": "httpuv", 567 | "Version": "1.6.11", 568 | "Source": "Repository", 569 | "Repository": "RSPM", 570 | "Requirements": [ 571 | "R", 572 | "R6", 573 | "Rcpp", 574 | "later", 575 | "promises", 576 | "utils" 577 | ], 578 | "Hash": "838602f54e32c1a0f8cc80708cefcefa" 579 | }, 580 | "httr": { 581 | "Package": "httr", 582 | "Version": "1.4.6", 583 | "Source": "Repository", 584 | "Repository": "RSPM", 585 | "Requirements": [ 586 | "R", 587 | "R6", 588 | "curl", 589 | "jsonlite", 590 | "mime", 591 | "openssl" 592 | ], 593 | "Hash": "7e5e3cbd2a7bc07880c94e22348fb661" 594 | }, 595 | "isoband": { 596 | "Package": "isoband", 597 | "Version": "0.2.7", 598 | "Source": "Repository", 599 | "Repository": "RSPM", 600 | "Requirements": [ 601 | "grid", 602 | "utils" 603 | ], 604 | "Hash": "0080607b4a1a7b28979aecef976d8bc2" 605 | }, 606 | "jquerylib": { 607 | "Package": "jquerylib", 608 | "Version": "0.1.4", 609 | "Source": "Repository", 610 | "Repository": "RSPM", 611 | "Requirements": [ 612 | "htmltools" 613 | ], 614 | "Hash": "5aab57a3bd297eee1c1d862735972182" 615 | }, 616 | "jsonlite": { 617 | "Package": "jsonlite", 618 | "Version": "1.8.7", 619 | "Source": "Repository", 620 | "Repository": "RSPM", 621 | "Requirements": [ 622 | "methods" 623 | ], 624 | "Hash": "266a20443ca13c65688b2116d5220f76" 625 | }, 626 | "knitr": { 627 | "Package": "knitr", 628 | "Version": "1.43", 629 | "Source": "Repository", 630 | "Repository": "RSPM", 631 | "Requirements": [ 632 | "R", 633 | "evaluate", 634 | "highr", 635 | "methods", 636 | "tools", 637 | "xfun", 638 | "yaml" 639 | ], 640 | "Hash": "9775eb076713f627c07ce41d8199d8f6" 641 | }, 642 | "labeling": { 643 | "Package": "labeling", 644 | "Version": "0.4.2", 645 | "Source": "Repository", 646 | "Repository": "RSPM", 647 | "Requirements": [ 648 | "graphics", 649 | "stats" 650 | ], 651 | "Hash": "3d5108641f47470611a32d0bdf357a72" 652 | }, 653 | "later": { 654 | "Package": "later", 655 | "Version": "1.3.1", 656 | "Source": "Repository", 657 | "Repository": "RSPM", 658 | "Requirements": [ 659 | "Rcpp", 660 | "rlang" 661 | ], 662 | "Hash": "40401c9cf2bc2259dfe83311c9384710" 663 | }, 664 | "lattice": { 665 | "Package": "lattice", 666 | "Version": "0.20-45", 667 | "Source": "Repository", 668 | "Repository": "RSPM", 669 | "Requirements": [ 670 | "R", 671 | "grDevices", 672 | "graphics", 673 | "grid", 674 | "stats", 675 | "utils" 676 | ], 677 | "Hash": "b64cdbb2b340437c4ee047a1f4c4377b" 678 | }, 679 | "lazyeval": { 680 | "Package": "lazyeval", 681 | "Version": "0.2.2", 682 | "Source": "Repository", 683 | "Repository": "RSPM", 684 | "Requirements": [ 685 | "R" 686 | ], 687 | "Hash": "d908914ae53b04d4c0c0fd72ecc35370" 688 | }, 689 | "lifecycle": { 690 | "Package": "lifecycle", 691 | "Version": "1.0.3", 692 | "Source": "Repository", 693 | "Repository": "RSPM", 694 | "Requirements": [ 695 | "R", 696 | "cli", 697 | "glue", 698 | "rlang" 699 | ], 700 | "Hash": "001cecbeac1cff9301bdc3775ee46a86" 701 | }, 702 | "lubridate": { 703 | "Package": "lubridate", 704 | "Version": "1.9.2", 705 | "Source": "Repository", 706 | "Repository": "RSPM", 707 | "Requirements": [ 708 | "R", 709 | "generics", 710 | "methods", 711 | "timechange" 712 | ], 713 | "Hash": "e25f18436e3efd42c7c590a1c4c15390" 714 | }, 715 | "magrittr": { 716 | "Package": "magrittr", 717 | "Version": "2.0.3", 718 | "Source": "Repository", 719 | "Repository": "RSPM", 720 | "Requirements": [ 721 | "R" 722 | ], 723 | "Hash": "7ce2733a9826b3aeb1775d56fd305472" 724 | }, 725 | "memoise": { 726 | "Package": "memoise", 727 | "Version": "2.0.1", 728 | "Source": "Repository", 729 | "Repository": "RSPM", 730 | "Requirements": [ 731 | "cachem", 732 | "rlang" 733 | ], 734 | "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" 735 | }, 736 | "mgcv": { 737 | "Package": "mgcv", 738 | "Version": "1.8-42", 739 | "Source": "Repository", 740 | "Repository": "CRAN", 741 | "Requirements": [ 742 | "Matrix", 743 | "R", 744 | "graphics", 745 | "methods", 746 | "nlme", 747 | "splines", 748 | "stats", 749 | "utils" 750 | ], 751 | "Hash": "3460beba7ccc8946249ba35327ba902a" 752 | }, 753 | "mime": { 754 | "Package": "mime", 755 | "Version": "0.12", 756 | "Source": "Repository", 757 | "Repository": "RSPM", 758 | "Requirements": [ 759 | "tools" 760 | ], 761 | "Hash": "18e9c28c1d3ca1560ce30658b22ce104" 762 | }, 763 | "munsell": { 764 | "Package": "munsell", 765 | "Version": "0.5.0", 766 | "Source": "Repository", 767 | "Repository": "RSPM", 768 | "Requirements": [ 769 | "colorspace", 770 | "methods" 771 | ], 772 | "Hash": "6dfe8bf774944bd5595785e3229d8771" 773 | }, 774 | "nlme": { 775 | "Package": "nlme", 776 | "Version": "3.1-162", 777 | "Source": "Repository", 778 | "Repository": "CRAN", 779 | "Requirements": [ 780 | "R", 781 | "graphics", 782 | "lattice", 783 | "stats", 784 | "utils" 785 | ], 786 | "Hash": "0984ce8da8da9ead8643c5cbbb60f83e" 787 | }, 788 | "openssl": { 789 | "Package": "openssl", 790 | "Version": "2.1.0", 791 | "Source": "Repository", 792 | "Repository": "RSPM", 793 | "Requirements": [ 794 | "askpass" 795 | ], 796 | "Hash": "273a6bb4a9844c296a459d2176673270" 797 | }, 798 | "pillar": { 799 | "Package": "pillar", 800 | "Version": "1.9.0", 801 | "Source": "Repository", 802 | "Repository": "RSPM", 803 | "Requirements": [ 804 | "cli", 805 | "fansi", 806 | "glue", 807 | "lifecycle", 808 | "rlang", 809 | "utf8", 810 | "utils", 811 | "vctrs" 812 | ], 813 | "Hash": "15da5a8412f317beeee6175fbc76f4bb" 814 | }, 815 | "pkgconfig": { 816 | "Package": "pkgconfig", 817 | "Version": "2.0.3", 818 | "Source": "Repository", 819 | "Repository": "RSPM", 820 | "Requirements": [ 821 | "utils" 822 | ], 823 | "Hash": "01f28d4278f15c76cddbea05899c5d6f" 824 | }, 825 | "plotly": { 826 | "Package": "plotly", 827 | "Version": "4.10.2", 828 | "Source": "Repository", 829 | "Repository": "RSPM", 830 | "Requirements": [ 831 | "R", 832 | "RColorBrewer", 833 | "base64enc", 834 | "crosstalk", 835 | "data.table", 836 | "digest", 837 | "dplyr", 838 | "ggplot2", 839 | "htmltools", 840 | "htmlwidgets", 841 | "httr", 842 | "jsonlite", 843 | "lazyeval", 844 | "magrittr", 845 | "promises", 846 | "purrr", 847 | "rlang", 848 | "scales", 849 | "tibble", 850 | "tidyr", 851 | "tools", 852 | "vctrs", 853 | "viridisLite" 854 | ], 855 | "Hash": "6c00a09ba7d34917d9a3e28b15dd74e3" 856 | }, 857 | "prettyunits": { 858 | "Package": "prettyunits", 859 | "Version": "1.1.1", 860 | "Source": "Repository", 861 | "Repository": "RSPM", 862 | "Hash": "95ef9167b75dde9d2ccc3c7528393e7e" 863 | }, 864 | "progress": { 865 | "Package": "progress", 866 | "Version": "1.2.2", 867 | "Source": "Repository", 868 | "Repository": "RSPM", 869 | "Requirements": [ 870 | "R6", 871 | "crayon", 872 | "hms", 873 | "prettyunits" 874 | ], 875 | "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061" 876 | }, 877 | "promises": { 878 | "Package": "promises", 879 | "Version": "1.2.0.1", 880 | "Source": "Repository", 881 | "Repository": "RSPM", 882 | "Requirements": [ 883 | "R6", 884 | "Rcpp", 885 | "later", 886 | "magrittr", 887 | "rlang", 888 | "stats" 889 | ], 890 | "Hash": "4ab2c43adb4d4699cf3690acd378d75d" 891 | }, 892 | "purrr": { 893 | "Package": "purrr", 894 | "Version": "1.0.1", 895 | "Source": "Repository", 896 | "Repository": "RSPM", 897 | "Requirements": [ 898 | "R", 899 | "cli", 900 | "lifecycle", 901 | "magrittr", 902 | "rlang", 903 | "vctrs" 904 | ], 905 | "Hash": "d71c815267c640f17ddbf7f16144b4bb" 906 | }, 907 | "rappdirs": { 908 | "Package": "rappdirs", 909 | "Version": "0.3.3", 910 | "Source": "Repository", 911 | "Repository": "RSPM", 912 | "Requirements": [ 913 | "R" 914 | ], 915 | "Hash": "5e3c5dc0b071b21fa128676560dbe94d" 916 | }, 917 | "renv": { 918 | "Package": "renv", 919 | "Version": "1.0.0", 920 | "Source": "Repository", 921 | "Repository": "RSPM", 922 | "Requirements": [ 923 | "utils" 924 | ], 925 | "Hash": "c321cd99d56443dbffd1c9e673c0c1a2" 926 | }, 927 | "rlang": { 928 | "Package": "rlang", 929 | "Version": "1.1.1", 930 | "Source": "Repository", 931 | "Repository": "RSPM", 932 | "Requirements": [ 933 | "R", 934 | "utils" 935 | ], 936 | "Hash": "a85c767b55f0bf9b7ad16c6d7baee5bb" 937 | }, 938 | "rmarkdown": { 939 | "Package": "rmarkdown", 940 | "Version": "2.22", 941 | "Source": "Repository", 942 | "Repository": "RSPM", 943 | "Requirements": [ 944 | "R", 945 | "bslib", 946 | "evaluate", 947 | "fontawesome", 948 | "htmltools", 949 | "jquerylib", 950 | "jsonlite", 951 | "knitr", 952 | "methods", 953 | "stringr", 954 | "tinytex", 955 | "tools", 956 | "utils", 957 | "xfun", 958 | "yaml" 959 | ], 960 | "Hash": "75a01be060d800ceb14e32c666cacac9" 961 | }, 962 | "rprojroot": { 963 | "Package": "rprojroot", 964 | "Version": "2.0.3", 965 | "Source": "Repository", 966 | "Repository": "RSPM", 967 | "Requirements": [ 968 | "R" 969 | ], 970 | "Hash": "1de7ab598047a87bba48434ba35d497d" 971 | }, 972 | "rstudioapi": { 973 | "Package": "rstudioapi", 974 | "Version": "0.15.0", 975 | "Source": "Repository", 976 | "Repository": "RSPM", 977 | "Hash": "5564500e25cffad9e22244ced1379887" 978 | }, 979 | "sass": { 980 | "Package": "sass", 981 | "Version": "0.4.6", 982 | "Source": "Repository", 983 | "Repository": "RSPM", 984 | "Requirements": [ 985 | "R6", 986 | "fs", 987 | "htmltools", 988 | "rappdirs", 989 | "rlang" 990 | ], 991 | "Hash": "cc3ec7dd33982ef56570229b62d6388e" 992 | }, 993 | "scales": { 994 | "Package": "scales", 995 | "Version": "1.2.1", 996 | "Source": "Repository", 997 | "Repository": "RSPM", 998 | "Requirements": [ 999 | "R", 1000 | "R6", 1001 | "RColorBrewer", 1002 | "farver", 1003 | "labeling", 1004 | "lifecycle", 1005 | "munsell", 1006 | "rlang", 1007 | "viridisLite" 1008 | ], 1009 | "Hash": "906cb23d2f1c5680b8ce439b44c6fa63" 1010 | }, 1011 | "shiny": { 1012 | "Package": "shiny", 1013 | "Version": "1.7.5", 1014 | "Source": "Repository", 1015 | "Repository": "RSPM", 1016 | "Requirements": [ 1017 | "R", 1018 | "R6", 1019 | "bslib", 1020 | "cachem", 1021 | "commonmark", 1022 | "crayon", 1023 | "ellipsis", 1024 | "fastmap", 1025 | "fontawesome", 1026 | "glue", 1027 | "grDevices", 1028 | "htmltools", 1029 | "httpuv", 1030 | "jsonlite", 1031 | "later", 1032 | "lifecycle", 1033 | "methods", 1034 | "mime", 1035 | "promises", 1036 | "rlang", 1037 | "sourcetools", 1038 | "tools", 1039 | "utils", 1040 | "withr", 1041 | "xtable" 1042 | ], 1043 | "Hash": "438b99792adbe82a8329ad8697d45afe" 1044 | }, 1045 | "showtext": { 1046 | "Package": "showtext", 1047 | "Version": "0.9-6", 1048 | "Source": "Repository", 1049 | "Repository": "RSPM", 1050 | "Requirements": [ 1051 | "grDevices", 1052 | "showtextdb", 1053 | "sysfonts" 1054 | ], 1055 | "Hash": "c0fd332d248b195bbcb94a0dfda37b0d" 1056 | }, 1057 | "showtextdb": { 1058 | "Package": "showtextdb", 1059 | "Version": "3.0", 1060 | "Source": "Repository", 1061 | "Repository": "RSPM", 1062 | "Requirements": [ 1063 | "sysfonts", 1064 | "utils" 1065 | ], 1066 | "Hash": "c12e756cf947e58b0f2c2a520521a5a8" 1067 | }, 1068 | "sourcetools": { 1069 | "Package": "sourcetools", 1070 | "Version": "0.1.7-1", 1071 | "Source": "Repository", 1072 | "Repository": "RSPM", 1073 | "Requirements": [ 1074 | "R" 1075 | ], 1076 | "Hash": "5f5a7629f956619d519205ec475fe647" 1077 | }, 1078 | "stringi": { 1079 | "Package": "stringi", 1080 | "Version": "1.7.12", 1081 | "Source": "Repository", 1082 | "Repository": "RSPM", 1083 | "Requirements": [ 1084 | "R", 1085 | "stats", 1086 | "tools", 1087 | "utils" 1088 | ], 1089 | "Hash": "ca8bd84263c77310739d2cf64d84d7c9" 1090 | }, 1091 | "stringr": { 1092 | "Package": "stringr", 1093 | "Version": "1.5.0", 1094 | "Source": "Repository", 1095 | "Repository": "RSPM", 1096 | "Requirements": [ 1097 | "R", 1098 | "cli", 1099 | "glue", 1100 | "lifecycle", 1101 | "magrittr", 1102 | "rlang", 1103 | "stringi", 1104 | "vctrs" 1105 | ], 1106 | "Hash": "671a4d384ae9d32fc47a14e98bfa3dc8" 1107 | }, 1108 | "sys": { 1109 | "Package": "sys", 1110 | "Version": "3.4.2", 1111 | "Source": "Repository", 1112 | "Repository": "RSPM", 1113 | "Hash": "3a1be13d68d47a8cd0bfd74739ca1555" 1114 | }, 1115 | "sysfonts": { 1116 | "Package": "sysfonts", 1117 | "Version": "0.8.8", 1118 | "Source": "Repository", 1119 | "Repository": "RSPM", 1120 | "Hash": "7f4dac41a3e348ae12832167e6beb875" 1121 | }, 1122 | "thematic": { 1123 | "Package": "thematic", 1124 | "Version": "0.1.3", 1125 | "Source": "Repository", 1126 | "Repository": "RSPM", 1127 | "Requirements": [ 1128 | "R", 1129 | "farver", 1130 | "ggplot2", 1131 | "grDevices", 1132 | "graphics", 1133 | "grid", 1134 | "rappdirs", 1135 | "rlang", 1136 | "rstudioapi", 1137 | "scales", 1138 | "utils" 1139 | ], 1140 | "Hash": "c8f5a8485943a04f20864e7e526a75db" 1141 | }, 1142 | "tibble": { 1143 | "Package": "tibble", 1144 | "Version": "3.2.1", 1145 | "Source": "Repository", 1146 | "Repository": "RSPM", 1147 | "Requirements": [ 1148 | "R", 1149 | "fansi", 1150 | "lifecycle", 1151 | "magrittr", 1152 | "methods", 1153 | "pillar", 1154 | "pkgconfig", 1155 | "rlang", 1156 | "utils", 1157 | "vctrs" 1158 | ], 1159 | "Hash": "a84e2cc86d07289b3b6f5069df7a004c" 1160 | }, 1161 | "tidyr": { 1162 | "Package": "tidyr", 1163 | "Version": "1.3.0", 1164 | "Source": "Repository", 1165 | "Repository": "RSPM", 1166 | "Requirements": [ 1167 | "R", 1168 | "cli", 1169 | "cpp11", 1170 | "dplyr", 1171 | "glue", 1172 | "lifecycle", 1173 | "magrittr", 1174 | "purrr", 1175 | "rlang", 1176 | "stringr", 1177 | "tibble", 1178 | "tidyselect", 1179 | "utils", 1180 | "vctrs" 1181 | ], 1182 | "Hash": "e47debdc7ce599b070c8e78e8ac0cfcf" 1183 | }, 1184 | "tidyselect": { 1185 | "Package": "tidyselect", 1186 | "Version": "1.2.0", 1187 | "Source": "Repository", 1188 | "Repository": "RSPM", 1189 | "Requirements": [ 1190 | "R", 1191 | "cli", 1192 | "glue", 1193 | "lifecycle", 1194 | "rlang", 1195 | "vctrs", 1196 | "withr" 1197 | ], 1198 | "Hash": "79540e5fcd9e0435af547d885f184fd5" 1199 | }, 1200 | "timechange": { 1201 | "Package": "timechange", 1202 | "Version": "0.2.0", 1203 | "Source": "Repository", 1204 | "Repository": "RSPM", 1205 | "Requirements": [ 1206 | "R", 1207 | "cpp11" 1208 | ], 1209 | "Hash": "8548b44f79a35ba1791308b61e6012d7" 1210 | }, 1211 | "tinytex": { 1212 | "Package": "tinytex", 1213 | "Version": "0.45", 1214 | "Source": "Repository", 1215 | "Repository": "RSPM", 1216 | "Requirements": [ 1217 | "xfun" 1218 | ], 1219 | "Hash": "e4e357f28c2edff493936b6cb30c3d65" 1220 | }, 1221 | "utf8": { 1222 | "Package": "utf8", 1223 | "Version": "1.2.3", 1224 | "Source": "Repository", 1225 | "Repository": "RSPM", 1226 | "Requirements": [ 1227 | "R" 1228 | ], 1229 | "Hash": "1fe17157424bb09c48a8b3b550c753bc" 1230 | }, 1231 | "uuid": { 1232 | "Package": "uuid", 1233 | "Version": "1.1-0", 1234 | "Source": "Repository", 1235 | "Repository": "RSPM", 1236 | "Requirements": [ 1237 | "R" 1238 | ], 1239 | "Hash": "f1cb46c157d080b729159d407be83496" 1240 | }, 1241 | "vctrs": { 1242 | "Package": "vctrs", 1243 | "Version": "0.6.3", 1244 | "Source": "Repository", 1245 | "Repository": "RSPM", 1246 | "Requirements": [ 1247 | "R", 1248 | "cli", 1249 | "glue", 1250 | "lifecycle", 1251 | "rlang" 1252 | ], 1253 | "Hash": "d0ef2856b83dc33ea6e255caf6229ee2" 1254 | }, 1255 | "viridisLite": { 1256 | "Package": "viridisLite", 1257 | "Version": "0.4.2", 1258 | "Source": "Repository", 1259 | "Repository": "RSPM", 1260 | "Requirements": [ 1261 | "R" 1262 | ], 1263 | "Hash": "c826c7c4241b6fc89ff55aaea3fa7491" 1264 | }, 1265 | "withr": { 1266 | "Package": "withr", 1267 | "Version": "2.5.0", 1268 | "Source": "Repository", 1269 | "Repository": "RSPM", 1270 | "Requirements": [ 1271 | "R", 1272 | "grDevices", 1273 | "graphics", 1274 | "stats" 1275 | ], 1276 | "Hash": "c0e49a9760983e81e55cdd9be92e7182" 1277 | }, 1278 | "xfun": { 1279 | "Package": "xfun", 1280 | "Version": "0.39", 1281 | "Source": "Repository", 1282 | "Repository": "RSPM", 1283 | "Requirements": [ 1284 | "stats", 1285 | "tools" 1286 | ], 1287 | "Hash": "8f56e9acb54fb525e66464d57ab58bcb" 1288 | }, 1289 | "xtable": { 1290 | "Package": "xtable", 1291 | "Version": "1.8-4", 1292 | "Source": "Repository", 1293 | "Repository": "RSPM", 1294 | "Requirements": [ 1295 | "R", 1296 | "stats", 1297 | "utils" 1298 | ], 1299 | "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2" 1300 | }, 1301 | "yaml": { 1302 | "Package": "yaml", 1303 | "Version": "2.3.7", 1304 | "Source": "Repository", 1305 | "Repository": "RSPM", 1306 | "Hash": "0d0056cc5383fbc240ccd0cb584bf436" 1307 | } 1308 | } 1309 | } 1310 | -------------------------------------------------------------------------------- /examples/last_30_days/renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | cellar/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | -------------------------------------------------------------------------------- /examples/last_30_days/renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "1.0.0" 6 | attr(version, "sha") <- NULL 7 | 8 | # the project directory 9 | project <- getwd() 10 | 11 | # figure out whether the autoloader is enabled 12 | enabled <- local({ 13 | 14 | # first, check config option 15 | override <- getOption("renv.config.autoloader.enabled") 16 | if (!is.null(override)) 17 | return(override) 18 | 19 | # next, check environment variables 20 | # TODO: prefer using the configuration one in the future 21 | envvars <- c( 22 | "RENV_CONFIG_AUTOLOADER_ENABLED", 23 | "RENV_AUTOLOADER_ENABLED", 24 | "RENV_ACTIVATE_PROJECT" 25 | ) 26 | 27 | for (envvar in envvars) { 28 | envval <- Sys.getenv(envvar, unset = NA) 29 | if (!is.na(envval)) 30 | return(tolower(envval) %in% c("true", "t", "1")) 31 | } 32 | 33 | # enable by default 34 | TRUE 35 | 36 | }) 37 | 38 | if (!enabled) 39 | return(FALSE) 40 | 41 | # avoid recursion 42 | if (identical(getOption("renv.autoloader.running"), TRUE)) { 43 | warning("ignoring recursive attempt to run renv autoloader") 44 | return(invisible(TRUE)) 45 | } 46 | 47 | # signal that we're loading renv during R startup 48 | options(renv.autoloader.running = TRUE) 49 | on.exit(options(renv.autoloader.running = NULL), add = TRUE) 50 | 51 | # signal that we've consented to use renv 52 | options(renv.consent = TRUE) 53 | 54 | # load the 'utils' package eagerly -- this ensures that renv shims, which 55 | # mask 'utils' packages, will come first on the search path 56 | library(utils, lib.loc = .Library) 57 | 58 | # unload renv if it's already been loaded 59 | if ("renv" %in% loadedNamespaces()) 60 | unloadNamespace("renv") 61 | 62 | # load bootstrap tools 63 | `%||%` <- function(x, y) { 64 | if (is.null(x)) y else x 65 | } 66 | 67 | catf <- function(fmt, ..., appendLF = TRUE) { 68 | 69 | quiet <- getOption("renv.bootstrap.quiet", default = FALSE) 70 | if (quiet) 71 | return(invisible()) 72 | 73 | msg <- sprintf(fmt, ...) 74 | cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") 75 | 76 | invisible(msg) 77 | 78 | } 79 | 80 | header <- function(label, 81 | ..., 82 | prefix = "#", 83 | suffix = "-", 84 | n = min(getOption("width"), 78)) 85 | { 86 | label <- sprintf(label, ...) 87 | n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) 88 | if (n <= 0) 89 | return(paste(prefix, label)) 90 | 91 | tail <- paste(rep.int(suffix, n), collapse = "") 92 | paste0(prefix, " ", label, " ", tail) 93 | 94 | } 95 | 96 | startswith <- function(string, prefix) { 97 | substring(string, 1, nchar(prefix)) == prefix 98 | } 99 | 100 | bootstrap <- function(version, library) { 101 | 102 | friendly <- renv_bootstrap_version_friendly(version) 103 | section <- header(sprintf("Bootstrapping renv %s", friendly)) 104 | catf(section) 105 | 106 | # attempt to download renv 107 | catf("- Downloading renv ... ", appendLF = FALSE) 108 | withCallingHandlers( 109 | tarball <- renv_bootstrap_download(version), 110 | error = function(err) { 111 | catf("FAILED") 112 | stop("failed to download:\n", conditionMessage(err)) 113 | } 114 | ) 115 | catf("OK") 116 | on.exit(unlink(tarball), add = TRUE) 117 | 118 | # now attempt to install 119 | catf("- Installing renv ... ", appendLF = FALSE) 120 | withCallingHandlers( 121 | status <- renv_bootstrap_install(version, tarball, library), 122 | error = function(err) { 123 | catf("FAILED") 124 | stop("failed to install:\n", conditionMessage(err)) 125 | } 126 | ) 127 | catf("OK") 128 | 129 | # add empty line to break up bootstrapping from normal output 130 | catf("") 131 | 132 | return(invisible()) 133 | } 134 | 135 | renv_bootstrap_tests_running <- function() { 136 | getOption("renv.tests.running", default = FALSE) 137 | } 138 | 139 | renv_bootstrap_repos <- function() { 140 | 141 | # get CRAN repository 142 | cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") 143 | 144 | # check for repos override 145 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 146 | if (!is.na(repos)) { 147 | 148 | # check for RSPM; if set, use a fallback repository for renv 149 | rspm <- Sys.getenv("RSPM", unset = NA) 150 | if (identical(rspm, repos)) 151 | repos <- c(RSPM = rspm, CRAN = cran) 152 | 153 | return(repos) 154 | 155 | } 156 | 157 | # check for lockfile repositories 158 | repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) 159 | if (!inherits(repos, "error") && length(repos)) 160 | return(repos) 161 | 162 | # retrieve current repos 163 | repos <- getOption("repos") 164 | 165 | # ensure @CRAN@ entries are resolved 166 | repos[repos == "@CRAN@"] <- cran 167 | 168 | # add in renv.bootstrap.repos if set 169 | default <- c(FALLBACK = "https://cloud.r-project.org") 170 | extra <- getOption("renv.bootstrap.repos", default = default) 171 | repos <- c(repos, extra) 172 | 173 | # remove duplicates that might've snuck in 174 | dupes <- duplicated(repos) | duplicated(names(repos)) 175 | repos[!dupes] 176 | 177 | } 178 | 179 | renv_bootstrap_repos_lockfile <- function() { 180 | 181 | lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") 182 | if (!file.exists(lockpath)) 183 | return(NULL) 184 | 185 | lockfile <- tryCatch(renv_json_read(lockpath), error = identity) 186 | if (inherits(lockfile, "error")) { 187 | warning(lockfile) 188 | return(NULL) 189 | } 190 | 191 | repos <- lockfile$R$Repositories 192 | if (length(repos) == 0) 193 | return(NULL) 194 | 195 | keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) 196 | vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) 197 | names(vals) <- keys 198 | 199 | return(vals) 200 | 201 | } 202 | 203 | renv_bootstrap_download <- function(version) { 204 | 205 | sha <- attr(version, "sha", exact = TRUE) 206 | 207 | methods <- if (!is.null(sha)) { 208 | 209 | # attempting to bootstrap a development version of renv 210 | c( 211 | function() renv_bootstrap_download_tarball(sha), 212 | function() renv_bootstrap_download_github(sha) 213 | ) 214 | 215 | } else { 216 | 217 | # attempting to bootstrap a release version of renv 218 | c( 219 | function() renv_bootstrap_download_tarball(version), 220 | function() renv_bootstrap_download_cran_latest(version), 221 | function() renv_bootstrap_download_cran_archive(version) 222 | ) 223 | 224 | } 225 | 226 | for (method in methods) { 227 | path <- tryCatch(method(), error = identity) 228 | if (is.character(path) && file.exists(path)) 229 | return(path) 230 | } 231 | 232 | stop("All download methods failed") 233 | 234 | } 235 | 236 | renv_bootstrap_download_impl <- function(url, destfile) { 237 | 238 | mode <- "wb" 239 | 240 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 241 | fixup <- 242 | Sys.info()[["sysname"]] == "Windows" && 243 | substring(url, 1L, 5L) == "file:" 244 | 245 | if (fixup) 246 | mode <- "w+b" 247 | 248 | args <- list( 249 | url = url, 250 | destfile = destfile, 251 | mode = mode, 252 | quiet = TRUE 253 | ) 254 | 255 | if ("headers" %in% names(formals(utils::download.file))) 256 | args$headers <- renv_bootstrap_download_custom_headers(url) 257 | 258 | do.call(utils::download.file, args) 259 | 260 | } 261 | 262 | renv_bootstrap_download_custom_headers <- function(url) { 263 | 264 | headers <- getOption("renv.download.headers") 265 | if (is.null(headers)) 266 | return(character()) 267 | 268 | if (!is.function(headers)) 269 | stopf("'renv.download.headers' is not a function") 270 | 271 | headers <- headers(url) 272 | if (length(headers) == 0L) 273 | return(character()) 274 | 275 | if (is.list(headers)) 276 | headers <- unlist(headers, recursive = FALSE, use.names = TRUE) 277 | 278 | ok <- 279 | is.character(headers) && 280 | is.character(names(headers)) && 281 | all(nzchar(names(headers))) 282 | 283 | if (!ok) 284 | stop("invocation of 'renv.download.headers' did not return a named character vector") 285 | 286 | headers 287 | 288 | } 289 | 290 | renv_bootstrap_download_cran_latest <- function(version) { 291 | 292 | spec <- renv_bootstrap_download_cran_latest_find(version) 293 | type <- spec$type 294 | repos <- spec$repos 295 | 296 | baseurl <- utils::contrib.url(repos = repos, type = type) 297 | ext <- if (identical(type, "source")) 298 | ".tar.gz" 299 | else if (Sys.info()[["sysname"]] == "Windows") 300 | ".zip" 301 | else 302 | ".tgz" 303 | name <- sprintf("renv_%s%s", version, ext) 304 | url <- paste(baseurl, name, sep = "/") 305 | 306 | destfile <- file.path(tempdir(), name) 307 | status <- tryCatch( 308 | renv_bootstrap_download_impl(url, destfile), 309 | condition = identity 310 | ) 311 | 312 | if (inherits(status, "condition")) 313 | return(FALSE) 314 | 315 | # report success and return 316 | destfile 317 | 318 | } 319 | 320 | renv_bootstrap_download_cran_latest_find <- function(version) { 321 | 322 | # check whether binaries are supported on this system 323 | binary <- 324 | getOption("renv.bootstrap.binary", default = TRUE) && 325 | !identical(.Platform$pkgType, "source") && 326 | !identical(getOption("pkgType"), "source") && 327 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows") 328 | 329 | types <- c(if (binary) "binary", "source") 330 | 331 | # iterate over types + repositories 332 | for (type in types) { 333 | for (repos in renv_bootstrap_repos()) { 334 | 335 | # retrieve package database 336 | db <- tryCatch( 337 | as.data.frame( 338 | utils::available.packages(type = type, repos = repos), 339 | stringsAsFactors = FALSE 340 | ), 341 | error = identity 342 | ) 343 | 344 | if (inherits(db, "error")) 345 | next 346 | 347 | # check for compatible entry 348 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 349 | if (nrow(entry) == 0) 350 | next 351 | 352 | # found it; return spec to caller 353 | spec <- list(entry = entry, type = type, repos = repos) 354 | return(spec) 355 | 356 | } 357 | } 358 | 359 | # if we got here, we failed to find renv 360 | fmt <- "renv %s is not available from your declared package repositories" 361 | stop(sprintf(fmt, version)) 362 | 363 | } 364 | 365 | renv_bootstrap_download_cran_archive <- function(version) { 366 | 367 | name <- sprintf("renv_%s.tar.gz", version) 368 | repos <- renv_bootstrap_repos() 369 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 370 | destfile <- file.path(tempdir(), name) 371 | 372 | for (url in urls) { 373 | 374 | status <- tryCatch( 375 | renv_bootstrap_download_impl(url, destfile), 376 | condition = identity 377 | ) 378 | 379 | if (identical(status, 0L)) 380 | return(destfile) 381 | 382 | } 383 | 384 | return(FALSE) 385 | 386 | } 387 | 388 | renv_bootstrap_download_tarball <- function(version) { 389 | 390 | # if the user has provided the path to a tarball via 391 | # an environment variable, then use it 392 | tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) 393 | if (is.na(tarball)) 394 | return() 395 | 396 | # allow directories 397 | if (dir.exists(tarball)) { 398 | name <- sprintf("renv_%s.tar.gz", version) 399 | tarball <- file.path(tarball, name) 400 | } 401 | 402 | # bail if it doesn't exist 403 | if (!file.exists(tarball)) { 404 | 405 | # let the user know we weren't able to honour their request 406 | fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." 407 | msg <- sprintf(fmt, tarball) 408 | warning(msg) 409 | 410 | # bail 411 | return() 412 | 413 | } 414 | 415 | catf("- Using local tarball '%s'.", tarball) 416 | tarball 417 | 418 | } 419 | 420 | renv_bootstrap_download_github <- function(version) { 421 | 422 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 423 | if (!identical(enabled, "TRUE")) 424 | return(FALSE) 425 | 426 | # prepare download options 427 | pat <- Sys.getenv("GITHUB_PAT") 428 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 429 | fmt <- "--location --fail --header \"Authorization: token %s\"" 430 | extra <- sprintf(fmt, pat) 431 | saved <- options("download.file.method", "download.file.extra") 432 | options(download.file.method = "curl", download.file.extra = extra) 433 | on.exit(do.call(base::options, saved), add = TRUE) 434 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 435 | fmt <- "--header=\"Authorization: token %s\"" 436 | extra <- sprintf(fmt, pat) 437 | saved <- options("download.file.method", "download.file.extra") 438 | options(download.file.method = "wget", download.file.extra = extra) 439 | on.exit(do.call(base::options, saved), add = TRUE) 440 | } 441 | 442 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 443 | name <- sprintf("renv_%s.tar.gz", version) 444 | destfile <- file.path(tempdir(), name) 445 | 446 | status <- tryCatch( 447 | renv_bootstrap_download_impl(url, destfile), 448 | condition = identity 449 | ) 450 | 451 | if (!identical(status, 0L)) 452 | return(FALSE) 453 | 454 | renv_bootstrap_download_augment(destfile) 455 | 456 | return(destfile) 457 | 458 | } 459 | 460 | # Add Sha to DESCRIPTION. This is stop gap until #890, after which we 461 | # can use renv::install() to fully capture metadata. 462 | renv_bootstrap_download_augment <- function(destfile) { 463 | sha <- renv_bootstrap_git_extract_sha1_tar(destfile) 464 | if (is.null(sha)) { 465 | return() 466 | } 467 | 468 | # Untar 469 | tempdir <- tempfile("renv-github-") 470 | on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) 471 | untar(destfile, exdir = tempdir) 472 | pkgdir <- dir(tempdir, full.names = TRUE)[[1]] 473 | 474 | # Modify description 475 | desc_path <- file.path(pkgdir, "DESCRIPTION") 476 | desc_lines <- readLines(desc_path) 477 | remotes_fields <- c( 478 | "RemoteType: github", 479 | "RemoteHost: api.github.com", 480 | "RemoteRepo: renv", 481 | "RemoteUsername: rstudio", 482 | "RemotePkgRef: rstudio/renv", 483 | paste("RemoteRef: ", sha), 484 | paste("RemoteSha: ", sha) 485 | ) 486 | writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) 487 | 488 | # Re-tar 489 | local({ 490 | old <- setwd(tempdir) 491 | on.exit(setwd(old), add = TRUE) 492 | 493 | tar(destfile, compression = "gzip") 494 | }) 495 | invisible() 496 | } 497 | 498 | # Extract the commit hash from a git archive. Git archives include the SHA1 499 | # hash as the comment field of the tarball pax extended header 500 | # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) 501 | # For GitHub archives this should be the first header after the default one 502 | # (512 byte) header. 503 | renv_bootstrap_git_extract_sha1_tar <- function(bundle) { 504 | 505 | # open the bundle for reading 506 | # We use gzcon for everything because (from ?gzcon) 507 | # > Reading from a connection which does not supply a ‘gzip’ magic 508 | # > header is equivalent to reading from the original connection 509 | conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) 510 | on.exit(close(conn)) 511 | 512 | # The default pax header is 512 bytes long and the first pax extended header 513 | # with the comment should be 51 bytes long 514 | # `52 comment=` (11 chars) + 40 byte SHA1 hash 515 | len <- 0x200 + 0x33 516 | res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) 517 | 518 | if (grepl("^52 comment=", res)) { 519 | sub("52 comment=", "", res) 520 | } else { 521 | NULL 522 | } 523 | } 524 | 525 | renv_bootstrap_install <- function(version, tarball, library) { 526 | 527 | # attempt to install it into project library 528 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 529 | output <- renv_bootstrap_install_impl(library, tarball) 530 | 531 | # check for successful install 532 | status <- attr(output, "status") 533 | if (is.null(status) || identical(status, 0L)) 534 | return(status) 535 | 536 | # an error occurred; report it 537 | header <- "installation of renv failed" 538 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 539 | text <- paste(c(header, lines, output), collapse = "\n") 540 | stop(text) 541 | 542 | } 543 | 544 | renv_bootstrap_install_impl <- function(library, tarball) { 545 | 546 | # invoke using system2 so we can capture and report output 547 | bin <- R.home("bin") 548 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 549 | R <- file.path(bin, exe) 550 | 551 | args <- c( 552 | "--vanilla", "CMD", "INSTALL", "--no-multiarch", 553 | "-l", shQuote(path.expand(library)), 554 | shQuote(path.expand(tarball)) 555 | ) 556 | 557 | system2(R, args, stdout = TRUE, stderr = TRUE) 558 | 559 | } 560 | 561 | renv_bootstrap_platform_prefix <- function() { 562 | 563 | # construct version prefix 564 | version <- paste(R.version$major, R.version$minor, sep = ".") 565 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 566 | 567 | # include SVN revision for development versions of R 568 | # (to avoid sharing platform-specific artefacts with released versions of R) 569 | devel <- 570 | identical(R.version[["status"]], "Under development (unstable)") || 571 | identical(R.version[["nickname"]], "Unsuffered Consequences") 572 | 573 | if (devel) 574 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 575 | 576 | # build list of path components 577 | components <- c(prefix, R.version$platform) 578 | 579 | # include prefix if provided by user 580 | prefix <- renv_bootstrap_platform_prefix_impl() 581 | if (!is.na(prefix) && nzchar(prefix)) 582 | components <- c(prefix, components) 583 | 584 | # build prefix 585 | paste(components, collapse = "/") 586 | 587 | } 588 | 589 | renv_bootstrap_platform_prefix_impl <- function() { 590 | 591 | # if an explicit prefix has been supplied, use it 592 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) 593 | if (!is.na(prefix)) 594 | return(prefix) 595 | 596 | # if the user has requested an automatic prefix, generate it 597 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) 598 | if (auto %in% c("TRUE", "True", "true", "1")) 599 | return(renv_bootstrap_platform_prefix_auto()) 600 | 601 | # empty string on failure 602 | "" 603 | 604 | } 605 | 606 | renv_bootstrap_platform_prefix_auto <- function() { 607 | 608 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) 609 | if (inherits(prefix, "error") || prefix %in% "unknown") { 610 | 611 | msg <- paste( 612 | "failed to infer current operating system", 613 | "please file a bug report at https://github.com/rstudio/renv/issues", 614 | sep = "; " 615 | ) 616 | 617 | warning(msg) 618 | 619 | } 620 | 621 | prefix 622 | 623 | } 624 | 625 | renv_bootstrap_platform_os <- function() { 626 | 627 | sysinfo <- Sys.info() 628 | sysname <- sysinfo[["sysname"]] 629 | 630 | # handle Windows + macOS up front 631 | if (sysname == "Windows") 632 | return("windows") 633 | else if (sysname == "Darwin") 634 | return("macos") 635 | 636 | # check for os-release files 637 | for (file in c("/etc/os-release", "/usr/lib/os-release")) 638 | if (file.exists(file)) 639 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) 640 | 641 | # check for redhat-release files 642 | if (file.exists("/etc/redhat-release")) 643 | return(renv_bootstrap_platform_os_via_redhat_release()) 644 | 645 | "unknown" 646 | 647 | } 648 | 649 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { 650 | 651 | # read /etc/os-release 652 | release <- utils::read.table( 653 | file = file, 654 | sep = "=", 655 | quote = c("\"", "'"), 656 | col.names = c("Key", "Value"), 657 | comment.char = "#", 658 | stringsAsFactors = FALSE 659 | ) 660 | 661 | vars <- as.list(release$Value) 662 | names(vars) <- release$Key 663 | 664 | # get os name 665 | os <- tolower(sysinfo[["sysname"]]) 666 | 667 | # read id 668 | id <- "unknown" 669 | for (field in c("ID", "ID_LIKE")) { 670 | if (field %in% names(vars) && nzchar(vars[[field]])) { 671 | id <- vars[[field]] 672 | break 673 | } 674 | } 675 | 676 | # read version 677 | version <- "unknown" 678 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { 679 | if (field %in% names(vars) && nzchar(vars[[field]])) { 680 | version <- vars[[field]] 681 | break 682 | } 683 | } 684 | 685 | # join together 686 | paste(c(os, id, version), collapse = "-") 687 | 688 | } 689 | 690 | renv_bootstrap_platform_os_via_redhat_release <- function() { 691 | 692 | # read /etc/redhat-release 693 | contents <- readLines("/etc/redhat-release", warn = FALSE) 694 | 695 | # infer id 696 | id <- if (grepl("centos", contents, ignore.case = TRUE)) 697 | "centos" 698 | else if (grepl("redhat", contents, ignore.case = TRUE)) 699 | "redhat" 700 | else 701 | "unknown" 702 | 703 | # try to find a version component (very hacky) 704 | version <- "unknown" 705 | 706 | parts <- strsplit(contents, "[[:space:]]")[[1L]] 707 | for (part in parts) { 708 | 709 | nv <- tryCatch(numeric_version(part), error = identity) 710 | if (inherits(nv, "error")) 711 | next 712 | 713 | version <- nv[1, 1] 714 | break 715 | 716 | } 717 | 718 | paste(c("linux", id, version), collapse = "-") 719 | 720 | } 721 | 722 | renv_bootstrap_library_root_name <- function(project) { 723 | 724 | # use project name as-is if requested 725 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 726 | if (asis) 727 | return(basename(project)) 728 | 729 | # otherwise, disambiguate based on project's path 730 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 731 | paste(basename(project), id, sep = "-") 732 | 733 | } 734 | 735 | renv_bootstrap_library_root <- function(project) { 736 | 737 | prefix <- renv_bootstrap_profile_prefix() 738 | 739 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 740 | if (!is.na(path)) 741 | return(paste(c(path, prefix), collapse = "/")) 742 | 743 | path <- renv_bootstrap_library_root_impl(project) 744 | if (!is.null(path)) { 745 | name <- renv_bootstrap_library_root_name(project) 746 | return(paste(c(path, prefix, name), collapse = "/")) 747 | } 748 | 749 | renv_bootstrap_paths_renv("library", project = project) 750 | 751 | } 752 | 753 | renv_bootstrap_library_root_impl <- function(project) { 754 | 755 | root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 756 | if (!is.na(root)) 757 | return(root) 758 | 759 | type <- renv_bootstrap_project_type(project) 760 | if (identical(type, "package")) { 761 | userdir <- renv_bootstrap_user_dir() 762 | return(file.path(userdir, "library")) 763 | } 764 | 765 | } 766 | 767 | renv_bootstrap_validate_version <- function(version, description = NULL) { 768 | 769 | # resolve description file 770 | description <- description %||% { 771 | path <- getNamespaceInfo("renv", "path") 772 | packageDescription("renv", lib.loc = dirname(path)) 773 | } 774 | 775 | # check whether requested version 'version' matches loaded version of renv 776 | sha <- attr(version, "sha", exact = TRUE) 777 | valid <- if (!is.null(sha)) 778 | renv_bootstrap_validate_version_dev(sha, description) 779 | else 780 | renv_bootstrap_validate_version_release(version, description) 781 | 782 | if (valid) 783 | return(TRUE) 784 | 785 | # the loaded version of renv doesn't match the requested version; 786 | # give the user instructions on how to proceed 787 | remote <- if (!is.null(description[["RemoteSha"]])) { 788 | paste("rstudio/renv", description[["RemoteSha"]], sep = "@") 789 | } else { 790 | paste("renv", description[["Version"]], sep = "@") 791 | } 792 | 793 | # display both loaded version + sha if available 794 | friendly <- renv_bootstrap_version_friendly( 795 | version = description[["Version"]], 796 | sha = description[["RemoteSha"]] 797 | ) 798 | 799 | fmt <- paste( 800 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", 801 | "- Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", 802 | "- Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 803 | sep = "\n" 804 | ) 805 | catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) 806 | 807 | FALSE 808 | 809 | } 810 | 811 | renv_bootstrap_validate_version_dev <- function(version, description) { 812 | expected <- description[["RemoteSha"]] 813 | is.character(expected) && startswith(expected, version) 814 | } 815 | 816 | renv_bootstrap_validate_version_release <- function(version, description) { 817 | expected <- description[["Version"]] 818 | is.character(expected) && identical(expected, version) 819 | } 820 | 821 | renv_bootstrap_hash_text <- function(text) { 822 | 823 | hashfile <- tempfile("renv-hash-") 824 | on.exit(unlink(hashfile), add = TRUE) 825 | 826 | writeLines(text, con = hashfile) 827 | tools::md5sum(hashfile) 828 | 829 | } 830 | 831 | renv_bootstrap_load <- function(project, libpath, version) { 832 | 833 | # try to load renv from the project library 834 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 835 | return(FALSE) 836 | 837 | # warn if the version of renv loaded does not match 838 | renv_bootstrap_validate_version(version) 839 | 840 | # execute renv load hooks, if any 841 | hooks <- getHook("renv::autoload") 842 | for (hook in hooks) 843 | if (is.function(hook)) 844 | tryCatch(hook(), error = warning) 845 | 846 | # load the project 847 | renv::load(project) 848 | 849 | TRUE 850 | 851 | } 852 | 853 | renv_bootstrap_profile_load <- function(project) { 854 | 855 | # if RENV_PROFILE is already set, just use that 856 | profile <- Sys.getenv("RENV_PROFILE", unset = NA) 857 | if (!is.na(profile) && nzchar(profile)) 858 | return(profile) 859 | 860 | # check for a profile file (nothing to do if it doesn't exist) 861 | path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) 862 | if (!file.exists(path)) 863 | return(NULL) 864 | 865 | # read the profile, and set it if it exists 866 | contents <- readLines(path, warn = FALSE) 867 | if (length(contents) == 0L) 868 | return(NULL) 869 | 870 | # set RENV_PROFILE 871 | profile <- contents[[1L]] 872 | if (!profile %in% c("", "default")) 873 | Sys.setenv(RENV_PROFILE = profile) 874 | 875 | profile 876 | 877 | } 878 | 879 | renv_bootstrap_profile_prefix <- function() { 880 | profile <- renv_bootstrap_profile_get() 881 | if (!is.null(profile)) 882 | return(file.path("profiles", profile, "renv")) 883 | } 884 | 885 | renv_bootstrap_profile_get <- function() { 886 | profile <- Sys.getenv("RENV_PROFILE", unset = "") 887 | renv_bootstrap_profile_normalize(profile) 888 | } 889 | 890 | renv_bootstrap_profile_set <- function(profile) { 891 | profile <- renv_bootstrap_profile_normalize(profile) 892 | if (is.null(profile)) 893 | Sys.unsetenv("RENV_PROFILE") 894 | else 895 | Sys.setenv(RENV_PROFILE = profile) 896 | } 897 | 898 | renv_bootstrap_profile_normalize <- function(profile) { 899 | 900 | if (is.null(profile) || profile %in% c("", "default")) 901 | return(NULL) 902 | 903 | profile 904 | 905 | } 906 | 907 | renv_bootstrap_path_absolute <- function(path) { 908 | 909 | substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( 910 | substr(path, 1L, 1L) %in% c(letters, LETTERS) && 911 | substr(path, 2L, 3L) %in% c(":/", ":\\") 912 | ) 913 | 914 | } 915 | 916 | renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { 917 | renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") 918 | root <- if (renv_bootstrap_path_absolute(renv)) NULL else project 919 | prefix <- if (profile) renv_bootstrap_profile_prefix() 920 | components <- c(root, renv, prefix, ...) 921 | paste(components, collapse = "/") 922 | } 923 | 924 | renv_bootstrap_project_type <- function(path) { 925 | 926 | descpath <- file.path(path, "DESCRIPTION") 927 | if (!file.exists(descpath)) 928 | return("unknown") 929 | 930 | desc <- tryCatch( 931 | read.dcf(descpath, all = TRUE), 932 | error = identity 933 | ) 934 | 935 | if (inherits(desc, "error")) 936 | return("unknown") 937 | 938 | type <- desc$Type 939 | if (!is.null(type)) 940 | return(tolower(type)) 941 | 942 | package <- desc$Package 943 | if (!is.null(package)) 944 | return("package") 945 | 946 | "unknown" 947 | 948 | } 949 | 950 | renv_bootstrap_user_dir <- function() { 951 | dir <- renv_bootstrap_user_dir_impl() 952 | path.expand(chartr("\\", "/", dir)) 953 | } 954 | 955 | renv_bootstrap_user_dir_impl <- function() { 956 | 957 | # use local override if set 958 | override <- getOption("renv.userdir.override") 959 | if (!is.null(override)) 960 | return(override) 961 | 962 | # use R_user_dir if available 963 | tools <- asNamespace("tools") 964 | if (is.function(tools$R_user_dir)) 965 | return(tools$R_user_dir("renv", "cache")) 966 | 967 | # try using our own backfill for older versions of R 968 | envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") 969 | for (envvar in envvars) { 970 | root <- Sys.getenv(envvar, unset = NA) 971 | if (!is.na(root)) 972 | return(file.path(root, "R/renv")) 973 | } 974 | 975 | # use platform-specific default fallbacks 976 | if (Sys.info()[["sysname"]] == "Windows") 977 | file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") 978 | else if (Sys.info()[["sysname"]] == "Darwin") 979 | "~/Library/Caches/org.R-project.R/R/renv" 980 | else 981 | "~/.cache/R/renv" 982 | 983 | } 984 | 985 | renv_bootstrap_version_friendly <- function(version, sha = NULL) { 986 | sha <- sha %||% attr(version, "sha", exact = TRUE) 987 | parts <- c(version, sprintf("[sha: %s]", substring(sha, 1L, 7L))) 988 | paste(parts, collapse = " ") 989 | } 990 | 991 | renv_bootstrap_run <- function(version, libpath) { 992 | 993 | # perform bootstrap 994 | bootstrap(version, libpath) 995 | 996 | # exit early if we're just testing bootstrap 997 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 998 | return(TRUE) 999 | 1000 | # try again to load 1001 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 1002 | return(renv::load(project = getwd())) 1003 | } 1004 | 1005 | # failed to download or load renv; warn the user 1006 | msg <- c( 1007 | "Failed to find an renv installation: the project will not be loaded.", 1008 | "Use `renv::activate()` to re-initialize the project." 1009 | ) 1010 | 1011 | warning(paste(msg, collapse = "\n"), call. = FALSE) 1012 | 1013 | } 1014 | 1015 | 1016 | renv_bootstrap_in_rstudio <- function() { 1017 | commandArgs()[[1]] == "RStudio" 1018 | } 1019 | 1020 | renv_json_read <- function(file = NULL, text = NULL) { 1021 | 1022 | jlerr <- NULL 1023 | 1024 | # if jsonlite is loaded, use that instead 1025 | if ("jsonlite" %in% loadedNamespaces()) { 1026 | 1027 | json <- catch(renv_json_read_jsonlite(file, text)) 1028 | if (!inherits(json, "error")) 1029 | return(json) 1030 | 1031 | jlerr <- json 1032 | 1033 | } 1034 | 1035 | # otherwise, fall back to the default JSON reader 1036 | json <- catch(renv_json_read_default(file, text)) 1037 | if (!inherits(json, "error")) 1038 | return(json) 1039 | 1040 | # report an error 1041 | if (!is.null(jlerr)) 1042 | stop(jlerr) 1043 | else 1044 | stop(json) 1045 | 1046 | } 1047 | 1048 | renv_json_read_jsonlite <- function(file = NULL, text = NULL) { 1049 | text <- paste(text %||% read(file), collapse = "\n") 1050 | jsonlite::fromJSON(txt = text, simplifyVector = FALSE) 1051 | } 1052 | 1053 | renv_json_read_default <- function(file = NULL, text = NULL) { 1054 | 1055 | # find strings in the JSON 1056 | text <- paste(text %||% read(file), collapse = "\n") 1057 | pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 1058 | locs <- gregexpr(pattern, text, perl = TRUE)[[1]] 1059 | 1060 | # if any are found, replace them with placeholders 1061 | replaced <- text 1062 | strings <- character() 1063 | replacements <- character() 1064 | 1065 | if (!identical(c(locs), -1L)) { 1066 | 1067 | # get the string values 1068 | starts <- locs 1069 | ends <- locs + attr(locs, "match.length") - 1L 1070 | strings <- substring(text, starts, ends) 1071 | 1072 | # only keep those requiring escaping 1073 | strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) 1074 | 1075 | # compute replacements 1076 | replacements <- sprintf('"\032%i\032"', seq_along(strings)) 1077 | 1078 | # replace the strings 1079 | mapply(function(string, replacement) { 1080 | replaced <<- sub(string, replacement, replaced, fixed = TRUE) 1081 | }, strings, replacements) 1082 | 1083 | } 1084 | 1085 | # transform the JSON into something the R parser understands 1086 | transformed <- replaced 1087 | transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) 1088 | transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) 1089 | transformed <- gsub("[]}]", ")", transformed, perl = TRUE) 1090 | transformed <- gsub(":", "=", transformed, fixed = TRUE) 1091 | text <- paste(transformed, collapse = "\n") 1092 | 1093 | # parse it 1094 | json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] 1095 | 1096 | # construct map between source strings, replaced strings 1097 | map <- as.character(parse(text = strings)) 1098 | names(map) <- as.character(parse(text = replacements)) 1099 | 1100 | # convert to list 1101 | map <- as.list(map) 1102 | 1103 | # remap strings in object 1104 | remapped <- renv_json_remap(json, map) 1105 | 1106 | # evaluate 1107 | eval(remapped, envir = baseenv()) 1108 | 1109 | } 1110 | 1111 | renv_json_remap <- function(json, map) { 1112 | 1113 | # fix names 1114 | if (!is.null(names(json))) { 1115 | lhs <- match(names(json), names(map), nomatch = 0L) 1116 | rhs <- match(names(map), names(json), nomatch = 0L) 1117 | names(json)[rhs] <- map[lhs] 1118 | } 1119 | 1120 | # fix values 1121 | if (is.character(json)) 1122 | return(map[[json]] %||% json) 1123 | 1124 | # handle true, false, null 1125 | if (is.name(json)) { 1126 | text <- as.character(json) 1127 | if (text == "true") 1128 | return(TRUE) 1129 | else if (text == "false") 1130 | return(FALSE) 1131 | else if (text == "null") 1132 | return(NULL) 1133 | } 1134 | 1135 | # recurse 1136 | if (is.recursive(json)) { 1137 | for (i in seq_along(json)) { 1138 | json[i] <- list(renv_json_remap(json[[i]], map)) 1139 | } 1140 | } 1141 | 1142 | json 1143 | 1144 | } 1145 | 1146 | # load the renv profile, if any 1147 | renv_bootstrap_profile_load(project) 1148 | 1149 | # construct path to library root 1150 | root <- renv_bootstrap_library_root(project) 1151 | 1152 | # construct library prefix for platform 1153 | prefix <- renv_bootstrap_platform_prefix() 1154 | 1155 | # construct full libpath 1156 | libpath <- file.path(root, prefix) 1157 | 1158 | # attempt to load 1159 | if (renv_bootstrap_load(project, libpath, version)) 1160 | return(TRUE) 1161 | 1162 | if (renv_bootstrap_in_rstudio()) { 1163 | setHook("rstudio.sessionInit", function(...) { 1164 | renv_bootstrap_run(version, libpath) 1165 | 1166 | # Work around buglet in RStudio if hook uses readline 1167 | tryCatch( 1168 | { 1169 | tools <- as.environment("tools:rstudio") 1170 | tools$.rs.api.sendToConsole("", echo = FALSE, focus = FALSE) 1171 | }, 1172 | error = function(cnd) {} 1173 | ) 1174 | }) 1175 | } else { 1176 | renv_bootstrap_run(version, libpath) 1177 | } 1178 | 1179 | invisible() 1180 | 1181 | }) 1182 | -------------------------------------------------------------------------------- /examples/last_30_days/renv/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bioconductor.version": null, 3 | "external.libraries": [], 4 | "ignored.packages": [], 5 | "package.dependency.fields": [ 6 | "Imports", 7 | "Depends", 8 | "LinkingTo" 9 | ], 10 | "ppm.enabled": null, 11 | "ppm.ignored.urls": [], 12 | "r.version": null, 13 | "snapshot.type": "implicit", 14 | "use.cache": true, 15 | "vcs.ignore.cellar": true, 16 | "vcs.ignore.library": true, 17 | "vcs.ignore.local": true, 18 | "vcs.manage.ignores": true 19 | } 20 | -------------------------------------------------------------------------------- /examples/last_30_days/report-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/last_30_days/report-screenshot.png -------------------------------------------------------------------------------- /examples/last_30_days/rsc-usage.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Posit Connect Usage - Last `r as.numeric(Sys.getenv('DAYSBACK', 30))` Days" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | orientation: columns 6 | vertical_layout: fill 7 | theme: 8 | version: 4 9 | bg: "#fff" 10 | fg: "#447099" 11 | primary: "#447099" 12 | #navbar-bg: "#447099" 13 | base_font: 14 | google: Open Sans 15 | editor_options: 16 | chunk_output_type: console 17 | --- 18 | 19 | ```{r preflight_check, results='asis', include=TRUE} 20 | # --------------------------------------------------------------------------- 21 | # this section is used to stop the report from rendering 22 | # if important variables are missing (CONNECT_SERVER and CONNECT_API_KEY) 23 | # --------------------------------------------------------------------------- 24 | if ( 25 | nchar(Sys.getenv("CONNECT_SERVER")) == 0 || 26 | nchar(Sys.getenv("CONNECT_API_KEY")) == 0 27 | ) { 28 | print(htmltools::h4("ERROR: Variables Not Defined")) 29 | print(htmltools::div( 30 | "The CONNECT_SERVER and CONNECT_API_KEY", 31 | "environment variables are required in order for this report", 32 | "to pull usage data.", 33 | htmltools::br(), 34 | htmltools::br(), 35 | "Please define these variables", 36 | "and then re-run the report.", 37 | htmltools::br(), 38 | htmltools::br(), 39 | style = "max-width: 600px" 40 | )) 41 | knitr::knit_exit("Terminating the report early.") 42 | } 43 | ``` 44 | 45 | ```{r setup, include=FALSE} 46 | library(flexdashboard) 47 | library(dplyr) 48 | library(plotly) 49 | library(ggplot2) 50 | library(shiny) 51 | library(lubridate) 52 | library(blastula) 53 | library(prettyunits) 54 | library(connectapi) 55 | library(thematic) 56 | library(showtext) 57 | 58 | 59 | thematic::thematic_rmd(font = "Open Sans") 60 | 61 | days_back <- as.numeric(Sys.getenv("DAYSBACK", 30)) 62 | 63 | default_content_title <- "Unknown (Deleted Content?)" 64 | 65 | report_from <- lubridate::today() - lubridate::ddays(days_back) 66 | 67 | client <- connect() 68 | shiny <- get_usage_shiny( 69 | client, 70 | from = report_from, 71 | limit = Inf 72 | ) %>% 73 | mutate( 74 | started = lubridate::ymd_hms(started), 75 | ended = lubridate::ymd_hms(ended), 76 | session_duration = ended - started 77 | ) %>% 78 | filter(session_duration > lubridate::dseconds(5)) 79 | content <- get_usage_static( 80 | client, 81 | from = report_from, 82 | limit = Inf 83 | ) 84 | 85 | all_users <- get_users(client, page_size = 500) 86 | 87 | data_arr <- list(shiny = shiny, content = content) 88 | 89 | ``` 90 | 91 | 92 | This content summary may contain privileged information. The report is generated 93 | using the [Posit Connect Server API](http://docs.rstudio.com/connect/api) and 94 | the source code is [available online](https://github.com/sol-eng/connect-usage) 95 | if you'd like to customize your analysis. Data is limited to the last 96 | `r days_back` days. 97 | 98 | The report uses the environment variables `CONNECT_SERVER` and `CONNECT_API_KEY` 99 | to collect the data. To limit the results to a single publisher, use a publisher 100 | API key. 101 | 102 | Column 103 | ----------------------------------------------------------------------- 104 | 105 | ### Shiny Sessions by User (Top 5) 106 | 107 | ```{r shiny_by_user} 108 | data_arr$shiny %>% 109 | group_by(user_guid) %>% 110 | summarise(visits = n()) %>% 111 | left_join(all_users, by = c(user_guid = "guid")) %>% 112 | mutate(username = coalesce(username, "anonymous")) %>% 113 | select(username, visits) %>% 114 | arrange(desc(visits)) %>% 115 | head(5) %>% 116 | {ggplot(., aes(reorder(username, visits), visits)) + 117 | geom_bar(stat = "identity", fill="#447099") + 118 | coord_flip() + 119 | theme_minimal() + 120 | labs( 121 | y = "Number of Shiny Sessions", 122 | x = NULL 123 | ) + theme(legend.position="none")} %>% 124 | ggplotly(tooltip = c("y")) %>% 125 | config(displayModeBar = F) 126 | ``` 127 | 128 | ### Static Content Hits by User (Top 5) 129 | 130 | ```{r static_by_user} 131 | data_arr$content %>% 132 | group_by(user_guid) %>% 133 | summarise(visits = n()) %>% 134 | left_join(all_users, by = c(user_guid = "guid")) %>% 135 | mutate(username = coalesce(username, "anonymous")) %>% 136 | select(username, visits) %>% 137 | arrange(desc(visits)) %>% 138 | head(5) %>% 139 | {ggplot(., aes(reorder(username, visits), visits)) + 140 | geom_bar(stat = "identity", fill="#447099") + 141 | coord_flip() + 142 | theme_minimal() + 143 | labs( 144 | y = "Number of Content Visits", 145 | x = NULL 146 | ) + theme(legend.position="none")} %>% 147 | ggplotly(tooltip = c("y")) %>% 148 | config(displayModeBar = F) 149 | ``` 150 | 151 | 152 | 153 | Column 154 | ----------------------------------------------------------------------- 155 | 156 | ### Shiny Sessions Over Time 157 | 158 | ```{r shiny_over_time} 159 | data_arr$shiny %>% 160 | mutate(day = round_date(started, "day")) %>% 161 | filter(day > today() - ddays(days_back)) %>% 162 | group_by(day) %>% 163 | summarise(visits = n()) %>% 164 | arrange(desc(visits)) %>% 165 | {ggplot(., aes(day, visits)) + 166 | geom_point() + 167 | geom_smooth(se = FALSE) + 168 | theme_minimal() + 169 | labs( 170 | y = "# of Shiny Sessions", 171 | x = NULL 172 | ) + theme(legend.position="none")} %>% 173 | ggplotly(tooltip = c("y")) %>% 174 | config(displayModeBar = F) 175 | ``` 176 | 177 | 178 | ### Static Content Visits Over Time 179 | 180 | ```{r static_over_time} 181 | data_arr$content %>% 182 | mutate(time = ymd_hms(time), 183 | day = round_date(time, "day")) %>% 184 | filter(day > today() - ddays(days_back)) %>% 185 | group_by(day) %>% 186 | summarise(visits = n()) %>% 187 | arrange(desc(visits)) %>% 188 | {ggplot(., aes(day, visits)) + 189 | geom_point() + 190 | geom_smooth(se = FALSE) + 191 | theme_minimal() + 192 | labs( 193 | y = "Content Hits", 194 | x = NULL 195 | ) + theme(legend.position="none")} %>% 196 | ggplotly(tooltip = c("y")) %>% 197 | config(displayModeBar = F) 198 | ``` 199 | 200 | Column 201 | ----------------------------------------------------------------------- 202 | 203 | ### Top Applications 204 | 205 | ```{r top_shiny} 206 | data_arr$shiny %>% 207 | group_by(content_guid) %>% 208 | summarize(visits = n()) %>% 209 | arrange(desc(visits)) %>% 210 | head() %>% 211 | mutate(name = purrr::map_chr(content_guid, ~ content_title(client, .x, default_content_title))) %>% 212 | {ggplot(., aes(reorder(stringr::str_wrap(name, 30), visits), visits)) + 213 | geom_bar(stat = "identity", fill="#447099") + 214 | coord_flip() + 215 | theme_minimal() + 216 | labs( 217 | y = "# of Shiny Sessions", 218 | x = NULL 219 | ) + theme(legend.position="none")} %>% 220 | ggplotly(tooltip = c("y")) %>% 221 | layout(margin = list(l = 0)) %>% 222 | config(displayModeBar = F) 223 | ``` 224 | 225 | ### Top Static Content 226 | 227 | ```{r top_static} 228 | data_arr$content %>% 229 | group_by(content_guid) %>% 230 | summarize(visits = n()) %>% 231 | arrange(desc(visits)) %>% 232 | head() %>% 233 | mutate(name = purrr::map_chr(content_guid, ~ content_title(client, .x, default_content_title))) %>% 234 | {ggplot(., aes(reorder(stringr::str_wrap(name, 30), visits), visits)) + 235 | geom_bar(stat = "identity", fill="#447099") + 236 | coord_flip() + 237 | theme_minimal() + 238 | labs( 239 | y = "Content Hits", 240 | x = NULL 241 | ) + theme(legend.position="none")} %>% 242 | ggplotly(tooltip = c("y")) %>% 243 | layout(margin = list(l = 0)) %>% 244 | config(displayModeBar = F) 245 | ``` 246 | 247 | 248 | ```{r render_custom_email} 249 | render_connect_email(input = "usage-email.Rmd") %>% 250 | attach_connect_email( 251 | subject = sprintf(" Content Usage Report For %s", month(today(), label = TRUE, abbr = FALSE)) 252 | ) 253 | ``` 254 | -------------------------------------------------------------------------------- /examples/last_30_days/usage-email.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Content Usage Report" 3 | output: blastula::blastula_email 4 | editor_options: 5 | chunk_output_type: console 6 | --- 7 | ```{r setup_email, include=FALSE} 8 | knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE) 9 | default_content_title <- "Unknown (Deleted Content?)" 10 | 11 | 12 | ``` 13 | 14 | ```{r custom_email} 15 | content_summary <- data_arr$content %>% 16 | group_by(content_guid) %>% 17 | summarize(visits = n()) %>% 18 | arrange(desc(visits)) %>% 19 | head() %>% 20 | mutate(name = purrr::map_chr(content_guid, ~ content_title(client, .x, default_content_title))) 21 | 22 | app_summary <- data_arr$shiny %>% 23 | group_by(content_guid) %>% 24 | summarize(visits = n()) %>% 25 | arrange(desc(visits)) %>% 26 | head() %>% 27 | mutate(name = purrr::map_chr(content_guid, ~ content_title(client, .x, default_content_title))) 28 | 29 | leaderboard <- dplyr::bind_rows(app_summary, content_summary) %>% 30 | arrange(desc(visits)) %>% 31 | ggplot(., aes(reorder(name, visits), visits)) + 32 | geom_bar(stat = "identity", fill="#447099") + 33 | coord_flip() + 34 | theme_minimal() + 35 | labs( 36 | y = "Visits", 37 | x = NULL 38 | ) 39 | ``` 40 | 41 | Hi Data Science Team! 42 | 43 | Here is the top viewed content for the last `r days_back` days: 44 | 45 | ```{r add_plot_prep, message=FALSE} 46 | 47 | blastula::add_ggplot(leaderboard) 48 | ``` 49 | 50 | Best, 51 | 52 | Posit Connect 53 | -------------------------------------------------------------------------------- /examples/realtime/README.md: -------------------------------------------------------------------------------- 1 | # Real-Time Usage Data 2 | 3 | This report queries the RStudio Connect Server API regularly (using a 4 | configurable interval) to fetch the latest usage data. This shows an interesting 5 | "real-time" view of app and content usage on the server. 6 | 7 | To configure, use these environment variables: 8 | - `CONNECT_SERVER` / `CONNECT_API_KEY` - to retrieve the data. This is set by default on RStudio Connect 9 | - `HOURS_BACK` - At app startup, how far back will data start being aggregated? 10 | This is the "baseline" for how much usage is currently present. Default: 4 (4 hours) 11 | - `FETCH_DELAY_MS` - how long (in milliseconds) should the app wait before fetching new data? Default: 60000 (1 minute) 12 | 13 |
14 | -------------------------------------------------------------------------------- /examples/realtime/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(apexcharter) 3 | library(connectapi) 4 | library(dplyr) 5 | library(tidyr) 6 | 7 | client <- connect() 8 | 9 | hours_back <- as.numeric(Sys.getenv("HOURS_BACK", 4)) 10 | fetch_delay <- as.numeric(Sys.getenv("FETCH_DELAY_MS", 60000)) 11 | report_from <- lubridate::now() - lubridate::dhours(hours_back) 12 | 13 | get_usage_cumulative <- function(client) { 14 | raw_data <- connectapi::get_usage_shiny(client, from = report_from, limit = Inf) 15 | prep <- raw_data %>% 16 | filter(started >= report_from) %>% 17 | arrange(started, ended) %>% 18 | mutate(row_id = row_number()) %>% 19 | filter(!is.na(started)) # should never happen 20 | 21 | tall_dat <- prep %>% 22 | gather(key = "type", value = "ts", started, ended) %>% 23 | mutate(value = case_when(type == "started" ~ 1, type == "ended" ~ -1)) %>% 24 | filter(!is.na(ts)) %>% 25 | arrange(ts) %>% 26 | mutate(n = cumsum(value)) %>% 27 | mutate( 28 | ts_disp = format(ts, format="%Y-%m-%dT%H:%M:%S") 29 | ) 30 | 31 | return(tall_dat) 32 | } 33 | 34 | get_usage <- function(client) { 35 | raw_data <- connectapi::get_usage_shiny(client, from = report_from, limit = Inf) 36 | message(glue::glue("Got {nrow(raw_data)} records")) 37 | prep <- raw_data %>% 38 | filter(started >= report_from) %>% 39 | mutate(ts = lubridate::floor_date(started, "minute")) %>% 40 | group_by(ts) %>% 41 | tally() %>% 42 | mutate( 43 | ts_disp = format(ts, format="%Y-%m-%dT%H:%M:%S") 44 | ) %>% 45 | arrange(ts) 46 | 47 | } 48 | 49 | ui <- fluidPage( 50 | verticalLayout( 51 | titlePanel("Real-Time Connect Usage"), 52 | apexchartOutput("shiny_realtime") 53 | ) 54 | ) 55 | 56 | server <- function(input, output) { 57 | 58 | data_today <- reactiveVal(get_usage_cumulative(client)) 59 | 60 | observe({ 61 | data_today(get_usage_cumulative(client)) 62 | invalidateLater(fetch_delay) 63 | }) 64 | 65 | output$shiny_realtime <- renderApexchart( 66 | apexchart(auto_update = TRUE) %>% 67 | ax_chart(type = "line") %>% 68 | ax_title("Shiny Usage By Minute") %>% 69 | ax_plotOptions() %>% 70 | ax_series(list( 71 | name = "Count", 72 | data = purrr::map2(data_today()$ts_disp, data_today()$n, ~ list(.x, .y)) 73 | )) %>% 74 | ax_xaxis( 75 | type = "datetime" 76 | ) 77 | ) 78 | } 79 | 80 | shinyApp(ui = ui, server = server) 81 | -------------------------------------------------------------------------------- /examples/realtime/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sol-eng/connect-usage/44561e18bfee69daf53d529a26e40a5a47c63740/examples/realtime/screenshot.png -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.1.2", 4 | "Repositories": [ 5 | { 6 | "Name": "RSPM", 7 | "URL": "https://packagemanager.rstudio.com/cran/latest" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "BH": { 13 | "Package": "BH", 14 | "Version": "1.72.0-3", 15 | "Source": "Repository", 16 | "Repository": "RSPM", 17 | "Hash": "8f9ce74c6417d61f0782cbae5fd2b7b0", 18 | "Requirements": [] 19 | }, 20 | "DT": { 21 | "Package": "DT", 22 | "Version": "0.20", 23 | "Source": "Repository", 24 | "Repository": "RSPM", 25 | "Hash": "0163cd2ce6c7995005a0fc363d570963", 26 | "Requirements": [ 27 | "crosstalk", 28 | "htmltools", 29 | "htmlwidgets", 30 | "jquerylib", 31 | "jsonlite", 32 | "magrittr", 33 | "promises" 34 | ] 35 | }, 36 | "MASS": { 37 | "Package": "MASS", 38 | "Version": "7.3-51.5", 39 | "Source": "Repository", 40 | "Repository": "RSPM", 41 | "Hash": "9efe80472b21189ebab1b74169808c26", 42 | "Requirements": [] 43 | }, 44 | "Matrix": { 45 | "Package": "Matrix", 46 | "Version": "1.2-18", 47 | "Source": "Repository", 48 | "Repository": "RSPM", 49 | "Hash": "08588806cba69f04797dab50627428ed", 50 | "Requirements": [ 51 | "lattice" 52 | ] 53 | }, 54 | "R6": { 55 | "Package": "R6", 56 | "Version": "2.5.1", 57 | "Source": "Repository", 58 | "Repository": "RSPM", 59 | "Hash": "470851b6d5d0ac559e9d01bb352b4021", 60 | "Requirements": [] 61 | }, 62 | "RColorBrewer": { 63 | "Package": "RColorBrewer", 64 | "Version": "1.1-2", 65 | "Source": "Repository", 66 | "Repository": "RSPM", 67 | "Hash": "e031418365a7f7a766181ab5a41a5716", 68 | "Requirements": [] 69 | }, 70 | "Rcpp": { 71 | "Package": "Rcpp", 72 | "Version": "1.0.8", 73 | "Source": "Repository", 74 | "Repository": "RSPM", 75 | "Hash": "22b546dd7e337f6c0c58a39983a496bc", 76 | "Requirements": [] 77 | }, 78 | "apexcharter": { 79 | "Package": "apexcharter", 80 | "Version": "0.3.1", 81 | "Source": "Repository", 82 | "Repository": "RSPM", 83 | "Hash": "d15a40c224bf3c1d8a32a0c12534d2c4", 84 | "Requirements": [ 85 | "ggplot2", 86 | "htmltools", 87 | "htmlwidgets", 88 | "jsonlite", 89 | "magrittr", 90 | "rlang", 91 | "shiny" 92 | ] 93 | }, 94 | "askpass": { 95 | "Package": "askpass", 96 | "Version": "1.1", 97 | "Source": "Repository", 98 | "Repository": "RSPM", 99 | "Hash": "e8a22846fff485f0be3770c2da758713", 100 | "Requirements": [ 101 | "sys" 102 | ] 103 | }, 104 | "backports": { 105 | "Package": "backports", 106 | "Version": "1.1.5", 107 | "Source": "Repository", 108 | "Repository": "RSPM", 109 | "Hash": "e9f705633dc932bfd5b02b17a5053a06", 110 | "Requirements": [] 111 | }, 112 | "base64enc": { 113 | "Package": "base64enc", 114 | "Version": "0.1-3", 115 | "Source": "Repository", 116 | "Repository": "RSPM", 117 | "Hash": "543776ae6848fde2f48ff3816d0628bc", 118 | "Requirements": [] 119 | }, 120 | "bit": { 121 | "Package": "bit", 122 | "Version": "4.0.4", 123 | "Source": "Repository", 124 | "Repository": "RSPM", 125 | "Hash": "f36715f14d94678eea9933af927bc15d", 126 | "Requirements": [] 127 | }, 128 | "bit64": { 129 | "Package": "bit64", 130 | "Version": "4.0.5", 131 | "Source": "Repository", 132 | "Repository": "RSPM", 133 | "Hash": "9fe98599ca456d6552421db0d6772d8f", 134 | "Requirements": [ 135 | "bit" 136 | ] 137 | }, 138 | "blastula": { 139 | "Package": "blastula", 140 | "Version": "0.3.2", 141 | "Source": "Repository", 142 | "Repository": "RSPM", 143 | "Hash": "1f45952e8aadddb043afb988b988dd52", 144 | "Requirements": [ 145 | "base64enc", 146 | "commonmark", 147 | "curl", 148 | "digest", 149 | "dplyr", 150 | "fs", 151 | "getPass", 152 | "here", 153 | "htmltools", 154 | "httr", 155 | "jsonlite", 156 | "magrittr", 157 | "mime", 158 | "rlang", 159 | "rmarkdown", 160 | "stringr", 161 | "uuid" 162 | ] 163 | }, 164 | "broom": { 165 | "Package": "broom", 166 | "Version": "0.7.9", 167 | "Source": "Repository", 168 | "Repository": "RSPM", 169 | "Hash": "9b1e2c1f75b349e3ffa7e9c69eec0c52", 170 | "Requirements": [ 171 | "backports", 172 | "dplyr", 173 | "ellipsis", 174 | "generics", 175 | "glue", 176 | "purrr", 177 | "rlang", 178 | "stringr", 179 | "tibble", 180 | "tidyr" 181 | ] 182 | }, 183 | "bslib": { 184 | "Package": "bslib", 185 | "Version": "0.3.1", 186 | "Source": "Repository", 187 | "Repository": "RSPM", 188 | "Hash": "56ae7e1987b340186a8a5a157c2ec358", 189 | "Requirements": [ 190 | "htmltools", 191 | "jquerylib", 192 | "jsonlite", 193 | "rlang", 194 | "sass" 195 | ] 196 | }, 197 | "cachem": { 198 | "Package": "cachem", 199 | "Version": "1.0.6", 200 | "Source": "Repository", 201 | "Repository": "RSPM", 202 | "Hash": "648c5b3d71e6a37e3043617489a0a0e9", 203 | "Requirements": [ 204 | "fastmap", 205 | "rlang" 206 | ] 207 | }, 208 | "cli": { 209 | "Package": "cli", 210 | "Version": "3.2.0", 211 | "Source": "Repository", 212 | "Repository": "RSPM", 213 | "Hash": "1bdb126893e9ce6aae50ad1d6fc32faf", 214 | "Requirements": [ 215 | "glue" 216 | ] 217 | }, 218 | "colorspace": { 219 | "Package": "colorspace", 220 | "Version": "1.4-1", 221 | "Source": "Repository", 222 | "Repository": "RSPM", 223 | "Hash": "6b436e95723d1f0e861224dd9b094dfb", 224 | "Requirements": [] 225 | }, 226 | "commonmark": { 227 | "Package": "commonmark", 228 | "Version": "1.7", 229 | "Source": "Repository", 230 | "Repository": "RSPM", 231 | "Hash": "0f22be39ec1d141fd03683c06f3a6e67", 232 | "Requirements": [] 233 | }, 234 | "config": { 235 | "Package": "config", 236 | "Version": "0.3.1", 237 | "Source": "Repository", 238 | "Repository": "RSPM", 239 | "Hash": "31d77b09f63550cee9ecb5a08bf76e8f", 240 | "Requirements": [ 241 | "yaml" 242 | ] 243 | }, 244 | "connectViz": { 245 | "Package": "connectViz", 246 | "Version": "0.0.0.9002", 247 | "Source": "GitHub", 248 | "RemoteType": "github", 249 | "RemoteHost": "api.github.com", 250 | "RemoteUsername": "RinteRface", 251 | "RemoteRepo": "connectViz", 252 | "RemoteRef": "main", 253 | "RemoteSha": "553acce10009ce86987778e8ac29132e609c6195", 254 | "Remotes": "rstudio/connectapi", 255 | "Hash": "b733be3ee230028880ed6bf4f2384b42", 256 | "Requirements": [ 257 | "apexcharter", 258 | "connectapi", 259 | "data.table", 260 | "dplyr", 261 | "echarts4r", 262 | "lubridate", 263 | "magrittr", 264 | "purrr", 265 | "rlang", 266 | "scales", 267 | "shiny", 268 | "tibble", 269 | "tidyr", 270 | "toastui", 271 | "visNetwork" 272 | ] 273 | }, 274 | "connectapi": { 275 | "Package": "connectapi", 276 | "Version": "0.1.0.9033", 277 | "Source": "GitHub", 278 | "RemoteType": "github", 279 | "RemoteHost": "api.github.com", 280 | "RemoteUsername": "rstudio", 281 | "RemoteRepo": "connectapi", 282 | "RemoteRef": "main", 283 | "RemoteSha": "9ca84e01f04f42962dcf65b23e211eeecfab40eb", 284 | "Hash": "411ce1d4e954707d436e09d255a185f5", 285 | "Requirements": [ 286 | "R6", 287 | "bit64", 288 | "config", 289 | "dplyr", 290 | "fs", 291 | "glue", 292 | "httr", 293 | "jsonlite", 294 | "lifecycle", 295 | "magrittr", 296 | "progress", 297 | "purrr", 298 | "rlang", 299 | "tibble", 300 | "uuid", 301 | "vctrs", 302 | "yaml" 303 | ] 304 | }, 305 | "corrplot": { 306 | "Package": "corrplot", 307 | "Version": "0.90", 308 | "Source": "Repository", 309 | "Repository": "RSPM", 310 | "Hash": "0514bd6a3e99a2a17145c97c1cfebb09", 311 | "Requirements": [] 312 | }, 313 | "countrycode": { 314 | "Package": "countrycode", 315 | "Version": "1.3.0", 316 | "Source": "Repository", 317 | "Repository": "RSPM", 318 | "Hash": "0b2ceb4d9dc48332decc876ecd01ed00", 319 | "Requirements": [] 320 | }, 321 | "crayon": { 322 | "Package": "crayon", 323 | "Version": "1.5.0", 324 | "Source": "Repository", 325 | "Repository": "RSPM", 326 | "Hash": "741c2e098e98afe3dc26a7b0e5489f4e", 327 | "Requirements": [] 328 | }, 329 | "crosstalk": { 330 | "Package": "crosstalk", 331 | "Version": "1.2.0", 332 | "Source": "Repository", 333 | "Repository": "RSPM", 334 | "Hash": "6aa54f69598c32177e920eb3402e8293", 335 | "Requirements": [ 336 | "R6", 337 | "htmltools", 338 | "jsonlite", 339 | "lazyeval" 340 | ] 341 | }, 342 | "curl": { 343 | "Package": "curl", 344 | "Version": "4.3.2", 345 | "Source": "Repository", 346 | "Repository": "RSPM", 347 | "Hash": "022c42d49c28e95d69ca60446dbabf88", 348 | "Requirements": [] 349 | }, 350 | "data.table": { 351 | "Package": "data.table", 352 | "Version": "1.12.8", 353 | "Source": "Repository", 354 | "Repository": "RSPM", 355 | "Hash": "cd711af60c47207a776213a368626369", 356 | "Requirements": [] 357 | }, 358 | "digest": { 359 | "Package": "digest", 360 | "Version": "0.6.29", 361 | "Source": "Repository", 362 | "Repository": "RSPM", 363 | "Hash": "cf6b206a045a684728c3267ef7596190", 364 | "Requirements": [] 365 | }, 366 | "dplyr": { 367 | "Package": "dplyr", 368 | "Version": "1.0.8", 369 | "Source": "Repository", 370 | "Repository": "RSPM", 371 | "Hash": "ef47665e64228a17609d6df877bf86f2", 372 | "Requirements": [ 373 | "R6", 374 | "generics", 375 | "glue", 376 | "lifecycle", 377 | "magrittr", 378 | "pillar", 379 | "rlang", 380 | "tibble", 381 | "tidyselect", 382 | "vctrs" 383 | ] 384 | }, 385 | "echarts4r": { 386 | "Package": "echarts4r", 387 | "Version": "0.4.2", 388 | "Source": "Repository", 389 | "Repository": "RSPM", 390 | "Hash": "8c4e1bebb2c0bb42b1995167b331a484", 391 | "Requirements": [ 392 | "broom", 393 | "corrplot", 394 | "countrycode", 395 | "dplyr", 396 | "htmltools", 397 | "htmlwidgets", 398 | "jsonlite", 399 | "purrr", 400 | "rstudioapi", 401 | "scales", 402 | "shiny" 403 | ] 404 | }, 405 | "ellipsis": { 406 | "Package": "ellipsis", 407 | "Version": "0.3.2", 408 | "Source": "Repository", 409 | "Repository": "RSPM", 410 | "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077", 411 | "Requirements": [ 412 | "rlang" 413 | ] 414 | }, 415 | "evaluate": { 416 | "Package": "evaluate", 417 | "Version": "0.14", 418 | "Source": "Repository", 419 | "Repository": "RSPM", 420 | "Hash": "ec8ca05cffcc70569eaaad8469d2a3a7", 421 | "Requirements": [] 422 | }, 423 | "fansi": { 424 | "Package": "fansi", 425 | "Version": "1.0.2", 426 | "Source": "Repository", 427 | "Repository": "RSPM", 428 | "Hash": "f28149c2d7a1342a834b314e95e67260", 429 | "Requirements": [] 430 | }, 431 | "farver": { 432 | "Package": "farver", 433 | "Version": "2.0.3", 434 | "Source": "Repository", 435 | "Repository": "RSPM", 436 | "Hash": "dad6793a5a1f73c8e91f1a1e3e834b05", 437 | "Requirements": [] 438 | }, 439 | "fastmap": { 440 | "Package": "fastmap", 441 | "Version": "1.1.0", 442 | "Source": "Repository", 443 | "Repository": "RSPM", 444 | "Hash": "77bd60a6157420d4ffa93b27cf6a58b8", 445 | "Requirements": [] 446 | }, 447 | "flexdashboard": { 448 | "Package": "flexdashboard", 449 | "Version": "0.5.1.1", 450 | "Source": "Repository", 451 | "Repository": "RSPM", 452 | "Hash": "8f01ed8fed9f0de7528d9d45202913f1", 453 | "Requirements": [ 454 | "htmltools", 455 | "htmlwidgets", 456 | "jsonlite", 457 | "knitr", 458 | "rmarkdown", 459 | "shiny" 460 | ] 461 | }, 462 | "fontawesome": { 463 | "Package": "fontawesome", 464 | "Version": "0.2.2", 465 | "Source": "Repository", 466 | "Repository": "RSPM", 467 | "Hash": "55624ed409e46c5f358b2c060be87f67", 468 | "Requirements": [ 469 | "htmltools", 470 | "rlang" 471 | ] 472 | }, 473 | "fs": { 474 | "Package": "fs", 475 | "Version": "1.5.2", 476 | "Source": "Repository", 477 | "Repository": "RSPM", 478 | "Hash": "7c89603d81793f0d5486d91ab1fc6f1d", 479 | "Requirements": [] 480 | }, 481 | "generics": { 482 | "Package": "generics", 483 | "Version": "0.1.2", 484 | "Source": "Repository", 485 | "Repository": "RSPM", 486 | "Hash": "177475892cf4a55865868527654a7741", 487 | "Requirements": [] 488 | }, 489 | "getPass": { 490 | "Package": "getPass", 491 | "Version": "0.2-2", 492 | "Source": "Repository", 493 | "Repository": "RSPM", 494 | "Hash": "07a91f99e56951818ab911366db77700", 495 | "Requirements": [ 496 | "rstudioapi" 497 | ] 498 | }, 499 | "ggplot2": { 500 | "Package": "ggplot2", 501 | "Version": "3.3.5", 502 | "Source": "Repository", 503 | "Repository": "RSPM", 504 | "Hash": "d7566c471c7b17e095dd023b9ef155ad", 505 | "Requirements": [ 506 | "MASS", 507 | "digest", 508 | "glue", 509 | "gtable", 510 | "isoband", 511 | "mgcv", 512 | "rlang", 513 | "scales", 514 | "tibble", 515 | "withr" 516 | ] 517 | }, 518 | "glue": { 519 | "Package": "glue", 520 | "Version": "1.6.2", 521 | "Source": "Repository", 522 | "Repository": "RSPM", 523 | "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e", 524 | "Requirements": [] 525 | }, 526 | "gtable": { 527 | "Package": "gtable", 528 | "Version": "0.3.0", 529 | "Source": "Repository", 530 | "Repository": "RSPM", 531 | "Hash": "ac5c6baf7822ce8732b343f14c072c4d", 532 | "Requirements": [] 533 | }, 534 | "here": { 535 | "Package": "here", 536 | "Version": "0.1", 537 | "Source": "Repository", 538 | "Repository": "RSPM", 539 | "Hash": "2c0406b8e0a4c3516ab37be62da74e3c", 540 | "Requirements": [ 541 | "rprojroot" 542 | ] 543 | }, 544 | "hexbin": { 545 | "Package": "hexbin", 546 | "Version": "1.28.1", 547 | "Source": "Repository", 548 | "Repository": "RSPM", 549 | "Hash": "3d59212f2814d65dff517e6899813c58", 550 | "Requirements": [ 551 | "lattice" 552 | ] 553 | }, 554 | "highr": { 555 | "Package": "highr", 556 | "Version": "0.8", 557 | "Source": "Repository", 558 | "Repository": "RSPM", 559 | "Hash": "4dc5bb88961e347a0f4d8aad597cbfac", 560 | "Requirements": [] 561 | }, 562 | "hms": { 563 | "Package": "hms", 564 | "Version": "1.1.1", 565 | "Source": "Repository", 566 | "Repository": "RSPM", 567 | "Hash": "5b8a2dd0fdbe2ab4f6081e6c7be6dfca", 568 | "Requirements": [ 569 | "ellipsis", 570 | "lifecycle", 571 | "pkgconfig", 572 | "rlang", 573 | "vctrs" 574 | ] 575 | }, 576 | "htmltools": { 577 | "Package": "htmltools", 578 | "Version": "0.5.2", 579 | "Source": "Repository", 580 | "Repository": "RSPM", 581 | "Hash": "526c484233f42522278ab06fb185cb26", 582 | "Requirements": [ 583 | "base64enc", 584 | "digest", 585 | "fastmap", 586 | "rlang" 587 | ] 588 | }, 589 | "htmlwidgets": { 590 | "Package": "htmlwidgets", 591 | "Version": "1.5.4", 592 | "Source": "Repository", 593 | "Repository": "RSPM", 594 | "Hash": "76147821cd3fcd8c4b04e1ef0498e7fb", 595 | "Requirements": [ 596 | "htmltools", 597 | "jsonlite", 598 | "yaml" 599 | ] 600 | }, 601 | "httpuv": { 602 | "Package": "httpuv", 603 | "Version": "1.5.2", 604 | "Source": "Repository", 605 | "Repository": "RSPM", 606 | "Hash": "f793dad2c9ae14fbb1d22f16f23f8326", 607 | "Requirements": [ 608 | "BH", 609 | "R6", 610 | "Rcpp", 611 | "later", 612 | "promises" 613 | ] 614 | }, 615 | "httr": { 616 | "Package": "httr", 617 | "Version": "1.4.2", 618 | "Source": "Repository", 619 | "Repository": "RSPM", 620 | "Hash": "a525aba14184fec243f9eaec62fbed43", 621 | "Requirements": [ 622 | "R6", 623 | "curl", 624 | "jsonlite", 625 | "mime", 626 | "openssl" 627 | ] 628 | }, 629 | "isoband": { 630 | "Package": "isoband", 631 | "Version": "0.2.5", 632 | "Source": "Repository", 633 | "Repository": "RSPM", 634 | "Hash": "7ab57a6de7f48a8dc84910d1eca42883", 635 | "Requirements": [] 636 | }, 637 | "jquerylib": { 638 | "Package": "jquerylib", 639 | "Version": "0.1.4", 640 | "Source": "Repository", 641 | "Repository": "RSPM", 642 | "Hash": "5aab57a3bd297eee1c1d862735972182", 643 | "Requirements": [ 644 | "htmltools" 645 | ] 646 | }, 647 | "jsonlite": { 648 | "Package": "jsonlite", 649 | "Version": "1.8.0", 650 | "Source": "Repository", 651 | "Repository": "RSPM", 652 | "Hash": "d07e729b27b372429d42d24d503613a0", 653 | "Requirements": [] 654 | }, 655 | "knitr": { 656 | "Package": "knitr", 657 | "Version": "1.27", 658 | "Source": "Repository", 659 | "Repository": "RSPM", 660 | "Hash": "0eaaccb0946bf5ee33e2762882a4ae07", 661 | "Requirements": [ 662 | "evaluate", 663 | "highr", 664 | "markdown", 665 | "stringr", 666 | "xfun", 667 | "yaml" 668 | ] 669 | }, 670 | "labeling": { 671 | "Package": "labeling", 672 | "Version": "0.3", 673 | "Source": "Repository", 674 | "Repository": "RSPM", 675 | "Hash": "73832978c1de350df58108c745ed0e3e", 676 | "Requirements": [] 677 | }, 678 | "later": { 679 | "Package": "later", 680 | "Version": "1.3.0", 681 | "Source": "Repository", 682 | "Repository": "RSPM", 683 | "Hash": "7e7b457d7766bc47f2a5f21cc2984f8e", 684 | "Requirements": [ 685 | "Rcpp", 686 | "rlang" 687 | ] 688 | }, 689 | "lattice": { 690 | "Package": "lattice", 691 | "Version": "0.20-38", 692 | "Source": "Repository", 693 | "Repository": "RSPM", 694 | "Hash": "848f8c593fd1050371042d18d152e3d7", 695 | "Requirements": [] 696 | }, 697 | "lazyeval": { 698 | "Package": "lazyeval", 699 | "Version": "0.2.2", 700 | "Source": "Repository", 701 | "Repository": "RSPM", 702 | "Hash": "d908914ae53b04d4c0c0fd72ecc35370", 703 | "Requirements": [] 704 | }, 705 | "lifecycle": { 706 | "Package": "lifecycle", 707 | "Version": "1.0.1", 708 | "Source": "Repository", 709 | "Repository": "RSPM", 710 | "Hash": "a6b6d352e3ed897373ab19d8395c98d0", 711 | "Requirements": [ 712 | "glue", 713 | "rlang" 714 | ] 715 | }, 716 | "lubridate": { 717 | "Package": "lubridate", 718 | "Version": "1.7.4", 719 | "Source": "Repository", 720 | "Repository": "RSPM", 721 | "Hash": "796afeea047cda6bdb308d374a33eeb6", 722 | "Requirements": [ 723 | "Rcpp", 724 | "stringr" 725 | ] 726 | }, 727 | "magrittr": { 728 | "Package": "magrittr", 729 | "Version": "2.0.2", 730 | "Source": "Repository", 731 | "Repository": "RSPM", 732 | "Hash": "cdc87ecd81934679d1557633d8e1fe51", 733 | "Requirements": [] 734 | }, 735 | "markdown": { 736 | "Package": "markdown", 737 | "Version": "1.1", 738 | "Source": "Repository", 739 | "Repository": "RSPM", 740 | "Hash": "61e4a10781dd00d7d81dd06ca9b94e95", 741 | "Requirements": [ 742 | "mime", 743 | "xfun" 744 | ] 745 | }, 746 | "memoise": { 747 | "Package": "memoise", 748 | "Version": "2.0.1", 749 | "Source": "Repository", 750 | "Repository": "RSPM", 751 | "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c", 752 | "Requirements": [ 753 | "cachem", 754 | "rlang" 755 | ] 756 | }, 757 | "mgcv": { 758 | "Package": "mgcv", 759 | "Version": "1.8-31", 760 | "Source": "Repository", 761 | "Repository": "RSPM", 762 | "Hash": "4bb7e0c4f3557583e1e8d3c9ffb8ba5c", 763 | "Requirements": [ 764 | "Matrix", 765 | "nlme" 766 | ] 767 | }, 768 | "mime": { 769 | "Package": "mime", 770 | "Version": "0.12", 771 | "Source": "Repository", 772 | "Repository": "RSPM", 773 | "Hash": "18e9c28c1d3ca1560ce30658b22ce104", 774 | "Requirements": [] 775 | }, 776 | "munsell": { 777 | "Package": "munsell", 778 | "Version": "0.5.0", 779 | "Source": "Repository", 780 | "Repository": "RSPM", 781 | "Hash": "6dfe8bf774944bd5595785e3229d8771", 782 | "Requirements": [ 783 | "colorspace" 784 | ] 785 | }, 786 | "nlme": { 787 | "Package": "nlme", 788 | "Version": "3.1-143", 789 | "Source": "Repository", 790 | "Repository": "RSPM", 791 | "Hash": "7518d3b54a24f9291735a25467997858", 792 | "Requirements": [ 793 | "lattice" 794 | ] 795 | }, 796 | "openssl": { 797 | "Package": "openssl", 798 | "Version": "1.4.6", 799 | "Source": "Repository", 800 | "Repository": "RSPM", 801 | "Hash": "69fdf291af288f32fd4cd93315084ea8", 802 | "Requirements": [ 803 | "askpass" 804 | ] 805 | }, 806 | "pillar": { 807 | "Package": "pillar", 808 | "Version": "1.7.0", 809 | "Source": "Repository", 810 | "Repository": "RSPM", 811 | "Hash": "51dfc97e1b7069e9f7e6f83f3589c22e", 812 | "Requirements": [ 813 | "cli", 814 | "crayon", 815 | "ellipsis", 816 | "fansi", 817 | "glue", 818 | "lifecycle", 819 | "rlang", 820 | "utf8", 821 | "vctrs" 822 | ] 823 | }, 824 | "pkgconfig": { 825 | "Package": "pkgconfig", 826 | "Version": "2.0.3", 827 | "Source": "Repository", 828 | "Repository": "RSPM", 829 | "Hash": "01f28d4278f15c76cddbea05899c5d6f", 830 | "Requirements": [] 831 | }, 832 | "plotly": { 833 | "Package": "plotly", 834 | "Version": "4.9.1", 835 | "Source": "Repository", 836 | "Repository": "RSPM", 837 | "Hash": "f944fd279ce84daf29b179611bea0345", 838 | "Requirements": [ 839 | "RColorBrewer", 840 | "base64enc", 841 | "crosstalk", 842 | "data.table", 843 | "digest", 844 | "dplyr", 845 | "ggplot2", 846 | "hexbin", 847 | "htmltools", 848 | "htmlwidgets", 849 | "httr", 850 | "jsonlite", 851 | "lazyeval", 852 | "magrittr", 853 | "promises", 854 | "purrr", 855 | "rlang", 856 | "scales", 857 | "tibble", 858 | "tidyr", 859 | "viridisLite" 860 | ] 861 | }, 862 | "prettyunits": { 863 | "Package": "prettyunits", 864 | "Version": "1.1.1", 865 | "Source": "Repository", 866 | "Repository": "RSPM", 867 | "Hash": "95ef9167b75dde9d2ccc3c7528393e7e", 868 | "Requirements": [] 869 | }, 870 | "progress": { 871 | "Package": "progress", 872 | "Version": "1.2.2", 873 | "Source": "Repository", 874 | "Repository": "RSPM", 875 | "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061", 876 | "Requirements": [ 877 | "R6", 878 | "crayon", 879 | "hms", 880 | "prettyunits" 881 | ] 882 | }, 883 | "promises": { 884 | "Package": "promises", 885 | "Version": "1.2.0.1", 886 | "Source": "Repository", 887 | "Repository": "RSPM", 888 | "Hash": "4ab2c43adb4d4699cf3690acd378d75d", 889 | "Requirements": [ 890 | "R6", 891 | "Rcpp", 892 | "later", 893 | "magrittr", 894 | "rlang" 895 | ] 896 | }, 897 | "purrr": { 898 | "Package": "purrr", 899 | "Version": "0.3.4", 900 | "Source": "Repository", 901 | "Repository": "RSPM", 902 | "Hash": "97def703420c8ab10d8f0e6c72101e02", 903 | "Requirements": [ 904 | "magrittr", 905 | "rlang" 906 | ] 907 | }, 908 | "rappdirs": { 909 | "Package": "rappdirs", 910 | "Version": "0.3.3", 911 | "Source": "Repository", 912 | "Repository": "RSPM", 913 | "Hash": "5e3c5dc0b071b21fa128676560dbe94d", 914 | "Requirements": [] 915 | }, 916 | "renv": { 917 | "Package": "renv", 918 | "Version": "0.15.2", 919 | "Source": "Repository", 920 | "Repository": "RSPM", 921 | "Hash": "206c4ef8b7ad6fb1060d69aa7b9dfe69", 922 | "Requirements": [] 923 | }, 924 | "rlang": { 925 | "Package": "rlang", 926 | "Version": "1.0.1", 927 | "Source": "Repository", 928 | "Repository": "RSPM", 929 | "Hash": "3bf0219f19d9f5b3c682acbb3546a151", 930 | "Requirements": [] 931 | }, 932 | "rmarkdown": { 933 | "Package": "rmarkdown", 934 | "Version": "2.11", 935 | "Source": "Repository", 936 | "Repository": "RSPM", 937 | "Hash": "320017b52d05a943981272b295750388", 938 | "Requirements": [ 939 | "evaluate", 940 | "htmltools", 941 | "jquerylib", 942 | "jsonlite", 943 | "knitr", 944 | "stringr", 945 | "tinytex", 946 | "xfun", 947 | "yaml" 948 | ] 949 | }, 950 | "rprojroot": { 951 | "Package": "rprojroot", 952 | "Version": "1.3-2", 953 | "Source": "Repository", 954 | "Repository": "RSPM", 955 | "Hash": "f6a407ae5dd21f6f80a6708bbb6eb3ae", 956 | "Requirements": [ 957 | "backports" 958 | ] 959 | }, 960 | "rstudioapi": { 961 | "Package": "rstudioapi", 962 | "Version": "0.11", 963 | "Source": "Repository", 964 | "Repository": "RSPM", 965 | "Hash": "33a5b27a03da82ac4b1d43268f80088a", 966 | "Requirements": [] 967 | }, 968 | "sass": { 969 | "Package": "sass", 970 | "Version": "0.4.0", 971 | "Source": "Repository", 972 | "Repository": "RSPM", 973 | "Hash": "50cf822feb64bb3977bda0b7091be623", 974 | "Requirements": [ 975 | "R6", 976 | "fs", 977 | "htmltools", 978 | "rappdirs", 979 | "rlang" 980 | ] 981 | }, 982 | "scales": { 983 | "Package": "scales", 984 | "Version": "1.1.0", 985 | "Source": "Repository", 986 | "Repository": "RSPM", 987 | "Hash": "a1c68369c629ea3188d0676e37069c65", 988 | "Requirements": [ 989 | "R6", 990 | "RColorBrewer", 991 | "farver", 992 | "labeling", 993 | "lifecycle", 994 | "munsell", 995 | "viridisLite" 996 | ] 997 | }, 998 | "shiny": { 999 | "Package": "shiny", 1000 | "Version": "1.7.1", 1001 | "Source": "Repository", 1002 | "Repository": "RSPM", 1003 | "Hash": "00344c227c7bd0ab5d78052c5d736c44", 1004 | "Requirements": [ 1005 | "R6", 1006 | "bslib", 1007 | "cachem", 1008 | "commonmark", 1009 | "crayon", 1010 | "ellipsis", 1011 | "fastmap", 1012 | "fontawesome", 1013 | "glue", 1014 | "htmltools", 1015 | "httpuv", 1016 | "jsonlite", 1017 | "later", 1018 | "lifecycle", 1019 | "mime", 1020 | "promises", 1021 | "rlang", 1022 | "sourcetools", 1023 | "withr", 1024 | "xtable" 1025 | ] 1026 | }, 1027 | "shinyWidgets": { 1028 | "Package": "shinyWidgets", 1029 | "Version": "0.6.2", 1030 | "Source": "Repository", 1031 | "Repository": "RSPM", 1032 | "Hash": "9bdabea3a78fd6a0768c2a319d36264e", 1033 | "Requirements": [ 1034 | "bslib", 1035 | "htmltools", 1036 | "jsonlite", 1037 | "sass", 1038 | "shiny" 1039 | ] 1040 | }, 1041 | "shinycssloaders": { 1042 | "Package": "shinycssloaders", 1043 | "Version": "1.0.0", 1044 | "Source": "Repository", 1045 | "Repository": "RSPM", 1046 | "Hash": "f39bb3c44a9b496723ec7e86f9a771d8", 1047 | "Requirements": [ 1048 | "digest", 1049 | "glue", 1050 | "shiny" 1051 | ] 1052 | }, 1053 | "shinydashboard": { 1054 | "Package": "shinydashboard", 1055 | "Version": "0.7.2", 1056 | "Source": "Repository", 1057 | "Repository": "RSPM", 1058 | "Hash": "e418b532e9bb4eb22a714b9a9f1acee7", 1059 | "Requirements": [ 1060 | "htmltools", 1061 | "promises", 1062 | "shiny" 1063 | ] 1064 | }, 1065 | "sourcetools": { 1066 | "Package": "sourcetools", 1067 | "Version": "0.1.7", 1068 | "Source": "Repository", 1069 | "Repository": "RSPM", 1070 | "Hash": "947e4e02a79effa5d512473e10f41797", 1071 | "Requirements": [] 1072 | }, 1073 | "stringi": { 1074 | "Package": "stringi", 1075 | "Version": "1.4.5", 1076 | "Source": "Repository", 1077 | "Repository": "RSPM", 1078 | "Hash": "ced3b63472796155f74abc4eb5266c78", 1079 | "Requirements": [] 1080 | }, 1081 | "stringr": { 1082 | "Package": "stringr", 1083 | "Version": "1.4.0", 1084 | "Source": "Repository", 1085 | "Repository": "RSPM", 1086 | "Hash": "0759e6b6c0957edb1311028a49a35e76", 1087 | "Requirements": [ 1088 | "glue", 1089 | "magrittr", 1090 | "stringi" 1091 | ] 1092 | }, 1093 | "sys": { 1094 | "Package": "sys", 1095 | "Version": "3.4", 1096 | "Source": "Repository", 1097 | "Repository": "RSPM", 1098 | "Hash": "b227d13e29222b4574486cfcbde077fa", 1099 | "Requirements": [] 1100 | }, 1101 | "tibble": { 1102 | "Package": "tibble", 1103 | "Version": "3.1.6", 1104 | "Source": "Repository", 1105 | "Repository": "RSPM", 1106 | "Hash": "8a8f02d1934dfd6431c671361510dd0b", 1107 | "Requirements": [ 1108 | "ellipsis", 1109 | "fansi", 1110 | "lifecycle", 1111 | "magrittr", 1112 | "pillar", 1113 | "pkgconfig", 1114 | "rlang", 1115 | "vctrs" 1116 | ] 1117 | }, 1118 | "tidyr": { 1119 | "Package": "tidyr", 1120 | "Version": "1.0.2", 1121 | "Source": "Repository", 1122 | "Repository": "RSPM", 1123 | "Hash": "fb73a010ace00d6c584c2b53a21b969c", 1124 | "Requirements": [ 1125 | "Rcpp", 1126 | "dplyr", 1127 | "ellipsis", 1128 | "glue", 1129 | "lifecycle", 1130 | "magrittr", 1131 | "purrr", 1132 | "rlang", 1133 | "stringi", 1134 | "tibble", 1135 | "tidyselect", 1136 | "vctrs" 1137 | ] 1138 | }, 1139 | "tidyselect": { 1140 | "Package": "tidyselect", 1141 | "Version": "1.1.2", 1142 | "Source": "Repository", 1143 | "Repository": "RSPM", 1144 | "Hash": "17f6da8cfd7002760a859915ce7eef8f", 1145 | "Requirements": [ 1146 | "ellipsis", 1147 | "glue", 1148 | "purrr", 1149 | "rlang", 1150 | "vctrs" 1151 | ] 1152 | }, 1153 | "tinytex": { 1154 | "Package": "tinytex", 1155 | "Version": "0.35", 1156 | "Source": "Repository", 1157 | "Repository": "RSPM", 1158 | "Hash": "1d7220fe46159fb9f5c99a44354a2bff", 1159 | "Requirements": [ 1160 | "xfun" 1161 | ] 1162 | }, 1163 | "toastui": { 1164 | "Package": "toastui", 1165 | "Version": "0.2.1", 1166 | "Source": "Repository", 1167 | "Repository": "RSPM", 1168 | "Hash": "c4bc53296d24935a59b96a4a6c65ff61", 1169 | "Requirements": [ 1170 | "htmltools", 1171 | "htmlwidgets", 1172 | "magrittr", 1173 | "rlang", 1174 | "shiny", 1175 | "shinyWidgets" 1176 | ] 1177 | }, 1178 | "utf8": { 1179 | "Package": "utf8", 1180 | "Version": "1.2.2", 1181 | "Source": "Repository", 1182 | "Repository": "RSPM", 1183 | "Hash": "c9c462b759a5cc844ae25b5942654d13", 1184 | "Requirements": [] 1185 | }, 1186 | "uuid": { 1187 | "Package": "uuid", 1188 | "Version": "1.0-3", 1189 | "Source": "Repository", 1190 | "Repository": "RSPM", 1191 | "Hash": "2097822ba5e4440b81a0c7525d0315ce", 1192 | "Requirements": [] 1193 | }, 1194 | "vctrs": { 1195 | "Package": "vctrs", 1196 | "Version": "0.3.8", 1197 | "Source": "Repository", 1198 | "Repository": "RSPM", 1199 | "Hash": "ecf749a1b39ea72bd9b51b76292261f1", 1200 | "Requirements": [ 1201 | "ellipsis", 1202 | "glue", 1203 | "rlang" 1204 | ] 1205 | }, 1206 | "viridisLite": { 1207 | "Package": "viridisLite", 1208 | "Version": "0.3.0", 1209 | "Source": "Repository", 1210 | "Repository": "RSPM", 1211 | "Hash": "ce4f6271baa94776db692f1cb2055bee", 1212 | "Requirements": [] 1213 | }, 1214 | "visNetwork": { 1215 | "Package": "visNetwork", 1216 | "Version": "2.1.0", 1217 | "Source": "Repository", 1218 | "Repository": "RSPM", 1219 | "Hash": "99b515a03efb8dcc4e4be337f37bf087", 1220 | "Requirements": [ 1221 | "htmltools", 1222 | "htmlwidgets", 1223 | "jsonlite", 1224 | "magrittr" 1225 | ] 1226 | }, 1227 | "withr": { 1228 | "Package": "withr", 1229 | "Version": "2.1.2", 1230 | "Source": "Repository", 1231 | "Repository": "RSPM", 1232 | "Hash": "aa57ed55ff2df4bea697a07df528993d", 1233 | "Requirements": [] 1234 | }, 1235 | "xfun": { 1236 | "Package": "xfun", 1237 | "Version": "0.28", 1238 | "Source": "Repository", 1239 | "Repository": "RSPM", 1240 | "Hash": "f7f3a61ab62cd046d307577a8ae12999", 1241 | "Requirements": [] 1242 | }, 1243 | "xtable": { 1244 | "Package": "xtable", 1245 | "Version": "1.8-4", 1246 | "Source": "Repository", 1247 | "Repository": "RSPM", 1248 | "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2", 1249 | "Requirements": [] 1250 | }, 1251 | "yaml": { 1252 | "Package": "yaml", 1253 | "Version": "2.3.5", 1254 | "Source": "Repository", 1255 | "Repository": "RSPM", 1256 | "Hash": "458bb38374d73bf83b1bb85e353da200", 1257 | "Requirements": [] 1258 | } 1259 | } 1260 | } 1261 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | cellar/ 2 | local/ 3 | lock/ 4 | library/ 5 | python/ 6 | staging/ 7 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "0.15.2" 6 | 7 | # the project directory 8 | project <- getwd() 9 | 10 | # figure out whether the autoloader is enabled 11 | enabled <- local({ 12 | 13 | # first, check config option 14 | override <- getOption("renv.config.autoloader.enabled") 15 | if (!is.null(override)) 16 | return(override) 17 | 18 | # next, check environment variables 19 | # TODO: prefer using the configuration one in the future 20 | envvars <- c( 21 | "RENV_CONFIG_AUTOLOADER_ENABLED", 22 | "RENV_AUTOLOADER_ENABLED", 23 | "RENV_ACTIVATE_PROJECT" 24 | ) 25 | 26 | for (envvar in envvars) { 27 | envval <- Sys.getenv(envvar, unset = NA) 28 | if (!is.na(envval)) 29 | return(tolower(envval) %in% c("true", "t", "1")) 30 | } 31 | 32 | # enable by default 33 | TRUE 34 | 35 | }) 36 | 37 | if (!enabled) 38 | return(FALSE) 39 | 40 | # avoid recursion 41 | if (identical(getOption("renv.autoloader.running"), TRUE)) { 42 | warning("ignoring recursive attempt to run renv autoloader") 43 | return(invisible(TRUE)) 44 | } 45 | 46 | # signal that we're loading renv during R startup 47 | options(renv.autoloader.running = TRUE) 48 | on.exit(options(renv.autoloader.running = NULL), add = TRUE) 49 | 50 | # signal that we've consented to use renv 51 | options(renv.consent = TRUE) 52 | 53 | # load the 'utils' package eagerly -- this ensures that renv shims, which 54 | # mask 'utils' packages, will come first on the search path 55 | library(utils, lib.loc = .Library) 56 | 57 | # check to see if renv has already been loaded 58 | if ("renv" %in% loadedNamespaces()) { 59 | 60 | # if renv has already been loaded, and it's the requested version of renv, 61 | # nothing to do 62 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") 63 | if (identical(spec[["version"]], version)) 64 | return(invisible(TRUE)) 65 | 66 | # otherwise, unload and attempt to load the correct version of renv 67 | unloadNamespace("renv") 68 | 69 | } 70 | 71 | # load bootstrap tools 72 | `%||%` <- function(x, y) { 73 | if (is.environment(x) || length(x)) x else y 74 | } 75 | 76 | bootstrap <- function(version, library) { 77 | 78 | # attempt to download renv 79 | tarball <- tryCatch(renv_bootstrap_download(version), error = identity) 80 | if (inherits(tarball, "error")) 81 | stop("failed to download renv ", version) 82 | 83 | # now attempt to install 84 | status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) 85 | if (inherits(status, "error")) 86 | stop("failed to install renv ", version) 87 | 88 | } 89 | 90 | renv_bootstrap_tests_running <- function() { 91 | getOption("renv.tests.running", default = FALSE) 92 | } 93 | 94 | renv_bootstrap_repos <- function() { 95 | 96 | # check for repos override 97 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 98 | if (!is.na(repos)) 99 | return(repos) 100 | 101 | # check for lockfile repositories 102 | repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) 103 | if (!inherits(repos, "error") && length(repos)) 104 | return(repos) 105 | 106 | # if we're testing, re-use the test repositories 107 | if (renv_bootstrap_tests_running()) 108 | return(getOption("renv.tests.repos")) 109 | 110 | # retrieve current repos 111 | repos <- getOption("repos") 112 | 113 | # ensure @CRAN@ entries are resolved 114 | repos[repos == "@CRAN@"] <- getOption( 115 | "renv.repos.cran", 116 | "https://cloud.r-project.org" 117 | ) 118 | 119 | # add in renv.bootstrap.repos if set 120 | default <- c(FALLBACK = "https://cloud.r-project.org") 121 | extra <- getOption("renv.bootstrap.repos", default = default) 122 | repos <- c(repos, extra) 123 | 124 | # remove duplicates that might've snuck in 125 | dupes <- duplicated(repos) | duplicated(names(repos)) 126 | repos[!dupes] 127 | 128 | } 129 | 130 | renv_bootstrap_repos_lockfile <- function() { 131 | 132 | lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") 133 | if (!file.exists(lockpath)) 134 | return(NULL) 135 | 136 | lockfile <- tryCatch(renv_json_read(lockpath), error = identity) 137 | if (inherits(lockfile, "error")) { 138 | warning(lockfile) 139 | return(NULL) 140 | } 141 | 142 | repos <- lockfile$R$Repositories 143 | if (length(repos) == 0) 144 | return(NULL) 145 | 146 | keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) 147 | vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) 148 | names(vals) <- keys 149 | 150 | return(vals) 151 | 152 | } 153 | 154 | renv_bootstrap_download <- function(version) { 155 | 156 | # if the renv version number has 4 components, assume it must 157 | # be retrieved via github 158 | nv <- numeric_version(version) 159 | components <- unclass(nv)[[1]] 160 | 161 | methods <- if (length(components) == 4L) { 162 | list( 163 | renv_bootstrap_download_github 164 | ) 165 | } else { 166 | list( 167 | renv_bootstrap_download_cran_latest, 168 | renv_bootstrap_download_cran_archive 169 | ) 170 | } 171 | 172 | for (method in methods) { 173 | path <- tryCatch(method(version), error = identity) 174 | if (is.character(path) && file.exists(path)) 175 | return(path) 176 | } 177 | 178 | stop("failed to download renv ", version) 179 | 180 | } 181 | 182 | renv_bootstrap_download_impl <- function(url, destfile) { 183 | 184 | mode <- "wb" 185 | 186 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 187 | fixup <- 188 | Sys.info()[["sysname"]] == "Windows" && 189 | substring(url, 1L, 5L) == "file:" 190 | 191 | if (fixup) 192 | mode <- "w+b" 193 | 194 | utils::download.file( 195 | url = url, 196 | destfile = destfile, 197 | mode = mode, 198 | quiet = TRUE 199 | ) 200 | 201 | } 202 | 203 | renv_bootstrap_download_cran_latest <- function(version) { 204 | 205 | spec <- renv_bootstrap_download_cran_latest_find(version) 206 | 207 | message("* Downloading renv ", version, " ... ", appendLF = FALSE) 208 | 209 | type <- spec$type 210 | repos <- spec$repos 211 | 212 | info <- tryCatch( 213 | utils::download.packages( 214 | pkgs = "renv", 215 | destdir = tempdir(), 216 | repos = repos, 217 | type = type, 218 | quiet = TRUE 219 | ), 220 | condition = identity 221 | ) 222 | 223 | if (inherits(info, "condition")) { 224 | message("FAILED") 225 | return(FALSE) 226 | } 227 | 228 | # report success and return 229 | message("OK (downloaded ", type, ")") 230 | info[1, 2] 231 | 232 | } 233 | 234 | renv_bootstrap_download_cran_latest_find <- function(version) { 235 | 236 | # check whether binaries are supported on this system 237 | binary <- 238 | getOption("renv.bootstrap.binary", default = TRUE) && 239 | !identical(.Platform$pkgType, "source") && 240 | !identical(getOption("pkgType"), "source") && 241 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows") 242 | 243 | types <- c(if (binary) "binary", "source") 244 | 245 | # iterate over types + repositories 246 | for (type in types) { 247 | for (repos in renv_bootstrap_repos()) { 248 | 249 | # retrieve package database 250 | db <- tryCatch( 251 | as.data.frame( 252 | utils::available.packages(type = type, repos = repos), 253 | stringsAsFactors = FALSE 254 | ), 255 | error = identity 256 | ) 257 | 258 | if (inherits(db, "error")) 259 | next 260 | 261 | # check for compatible entry 262 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 263 | if (nrow(entry) == 0) 264 | next 265 | 266 | # found it; return spec to caller 267 | spec <- list(entry = entry, type = type, repos = repos) 268 | return(spec) 269 | 270 | } 271 | } 272 | 273 | # if we got here, we failed to find renv 274 | fmt <- "renv %s is not available from your declared package repositories" 275 | stop(sprintf(fmt, version)) 276 | 277 | } 278 | 279 | renv_bootstrap_download_cran_archive <- function(version) { 280 | 281 | name <- sprintf("renv_%s.tar.gz", version) 282 | repos <- renv_bootstrap_repos() 283 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 284 | destfile <- file.path(tempdir(), name) 285 | 286 | message("* Downloading renv ", version, " ... ", appendLF = FALSE) 287 | 288 | for (url in urls) { 289 | 290 | status <- tryCatch( 291 | renv_bootstrap_download_impl(url, destfile), 292 | condition = identity 293 | ) 294 | 295 | if (identical(status, 0L)) { 296 | message("OK") 297 | return(destfile) 298 | } 299 | 300 | } 301 | 302 | message("FAILED") 303 | return(FALSE) 304 | 305 | } 306 | 307 | renv_bootstrap_download_github <- function(version) { 308 | 309 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 310 | if (!identical(enabled, "TRUE")) 311 | return(FALSE) 312 | 313 | # prepare download options 314 | pat <- Sys.getenv("GITHUB_PAT") 315 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 316 | fmt <- "--location --fail --header \"Authorization: token %s\"" 317 | extra <- sprintf(fmt, pat) 318 | saved <- options("download.file.method", "download.file.extra") 319 | options(download.file.method = "curl", download.file.extra = extra) 320 | on.exit(do.call(base::options, saved), add = TRUE) 321 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 322 | fmt <- "--header=\"Authorization: token %s\"" 323 | extra <- sprintf(fmt, pat) 324 | saved <- options("download.file.method", "download.file.extra") 325 | options(download.file.method = "wget", download.file.extra = extra) 326 | on.exit(do.call(base::options, saved), add = TRUE) 327 | } 328 | 329 | message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) 330 | 331 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 332 | name <- sprintf("renv_%s.tar.gz", version) 333 | destfile <- file.path(tempdir(), name) 334 | 335 | status <- tryCatch( 336 | renv_bootstrap_download_impl(url, destfile), 337 | condition = identity 338 | ) 339 | 340 | if (!identical(status, 0L)) { 341 | message("FAILED") 342 | return(FALSE) 343 | } 344 | 345 | message("OK") 346 | return(destfile) 347 | 348 | } 349 | 350 | renv_bootstrap_install <- function(version, tarball, library) { 351 | 352 | # attempt to install it into project library 353 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 354 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 355 | 356 | # invoke using system2 so we can capture and report output 357 | bin <- R.home("bin") 358 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 359 | r <- file.path(bin, exe) 360 | args <- c("--vanilla", "CMD", "INSTALL", "--no-multiarch", "-l", shQuote(library), shQuote(tarball)) 361 | output <- system2(r, args, stdout = TRUE, stderr = TRUE) 362 | message("Done!") 363 | 364 | # check for successful install 365 | status <- attr(output, "status") 366 | if (is.numeric(status) && !identical(status, 0L)) { 367 | header <- "Error installing renv:" 368 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 369 | text <- c(header, lines, output) 370 | writeLines(text, con = stderr()) 371 | } 372 | 373 | status 374 | 375 | } 376 | 377 | renv_bootstrap_platform_prefix <- function() { 378 | 379 | # construct version prefix 380 | version <- paste(R.version$major, R.version$minor, sep = ".") 381 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 382 | 383 | # include SVN revision for development versions of R 384 | # (to avoid sharing platform-specific artefacts with released versions of R) 385 | devel <- 386 | identical(R.version[["status"]], "Under development (unstable)") || 387 | identical(R.version[["nickname"]], "Unsuffered Consequences") 388 | 389 | if (devel) 390 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 391 | 392 | # build list of path components 393 | components <- c(prefix, R.version$platform) 394 | 395 | # include prefix if provided by user 396 | prefix <- renv_bootstrap_platform_prefix_impl() 397 | if (!is.na(prefix) && nzchar(prefix)) 398 | components <- c(prefix, components) 399 | 400 | # build prefix 401 | paste(components, collapse = "/") 402 | 403 | } 404 | 405 | renv_bootstrap_platform_prefix_impl <- function() { 406 | 407 | # if an explicit prefix has been supplied, use it 408 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) 409 | if (!is.na(prefix)) 410 | return(prefix) 411 | 412 | # if the user has requested an automatic prefix, generate it 413 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) 414 | if (auto %in% c("TRUE", "True", "true", "1")) 415 | return(renv_bootstrap_platform_prefix_auto()) 416 | 417 | # empty string on failure 418 | "" 419 | 420 | } 421 | 422 | renv_bootstrap_platform_prefix_auto <- function() { 423 | 424 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) 425 | if (inherits(prefix, "error") || prefix %in% "unknown") { 426 | 427 | msg <- paste( 428 | "failed to infer current operating system", 429 | "please file a bug report at https://github.com/rstudio/renv/issues", 430 | sep = "; " 431 | ) 432 | 433 | warning(msg) 434 | 435 | } 436 | 437 | prefix 438 | 439 | } 440 | 441 | renv_bootstrap_platform_os <- function() { 442 | 443 | sysinfo <- Sys.info() 444 | sysname <- sysinfo[["sysname"]] 445 | 446 | # handle Windows + macOS up front 447 | if (sysname == "Windows") 448 | return("windows") 449 | else if (sysname == "Darwin") 450 | return("macos") 451 | 452 | # check for os-release files 453 | for (file in c("/etc/os-release", "/usr/lib/os-release")) 454 | if (file.exists(file)) 455 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) 456 | 457 | # check for redhat-release files 458 | if (file.exists("/etc/redhat-release")) 459 | return(renv_bootstrap_platform_os_via_redhat_release()) 460 | 461 | "unknown" 462 | 463 | } 464 | 465 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { 466 | 467 | # read /etc/os-release 468 | release <- utils::read.table( 469 | file = file, 470 | sep = "=", 471 | quote = c("\"", "'"), 472 | col.names = c("Key", "Value"), 473 | comment.char = "#", 474 | stringsAsFactors = FALSE 475 | ) 476 | 477 | vars <- as.list(release$Value) 478 | names(vars) <- release$Key 479 | 480 | # get os name 481 | os <- tolower(sysinfo[["sysname"]]) 482 | 483 | # read id 484 | id <- "unknown" 485 | for (field in c("ID", "ID_LIKE")) { 486 | if (field %in% names(vars) && nzchar(vars[[field]])) { 487 | id <- vars[[field]] 488 | break 489 | } 490 | } 491 | 492 | # read version 493 | version <- "unknown" 494 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { 495 | if (field %in% names(vars) && nzchar(vars[[field]])) { 496 | version <- vars[[field]] 497 | break 498 | } 499 | } 500 | 501 | # join together 502 | paste(c(os, id, version), collapse = "-") 503 | 504 | } 505 | 506 | renv_bootstrap_platform_os_via_redhat_release <- function() { 507 | 508 | # read /etc/redhat-release 509 | contents <- readLines("/etc/redhat-release", warn = FALSE) 510 | 511 | # infer id 512 | id <- if (grepl("centos", contents, ignore.case = TRUE)) 513 | "centos" 514 | else if (grepl("redhat", contents, ignore.case = TRUE)) 515 | "redhat" 516 | else 517 | "unknown" 518 | 519 | # try to find a version component (very hacky) 520 | version <- "unknown" 521 | 522 | parts <- strsplit(contents, "[[:space:]]")[[1L]] 523 | for (part in parts) { 524 | 525 | nv <- tryCatch(numeric_version(part), error = identity) 526 | if (inherits(nv, "error")) 527 | next 528 | 529 | version <- nv[1, 1] 530 | break 531 | 532 | } 533 | 534 | paste(c("linux", id, version), collapse = "-") 535 | 536 | } 537 | 538 | renv_bootstrap_library_root_name <- function(project) { 539 | 540 | # use project name as-is if requested 541 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 542 | if (asis) 543 | return(basename(project)) 544 | 545 | # otherwise, disambiguate based on project's path 546 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 547 | paste(basename(project), id, sep = "-") 548 | 549 | } 550 | 551 | renv_bootstrap_library_root <- function(project) { 552 | 553 | prefix <- renv_bootstrap_profile_prefix() 554 | 555 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 556 | if (!is.na(path)) 557 | return(paste(c(path, prefix), collapse = "/")) 558 | 559 | path <- renv_bootstrap_library_root_impl(project) 560 | if (!is.null(path)) { 561 | name <- renv_bootstrap_library_root_name(project) 562 | return(paste(c(path, prefix, name), collapse = "/")) 563 | } 564 | 565 | renv_bootstrap_paths_renv("library", project = project) 566 | 567 | } 568 | 569 | renv_bootstrap_library_root_impl <- function(project) { 570 | 571 | root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 572 | if (!is.na(root)) 573 | return(root) 574 | 575 | type <- renv_bootstrap_project_type(project) 576 | if (identical(type, "package")) { 577 | userdir <- renv_bootstrap_user_dir() 578 | return(file.path(userdir, "library")) 579 | } 580 | 581 | } 582 | 583 | renv_bootstrap_validate_version <- function(version) { 584 | 585 | loadedversion <- utils::packageDescription("renv", fields = "Version") 586 | if (version == loadedversion) 587 | return(TRUE) 588 | 589 | # assume four-component versions are from GitHub; three-component 590 | # versions are from CRAN 591 | components <- strsplit(loadedversion, "[.-]")[[1]] 592 | remote <- if (length(components) == 4L) 593 | paste("rstudio/renv", loadedversion, sep = "@") 594 | else 595 | paste("renv", loadedversion, sep = "@") 596 | 597 | fmt <- paste( 598 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", 599 | "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", 600 | "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 601 | sep = "\n" 602 | ) 603 | 604 | msg <- sprintf(fmt, loadedversion, version, remote) 605 | warning(msg, call. = FALSE) 606 | 607 | FALSE 608 | 609 | } 610 | 611 | renv_bootstrap_hash_text <- function(text) { 612 | 613 | hashfile <- tempfile("renv-hash-") 614 | on.exit(unlink(hashfile), add = TRUE) 615 | 616 | writeLines(text, con = hashfile) 617 | tools::md5sum(hashfile) 618 | 619 | } 620 | 621 | renv_bootstrap_load <- function(project, libpath, version) { 622 | 623 | # try to load renv from the project library 624 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 625 | return(FALSE) 626 | 627 | # warn if the version of renv loaded does not match 628 | renv_bootstrap_validate_version(version) 629 | 630 | # load the project 631 | renv::load(project) 632 | 633 | TRUE 634 | 635 | } 636 | 637 | renv_bootstrap_profile_load <- function(project) { 638 | 639 | # if RENV_PROFILE is already set, just use that 640 | profile <- Sys.getenv("RENV_PROFILE", unset = NA) 641 | if (!is.na(profile) && nzchar(profile)) 642 | return(profile) 643 | 644 | # check for a profile file (nothing to do if it doesn't exist) 645 | path <- renv_bootstrap_paths_renv("profile", profile = FALSE) 646 | if (!file.exists(path)) 647 | return(NULL) 648 | 649 | # read the profile, and set it if it exists 650 | contents <- readLines(path, warn = FALSE) 651 | if (length(contents) == 0L) 652 | return(NULL) 653 | 654 | # set RENV_PROFILE 655 | profile <- contents[[1L]] 656 | if (!profile %in% c("", "default")) 657 | Sys.setenv(RENV_PROFILE = profile) 658 | 659 | profile 660 | 661 | } 662 | 663 | renv_bootstrap_profile_prefix <- function() { 664 | profile <- renv_bootstrap_profile_get() 665 | if (!is.null(profile)) 666 | return(file.path("profiles", profile, "renv")) 667 | } 668 | 669 | renv_bootstrap_profile_get <- function() { 670 | profile <- Sys.getenv("RENV_PROFILE", unset = "") 671 | renv_bootstrap_profile_normalize(profile) 672 | } 673 | 674 | renv_bootstrap_profile_set <- function(profile) { 675 | profile <- renv_bootstrap_profile_normalize(profile) 676 | if (is.null(profile)) 677 | Sys.unsetenv("RENV_PROFILE") 678 | else 679 | Sys.setenv(RENV_PROFILE = profile) 680 | } 681 | 682 | renv_bootstrap_profile_normalize <- function(profile) { 683 | 684 | if (is.null(profile) || profile %in% c("", "default")) 685 | return(NULL) 686 | 687 | profile 688 | 689 | } 690 | 691 | renv_bootstrap_path_absolute <- function(path) { 692 | 693 | substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( 694 | substr(path, 1L, 1L) %in% c(letters, LETTERS) && 695 | substr(path, 2L, 3L) %in% c(":/", ":\\") 696 | ) 697 | 698 | } 699 | 700 | renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { 701 | renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") 702 | root <- if (renv_bootstrap_path_absolute(renv)) NULL else project 703 | prefix <- if (profile) renv_bootstrap_profile_prefix() 704 | components <- c(root, renv, prefix, ...) 705 | paste(components, collapse = "/") 706 | } 707 | 708 | renv_bootstrap_project_type <- function(path) { 709 | 710 | descpath <- file.path(path, "DESCRIPTION") 711 | if (!file.exists(descpath)) 712 | return("unknown") 713 | 714 | desc <- tryCatch( 715 | read.dcf(descpath, all = TRUE), 716 | error = identity 717 | ) 718 | 719 | if (inherits(desc, "error")) 720 | return("unknown") 721 | 722 | type <- desc$Type 723 | if (!is.null(type)) 724 | return(tolower(type)) 725 | 726 | package <- desc$Package 727 | if (!is.null(package)) 728 | return("package") 729 | 730 | "unknown" 731 | 732 | } 733 | 734 | renv_bootstrap_user_dir <- function(path) { 735 | dir <- renv_bootstrap_user_dir_impl(path) 736 | chartr("\\", "/", dir) 737 | } 738 | 739 | renv_bootstrap_user_dir_impl <- function(path) { 740 | 741 | # use R_user_dir if available 742 | tools <- asNamespace("tools") 743 | if (is.function(tools$R_user_dir)) 744 | return(tools$R_user_dir("renv", "cache")) 745 | 746 | # try using our own backfill for older versions of R 747 | envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") 748 | for (envvar in envvars) { 749 | root <- Sys.getenv(envvar, unset = NA) 750 | if (!is.na(root)) { 751 | path <- file.path(root, "R/renv") 752 | return(path) 753 | } 754 | } 755 | 756 | # use platform-specific default fallbacks 757 | if (Sys.info()[["sysname"]] == "Windows") 758 | file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") 759 | else if (Sys.info()[["sysname"]] == "Darwin") 760 | "~/Library/Caches/org.R-project.R/R/renv" 761 | else 762 | "~/.cache/R/renv" 763 | 764 | } 765 | 766 | renv_json_read <- function(file = NULL, text = NULL) { 767 | 768 | text <- paste(text %||% read(file), collapse = "\n") 769 | 770 | # find strings in the JSON 771 | pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' 772 | locs <- gregexpr(pattern, text)[[1]] 773 | 774 | # if any are found, replace them with placeholders 775 | replaced <- text 776 | strings <- character() 777 | replacements <- character() 778 | 779 | if (!identical(c(locs), -1L)) { 780 | 781 | # get the string values 782 | starts <- locs 783 | ends <- locs + attr(locs, "match.length") - 1L 784 | strings <- substring(text, starts, ends) 785 | 786 | # only keep those requiring escaping 787 | strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) 788 | 789 | # compute replacements 790 | replacements <- sprintf('"\032%i\032"', seq_along(strings)) 791 | 792 | # replace the strings 793 | mapply(function(string, replacement) { 794 | replaced <<- sub(string, replacement, replaced, fixed = TRUE) 795 | }, strings, replacements) 796 | 797 | } 798 | 799 | # transform the JSON into something the R parser understands 800 | transformed <- replaced 801 | transformed <- gsub("[[{]", "list(", transformed) 802 | transformed <- gsub("[]}]", ")", transformed) 803 | transformed <- gsub(":", "=", transformed, fixed = TRUE) 804 | text <- paste(transformed, collapse = "\n") 805 | 806 | # parse it 807 | json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] 808 | 809 | # construct map between source strings, replaced strings 810 | map <- as.character(parse(text = strings)) 811 | names(map) <- as.character(parse(text = replacements)) 812 | 813 | # convert to list 814 | map <- as.list(map) 815 | 816 | # remap strings in object 817 | remapped <- renv_json_remap(json, map) 818 | 819 | # evaluate 820 | eval(remapped, envir = baseenv()) 821 | 822 | } 823 | 824 | renv_json_remap <- function(json, map) { 825 | 826 | # fix names 827 | if (!is.null(names(json))) { 828 | lhs <- match(names(json), names(map), nomatch = 0L) 829 | rhs <- match(names(map), names(json), nomatch = 0L) 830 | names(json)[rhs] <- map[lhs] 831 | } 832 | 833 | # fix values 834 | if (is.character(json)) 835 | return(map[[json]] %||% json) 836 | 837 | # handle true, false, null 838 | if (is.name(json)) { 839 | text <- as.character(json) 840 | if (text == "true") 841 | return(TRUE) 842 | else if (text == "false") 843 | return(FALSE) 844 | else if (text == "null") 845 | return(NULL) 846 | } 847 | 848 | # recurse 849 | if (is.recursive(json)) { 850 | for (i in seq_along(json)) { 851 | json[i] <- list(renv_json_remap(json[[i]], map)) 852 | } 853 | } 854 | 855 | json 856 | 857 | } 858 | 859 | # load the renv profile, if any 860 | renv_bootstrap_profile_load(project) 861 | 862 | # construct path to library root 863 | root <- renv_bootstrap_library_root(project) 864 | 865 | # construct library prefix for platform 866 | prefix <- renv_bootstrap_platform_prefix() 867 | 868 | # construct full libpath 869 | libpath <- file.path(root, prefix) 870 | 871 | # attempt to load 872 | if (renv_bootstrap_load(project, libpath, version)) 873 | return(TRUE) 874 | 875 | # load failed; inform user we're about to bootstrap 876 | prefix <- paste("# Bootstrapping renv", version) 877 | postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") 878 | header <- paste(prefix, postfix) 879 | message(header) 880 | 881 | # perform bootstrap 882 | bootstrap(version, libpath) 883 | 884 | # exit early if we're just testing bootstrap 885 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 886 | return(TRUE) 887 | 888 | # try again to load 889 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 890 | message("* Successfully installed and loaded renv ", version, ".") 891 | return(renv::load()) 892 | } 893 | 894 | # failed to download or load renv; warn the user 895 | msg <- c( 896 | "Failed to find an renv installation: the project will not be loaded.", 897 | "Use `renv::activate()` to re-initialize the project." 898 | ) 899 | 900 | warning(paste(msg, collapse = "\n"), call. = FALSE) 901 | 902 | }) 903 | -------------------------------------------------------------------------------- /renv/settings.dcf: -------------------------------------------------------------------------------- 1 | external.libraries: 2 | ignored.packages: 3 | snapshot.type: packrat 4 | use.cache: TRUE 5 | vcs.ignore.library: TRUE 6 | -------------------------------------------------------------------------------- /scripts/generate-manifest.R: -------------------------------------------------------------------------------- 1 | exclude_projs <- c("connectAnalytics") 2 | all_projs <- fs::dir_ls(rprojroot::find_rstudio_root_file("examples/")) 3 | manifest_projs <- purrr::discard(all_projs, ~ any(stringr::str_detect(.x, exclude_projs))) 4 | 5 | purrr::map( 6 | manifest_projs, 7 | function(.x) {message(glue::glue("Generating manifest for: {.x}")); rsconnect::writeManifest(.x)} 8 | ) 9 | --------------------------------------------------------------------------------