Ejemplo n.º 1
0
        internal DevIssuesClient(GitHubClient gitHubClient)
        {
            GitHubClient = gitHubClient;

            IssueMap[GetKey("dotnet", "runtime", 702)]   = new GitHubIssueKey("jaredpar", "devops-util", 5);
            IssueMap[GetKey("dotnet", "runtime", 34472)] = new GitHubIssueKey("jaredpar", "devops-util", 8);
        }
Ejemplo n.º 2
0
 public void TryCreateTests(string uri, string organization, string repository, int number)
 {
     Assert.True(GitHubIssueKey.TryCreateFromUri(uri, out var key));
     Assert.Equal(organization, key.Organization);
     Assert.Equal(repository, key.Repository);
     Assert.Equal(number, key.Number);
 }
Ejemplo n.º 3
0
        /// <summary>
        /// Get the report for builds that are associated with the given GitHub Issue Key
        /// </summary>
        public async Task <string> GetAssociatedIssueReportAsync(GitHubIssueKey issueKey)
        {
            var query = TriageContextUtil
                        .GetModelGitHubIssuesQuery(issueKey)
                        .Select(x => new
            {
                x.ModelBuild.AzureOrganization,
                x.ModelBuild.AzureProject,
                x.ModelBuild.BuildNumber,
                x.ModelBuild.QueueTime,
                x.ModelBuild.PullRequestNumber,
                x.ModelBuild.GitHubOrganization,
                x.ModelBuild.GitHubRepository,
                x.ModelBuild.GitHubTargetBranch,
            });
            var builds = await query.ToListAsync().ConfigureAwait(false);

            var results = builds
                          .Select(x => (new BuildInfo(
                                            x.AzureOrganization,
                                            x.AzureProject,
                                            x.BuildNumber,
                                            new GitHubBuildInfo(x.GitHubOrganization, x.GitHubRepository, x.PullRequestNumber, x.GitHubTargetBranch)),
                                        x.QueueTime));
            var report = ReportBuilder.BuildManual(results);

            return(WrapInStartEndMarkers(report));
        }
Ejemplo n.º 4
0
        private async Task <bool> UpdateGitHubIssueReport(GitHubIssueKey issueKey, string reportBody)
        {
            try
            {
                var issueClient = GitHubClient.Issue;
                var issue       = await issueClient.Get(issueKey.Organization, issueKey.Repository, issueKey.Number);

                if (TryUpdateIssueText(reportBody, issue.Body, out var newIssueBody))
                {
                    var issueUpdate = issue.ToUpdate();
                    issueUpdate.Body = newIssueBody;
                    await issueClient.Update(issueKey.Organization, issueKey.Repository, issueKey.Number, issueUpdate);

                    return(true);
                }
                else
                {
                    Logger.LogInformation("Cannot find the replacement section in the issue");
                }
            }
            catch (Exception ex)
            {
                Logger.LogError($"Error updating issue {issueKey}: {ex.Message}");
            }

            return(false);
        }
Ejemplo n.º 5
0
        public async Task AssociatedIssueReport()
        {
            var def      = AddBuildDefinition("dnceng|public|roslyn|42");
            var issueKey = new GitHubIssueKey("dotnet", "test", 13);

            for (int i = 0; i < 5; i++)
            {
                AddGitHubIssue(issueKey, AddBuild($"|||2020-10-0{i + 1}", def));
                await Context.SaveChangesAsync();
            }

            var expected = @"
<!-- runfo report start -->
|Build|Kind|Start Time|
|---|---|---|
[0](https://dev.azure.com/dnceng/public/_build/results?buildId=0)|Rolling|2020-01-10|
[1](https://dev.azure.com/dnceng/public/_build/results?buildId=1)|Rolling|2020-02-10|
[2](https://dev.azure.com/dnceng/public/_build/results?buildId=2)|Rolling|2020-03-10|
[3](https://dev.azure.com/dnceng/public/_build/results?buildId=3)|Rolling|2020-04-10|
[4](https://dev.azure.com/dnceng/public/_build/results?buildId=4)|Rolling|2020-05-10|

<!-- runfo report end -->
";

            var report = await TrackingGitHubUtil.GetAssociatedIssueReportAsync(issueKey);

            Assert.Equal(expected.TrimNewlines(), report.TrimNewlines());
        }
Ejemplo n.º 6
0
        public bool TryCreateTimelineQuery(IssueKind kind, GitHubIssueKey issueKey, string text)
        {
            if (TryGetTimelineQuery(issueKey, out var timelineQuery))
            {
                return(false);
            }

            timelineQuery = new ModelTimelineQuery()
            {
                GitHubOrganization = issueKey.Organization,
                GitHubRepository   = issueKey.Repository,
                IssueNumber        = issueKey.Number,
                SearchText         = text
            };

            try
            {
                Context.ModelTimelineQueries.Add(timelineQuery);
                Context.SaveChanges();
                return(true);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return(false);
            }
        }
Ejemplo n.º 7
0
 public bool TryGetTimelineQuery(GitHubIssueKey issueKey, [NotNullWhen(true)] out ModelTimelineQuery timelineQuery)
 {
     timelineQuery = Context.ModelTimelineQueries
                     .Where(x =>
                            x.GitHubOrganization == issueKey.Organization &&
                            x.GitHubRepository == issueKey.Repository &&
                            x.IssueNumber == issueKey.Number)
                     .FirstOrDefault();
     return(timelineQuery is object);
 }
Ejemplo n.º 8
0
        public async Task <IActionResult> OnPost(string gitHubIssueUri, string formAction)
        {
            if (!GitHubIssueKey.TryCreateFromUri(gitHubIssueUri, out var issueKey))
            {
                return(await OnError("Not a valid GitHub Issue Url"));
            }

            var buildKey = GetBuildKey(Number !.Value);

            if (formAction == "addIssue")
            {
                try
                {
                    var modelBuild = await TriageContextUtil.GetModelBuildAsync(buildKey);

                    await TriageContextUtil.EnsureGitHubIssueAsync(modelBuild, issueKey, saveChanges : true);
                }
                catch (DbUpdateException ex)
                {
                    if (!ex.IsUniqueKeyViolation())
                    {
                        throw;
                    }
                }
            }
            else
            {
                var query = TriageContextUtil
                            .GetModelBuildQuery(buildKey)
                            .SelectMany(x => x.ModelGitHubIssues)
                            .Where(x =>
                                   x.Organization == issueKey.Organization &&
                                   x.Repository == issueKey.Repository &&
                                   x.Number == issueKey.Number);

                var modelGitHubIssue = await query.FirstOrDefaultAsync();

                if (modelGitHubIssue is object)
                {
                    TriageContextUtil.Context.ModelGitHubIssues.Remove(modelGitHubIssue);
                    await TriageContextUtil.Context.SaveChangesAsync();
                }
            }

            var util = new TrackingGitHubUtil(GitHubClientFactory, TriageContextUtil.Context, SiteLinkUtil.Published, Logger);
            await util.UpdateAssociatedGitHubIssueAsync(issueKey);

            return(await OnGet());

            async Task <IActionResult> OnError(string message)
            {
                GitHubIssueAddErrorMessage = message;
                return(await OnGet());
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Ensure the issue has the appropriate start / end markers in the body so it can be updated later
        /// by automation
        /// </summary>
        public async Task EnsureGitHubIssueHasMarkers(GitHubIssueKey issueKey)
        {
            IGitHubClient?gitHubClient = await TryCreateForIssue(issueKey).ConfigureAwait(false);

            if (gitHubClient is null)
            {
                return;
            }

            await EnsureGitHubIssueHasMarkers(gitHubClient, issueKey).ConfigureAwait(false);
        }
Ejemplo n.º 10
0
        public ModelGitHubIssue AddGitHubIssue(GitHubIssueKey issueKey, ModelBuild build)
        {
            var issue = new ModelGitHubIssue()
            {
                Organization = issueKey.Organization,
                Repository   = issueKey.Repository,
                Number       = issueKey.Number,
                ModelBuild   = build,
            };

            Context.ModelGitHubIssues.Add(issue);
            return(issue);
        }
Ejemplo n.º 11
0
        public async Task UpdateStatusIssue()
        {
            var gitHubClient = await GitHubClientFactory.CreateForAppAsync("dotnet", "runtime").ConfigureAwait(false);

            var text = await GetStatusIssueTextAsync(gitHubClient).ConfigureAwait(false);

            var issueKey    = new GitHubIssueKey("dotnet", "runtime", 702);
            var issueClient = gitHubClient.Issue;
            var issue       = await issueClient.Get(issueKey.Organization, issueKey.Repository, issueKey.Number).ConfigureAwait(false);

            var updateIssue = issue.ToUpdate();

            updateIssue.Body = text;
            await gitHubClient.Issue.Update(issueKey.Organization, issueKey.Repository, issueKey.Number, updateIssue).ConfigureAwait(false);
        }
Ejemplo n.º 12
0
        public async Task <IGitHubClient?> TryCreateForIssue(GitHubIssueKey issueKey)
        {
            try
            {
                var gitHubClient = await GitHubClientFactory.CreateForAppAsync(
                    issueKey.Organization,
                    issueKey.Repository).ConfigureAwait(false);

                return(gitHubClient);
            }
            catch (Exception ex)
            {
                Logger.LogError($"Cannot create GitHubClient for {issueKey.Organization} {issueKey.Repository}: {ex.Message}");
                return(null);
            }
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Update the GitHub issue with the associated builds
        /// </summary>
        public async Task <bool> UpdateAssociatedGitHubIssueAsync(GitHubIssueKey issueKey)
        {
            IGitHubClient?gitHubClient = await TryCreateForIssue(issueKey).ConfigureAwait(false);

            if (gitHubClient is null)
            {
                return(false);
            }

            await EnsureGitHubIssueHasMarkers(gitHubClient, issueKey).ConfigureAwait(false);

            var report = await GetAssociatedIssueReportAsync(issueKey).ConfigureAwait(false);

            var succeeded = await UpdateGitHubIssueReport(gitHubClient, issueKey, report);

            Logger.LogInformation($"Updated {issueKey.IssueUri}");
            return(true);
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Ensure the issue has the appropriate start / end markers in the body so it can be updated later
        /// by automation
        /// </summary>
        public static async Task EnsureGitHubIssueHasMarkers(IGitHubClient gitHubClient, GitHubIssueKey issueKey)
        {
            var issue = await gitHubClient.Issue.Get(issueKey.Organization, issueKey.Repository, issueKey.Number).ConfigureAwait(false);

            if (HasMarkers())
            {
                return;
            }

            var builder = new StringBuilder();

            builder.AppendLine(issue.Body);
            builder.AppendLine(MarkdownReportStart);
            builder.AppendLine(MarkdownReportEnd);

            var issueUpdate = issue.ToUpdate();

            issueUpdate.Body = builder.ToString();

            await gitHubClient.Issue.Update(issueKey.Organization, issueKey.Repository, issueKey.Number, issueUpdate).ConfigureAwait(false);

            bool HasMarkers()
            {
                using var reader = new StringReader(issue.Body);
                bool foundStart = false;

                while (reader.ReadLine() is string line)
                {
                    if (MarkdownReportStartRegex.IsMatch(line))
                    {
                        foundStart = true;
                    }

                    if (MarkdownReportEndRegex.IsMatch(line) && foundStart)
                    {
                        return(true);
                    }
                }

                return(false);
            }
        }
Ejemplo n.º 15
0
        private async Task <bool> UpdateTrackingGitHubIssueAsync(ModelTrackingIssue modelTrackingIssue, GitHubIssueKey issueKey)
        {
            IGitHubClient?gitHubClient = await TryCreateForIssue(issueKey).ConfigureAwait(false);

            if (gitHubClient is null)
            {
                return(false);
            }

            var report = await GetTrackingIssueReport(modelTrackingIssue).ConfigureAwait(false);

            var succeeded = await UpdateGitHubIssueReport(gitHubClient, issueKey, report).ConfigureAwait(false);

            Logger.LogInformation($"Updated {issueKey.IssueUri}");
            return(succeeded);
        }
Ejemplo n.º 16
0
        public async Task <ModelGitHubIssue> EnsureGitHubIssueAsync(ModelBuild modelBuild, GitHubIssueKey issueKey, bool saveChanges)
        {
            var query = GetModelBuildQuery(modelBuild.GetBuildKey())
                        .SelectMany(x => x.ModelGitHubIssues)
                        .Where(x =>
                               x.Number == issueKey.Number &&
                               x.Organization == issueKey.Organization &&
                               x.Repository == issueKey.Repository);
            var modelGitHubIssue = await query.SingleOrDefaultAsync().ConfigureAwait(false);

            if (modelGitHubIssue is object)
            {
                return(modelGitHubIssue);
            }

            modelGitHubIssue = new ModelGitHubIssue()
            {
                Organization = issueKey.Organization,
                Repository   = issueKey.Repository,
                Number       = issueKey.Number,
                ModelBuild   = modelBuild,
            };

            Context.ModelGitHubIssues.Add(modelGitHubIssue);

            if (saveChanges)
            {
                await Context.SaveChangesAsync().ConfigureAwait(false);
            }

            return(modelGitHubIssue);
        }
Ejemplo n.º 17
0
 public IQueryable <ModelGitHubIssue> GetModelGitHubIssuesQuery(GitHubIssueKey issueKey) => Context
 .ModelGitHubIssues
 .Where(x =>
        x.Number == issueKey.Number &&
        x.Organization == issueKey.Organization &&
        x.Repository == issueKey.Repository);
Ejemplo n.º 18
0
        public async Task <IActionResult> OnPost()
        {
            if (TrackingKind == TrackingKind.Unknown)
            {
                ErrorMessage = "Invalid Tracking Kind";
                return(Page());
            }

            if (string.IsNullOrEmpty(IssueTitle))
            {
                ErrorMessage = "Need an issue title";
                return(Page());
            }

            if (IssueTitle.Length >= ModelTrackingIssue.IssueTitleLengthLimit)
            {
                ErrorMessage = $"Please limit issue title to {ModelTrackingIssue.IssueTitleLengthLimit} characters";
                return(Page());
            }

            if (string.IsNullOrEmpty(SearchText))
            {
                ErrorMessage = "Must provide search text";
                return(Page());
            }

            ModelBuildDefinition?modelBuildDefinition = null;

            if (!string.IsNullOrEmpty(Definition))
            {
                modelBuildDefinition = await TriageContextUtil.FindModelBuildDefinitionAsync(Definition);

                if (modelBuildDefinition is null)
                {
                    ErrorMessage = $"Cannot find build definition with name or ID: {Definition}";
                    return(Page());
                }
            }

            switch (TrackingKind)
            {
#pragma warning disable 618
            case TrackingKind.HelixConsole:
            case TrackingKind.HelixRunClient:
                ErrorMessage = $"'{TrackingKind}' is deprecated. Please use {TrackingKind.HelixLogs}";
                return(Page());

            case TrackingKind.HelixLogs:
            {
                if (TryParseQueryString <SearchHelixLogsRequest>(out var request))
                {
                    if (request.HelixLogKinds.Count == 0)
                    {
                        ErrorMessage = "Need to specify at least one log kind to search";
                        return(Page());
                    }
                }
                else
                {
                    return(Page());
                }
            }
            break;

            case TrackingKind.Test:
                if (!TryParseQueryString <SearchTestsRequest>(out _))
                {
                    return(Page());
                }
                break;

            case TrackingKind.Timeline:
                if (!TryParseQueryString <SearchTimelinesRequest>(out _))
                {
                    return(Page());
                }
                break;
            }

            GitHubIssueKey?issueKey = null;
            if (!string.IsNullOrEmpty(GitHubIssueUri))
            {
                if (GitHubIssueKey.TryCreateFromUri(GitHubIssueUri, out var key))
                {
                    issueKey           = key;
                    GitHubOrganization = key.Organization;
                    GitHubRepository   = key.Repository;
                }
                else
                {
                    ErrorMessage = $"Invalid GitHub issue link: {GitHubIssueUri}";
                    return(Page());
                }
            }
            else if (string.IsNullOrEmpty(GitHubRepository) || string.IsNullOrEmpty(GitHubOrganization))
            {
                ErrorMessage = "Must provide GitHub organization and repository";
                return(Page());
            }

            IGitHubClient?gitHubClient = null;
            try
            {
                gitHubClient = await GitHubClientFactory.CreateForAppAsync(GitHubOrganization, GitHubRepository);
            }
            catch (Exception ex)
            {
                ErrorMessage = $"Cannot create GitHub client for that repository: {ex.Message}";
                return(Page());
            }

            var modelTrackingIssue = await CreateTrackingIssue(gitHubClient);

            return(RedirectToPage(
                       "./Issue",
                       new { id = modelTrackingIssue.Id }));

            async Task <ModelTrackingIssue> CreateTrackingIssue(IGitHubClient gitHubClient)
            {
                var issueKey = await GetOrCreateGitHubIssueAsync(gitHubClient);

                var modelTrackingIssue = new ModelTrackingIssue()
                {
                    IsActive             = true,
                    IssueTitle           = IssueTitle,
                    TrackingKind         = TrackingKind,
                    SearchQuery          = SearchText,
                    ModelBuildDefinition = modelBuildDefinition,
                    GitHubOrganization   = issueKey.Organization,
                    GitHubRepository     = issueKey.Repository,
                    GitHubIssueNumber    = issueKey.Number,
                };

                TriageContext.ModelTrackingIssues.Add(modelTrackingIssue);
                await TriageContext.SaveChangesAsync();

                await InitialTriageAsync(modelTrackingIssue);

                return(modelTrackingIssue);
            }

            async Task InitialTriageAsync(ModelTrackingIssue modelTrackingIssue)
            {
                if (modelBuildDefinition is object)
                {
                    // The initial triage is only done for tracking issues that have definitions
                    // associated with. Lacking a definition we end up querying all builds and that
                    // can produce a *lot* of data. Hard to find a good formula that is performant
                    // there hence we limit to only builds with definitions.
                    var request = new SearchBuildsRequest()
                    {
                        Definition = modelBuildDefinition?.DefinitionId.ToString() ?? null,
                        Queued     = new DateRequestValue(7, RelationalKind.GreaterThan),
                        Result     = new BuildResultRequestValue(BuildResult.Succeeded, EqualsKind.NotEquals),
                    };

                    await FunctionQueueUtil.QueueTriageBuildAttempts(TriageContextUtil, modelTrackingIssue, request);

                    // Issues are bulk updated on a 15 minute cycle. This is a new issue though so want to make sure that
                    // the user sees progress soon. Schedule two manual updates in the near future on this so the issue
                    // gets rolling then it will fall into the 15 minute bulk cycle.
                    await FunctionQueueUtil.QueueUpdateIssueAsync(modelTrackingIssue, TimeSpan.FromSeconds(30));

                    await FunctionQueueUtil.QueueUpdateIssueAsync(modelTrackingIssue, TimeSpan.FromMinutes(2));
                }
                else
                {
                    await FunctionQueueUtil.QueueUpdateIssueAsync(modelTrackingIssue, TimeSpan.FromMinutes(0));
                }
            }

            async Task <GitHubIssueKey> GetOrCreateGitHubIssueAsync(IGitHubClient gitHubClient)
            {
                if (issueKey is { } key)
                {
                    await TrackingGitHubUtil.EnsureGitHubIssueHasMarkers(gitHubClient, key);

                    return(key);
                }

                var newIssue = new NewIssue(IssueTitle)
                {
                    Body = TrackingGitHubUtil.WrapInStartEndMarkers("Runfo Creating Tracking Issue (data being generated)")
                };

                var issue = await gitHubClient.Issue.Create(GitHubOrganization, GitHubRepository, newIssue);

                return(issue.GetIssueKey());
            }

            bool TryParseQueryString <T>(out T value)
                where T : ISearchRequest, new()
            {
                value = new T();
                try
                {
                    value.ParseQueryString(SearchText ?? "");
                    return(true);
                }
                catch (Exception ex)
                {
                    ErrorMessage = ex.ToString();
                    return(false);
                }
            }
        }
Ejemplo n.º 19
0
        public async Task UpdateStatusIssue()
        {
            var header = new StringBuilder();
            var body   = new StringBuilder();
            var footer = new StringBuilder();

            header.AppendLine("## Overview");
            header.AppendLine("Please use this queries to discover issues");

            await BuildOne("Blocking CI", "blocking-clean-ci", DotNetUtil.GetBuildDefinitionKeyFromFriendlyName("runtime"));
            await BuildOne("Blocking Official Build", "blocking-official-build", DotNetUtil.GetBuildDefinitionKeyFromFriendlyName("runtime-official"));
            await BuildOne("Blocking CI Optional", "blocking-clean-ci-optional", DotNetUtil.GetBuildDefinitionKeyFromFriendlyName("runtime"));
            await BuildOne("Blocking Outerloop", "blocking-outerloop", null);

            // Blank line to move past the table
            header.AppendLine("");
            BuildFooter();

            await UpdateIssue();

            void BuildFooter()
            {
                footer.AppendLine(@"## Goals

1. A minimum 95% passing rate for the `runtime` pipeline

## Resources

1. [runtime pipeline analytics](https://dnceng.visualstudio.com/public/_build?definitionId=686&view=ms.vss-pipelineanalytics-web.new-build-definition-pipeline-analytics-view-cardmetrics)");
            }

            async Task BuildOne(string title, string label, BuildDefinitionKey?definitionKey)
            {
                header.AppendLine($"- [{title}](https://github.com/dotnet/runtime/issues?q=is%3Aopen+is%3Aissue+label%3A{label})");

                body.AppendLine($"## {title}");
                body.AppendLine("|Status|Issue|Build Count|");
                body.AppendLine("|---|---|---|");

                var query = (await DoSearch(label))
                            .Select(x =>
                {
                    var issueKey = x.GetIssueKey();
                    var count    = definitionKey.HasValue
                                ? GetImpactedBuildsCount(issueKey, definitionKey.Value)
                                : null;

                    return(x, Count: count);
                })
                            .OrderByDescending(x => x.Count);

                foreach (var(issue, count) in query)
                {
                    var emoji = issue.Labels.Any(x => x.Name == "intermittent")
                        ? ":warning:"
                        : ":fire:";
                    var titleLimit = 75;
                    var issueText  = issue.Title.Length >= titleLimit
                        ? issue.Title.Substring(0, titleLimit - 5) + " ..."
                        : issue.Title;

                    var issueEntry = $"[{issueText}]({issue.HtmlUrl})";
                    var countStr   = count.HasValue ? count.ToString() : "N/A";

                    body.AppendLine($"|{emoji}|{issueEntry}|{countStr}|");
                }
            }

            async Task <List <Octokit.Issue> > DoSearch(string label)
            {
                var request = new SearchIssuesRequest()
                {
                    Labels = new [] { label },
                    State  = ItemState.Open,
                    Type   = IssueTypeQualifier.Issue,
                    Repos  = { { "dotnet", "runtime" } },
                };
                var result = await GitHubClient.Search.SearchIssues(request);

                return(result.Items.ToList());
            }

            async Task UpdateIssue()
            {
                var issueKey    = new GitHubIssueKey("dotnet", "runtime", 702);
                var issueClient = GitHubClient.Issue;
                var issue       = await issueClient.Get(issueKey.Organization, issueKey.Repository, issueKey.Number);

                var updateIssue = issue.ToUpdate();

                updateIssue.Body = header.ToString() + body.ToString() + footer.ToString();
                await GitHubClient.Issue.Update(issueKey.Organization, issueKey.Repository, issueKey.Number, updateIssue);
            }

            int?GetImpactedBuildsCount(GitHubIssueKey issueKey, BuildDefinitionKey definitionKey)
            {
                if (!TriageContextUtil.TryGetTimelineQuery(issueKey, out var timelineQuery))
                {
                    return(null);
                }

                // TODO: need to be able to filter to the repo the build ran against
                var count = Context.ModelTimelineItems
                            .Include(x => x.ModelBuild)
                            .ThenInclude(x => x.ModelBuildDefinition)
                            .Where(x =>
                                   x.ModelTimelineQueryId == timelineQuery.Id &&
                                   x.ModelBuild.ModelBuildDefinition.AzureOrganization == definitionKey.Organization &&
                                   x.ModelBuild.ModelBuildDefinition.AzureProject == definitionKey.Project &&
                                   x.ModelBuild.ModelBuildDefinition.DefinitionId == definitionKey.Id)
                            .Count();

                return(count);;
            }
        }