├── .gitignore ├── NEWS.md ├── README.md ├── githubdashboard.Rproj ├── README.Rmd ├── issue_detail.Rmd └── github-dashboard.Rmd /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | github-dashboard_cache 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ### 2017-02-18 2 | 3 | * Added issue title to overview list view 4 | * Added recent issue (<48 hrs old) detail page 5 | * Updated `index.html` example 6 | 7 | ### 2017-02-16 8 | 9 | * Implemented crazy idea 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GitHub Dashboard in R 2 | ================ 3 | 4 | Example 5 | ------- 6 | 7 | - 8 | 9 | R flexdashboard expectations 10 | ---------------------------- 11 | 12 | - expects `GITHUB_USER` in the environment 13 | - expects `GITHUB_PAT` in the environment 14 | -------------------------------------------------------------------------------- /githubdashboard.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 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "GitHub Dashboard in R" 3 | output: github_document 4 | --- 5 | 6 | ```{r setup, include=FALSE} 7 | knitr::opts_chunk$set(echo = TRUE) 8 | ``` 9 | 10 | ## Example 11 | 12 | - 13 | 14 | ## R flexdashboard expectations 15 | 16 | - expects `GITHUB_USER` in the environment 17 | - expects `GITHUB_PAT` in the environment 18 | -------------------------------------------------------------------------------- /issue_detail.Rmd: -------------------------------------------------------------------------------- 1 | ```{r include=FALSE} 2 | # TODO Make this smarter 3 | body <- recent_issues$body[i] 4 | body <- gsub("![", "[=> Image link ... ", body, fixed=TRUE) 5 | ``` 6 | 7 | Row 8 | ----------------------------------------------------------------------- 9 | 10 | ### `r sprintf("%s > %s (#%s)", recent_issues$repo_name[i], recent_issues$Title[i], recent_issues$number[i])` 11 | 12 | `r body` 13 | 14 | > `r sprintf('Issue Link', recent_issues$html_url[i], recent_issues$number[i])` 15 | 16 | -------------------------------------------------------------------------------- /github-dashboard.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "`r sprintf('GitHub Dashboard for [%s] on [%s]', Sys.getenv('GITHUB_USER'), Sys.Date())`" 3 | output: 4 | flexdashboard::flex_dashboard: 5 | theme: yeti 6 | orientation: rows 7 | vertical_layout: scroll 8 | --- 9 | ```{r setup, include=FALSE} 10 | library(flexdashboard) 11 | library(gh) # devtools::install_github("r-pkgs/gh") 12 | library(anytime) 13 | library(tidyverse) 14 | library(DT) 15 | library(knitr) 16 | ``` 17 | 18 | ```{r dev, echo=FALSE, include=FALSE} 19 | USER <- Sys.getenv('GITHUB_USER') 20 | 21 | user <- gh("/users/:user", user=USER) 22 | repos <- gh("/users/:user/repos", user=USER, .limit = Inf) 23 | issues <- gh("/user/issues", .limit = Inf) 24 | ``` 25 | 26 | ```{r dev2, echo=FALSE, include=FALSE} 27 | map_df(repos, ~.[c("name", "html_url", "stargazers_count", "forks_count", "has_issues", 28 | "open_issues", "updated_at", "pushed_at")]) %>% 29 | mutate(updated_at = anytime(pushed_at, asUTC=TRUE), pushed_at = NULL) -> repos_df 30 | 31 | map_df(issues, function(x) { 32 | c(list(repo_name = x$repository$full_name, 33 | repo_url = x$repository$html_url, 34 | user_login = x$user$login, 35 | user_url = x$user$url), 36 | x[c("html_url", "number", "state", "updated_at", "created_at", "title", "body")]) 37 | }) %>% 38 | mutate(updated_at = anytime(created_at, asUTC=TRUE), created_at = NULL) -> issues_df 39 | ``` 40 | 41 | ```{r include=FALSE} 42 | options( 43 | DT.options = 44 | list( 45 | pageLength = 25, 46 | language = list(search = 'Filter:'), 47 | dom = 'Bfrtip', 48 | bInfo = FALSE) 49 | ) 50 | 51 | pretty_diff <- function(rel) { 52 | map_chr(rel, function(x) { 53 | x <- Sys.time() - as.POSIXct(x, tz=Sys.timezone()) 54 | y <- unclass(x) 55 | attr(y, "units") <- NULL 56 | # sprintf("%3.2f %s", abs(y), attr(x, "units")) 57 | paste(format(abs(y), digits = 0, nsmall = 2), attr(x, "units")) 58 | }) 59 | } 60 | 61 | repos_df %>% 62 | mutate(Repository = sprintf('%s', html_url, name)) %>% 63 | rename(Stars = stargazers_count, Forks = forks_count, 64 | `Issues` = open_issues) %>% 65 | select(Repository, everything(), -name, -html_url, -has_issues) -> repos_df 66 | 67 | issues_df %>% 68 | rename(Title=title) %>% 69 | mutate(Repository = sprintf('%s', repo_url, repo_name), 70 | `Submitted by` = sprintf('%s', user_url, user_login), 71 | `Issue #` = sprintf('#%s', html_url, number), 72 | Age = pretty_diff(updated_at)) -> issues_df 73 | ``` 74 | 75 | Overview 76 | ===================================== 77 | 78 | Row 79 | ----------------------------------------------------------------------- 80 | 81 | ### Total Repos 82 | 83 | ```{r} 84 | valueBox(scales::comma(nrow(repos_df)), 85 | icon = "fa-github") 86 | ``` 87 | 88 | ### Open Issues 89 | 90 | ```{r} 91 | valueBox(scales::comma(nrow(filter(issues_df, state == "open"))), 92 | icon = "fa-exclamation-triangle") 93 | ``` 94 | 95 | ### Total Stars 96 | 97 | ```{r} 98 | valueBox(scales::comma(sum(repos_df$Stars)), 99 | icon = "fa-star") 100 | ``` 101 | 102 | 103 | Row 104 | ----------------------------------------------------------------------- 105 | 106 | ### Top 10 Repos (sorted initially by stargazers) 107 | 108 | ```{r} 109 | arrange(repos_df, desc(Stars)) %>% 110 | head(10) %>% 111 | select(-updated_at) %>% 112 | datatable(options = list(bFilter=FALSE, paging=FALSE), escape=FALSE, filter="none") 113 | ``` 114 | 115 | Row 116 | ----------------------------------------------------------------------- 117 | 118 | ### Repos by time of last activity 119 | 120 | ```{r} 121 | arrange(repos_df, desc(updated_at)) %>% 122 | select(Repository, Age=updated_at, Stars, Forks, Issues) %>% 123 | mutate(Age=pretty_diff(Age)) %>% 124 | datatable(escape=FALSE, 125 | options = list(columnDefs = list(list(className = 'dt-right', targets = 2:5)))) 126 | ``` 127 | 128 | Row 129 | ----------------------------------------------------------------------- 130 | 131 | ### Open issues by date 132 | 133 | ```{r} 134 | filter(issues_df, state == "open") %>% 135 | arrange(desc(updated_at)) %>% 136 | select(Repository, `Submitted by`, Title, `Issue #`, Age) %>% 137 | datatable(escape=FALSE, 138 | options = list(columnDefs = list(list(className = 'dt-right', targets = c(4:5))))) 139 | ``` 140 | 141 | > `r sprintf("%s total open issues", scales::comma(nrow(filter(issues_df, state == "open"))))` 142 | 143 | Recent Issue Detail 144 | ===================================== 145 | 146 | ```{r issue_detail, include=FALSE} 147 | options(knitr.duplicate.label = 'allow') 148 | 149 | issues_df %>% 150 | mutate(hr_diff = as.numeric(difftime(Sys.time(), 151 | issues_df$updated_at, 152 | units = "hours"))) %>% 153 | filter(hr_diff <= 48) %>% 154 | arrange(hr_diff) -> recent_issues 155 | 156 | if (nrow(recent_issues) == 0) { 157 | out <- "### No new issues in the past 48 hours" 158 | } else { 159 | out <- NULL 160 | cat("", file = "/tmp/k.txt") 161 | for (i in 1:nrow(recent_issues)) { 162 | res <- knit_child('issue_detail.Rmd', envir=parent.frame()) 163 | cat(res, file="/tmp/k.txt", append = TRUE) 164 | out <- c(out, res) 165 | } 166 | 167 | } 168 | 169 | options(knitr.duplicate.label = 'no way') 170 | ``` 171 | 172 | `r paste(out, sep=" ", collapse="\n")` 173 | 174 | --------------------------------------------------------------------------------