Beispiel #1
0
        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());
            }
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        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);
        }