#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")); 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 }; #!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); 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()); 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(); 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(); 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);