├── .gitignore ├── BugReport ├── App.config ├── DataModel │ ├── DataModelIssue.cs │ ├── Extensions.cs │ └── IssueCollection.cs ├── GitHubBugReport.csproj ├── GitHubBugReport.sln ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Query │ ├── Exceptions.cs │ ├── ExpressionMultiRepo.cs │ ├── Expressions.cs │ ├── Expressions_Leaf.cs │ ├── Expressions_Logical.cs │ ├── Parser.cs │ ├── SyntaxParser.cs │ ├── Token.cs │ └── Tokenizer.cs ├── Reports │ ├── Alert.cs │ ├── Config.cs │ ├── ContributionsReport.cs │ ├── CsvReport │ │ ├── CsvTableReport.cs │ │ └── CsvWriter.cs │ ├── EmailReport │ │ ├── AlertReport.cs │ │ ├── AlertReport_Diff.cs │ │ ├── AlertReport_NeedsMSResponse.cs │ │ └── AlertReport_Untriaged.cs │ ├── ExpressionUntriaged.cs │ ├── HtmlReport │ │ ├── HtmlContributionsReport.cs │ │ ├── HtmlQueryCountLink.cs │ │ ├── HtmlTableReport.cs │ │ └── QueryReport.cs │ ├── IssueEntry.cs │ ├── TableReport.cs │ └── XlsxReport │ │ └── HistoryReport.cs ├── Repository.cs ├── Util │ ├── CommandLine │ │ ├── Option.cs │ │ └── Parser.cs │ └── Extensions.cs └── packages.config ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | ### VisualStudio ### 4 | 5 | # Tool Runtime Dir 6 | /[Tt]ools/ 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | msbuild.log 26 | msbuild.err 27 | msbuild.wrn 28 | 29 | # Cross building rootfs 30 | cross/rootfs/ 31 | 32 | # Visual Studio 2015 33 | .vs/ 34 | 35 | # Visual Studio 2015 Pre-CTP6 36 | *.sln.ide 37 | *.ide/ 38 | 39 | # MSTest test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | 43 | #NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding addin-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | *.pubxml 146 | *.publishproj 147 | 148 | # NuGet Packages 149 | *.nuget.props 150 | *.nuget.targets 151 | *.nupkg 152 | **/packages/* 153 | 154 | # NuGet package restore lockfiles 155 | project.lock.json 156 | 157 | # Windows Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Windows Store app package directory 162 | AppPackages/ 163 | 164 | # Others 165 | *.Cache 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *.dbmdl 170 | *.dbproj.schemaview 171 | *.pfx 172 | *.publishsettings 173 | node_modules/ 174 | *.metaproj 175 | *.metaproj.tmp 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | ### MonoDevelop ### 201 | 202 | *.pidb 203 | *.userprefs 204 | 205 | ### Windows ### 206 | 207 | # Windows image file caches 208 | Thumbs.db 209 | ehthumbs.db 210 | 211 | # Folder config file 212 | Desktop.ini 213 | 214 | # Recycle Bin used on file shares 215 | $RECYCLE.BIN/ 216 | 217 | # Windows Installer files 218 | *.cab 219 | *.msi 220 | *.msm 221 | *.msp 222 | 223 | # Windows shortcuts 224 | *.lnk 225 | 226 | ### Linux ### 227 | 228 | *~ 229 | 230 | # KDE directory preferences 231 | .directory 232 | 233 | ### OSX ### 234 | 235 | .DS_Store 236 | .AppleDouble 237 | .LSOverride 238 | 239 | # Icon must end with two \r 240 | Icon 241 | 242 | # Thumbnails 243 | ._* 244 | 245 | # Files that might appear on external disk 246 | .Spotlight-V100 247 | .Trashes 248 | 249 | # Directories potentially created on remote AFP share 250 | .AppleDB 251 | .AppleDesktop 252 | Network Trash Folder 253 | Temporary Items 254 | .apdisk 255 | 256 | # vim temporary files 257 | [._]*.s[a-w][a-z] 258 | [._]s[a-w][a-z] 259 | *.un~ 260 | Session.vim 261 | .netrwhist 262 | *~ 263 | 264 | # Visual Studio Code 265 | .vscode/ 266 | 267 | # Private test configuration and binaries. 268 | config.ps1 269 | **/IISApplications 270 | -------------------------------------------------------------------------------- /BugReport/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BugReport/DataModel/DataModelIssue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | 6 | namespace BugReport.DataModel 7 | { 8 | public class DataModelIssue 9 | { 10 | public int Id; 11 | public int Number; 12 | public string Title; 13 | public Octokit.ItemState State; 14 | public User Assignee; 15 | public Label[] Labels; 16 | public User User; 17 | public string HtmlUrl; 18 | 19 | public DateTimeOffset? CreatedAt; 20 | public DateTimeOffset? UpdatedAt; 21 | public DateTimeOffset? ClosedAt; 22 | public User ClosedBy; 23 | 24 | public PullRequest PullRequest; 25 | public Milestone Milestone; 26 | 27 | 28 | private Repository _repo; 29 | public Repository Repo 30 | { 31 | get 32 | { 33 | if (_repo == null) 34 | { 35 | _repo = Repository.FromHtmlUrl(HtmlUrl); 36 | } 37 | return _repo; 38 | } 39 | } 40 | 41 | public bool IsIssueOrComment 42 | { 43 | get => (PullRequest == null); 44 | } 45 | public bool IsPullRequest 46 | { 47 | get => (PullRequest != null); 48 | } 49 | public bool IsIssueKind(IssueKindFlags flags) 50 | { 51 | if (IsIssueOrComment) 52 | { 53 | if (HtmlUrl.Contains("issuecomment")) 54 | { 55 | return ((flags & IssueKindFlags.Comment) != 0); 56 | } 57 | else 58 | { 59 | return ((flags & IssueKindFlags.Issue) != 0); 60 | } 61 | } 62 | Debug.Assert(IsPullRequest); 63 | return ((flags & IssueKindFlags.PullRequest) != 0); 64 | } 65 | 66 | public bool IsOpen 67 | { 68 | get => (State == Octokit.ItemState.Open); 69 | } 70 | public bool IsClosed 71 | { 72 | get => (State == Octokit.ItemState.Closed); 73 | } 74 | 75 | public bool HasLabel(string labelName) => Labels.Contains_ByName(labelName); 76 | 77 | public bool IsMilestone(string milestoneName) 78 | { 79 | if (Milestone == null) 80 | { 81 | return (milestoneName == null); 82 | } 83 | return (Milestone.Title == milestoneName); 84 | } 85 | 86 | public bool HasAssignee(string assigneeName) 87 | { 88 | if (Assignee == null) 89 | { 90 | return (assigneeName == null); 91 | } 92 | return Assignee.Login.Equals(assigneeName, StringComparison.InvariantCultureIgnoreCase); 93 | } 94 | 95 | public override string ToString() 96 | { 97 | StringWriter sw = new StringWriter(); 98 | sw.WriteLine($"Number: {Number}"); 99 | sw.WriteLine("Type: {0}", (PullRequest == null) ? "Issue" : "PullRequest"); 100 | sw.WriteLine($"URL: {HtmlUrl}"); 101 | sw.WriteLine($"State: {State}"); 102 | sw.WriteLine("Assignee.Name: {0}", (Assignee == null) ? "" : Assignee.Name); 103 | sw.WriteLine(" .Login: {0}", (Assignee == null) ? "" : Assignee.Login); 104 | sw.WriteLine("Labels.Name:"); 105 | foreach (Label label in Labels) 106 | { 107 | sw.WriteLine($" {label.Name}"); 108 | } 109 | sw.WriteLine($"Title: {Title}"); 110 | //sw.WriteLine("Milestone.Title: {0}", (issue.Milestone == null) ? "" : issue.Milestone.Title); 111 | sw.WriteLine("User.Name: {0}", (User == null) ? "" : User.Name); 112 | sw.WriteLine(" .Login: {0}", (User == null) ? "" : User.Login); 113 | sw.WriteLine($"CreatedAt: {CreatedAt}"); 114 | sw.WriteLine($"UpdatedAt: {UpdatedAt}"); 115 | sw.WriteLine($"ClosedAt: {ClosedAt}"); 116 | sw.WriteLine("ClosedBy.Name: {0}", (ClosedBy == null) ? "" : ClosedBy.Name); 117 | sw.Write(" .Login: {0}", (ClosedBy == null) ? "" : ClosedBy.Login); 118 | 119 | string text = sw.ToString(); 120 | sw.Close(); 121 | return text; 122 | } 123 | 124 | // Returns true only if the issues represent the same issue number in the same repo 125 | public bool EqualsByNumber(DataModelIssue issue) 126 | { 127 | // Check also HtmlUrl which encodes repo 128 | return ((Number == issue.Number) && (HtmlUrl == issue.HtmlUrl)); 129 | } 130 | } 131 | 132 | [FlagsAttribute] 133 | public enum IssueKindFlags 134 | { 135 | Issue = 1, 136 | PullRequest = 2, 137 | Comment = 4, 138 | All = Issue | PullRequest | Comment 139 | } 140 | 141 | public class Label 142 | { 143 | public string Name; 144 | 145 | public Label(string name) 146 | { 147 | Name = name; 148 | } 149 | 150 | public bool Equals(string name) 151 | { 152 | return NameEqualityComparer.Equals(Name, name); 153 | } 154 | 155 | public static StringComparer NameEqualityComparer = StringComparer.InvariantCultureIgnoreCase; 156 | } 157 | 158 | public class User 159 | { 160 | public string Name; 161 | public string Login; 162 | public string Id; 163 | public string HtmlUrl; 164 | 165 | public override bool Equals(object obj) 166 | { 167 | return (obj is User user) && (user.Id == Id); 168 | } 169 | public override int GetHashCode() 170 | { 171 | return Id.GetHashCode(); 172 | } 173 | } 174 | 175 | public class Milestone 176 | { 177 | public int Number; 178 | public string Title; 179 | public string Description; 180 | public int OpenIssues; 181 | public int ClosedIssues; 182 | public Octokit.ItemState State; 183 | public User Creator; 184 | public DateTimeOffset CreatedAt; 185 | public DateTimeOffset? DueOn; 186 | public DateTimeOffset? ClosedAt; 187 | 188 | public bool Equals(string title) 189 | { 190 | return TitleComparer.Equals(Title, title); 191 | } 192 | 193 | public static StringComparer TitleComparer = StringComparer.InvariantCultureIgnoreCase; 194 | } 195 | 196 | public class PullRequest 197 | { 198 | //public string HtmlUrl; 199 | 200 | /* 201 | public DateTimeOffset? CreatedAt; 202 | public DateTimeOffset? UpdatedAt; 203 | public DateTimeOffset? ClosedAt; 204 | public DateTimeOffset? MergedAt; 205 | public bool Merged; 206 | */ 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /BugReport/DataModel/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using BugReport.Util; 8 | 9 | namespace BugReport.DataModel 10 | { 11 | public static class Extensions 12 | { 13 | public static IEnumerable PullRequests(this IEnumerable issues) 14 | { 15 | return issues.Where(i => i.IsPullRequest); 16 | } 17 | public static IEnumerable NonPullRequests(this IEnumerable issues) 18 | { 19 | return issues.Where(i => !i.IsPullRequest); 20 | } 21 | public static IEnumerable Except_ByIssueNumber(this IEnumerable issues, IEnumerable exceptIssues) 22 | { 23 | return issues.Where(i => !exceptIssues.Contains_ByIssueNumber(i)); 24 | } 25 | public static bool Contains_ByIssueNumber(this IEnumerable issues, DataModelIssue issue) 26 | { 27 | return issues.Where(i => issue.EqualsByNumber(i)).Any(); 28 | } 29 | public static IEnumerable Intersect_ByIssueNumber(this IEnumerable issues, IEnumerable intersectIssues) 30 | { 31 | return issues.Where(i => intersectIssues.Contains_ByIssueNumber(i)); 32 | } 33 | public static IEnumerable Where(this IEnumerable issues, Repository repo) 34 | { 35 | return issues.Where(i => (i.Repo == repo)); 36 | } 37 | public static DataModelIssue First_ByIssueNumber(this IEnumerable issues, DataModelIssue issue) 38 | { 39 | return issues.Where(i => issue.EqualsByNumber(i)).First(); 40 | } 41 | public static DataModelIssue Last_ByIssueNumber(this IEnumerable issues, DataModelIssue issue) 42 | { 43 | return issues.Where(i => issue.EqualsByNumber(i)).Last(); 44 | } 45 | 46 | public static DataModelIssue FirstOrNull_ByIssueNumber(this IEnumerable issues, DataModelIssue issue) 47 | { 48 | return issues.Where(i => i.EqualsByNumber(issue)).FirstOrDefault(); 49 | } 50 | 51 | public static IEnumerable DistinctFirst_ByIssueNumber(this IEnumerable issues) 52 | { 53 | return issues.Where(i => (i == issues.First_ByIssueNumber(i))); 54 | } 55 | public static IEnumerable DistinctLast_ByIssueNumber(this IEnumerable issues) 56 | { 57 | return issues.Where(i => (i == issues.Last_ByIssueNumber(i))); 58 | } 59 | 60 | public static bool Contains_ByName(this IEnumerable