Importing pacakges and setting up connection
#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:NodaTime,2.4.8"
#r "nuget:Octokit,0.47.0"
#r "nuget: XPlot.Plotly.Interactive, 3.0.4"
using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags;
using Microsoft.DotNet.Interactive.Formatting;
using Octokit;
using NodaTime;
using NodaTime.Extensions;
using XPlot.Plotly;
var organization = "dotnet";
var repositoryName = "interactive";
var options = new ApiOptions();
var gitHubClient = new GitHubClient(new ProductHeaderValue("notebook"));
Generate a user token to get rid of public api throttling policies for anonymous users
var tokenAuth = new Credentials("your token");
gitHubClient.Credentials = tokenAuth;
var today = SystemClock.Instance.InUtc().GetCurrentDate();
var startOfTheMonth = today.With(DateAdjusters.StartOfMonth);
var startOfPreviousMonth = today.With(DateAdjusters.StartOfMonth) - Period.FromMonths(1);
var startOfTheYear = new LocalDate(today.Year, 1, 1).AtMidnight();
var currentYearIssuesRequest = new RepositoryIssueRequest {
State = ItemStateFilter.All,
Since = startOfTheYear.ToDateTimeUnspecified()
};
var pullRequestRequest = new PullRequestRequest {
State = ItemStateFilter.All
};
Perform github queries
#!time
var branches = await gitHubClient.Repository.Branch.GetAll(organization, repositoryName);
var pullRequests = await gitHubClient.Repository.PullRequest.GetAllForRepository(organization, repositoryName, pullRequestRequest);
var forks = await gitHubClient.Repository.Forks.GetAll(organization, repositoryName);
var currentYearIssues = await gitHubClient.Issue.GetAllForRepository(organization, repositoryName, currentYearIssuesRequest);
Branch data
Pull request data
var pullRequestCreatedThisMonth = pullRequests.Where(pr => pr.CreatedAt > startOfTheMonth.ToDateTimeUnspecified());
var pullRequestClosedThisMonth =pullRequests.Where(pr => (pr.MergedAt != null && pr.MergedAt > startOfTheMonth.ToDateTimeUnspecified()));
var contributorsCount = pullRequestClosedThisMonth.GroupBy(pr => pr.User.Login);
var pullRequestLifespan = pullRequests.GroupBy(pr =>
{
var lifeSpan = (pr.ClosedAt ?? today.ToDateTimeUnspecified()) - pr.CreatedAt;
return Math.Max(0, Math.Ceiling(lifeSpan.TotalDays));
})
.Where(g => g.Key > 0)
.OrderBy(g => g.Key)
.ToDictionary(g => g.Key, g => g.Count());
Fork data
var forkCreatedThisMonth = forks.Where(fork => fork.CreatedAt >= startOfTheMonth.ToDateTimeUnspecified());
var forkCreatedPreviousMonth = forks.Where(fork => (fork.CreatedAt >= startOfPreviousMonth.ToDateTimeUnspecified()) && (fork.CreatedAt < startOfTheMonth.ToDateTimeUnspecified()));
var forkCreatedByMonth = forks.GroupBy(fork => new DateTime(fork.CreatedAt.Year, fork.CreatedAt.Month, 1));
var forkUpdateByMonth = forks.GroupBy(f => new DateTime(f.UpdatedAt.Year, f.UpdatedAt.Month, 1) ).Select(g => new {Date = g.Key, Count = g.Count()}).OrderBy(g => g.Date).ToArray();
var total = 0;
var forkCountByMonth = forkCreatedByMonth.OrderBy(g => g.Key).Select(g => new {Date = g.Key, Count = total += g.Count()}).ToArray();
Issues data
bool IsBug(Issue issue){
return issue.Labels.FirstOrDefault(l => l.Name == "bug")!= null;
}
bool TargetsArea(Issue issue){
return issue.Labels.FirstOrDefault(l => l.Name.StartsWith("Area-"))!= null;
}
string GetArea(Issue issue){
return issue.Labels.FirstOrDefault(l => l.Name.StartsWith("Area-"))?.Name;
}
var openIssues = currentYearIssues.Where(IsBug).Where(issue => issue.State == "open");
var closedIssues = currentYearIssues.Where(IsBug).Where(issue => issue.State == "closed");
var oldestIssues = openIssues.OrderBy(issue => today.ToDateTimeUnspecified() - issue.CreatedAt).Take(20);
var createdCurrentMonth = currentYearIssues.Where(IsBug).Where(issue => issue.CreatedAt >= startOfTheMonth.ToDateTimeUnspecified());
var createdPreviousMonth = currentYearIssues.Where(IsBug).Where(issue => (issue.CreatedAt >= startOfPreviousMonth.ToDateTimeUnspecified()) && (issue.CreatedAt < startOfTheMonth.ToDateTimeUnspecified()));
var openFromPreviousMonth = openIssues.Where(issue => (issue.CreatedAt > startOfPreviousMonth.ToDateTimeUnspecified()) && (issue.CreatedAt < startOfTheMonth.ToDateTimeUnspecified()));
var createdByMonth = currentYearIssues.Where(IsBug).GroupBy(issue => new DateTime(issue.CreatedAt.Year, issue.CreatedAt.Month, 1)).OrderBy(g=>g.Key).ToDictionary(g => g.Key, g => g.Count());
var closedByMonth = closedIssues.GroupBy(issue => new DateTime((int) issue.ClosedAt?.Year, (int) issue.ClosedAt?.Month, 1)).OrderBy(g=>g.Key).ToDictionary(g => g.Key, g => g.Count());
var openIssueAge = openIssues.GroupBy(issue => new DateTime(issue.CreatedAt.Year, issue.CreatedAt.Month, issue.CreatedAt.Day)).ToDictionary(g => g.Key, g => g.Max(issue =>Math.Max(0, Math.Ceiling( (today.ToDateTimeUnspecified() - issue.CreatedAt).TotalDays))));
var openByMonth = new Dictionary<DateTime, int>();
var minDate = createdByMonth.Min(g => g.Key);
var maxCreatedAtDate = createdByMonth.Max(g => g.Key);
var maxClosedAtDate = closedByMonth.Max(g => g.Key);
var maxDate = maxCreatedAtDate > maxClosedAtDate ?maxCreatedAtDate : maxClosedAtDate;
var cursor = minDate;
var runningTotal = 0;
var issuesCreatedThisMonthByArea = currentYearIssues.Where(issue => issue.CreatedAt >= startOfTheMonth.ToDateTimeUnspecified()).Where(issue => IsBug(issue) && TargetsArea(issue)).GroupBy(issue => GetArea(issue)).ToDictionary(g => g.Key, g => g.Count());
var openIssueByArea = currentYearIssues.Where(issue => issue.State == "open").Where(issue => IsBug(issue) && TargetsArea(issue)).GroupBy(issue => GetArea(issue)).ToDictionary(g => g.Key, g => g.Count());
while (cursor <= maxDate )
{
createdByMonth.TryGetValue(cursor, out var openCount);
closedByMonth.TryGetValue(cursor, out var closedCount);
runningTotal += (openCount - closedCount);
openByMonth[cursor] = runningTotal;
cursor = cursor.AddMonths(1);
}
var issueLifespan = currentYearIssues.Where(IsBug).GroupBy(issue =>
{
var lifeSpan = (issue.ClosedAt ?? today.ToDateTimeUnspecified()) - issue.CreatedAt;
return Math.Max(0, Math.Round(Math.Ceiling(lifeSpan.TotalDays),0));
})
.Where(g => g.Key > 0)
.OrderBy(g => g.Key)
.ToDictionary(g => g.Key, g => g.Count());
display(new {
less_then_one_sprint = issueLifespan.Where(i=> i.Key < 21).Select(i => i.Value).Sum(),
less_then_two_sprint = issueLifespan.Where(i=> i.Key >= 21 && i.Key < 42).Select(i => i.Value).Sum(),
more_then_two_sprint = issueLifespan.Where(i=> i.Key >= 42).Select(i => i.Value).Sum()
});
var createdByMonthSeries = new Graph.Scattergl{
name = "Created",
x = createdByMonth.Select(g => g.Key),
y = createdByMonth.Select(g => g.Value),
};
var openByMonthSeries = new Graph.Scattergl{
name = "Open",
x = openByMonth.Select(g => g.Key),
y = openByMonth.Select(g => g.Value),
};
var closedByMonthSeries = new Graph.Scattergl{
name = "Closed",
x = closedByMonth.Select(g => g.Key),
y = closedByMonth.Select(g => g.Value),
};
var issueChart = Chart.Plot(new[] {createdByMonthSeries, closedByMonthSeries, openByMonthSeries});
issueChart.WithTitle("Bugs by month");
display(issueChart);
var issueLifespanOnWeekSeries = new Graph.Bar
{
name = "One week old",
y = issueLifespan.Where(issue => issue.Key < 7).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = issueLifespan.Where(issue => issue.Key < 7).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "green"
}
};
var issueLifespanOneSprintSeries = new Graph.Bar
{
name = "One Sprint old",
y = issueLifespan.Where(issue => issue.Key >= 7 && issue.Key < 21).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = issueLifespan.Where(issue => issue.Key >= 7 && issue.Key < 21).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "yellow"
}
};
var issueLifespanOldSeries = new Graph.Bar
{
name = "More then a Sprint",
y = issueLifespan.Where(issue => issue.Key >= 21).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = issueLifespan.Where(issue => issue.Key >= 21).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "red"
}
};
var issueLifespanChart = Chart.Plot(new[] {issueLifespanOnWeekSeries, issueLifespanOneSprintSeries, issueLifespanOldSeries});
issueLifespanChart.WithLayout(new Layout.Layout
{
title = "Bugs by life span",
xaxis = new Graph.Xaxis {
title = "Number of days a bug stays open",
showgrid = false,
zeroline = false
},
yaxis = new Graph.Yaxis {
showgrid = true,
zeroline = false
}
});
display(issueLifespanChart);
var openIssuesAgeSeriesWeek = new Graph.Bar
{
name = "Closed in a week",
y = openIssueAge.Where(issue => issue.Value < 7).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = openIssueAge.Where(issue => issue.Value < 7).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "green"
}
};
var openIssuesAgeSeriesSprint = new Graph.Bar
{
name = "Closed within a sprint",
y = openIssueAge.Where(issue => issue.Value >= 7 && issue.Value < 21).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = openIssueAge.Where(issue => issue.Value >= 7 && issue.Value < 21).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "yellow"
}
};
var openIssuesAgeSeriesLong = new Graph.Bar
{
name = "Long standing",
y = openIssueAge.Where(issue => issue.Value >= 21).OrderBy(issue => issue.Key).Select(issue => issue.Value),
x = openIssueAge.Where(issue => issue.Value >= 21).OrderBy(issue => issue.Key).Select(issue => issue.Key) ,
marker = new Graph.Marker{
color = "red"
}
};
var openIssuesAgeChart = Chart.Plot(new[] {openIssuesAgeSeriesWeek, openIssuesAgeSeriesSprint, openIssuesAgeSeriesLong});
openIssuesAgeChart.WithLayout(new Layout.Layout
{
title = "Open bugs age",
yaxis = new Graph.Yaxis {
title = "Number of days a bug stays open",
showgrid = true,
zeroline = false
}
});
display(openIssuesAgeChart);
var createdThisMonthAreaSeries = new Graph.Pie {
values = issuesCreatedThisMonthByArea.Select(e => e.Value),
labels = issuesCreatedThisMonthByArea.Select(e => e.Key),
};
var createdArea = Chart.Plot(new[] {createdThisMonthAreaSeries});
createdArea.WithLayout(new Layout.Layout
{
title = "Bugs created this month by Area",
});
display(createdArea);
var openAreaSeries = new Graph.Pie {
values = openIssueByArea.Select(e => e.Value),
labels = openIssueByArea.Select(e => e.Key),
};
var openArea = Chart.Plot(new[] {openAreaSeries});
openArea.WithLayout(new Layout.Layout
{
title = "Open bugs by Area",
});
display(openArea);
var prColors = pullRequestLifespan.OrderBy(pr => pr.Key).Select(pr => pr.Key < 7 ? "green" : pr.Key < 21 ? "yellow" : "red");
var prLifespanOneWeekSeries = new Graph.Bar
{
name = "One week",
y = pullRequestLifespan.Where(issue => issue.Key < 7).OrderBy(pr => pr.Key).Select(pr => pr.Value),
x = pullRequestLifespan.Where(issue => issue.Key < 7).OrderBy(pr => pr.Key).Select(pr => pr.Key) ,
marker = new Graph.Marker{
color = "green"
}
};
var prLifespanOneSprintSeries = new Graph.Bar
{
name = "One Sprint",
y = pullRequestLifespan.Where(issue => issue.Key >= 7 && issue.Key < 21).OrderBy(pr => pr.Key).Select(pr => pr.Value),
x = pullRequestLifespan.Where(issue => issue.Key >= 7 && issue.Key < 21).OrderBy(pr => pr.Key).Select(pr => pr.Key) ,
marker = new Graph.Marker{
color = "yellow"
}
};
var prLifespanMoreThanASprintSeries = new Graph.Bar
{
name = "More than a Sprint",
y = pullRequestLifespan.Where(issue => issue.Key >= 21).OrderBy(pr => pr.Key).Select(pr => pr.Value),
x = pullRequestLifespan.Where(issue => issue.Key >= 21).OrderBy(pr => pr.Key).Select(pr => pr.Key) ,
marker = new Graph.Marker{
color = "red"
}
};
var prLifespanChart = Chart.Plot(new[] {prLifespanOneWeekSeries, prLifespanOneSprintSeries, prLifespanMoreThanASprintSeries});
prLifespanChart.WithLayout(new Layout.Layout
{
title = "Pull Request by life span",
xaxis = new Graph.Xaxis {
title = "Number of days a PR stays open",
showgrid = false,
zeroline = false
},
yaxis = new Graph.Yaxis {
title = "Number of PR",
showgrid = true,
zeroline = false
}
});
display(prLifespanChart);
var forkCreationSeries = new Graph.Scattergl
{
name = "created by month",
y = forkCreatedByMonth.Select(g => g.Count() ).ToArray(),
x = forkCreatedByMonth.Select(g => g.Key ).ToArray()
};
var forkTotalSeries = new Graph.Scattergl
{
name = "running total",
y = forkCountByMonth.Select(g => g.Count ).ToArray(),
x = forkCountByMonth.Select(g => g.Date ).ToArray()
};
var forkUpdateSeries = new Graph.Scattergl
{
name = "last update by month",
y = forkUpdateByMonth.Select(g => g.Count ).ToArray(),
x = forkUpdateByMonth.Select(g => g.Date ).ToArray()
};
var chart = Chart.Plot(new[] {forkCreationSeries,forkTotalSeries,forkUpdateSeries});
chart.WithTitle("Fork activity");
display(chart);