private static async Task CreateRegressionIssue(IEnumerable <Regression> regressions, string titleTemplate, string bodyTemplate) { if (regressions == null || !regressions.Any()) { return; } var body = await CreateIssueBody(regressions, bodyTemplate); var title = await CreateIssueTitle(regressions, titleTemplate); if (!_options.Debug) { var createIssue = new NewIssue(title) { Body = body }; TagIssue(createIssue, regressions); if (!_options.ReadOnly) { await GitHubHelper.GetClient().Issue.Create(_options.RepositoryId, createIssue); } } if (_options.Debug || _options.Verbose) { Console.WriteLine(body.ToString()); } }
/// <summary> /// Returns the issues from the past /// </summary> private static async Task <IReadOnlyList <Issue> > GetRecentIssues(Source source) { if (_recentIssues != null) { return(_recentIssues); } if (_options.Debug) { return(Enumerable.Empty <Issue>().ToArray()); } var recently = new RepositoryIssueRequest { Creator = _options.Username, Filter = IssueFilter.Created, State = ItemStateFilter.All, Since = DateTimeOffset.Now.AddDays(0 - source.DaysToLoad) }; var issues = await GitHubHelper.GetClient().Issue.GetAllForRepository(_options.RepositoryId, recently); return(_recentIssues = issues); }
/// <summary> /// Updates and closes existing issues based on regressions found. /// </summary> /// <returns> /// The remaining issues that haven't been reported yet. /// </returns> private static async Task <IEnumerable <Regression> > UpdateIssues(IEnumerable <Regression> regressions, Source source, string template) { if (!regressions.Any()) { return(regressions); } if (_options.Debug) { return(regressions); } var issues = await GetRecentIssues(source); Console.WriteLine($"Downloaded {issues.Count()} issues"); // The list of regressions that remain to be reported var regressionsToReport = new List <Regression>(regressions).ToDictionary(x => x.Identifier, x => x); foreach (var issue in issues) { if (_options.Verbose) { Console.WriteLine($"Checking issue: {issue.HtmlUrl}"); } if (String.IsNullOrWhiteSpace(issue.Body)) { continue; } // For each issue, extract the regressions and update their status (recovered). // If all regressions are recovered, close the issue. var existingRegressions = ExtractRegressionsBlock(issue.Body)?.ToDictionary(x => x.Identifier, x => x); if (existingRegressions == null) { continue; } // Find all regressions that are reported in this issue, and check if they have recovered var issueNeedsUpdate = false; // Update local regressions that have recovered foreach (var r in regressions) { if (existingRegressions.TryGetValue(r.Identifier, out var localRegression)) { // If the issue has been reported, exclude it if (regressionsToReport.Remove(r.Identifier)) { Console.WriteLine($"Issue already reported {r.CurrentResult.Description} at {r.CurrentResult.DateTimeUtc}"); } if (!localRegression.HasRecovered && r.HasRecovered) { Console.WriteLine($"Found update for {r.Identifier}"); existingRegressions.Remove(r.Identifier); existingRegressions.Add(r.Identifier, r); issueNeedsUpdate = true; } } } if (issueNeedsUpdate) { Console.WriteLine("Updating issue..."); var update = issue.ToUpdate(); update.Body = await CreateIssueBody(existingRegressions.Values, template); // If all regressions have recovered, close it if (existingRegressions.Values.All(x => x.HasRecovered)) { Console.WriteLine("All regressions have recovered, closing the issue"); update.State = ItemState.Closed; } if (!_options.ReadOnly) { await GitHubHelper.GetClient().Issue.Update(_options.RepositoryId, issue.Number, update); } } else { Console.WriteLine("Issue doesn't need to be updated"); } } return(regressionsToReport.Values); }
private static async Task <int> Controller(BotOptions options) { // Validate arguments try { options.Validate(); } catch (ArgumentException e) { Console.WriteLine(e.Message); return(1); } catch (Exception e) { Console.WriteLine("Unexpected exception: " + e.ToString()); return(1); } // Substitute with ENV value if it exists if (!String.IsNullOrEmpty(options.ConnectionString) && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(options.ConnectionString))) { options.ConnectionString = Environment.GetEnvironmentVariable(options.ConnectionString); } if (!String.IsNullOrEmpty(options.AccessToken) && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(options.AccessToken))) { options.AccessToken = Environment.GetEnvironmentVariable(options.AccessToken); } if (!String.IsNullOrEmpty(options.AppKey) && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(options.AppKey))) { options.AppKey = Environment.GetEnvironmentVariable(options.AppKey); } _options = options; // Load configuration files var sources = new List <Source>(); var templates = new Dictionary <string, string>(); foreach (var configurationFilenameOrUrl in options.Config) { try { var configuration = await LoadConfigurationAsync(configurationFilenameOrUrl); sources.AddRange(configuration.Sources); foreach (var template in configuration.Templates) { templates[template.Key] = template.Value; } } catch (RegressionBotException e) { Console.WriteLine(e.Message); return(1); } } if (!sources.Any()) { Console.WriteLine("No source could be found."); return(1); } // Creating GitHub credentials if (!options.Debug) { if (!String.IsNullOrEmpty(options.AccessToken)) { GitHubHelper.GetCredentialsForUser(options); } else { await GitHubHelper.GetCredentialsForAppAsync(options); } } // Regressions Console.WriteLine("Looking for regressions..."); foreach (var s in sources) { if (!String.IsNullOrEmpty(s.Name)) { Console.WriteLine($"Processing source '{s.Name}'"); } var template = templates[s.Regressions.Template]; var regressions = await FindRegression(s).ToListAsync(); if (!regressions.Any()) { continue; } Console.WriteLine($"Found {regressions.Count()} regressions"); Console.WriteLine("Updating existing issues..."); // The result of updating issues is the list of regressions that are not yet reported var newRegressions = await UpdateIssues(regressions, s, template); Console.WriteLine($"{newRegressions.Count()} of them were not reported"); // Exclude all regressions that have recovered, and have not been reported newRegressions = newRegressions.Where(x => !x.HasRecovered).ToArray(); Console.WriteLine($"{newRegressions.Count()} of them have not recovered"); // Group issues by trend, such that all improvements are in the same issue var positiveRegressions = newRegressions.Where(x => x.Change >= 0).ToArray(); var negativeRegressions = newRegressions.Where(x => x.Change < 0).ToArray(); if (newRegressions.Any()) { Console.WriteLine("Reporting new regressions..."); foreach (var regressionSet in new[] { positiveRegressions, negativeRegressions }) { if (_options.Verbose) { Console.WriteLine(JsonConvert.SerializeObject(regressionSet, Formatting.None)); } var skip = 0; var pageSize = 10; while (true) { // Create issues with 10 regressions per issue max var page = regressionSet.Skip(skip).Take(pageSize); if (!page.Any()) { break; } await CreateRegressionIssue(page, s.Regressions.Title, template); skip += pageSize; } } } else { Console.WriteLine("No new regressions were found."); } } return(0); }