#i "nuget:https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json"
#i "nuget:https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json"
#r "nuget:Octokit, 0.32.0"
#r "nuget:NodaTime, 2.4.6"
#r "nuget: XPlot.Plotly.Interactive, 4.0.2"
open Octokit
open NodaTime
open NodaTime.Extensions
open XPlot.Plotly
Create a GitHub public API client
let organization = "dotnet"
let repositoryName = "fsharp"
let options = ApiOptions()
let gitHubClient = GitHubClient(ProductHeaderValue("notebook"))
Generate a user token to get rid of public api throttling policies for anonymous users
// let tokenAuth = Credentials("YOUR-TOKEN-HERE")
// gitHubClient.Credentials <- tokenAuth
let today = SystemClock.Instance.InUtc().GetCurrentDate()
let startOfTheMonth = today.With(DateAdjusters.Month(1))
let startOfTheYear = LocalDate(today.Year, 1, 1).AtMidnight()
let since t = Nullable(DateTimeOffset(t))
Query GitHub for :
let createdIssuesRequest =
RepositoryIssueRequest(
Since = since (startOfTheMonth.ToDateTimeUnspecified()),
Filter = IssueFilter.Created)
let closedIssuesRequest =
RepositoryIssueRequest(
Since = since (startOfTheMonth.ToDateTimeUnspecified()),
State = ItemStateFilter.Closed)
let thisYearIssuesRequest =
RepositoryIssueRequest(
Since = since (startOfTheYear.ToDateTimeUnspecified()),
State = ItemStateFilter.All)
Start pulling data via the GitHub API
let createdThisMonthTask() =
gitHubClient.Issue.GetAllForRepository(organization, repositoryName, createdIssuesRequest)
|> Async.AwaitTask
let closedThisMonthTask() =
gitHubClient.Issue.GetAllForRepository(organization, repositoryName, closedIssuesRequest)
|> Async.AwaitTask
let thisYearIssuesTask() =
gitHubClient.Issue.GetAllForRepository(organization, repositoryName, thisYearIssuesRequest)
|> Async.AwaitTask
let results =
[| createdThisMonthTask(); closedThisMonthTask(); thisYearIssuesTask() |]
|> Async.Parallel
|> Async.RunSynchronously
let createdThisMonth = results.[0]
let closedThisMonth = results.[1]
let thisYearIssues = results.[2]
Group open and closed issues by month
let openSoFar =
createdThisMonth
|> Seq.sortBy (fun i -> i.CreatedAt)
|> Seq.filter (fun i -> i.State.StringValue = "open")
let openByMonthOfCreation =
openSoFar
|> Seq.groupBy (fun i -> {| Year = i.CreatedAt.Year; Month = i.CreatedAt.Month |})
|> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
let closedSoFar =
thisYearIssues
|> Seq.sortBy (fun i -> i.CreatedAt)
|> Seq.filter (fun i -> i.State.StringValue = "closed")
let closedByMonthOfClosure =
closedSoFar
|> Seq.groupBy (fun i -> {| Year = i.ClosedAt.Value.Year; Month = i.ClosedAt.Value.Month |})
|> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
let openCountByMonth =
let mutable runningTotal = thisYearIssues.Count
closedSoFar
|> List.ofSeq
|> List.groupBy (fun i -> {| Year = i.CreatedAt.Year; Month = i.CreatedAt.Month |})
|> List.map (fun (key, issues) ->
let dataPoint = {| Date = key; Count = Seq.count issues |}
dataPoint)
Show issues opened this month grouped by day
let createdThisMonthByDay =
createdThisMonth
|> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year,i.CreatedAt.Month, i.CreatedAt.Day))
|> Seq.map (fun (date, issues) -> (date, issues.Count()))
createdThisMonthByDay
|> Chart.Line
|> Chart.WithTitle("Daily created issues over the past year")
Show open issues, in descending order. Limit to 10 to save screen space.
openSoFar
|> Seq.map (fun i -> {| CreatedAt = i.CreatedAt; Title = i.Title; State = i.State.StringValue; Number = i.Number |})
|> Seq.sortByDescending (fun d -> d.CreatedAt)
|> Seq.take 10 // Limiting the output to 10 here!
Let's see what issues still opened, grouped by month, looks like
openByMonthOfCreation
|> Seq.map (fun g -> (DateTime(g.Date.Year, g.Date.Month, 1), g.Count))
|> Chart.Line
|> Chart.WithTitle("Issues still opened, grouped by month")
Now let's look at idle vs active issues.
let idleByMonth =
openSoFar
|> Seq.filter (fun i -> i.Comments = 0)
|> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year, i.CreatedAt.Month, 1))
|> Seq.map(fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
let activeByMonth =
openSoFar
|> Seq.filter (fun i -> i.Comments > 0)
|> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year, i.CreatedAt.Month, 1))
|> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
let idleSeries =
Graph.Scattergl(
name = "Idle",
y = (idleByMonth |> Seq.map (fun g -> g.Count)),
x = (idleByMonth |> Seq.map (fun g -> g.Date)))
let activeSeries =
Graph.Scattergl(
name = "Active",
y = (activeByMonth |> Seq.map (fun g -> g.Count)),
x = (activeByMonth |> Seq.map (fun g -> g.Date)))
[idleSeries; activeSeries]
|> Chart.Plot
|> Chart.WithTitle("Idle and active open issue report")
Now let's generate a report for the whole year.
let openDataPoints =
openByMonthOfCreation
|> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})
|> Seq.sortBy (fun d -> d.Date)
let closedDataPoints =
closedByMonthOfClosure
|> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})
|> Seq.sortBy (fun d -> d.Date)
let openCountByMonthDataPoints =
openCountByMonth
|> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})
|> Seq.sortBy (fun d -> d.Date)
let openSeries =
Graph.Scattergl(
name = "Created",
x = (openDataPoints |> Seq.map (fun g -> g.Date)),
y = (openDataPoints |> Seq.map (fun g -> g.Count)))
let closeSeries =
Graph.Scattergl(
name = "Closed",
x = (closedDataPoints |> Seq.map (fun g -> g.Date)),
y = (closedDataPoints |> Seq.map (fun g -> g.Count)))
let stillOpenSeries =
Graph.Scattergl(
name = "Open",
x = (openCountByMonthDataPoints |> Seq.map (fun g -> g.Date)),
y = (openCountByMonthDataPoints |> Seq.map (fun g -> g.Count)))
[openSeries; closeSeries; stillOpenSeries]
|> Chart.Plot
|> Chart.WithTitle("Issue report for the current year")
let forks =
async {
return!
gitHubClient.Repository.Forks.GetAll(organization, repositoryName)
|> Async.AwaitTask
} |> Async.RunSynchronously
let forksCreatedByMonth =
forks
|> Seq.groupBy (fun f -> DateTime(f.CreatedAt.Year, f.CreatedAt.Month, f.CreatedAt.Day))
|> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
|> Seq.sortBy (fun g -> g.Date)
let forksLastUpdateByMonth =
forks
|> Seq.groupBy (fun f -> DateTime(f.UpdatedAt.Year, f.UpdatedAt.Month, f.UpdatedAt.Day))
|> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})
|> Seq.sortBy (fun g -> g.Date)
let forkCountByMonth =
forksCreatedByMonth
|> Seq.sortBy (fun g -> g.Date)
|> Seq.map (fun g -> {| Date = g.Date; Count = g.Count |})
let forkCreationSeries =
Graph.Scattergl(
name = "created by month",
x = (forksCreatedByMonth |> Seq.map (fun g -> g.Date)),
y = (forksCreatedByMonth |> Seq.map (fun g -> g.Count)))
let forkTotalSeries =
Graph.Scattergl(
name = "running total",
x = (forkCountByMonth |> Seq.map (fun g -> g.Date)),
y = (forkCountByMonth |> Seq.map (fun g -> g.Count)))
let forkUpdateSeries =
Graph.Scattergl(
name = "last updated by month",
x = (forksLastUpdateByMonth |> Seq.map (fun g -> g.Date)),
y = (forksLastUpdateByMonth |> Seq.map (fun g -> g.Count)))
[forkCreationSeries; forkTotalSeries; forkUpdateSeries]
|> Chart.Plot
|> Chart.WithTitle("Fork activity")