Ejemplo n.º 1
0
 public HourlyEffortEntry(IssueComment githubComment)
 {
     ghComment     = githubComment;
     EffortInHours = ExtractEffort(ghComment.Body);
 }
 /// <summary>
 /// Overloaded constructor that binds the components to the data to display
 /// </summary>
 /// <param name="comment">The <c>IssueComment</c> entity instance to display</param>
 public IssueCommentControl(IssueComment comment) : this()
 {
     this.lblAuthor.Text     += comment.Author;
     this.lblAuthor.Text     += "  Date: " + comment.Date.ToString();
     this.lblCommentText.Text = comment.Text;
 }
 private static bool CouchMentioned(IssueComment[] comments, IEnumerable<string> couches, IEnumerable<string> keyPhrase)
 {
     return comments.Any(c => couches.Contains(c.User.Login) && keyPhrase.Any(phrase => c.Body.Contains(phrase)));
 }
Ejemplo n.º 4
0
 public void Load(IssueComment comment)
 {
     Comment = comment;
 }
Ejemplo n.º 5
0
 void BindComments()
 {
     grdComments.DataSource = IssueComment.GetIssueCommentsByIssueId(_IssueId);
     grdComments.DataBind();
 }
 private static bool CouchCommentedLast(string[] couches, IssueComment[] comments)
 {
     return couches.Contains(comments.Select(c => c.User.Login).Last());
 }
Ejemplo n.º 7
0
 public CommentViewModel(IssueComment comment)
 {
     Body   = comment.Body;
     Author = comment.User.Login;
 }
 public static IssueComment CreateIssueComment(int ID, string comment, global::System.DateTime commentDate, int issueComment_Issue, byte[] rowVersion)
 {
     IssueComment issueComment = new IssueComment();
     issueComment.Id = ID;
     issueComment.Comment = comment;
     issueComment.CommentDate = commentDate;
     issueComment.IssueComment_Issue = issueComment_Issue;
     issueComment.RowVersion = rowVersion;
     return issueComment;
 }
Ejemplo n.º 9
0
 internal void SetComment(IssueComment comment)
 {
     UsernameLabel.Text       = comment.Author;
     CreatedAtLabel.Text      = comment.CreatedAt.Humanize();
     BodyLabel.AttributedText = comment.Body.FromMarkdown();
 }
Ejemplo n.º 10
0
 public abstract bool UpdateIssueComment(IssueComment issueCommentToUpdate);
        public async Task <Issue> UpdateRepositoryIssueCommentAsync(string workspaceId, string repositorySlug, string issueId, string commentId, IssueComment issueComment)
        {
            var response = await GetIssuesUrl(workspaceId, repositorySlug, issueId)
                           .AppendPathSegment($"/comments/{commentId}")
                           .PutJsonAsync(issueComment)
                           .ConfigureAwait(false);

            return(await HandleResponseAsync <Issue>(response).ConfigureAwait(false));
        }
Ejemplo n.º 12
0
        private bool ProcessNewComment(List <string> recipients, POP3_ClientMessage message, Mail_Message mailHeader, MailboxReaderResult result)
        {
            string messageFrom = string.Empty;

            if (mailHeader.From.Count > 0)
            {
                messageFrom = string.Join("; ", mailHeader.From.ToList().Select(p => p.Address).ToArray()).Trim();
            }

            bool processed = false;

            foreach (var address in recipients)
            {
                Regex isReply      = new Regex(@"(.*)(\+iid-)(\d+)@(.*)");
                Match commentMatch = isReply.Match(address);
                if (commentMatch.Success && commentMatch.Groups.Count >= 4)
                {
                    // we are in a reply and group 4 must contain the id of the original issue
                    int issueId;
                    if (int.TryParse(commentMatch.Groups[3].Value, out issueId))
                    {
                        var _currentIssue = IssueManager.GetById(issueId);

                        if (_currentIssue != null)
                        {
                            var project = ProjectManager.GetById(_currentIssue.ProjectId);

                            var mailbody = Mail_Message.ParseFromByte(message.MessageToByte());

                            bool isHtml;
                            List <MIME_Entity> attachments = null;
                            string             content     = GetMessageContent(mailbody, project, out isHtml, ref attachments);

                            IssueComment comment = new IssueComment
                            {
                                IssueId     = issueId,
                                Comment     = content,
                                DateCreated = mailHeader.Date
                            };

                            // try to find if the creator is valid user in the project, otherwise take
                            // the user defined in the mailbox config
                            var users  = UserManager.GetUsersByProjectId(project.Id);
                            var emails = messageFrom.Split(';').Select(e => e.Trim().ToLower());
                            var user   = users.Find(x => emails.Contains(x.Email.ToLower()));
                            if (user != null)
                            {
                                comment.CreatorUserName = user.UserName;
                            }
                            else
                            {
                                // user not found
                                continue;
                            }

                            var saved = IssueCommentManager.SaveOrUpdate(comment);
                            if (saved)
                            {
                                //add history record
                                var history = new IssueHistory
                                {
                                    IssueId                 = issueId,
                                    CreatedUserName         = comment.CreatorUserName,
                                    DateChanged             = comment.DateCreated,
                                    FieldChanged            = ResourceStrings.GetGlobalResource(GlobalResources.SharedResources, "Comment", "Comment"),
                                    OldValue                = string.Empty,
                                    NewValue                = ResourceStrings.GetGlobalResource(GlobalResources.SharedResources, "Added", "Added"),
                                    TriggerLastUpdateChange = true
                                };
                                IssueHistoryManager.SaveOrUpdate(history);

                                var projectFolderPath = Path.Combine(Config.UploadsFolderPath, project.UploadPath);

                                // save attachments as new files
                                int attachmentsSavedCount = 1;
                                foreach (MIME_Entity mimeEntity in attachments)
                                {
                                    string fileName;
                                    var    contentType = mimeEntity.ContentType.Type.ToLower();

                                    var attachment = new IssueAttachment
                                    {
                                        Id                 = 0,
                                        Description        = "File attached by mailbox reader",
                                        DateCreated        = DateTime.Now,
                                        ContentType        = mimeEntity.ContentType.TypeWithSubtype,
                                        CreatorDisplayName = user.DisplayName,
                                        CreatorUserName    = user.UserName,
                                        IssueId            = issueId,
                                        ProjectFolderPath  = projectFolderPath
                                    };
                                    attachment.Attachment = ((MIME_b_SinglepartBase)mimeEntity.Body).Data;

                                    if (contentType.Equals("attachment")) // this is an attached email
                                    {
                                        fileName = mimeEntity.ContentDisposition.Param_FileName;
                                    }
                                    else if (contentType.Equals("message")) // message has no filename so we create one
                                    {
                                        fileName = string.Format("Attached_Message_{0}.eml", attachmentsSavedCount);
                                    }
                                    else
                                    {
                                        fileName = string.IsNullOrWhiteSpace(mimeEntity.ContentType.Param_Name) ?
                                                   string.Format("untitled.{0}", mimeEntity.ContentType.SubType) :
                                                   mimeEntity.ContentType.Param_Name;
                                    }

                                    attachment.FileName = fileName;

                                    var saveFile  = IsAllowedFileExtension(fileName);
                                    var fileSaved = false;

                                    // can we save the file?
                                    if (saveFile)
                                    {
                                        fileSaved = IssueAttachmentManager.SaveOrUpdate(attachment);

                                        if (fileSaved)
                                        {
                                            attachmentsSavedCount++;
                                        }
                                        else
                                        {
                                            LogWarning("MailboxReader: Attachment could not be saved, please see previous logs");
                                        }
                                    }
                                }

                                processed = true;

                                // add the entry if the save did not throw any exceptions
                                result.MailboxEntries.Add(new MailboxEntry());
                            }
                        }
                    }
                }
            }
            return(processed);
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Sends the new issue comment notification.
        /// </summary>
        /// <param name="issueId">The issue id.</param>
        /// <param name="newComment">The new comment.</param>
        public static void SendNewIssueCommentNotification(int issueId, IssueComment newComment)
        {
            if (issueId <= Globals.NEW_ID)
            {
                throw (new ArgumentOutOfRangeException("issueId"));
            }
            if (newComment == null)
            {
                throw new ArgumentNullException("newComment");
            }

            // TODO - create this via dependency injection at some point.
            IMailDeliveryService mailService = new SmtpMailDeliveryService();

            var issue            = DataProviderManager.Provider.GetIssueById(issueId);
            var issNotifications = DataProviderManager.Provider.GetIssueNotificationsByIssueId(issueId);
            var emailFormatType  = HostSettingManager.Get(HostSettingNames.SMTPEMailFormat, EmailFormatType.Text);

            // data for template
            var data = new Dictionary <string, object> {
                { "Issue", issue }, { "Comment", newComment }
            };
            var displayname = UserManager.GetUserDisplayName(Security.GetUserName());

            var          templateCache  = new List <CultureNotificationContent>();
            var          emailFormatKey = (emailFormatType == EmailFormatType.Text) ? "" : "HTML";
            const string subjectKey     = "NewIssueCommentSubject";
            var          bodyKey        = string.Concat("NewIssueComment", emailFormatKey);

            // get a list of distinct cultures
            var distinctCultures = (from c in issNotifications
                                    select c.NotificationCulture
                                    ).Distinct().ToList();

            // populate the template cache of the cultures needed
            foreach (var culture in from culture in distinctCultures let notificationContent = templateCache.FirstOrDefault(p => p.CultureString == culture) where notificationContent == null select culture)
            {
                templateCache.Add(new CultureNotificationContent().LoadContent(culture, subjectKey, bodyKey));
            }

            foreach (var notification in issNotifications)
            {
                var nc = templateCache.First(p => p.CultureString == notification.NotificationCulture);

                var emailSubject = nc.CultureContents
                                   .First(p => p.ContentKey == subjectKey)
                                   .FormatContent(issue.FullId, displayname);

                var bodyContent = nc.CultureContents
                                  .First(p => p.ContentKey == bodyKey)
                                  .TransformContent(data);

                try
                {
                    //send notifications to everyone except who changed it.
                    if (notification.NotificationUsername.ToLower() == Security.GetUserName().ToLower())
                    {
                        continue;
                    }

                    var user = UserManager.GetUser(notification.NotificationUsername);

                    // skip to the next user if this user is not approved
                    if (!user.IsApproved)
                    {
                        continue;
                    }
                    // skip to next user if this user doesn't have notifications enabled.
                    if (!new WebProfile().GetProfile(user.UserName).ReceiveEmailNotifications)
                    {
                        continue;
                    }

                    var message = new MailMessage
                    {
                        Subject    = emailSubject,
                        Body       = bodyContent,
                        IsBodyHtml = true
                    };

                    mailService.Send(user.Email, message);
                }
                catch (Exception ex)
                {
                    ProcessException(ex);
                }
            }
        }
        public async Task ItShouldAllowExecutingIfLastCommentWasCommand()
        {
            var url  = "https://google.com";
            var name = "Google";

            var schema = new MappingSchemaItem()
            {
                Mapping =
                {
                    { "websites", new SequenceSchemaItem()
                                            {
                                                Items =
                                                {
                                                    new MappingSchemaItem()
                                                    {
                                                        Name    = "Website",
                                                        Mapping =
                                                        {
                                                            { "name", new KeyValueSchemaItem()
                                            {
                                                Type = "str", Required = true, Unique = true
                                            } },
                                                            { "url", new KeyValueSchemaItem()
                                            {
                                                Type = "str", Required = true, Unique = true
                                            } }
                                                        }
                                                    }
                                                }
                                            } }
                }
            };

            var collaboratorUser = new User("", "", "", 0, "", DateTimeOffset.MinValue, DateTimeOffset.MinValue, 0, "", 0,
                                            0, null, "", 0, 0, "", "collaborator", null, 0, null, 0, 0, 0, "",
                                            new RepositoryPermissions(false, true, false), false, "", null);

            var externalUser = new User("", "", "", 0, "", DateTimeOffset.MinValue, DateTimeOffset.MinValue, 0, "", 0,
                                        0, null, "", 0, 0, "", "external", null, 0, null, 0, 0, 0, "",
                                        new RepositoryPermissions(false, false, false), false, "", null);

            var externalUserCommentBody = @"Wow, this looks great!

Will this be included in the next release? Can't wait!";

            var collaboratorUserCommentBody       = $"/abc url {url}";
            var secondCollaboratorUserCommentBody = $"/abc name {name}";

            var externalUserComment           = new IssueComment(0, "", "", externalUserCommentBody, DateTimeOffset.MinValue, null, externalUser);
            var collaboratorUserComment       = new IssueComment(0, "", "", collaboratorUserCommentBody, DateTimeOffset.MinValue, null, collaboratorUser);
            var secondCollaboratorUserComment = new IssueComment(0, "", "", secondCollaboratorUserCommentBody, DateTimeOffset.MinValue, null, collaboratorUser);

            var issueComments = new List <IssueComment>()
            {
                externalUserComment,
                collaboratorUserComment,
                secondCollaboratorUserComment,
            };

            var merchantDetails = new MerchantDetails();

            var githubService = new Mock <IGitHubService>();

            githubService.Setup(x => x.GetIssueComments(It.IsAny <RepositoryTarget>(), It.IsAny <int>())).ReturnsAsync(issueComments);
            githubService.Setup(x => x.IsCollaborator(It.IsAny <RepositoryTarget>(), It.Is <string>(y => y == "admin" || y == "collaborator"))).ReturnsAsync(true);
            githubService.Setup(x => x.IsCollaborator(It.IsAny <RepositoryTarget>(), It.Is <string>(y => y == "external"))).ReturnsAsync(false);

            var detailsLoader = new GithubIssueMerchantDetailsLoader(githubService.Object);

            await detailsLoader.ApplyIssueCommentCommandsToMerchantDetails(issueComments, schema, merchantDetails);

            Assert.Equal(url, merchantDetails.Values["url"].Value);
            Assert.Equal(name, merchantDetails.Values["name"].Value);
            Assert.False(merchantDetails.ShouldStopExecuting);
        }
Ejemplo n.º 15
0
 // IssueComments
 public abstract int CreateNewIssueComment(IssueComment newComment);
Ejemplo n.º 16
0
 private SDETaskNote NewSDETaskNote(string project, string title, IssueComment issueComment)
 {
     return(new SDETaskNote(project, title, issueComment.Body));
 }
Ejemplo n.º 17
0
        public async Task CreateIssueAndUpdateComment()
        {
            const string AnotherMethod = "ProcessPendingUpdatesAsync";
            RepositoryBranchUpdateHistoryEntry firstError =
                new RepositoryBranchUpdateHistoryEntry
            {
                Repository   = RepoUrl,
                Branch       = Branch,
                Method       = MethodName,
                Timestamp    = new DateTime(2200, 1, 1),
                Arguments    = $"[\"{SubscriptionId}\",\"{MethodName}\",\"{ErrorMessage}\"]",
                Success      = false,
                ErrorMessage = ErrorMessage,
                Action       = "Creating new issue"
            };
            RepositoryBranchUpdateHistoryEntry secondError =
                new RepositoryBranchUpdateHistoryEntry
            {
                Repository   = RepoUrl,
                Branch       = Branch,
                Method       = AnotherMethod,
                Timestamp    = new DateTime(2200, 2, 1),
                Arguments    = "ProcessPendingUpdatesAsync error",
                Success      = false,
                ErrorMessage = ErrorMessage,
                Action       = "Create a new issue comment",
            };

            RepositoryBranchUpdateHistoryEntry thirdError =
                new RepositoryBranchUpdateHistoryEntry
            {
                Repository   = RepoUrl,
                Branch       = Branch,
                Method       = AnotherMethod,
                Timestamp    = new DateTime(2200, 3, 1),
                Arguments    = "ProcessPendingUpdatesAsync arguments",
                Success      = false,
                ErrorMessage = ErrorMessage,
                Action       = "Update the comment",
            };

            Context.RepoBranchUpdateInMemory = new List <RepositoryBranchUpdateHistoryEntry>
            {
                firstError, secondError, thirdError
            };
            Maestro.Data.Models.Subscription subscription = new Maestro.Data.Models.Subscription
            {
                Id = Guid.Parse(SubscriptionId),
                SourceRepository = "Source Repo",
                TargetRepository = "Target Repo",
            };
            Context.Subscriptions.Add(subscription);
            Context.SaveChanges();
            Repository repository = new Repository();

            GithubClient.Setup(x => x.Repository.Get(It.IsAny <string>(), It.IsAny <string>())).ReturnsAsync(repository);
            Issue updateIssue = GetIssue();

            Octokit.AuthorAssociation author = new Octokit.AuthorAssociation();
            string       nodeId  = "1";
            IssueComment comment = new IssueComment
                                   (
                1,
                nodeId,
                "Url",
                "htmlUrl",
                $"[marker]: <> (subscriptionId: '', method: '{AnotherMethod}', errorMessage: '{ErrorMessage}')",
                new DateTime(2200, 02, 02),
                new DateTime(2200, 03, 01),
                new User(),
                new ReactionSummary(),
                author);
            List <IssueComment> issueComment = new List <IssueComment> {
                comment
            };
            List <NewIssue> newIssues      = new List <NewIssue>();
            List <string>   newCommentInfo = new List <string>();

            GithubClient.Setup(x => x.Issue.Create(It.IsAny <long>(), Capture.In(newIssues))).ReturnsAsync(updateIssue);
            GithubClient.Setup(x => x.Issue.Get(RepoId, IssueNumber)).ReturnsAsync(updateIssue);
            GithubClient.Setup(x => x.Issue.Comment.GetAllForIssue(RepoId, IssueNumber)).ReturnsAsync(issueComment);
            GithubClient.Setup(x => x.Issue.Comment.Create(RepoId, IssueNumber, Capture.In(newCommentInfo))).ReturnsAsync(comment);
            GithubClient.Setup(x => x.Issue.Comment.Update(RepoId, CommentId, Capture.In(newCommentInfo)))
            .ReturnsAsync(comment);
            DependencyUpdateErrorProcessor errorProcessor =
                ActivatorUtilities.CreateInstance <DependencyUpdateErrorProcessor>(Scope.ServiceProvider,
                                                                                   Context);
            await errorProcessor.ProcessDependencyUpdateErrorsAsync();

            newIssues.Should().ContainSingle();
            newIssues[0].Labels[0].Should().Be("DependencyUpdateError");
            newIssues[0].Body.Should().Contain(RepoUrl);
            newIssues[0].Body.Should().Contain(SubscriptionId);
            newCommentInfo[0].Should().Contain(AnotherMethod);
            newCommentInfo[0].Should().NotContain(SubscriptionId);
            newCommentInfo[0].Should().Contain("2/1/2200 12:00:00 AM");
            newCommentInfo[1].Should().Contain(AnotherMethod);
            newCommentInfo[1].Should().Contain("3/1/2200 12:00:00 AM");
            newCommentInfo[1].Should().NotContain(SubscriptionId);
        }
 public void AddToIssueComments(IssueComment issueComment)
 {
     base.AddObject("IssueComments", issueComment);
 }
Ejemplo n.º 19
0
        private async Task ProcessBuildNotificationsAsync(Build build)
        {
            const string fullBranchPrefix = "refs/heads/";

            foreach (var monitor in _options.Value.Monitor.Builds)
            {
                if (!string.Equals(build.Project.Name, monitor.Project, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (!string.Equals(monitor.DefinitionPath, $"{build.Definition.Path}\\{build.Definition.Name}", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (monitor.Branches.All(mb => !string.Equals($"{fullBranchPrefix}{mb}",
                                                              build.SourceBranch,
                                                              StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                if (monitor.Tags != null && monitor.Tags.Any() && !(monitor.Tags.Intersect(build.Tags).Any()))
                {
                    // We should only skip processing if tags were specified in the monitor, and none of those tags were found in the build
                    continue;
                }

                string prettyBranch = build.SourceBranch;
                if (prettyBranch.StartsWith(fullBranchPrefix))
                {
                    prettyBranch = prettyBranch.Substring(fullBranchPrefix.Length);
                }

                string prettyTags = (monitor.Tags != null && monitor.Tags.Any()) ? $"{string.Join(", ", build.Tags)}" : "";

                _logger.LogInformation(
                    "Build '{buildNumber}' in project '{projectName}' with definition '{definitionPath}', tags '{prettyTags}', and branch '{branch}' matches monitoring criteria, sending notification",
                    build.BuildNumber,
                    build.Project.Name,
                    build.Definition.Path,
                    prettyTags,
                    build.SourceBranch);

                _logger.LogInformation("Fetching timeline messages...");
                string timelineMessage = await BuildTimelineMessage(build);

                _logger.LogInformation("Fetching changes messages...");
                string changesMessage = await BuildChangesMessage(build);

                BuildMonitorOptions.IssuesOptions repo = _options.Value.Issues.SingleOrDefault(i => string.Equals(monitor.IssuesId, i.Id, StringComparison.OrdinalIgnoreCase));

                if (repo != null)
                {
                    IGitHubClient github = await _gitHubApplicationClientFactory.CreateGitHubClientAsync(repo.Owner, repo.Name);

                    DateTimeOffset?finishTime = DateTimeOffset.TryParse(build.FinishTime, out var parsedFinishTime) ?parsedFinishTime: (DateTimeOffset?)null;
                    DateTimeOffset?startTime  = DateTimeOffset.TryParse(build.StartTime, out var parsedStartTime) ? parsedStartTime:(DateTimeOffset?)null;

                    string timeString     = "";
                    string durationString = "";
                    if (finishTime.HasValue)
                    {
                        timeString = finishTime.Value.ToString("R");
                        if (startTime.HasValue)
                        {
                            durationString = ((int)(finishTime.Value - startTime.Value).TotalMinutes) + " minutes";
                        }
                    }

                    string icon = build.Result == "failed" ? ":x:" : ":warning:";

                    string body             = @$ "Build [#{build.BuildNumber}]({build.Links.Web.Href}) {build.Result}

## {icon} : {build.Project.Name} / {build.Definition.Name} {build.Result}

### Summary
**Finished** - {timeString}
**Duration** - {durationString}
**Requested for** - {build.RequestedFor.DisplayName}
**Reason** - {build.Reason}

### Details

{timelineMessage}

### Changes

{changesMessage}
";
                    string issueTitlePrefix = $"Build failed: {build.Definition.Name}/{prettyBranch} {prettyTags}";

                    if (repo.UpdateExisting)
                    {
                        // There is no way to get the username of our bot directly from the GithubApp with the C# api.
                        // Issue opened in Octokit: https://github.com/octokit/octokit.net/issues/2335
                        // We do, however, have access to the HtmlUrl, which ends with the name of the bot.
                        // Additionally, when the bot opens issues, the username used ends with [bot], which isn't strictly
                        // part of the name anywhere else. So, to get the correct creator name, get the HtmlUrl, grab
                        // the bot's name from it, and append [bot] to that string.
                        var    githubAppClient = _gitHubApplicationClientFactory.CreateGitHubAppClient();
                        string creator         = (await githubAppClient.GitHubApps.GetCurrent()).HtmlUrl.Split("/").Last();

                        RepositoryIssueRequest issueRequest = new RepositoryIssueRequest {
                            Creator       = $"{creator}[bot]",
                            State         = ItemStateFilter.Open,
                            SortProperty  = IssueSort.Created,
                            SortDirection = SortDirection.Descending
                        };

                        foreach (string label in repo.Labels.OrEmpty())
                        {
                            issueRequest.Labels.Add(label);
                        }

                        foreach (string label in monitor.Labels.OrEmpty())
                        {
                            issueRequest.Labels.Add(label);
                        }

                        List <Issue> matchingIssues = (await github.Issue.GetAllForRepository(repo.Owner, repo.Name, issueRequest)).ToList();
                        Issue        matchingIssue  = matchingIssues.FirstOrDefault(i => i.Title.StartsWith(issueTitlePrefix));

                        if (matchingIssue != null)
                        {
                            _logger.LogInformation("Found matching issue {issueNumber} in {owner}/{repo}. Will attempt to add a new comment.", matchingIssue.Number, repo.Owner, repo.Name);
                            // Add a new comment to the issue with the body
                            IssueComment newComment = await github.Issue.Comment.Create(repo.Owner, repo.Name, matchingIssue.Number, body);

                            _logger.LogInformation("Logged comment in {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, matchingIssue.Number);

                            return;
                        }
                        else
                        {
                            _logger.LogInformation("Matching issues for {issueTitlePrefix} not found. Creating a new issue.", issueTitlePrefix);
                        }
                    }

                    // Create new issue if repo.UpdateExisting is false or there were no matching issues
                    var newIssue =
                        new NewIssue($"{issueTitlePrefix} #{build.BuildNumber}")
                    {
                        Body = body,
                    };

                    if (!string.IsNullOrEmpty(monitor.Assignee))
                    {
                        newIssue.Assignees.Add(monitor.Assignee);
                    }

                    foreach (string label in repo.Labels.OrEmpty())
                    {
                        newIssue.Labels.Add(label);
                    }

                    foreach (string label in monitor.Labels.OrEmpty())
                    {
                        newIssue.Labels.Add(label);
                    }

                    Issue issue = await github.Issue.Create(repo.Owner, repo.Name, newIssue);

                    _logger.LogInformation("Logged issue {owner}/{repo}#{issueNumber} for build failure", repo.Owner, repo.Name, issue.Number);
                }