Skip to content

Instantly share code, notes, and snippets.

@zbjornson
Last active December 14, 2016 03:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zbjornson/984635f3b874a048137b27422ddcce5d to your computer and use it in GitHub Desktop.
Save zbjornson/984635f3b874a048137b27422ddcce5d to your computer and use it in GitHub Desktop.
GitHub Issue Charts in Mathematica
(* Common: *)
ToJSON[x_] := ImportString[FromCharacterCode[x, "Unicode"], "RawJSON"](*Ignore emoji*)
getIssues[repo_] := getIssues[repo, "*"]
getIssues[repo_, milestone_] :=
getIssues[repo, milestone] = Block[{url, head, pageCount, issues},
url = URLBuild[{"https://api.github.com/repos", repo, "issues"},
{"state" -> "all",
(*"access_token" -> "...",*) (* use for private repos, larger API quota*)
"sort" -> "created",
"direction" -> "desc",
"per_page" -> "100"}] <> "&milestone=" <>
ToString[milestone];(*Avoid M's encoding of '*' *)
head = URLRead[HTTPRequest[url, <|Method -> "HEAD"|>], "Headers"];(*Get number of pages*)
pageCount =
First@StringCases["link" /. head,
"page=" ~~ p : __?DigitQ ~~ ">; rel=\"last" :>
Min[ToExpression[p], 10]];
issues = Flatten[ToJSON /@ URLRead[
Table[HTTPRequest[url <> "&page=" <> ToString[p]],
{p, 0, pageCount}
], "BodyBytes"
], 1];
Select[issues, ("pull_request" /. #) == "pull_request" &](*Remove PRs*)
]
accumulateDates[x_] :=
Block[{y = Normal@CountsBy[Sort[DateObject /@ x], First]},(*Group by day and count*)
y[[All, 2]] = Accumulate[y[[All, 2]]];(*Accumulate issues per day*)
Association@y]
(** Created vs. Closed **)
issues = getIssues["nodejs/node" (*repo name*), "*" (* milestone or '*' *)];
closed = Cases["closed_at" /. issues, Except[Null]];
opened = ("created_at" /. issues);
DateListPlot[
accumulateDates /@ {opened, closed},
Filling -> {1 -> {{2}, {Directive[Opacity[0.2], Darker@Green],
Directive[Opacity[0.2], Red]}}},
PlotStyle -> {Red, Darker@Green},
PlotLabels -> {"Created", "Closed"},
FrameLabel -> {None, "Issues"},
BaseStyle -> {FontSize -> 12}
]
(** Burndown **)
repo = "nodejs/node";
milestones = {"number", "title", "due_on"} /.
ToJSON@URLRead["https://api.github.com/repos/" <> repo <> "/milestones?state=all", "BodyBytes"];(*Get milestones*)
Manipulate[
issues = getIssues[repo, milestone[[1]]];
closed = Cases["closed_at" /. issues, Except[Null | "closed_at"]];(*Get closing dates*)
accumulated = accumulateDates[closed];
beginning = DateObject@Normal[accumulated][[1, 1]];(*First issue close date*)
DateListPlot[
{
Length[issues] - accumulated,
{{beginning, Length[issues]}, {DateObject@milestone[[3]], 0}}
},
PlotRange -> {{Automatic, DateObject@milestone[[3]]}, Automatic},
PlotLabels -> {"Actual", "Ideal"},
FrameLabel -> {None, "Issues"},
ImageSize -> 450,
BaseStyle -> {FontSize -> 12}
],
{{milestone, First@milestones}, (# -> #[[2]]) & /@ milestones},
ControlType -> PopupMenu
]
@zbjornson
Copy link
Author

Created vs. resolved:
cvr

Burndown:
burndown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment