issues-burndown.pl 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #!/usr/bin/env perl
  2. use warnings;
  3. use strict;
  4. use Net::GitHub;
  5. use Time::Moment;
  6. use Term::ReadPassword;
  7. # This version of the script emits the cumulative number of bugs, split into open & closed
  8. # suitable for drawing the 'top' and 'bottom' of a burndown graph.
  9. #
  10. # N.B. this doesn't take into account issues changing priority over time, but only their most recent priority.
  11. #
  12. # If you want instead the number of open issues on a given day, then look at issues-no-state.pl
  13. my $gh = Net::GitHub->new(
  14. login => 'ara4n', pass => read_password("github password: "),
  15. );
  16. $gh->set_default_user_repo('vector-im', 'element-web');
  17. #my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
  18. my @issues = $gh->issue->repos_issues({ state => 'all' });
  19. while ($gh->issue->has_next_page) {
  20. push @issues, $gh->issue->next_page;
  21. }
  22. # we want:
  23. # day by day:
  24. # split by { open, closed }
  25. # split by { bug, feature, neither }
  26. # each split by { p1, p2, p3, p4, p5, unprioritised } <- priority
  27. # each split by { minor, major, critical, cosmetic, network, no-severity } <- severity
  28. # then split (with overlap between the groups) as { total, tag1, tag2, ... }?
  29. # ...and then all over again split by milestone.
  30. my $days = {};
  31. my $schema = {};
  32. my $now = Time::Moment->now;
  33. foreach my $issue (@issues) {
  34. next if ($issue->{pull_request});
  35. # use Data::Dumper;
  36. # print STDERR Dumper($issue);
  37. my @label_list = map { $_->{name} } @{$issue->{labels}};
  38. my $labels = {};
  39. $labels->{$_} = 1 foreach (@label_list);
  40. $labels->{bug}++ if ($labels->{cosmetic} && !$labels->{bug} && !$labels->{feature});
  41. my $extract_labels = sub {
  42. my $label = undef;
  43. foreach (@_) {
  44. $label ||= $_ if (delete $labels->{$_});
  45. }
  46. return $label;
  47. };
  48. my $state = $issue->{state};
  49. my $type = &$extract_labels(qw(bug feature)) || "neither";
  50. my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
  51. my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
  52. my $start = Time::Moment->from_string($issue->{created_at});
  53. do {
  54. my $ymd = $start->strftime('%F');
  55. $days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
  56. $schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
  57. foreach (keys %$labels) {
  58. $days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
  59. $schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
  60. }
  61. $start = $start->plus_days(1);
  62. # print STDERR "^";
  63. } while ($start->compare($now) < 0);
  64. if ($state eq 'closed') {
  65. my $end = Time::Moment->from_string($issue->{closed_at});
  66. do {
  67. my $ymd = $end->strftime('%F');
  68. $days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
  69. $schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
  70. foreach (keys %$labels) {
  71. $days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
  72. $schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
  73. }
  74. $end = $end->plus_days(1);
  75. } while ($end->compare($now) < 0);
  76. # print STDERR "v";
  77. }
  78. # print STDERR "\n";
  79. }
  80. print "day,";
  81. foreach my $state (sort keys %{$schema}) {
  82. foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
  83. foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
  84. foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
  85. # foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
  86. # print "\"$type\n$priority\n$severity\n$tag\",";
  87. # }
  88. print "\"$state\n$type\n$priority\n$severity\",";
  89. }
  90. }
  91. }
  92. }
  93. print "\n";
  94. foreach my $day (sort keys %$days) {
  95. print "$day,";
  96. foreach my $state (sort keys %{$schema}) {
  97. foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
  98. foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
  99. foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
  100. # foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
  101. # print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{$tag} || 0;
  102. # print ",";
  103. # }
  104. print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{total} || 0;
  105. print ",";
  106. }
  107. }
  108. }
  109. }
  110. print "\n";
  111. }