/// <summary> /// Returns threads that should be reopened. /// </summary> /// <param name="existingThreads">Existing discussion threads on the pull request.</param> /// <param name="issueComments">Issues and their related existing comments on the pull request.</param> /// <param name="reportIssuesToPullRequestSettings">Settings for posting the issues.</param> /// <returns>List of threads which should be reopened.</returns> private IEnumerable <IPullRequestDiscussionThread> GetThreadsToReopen( IReadOnlyCollection <IPullRequestDiscussionThread> existingThreads, IDictionary <IIssue, IssueCommentInfo> issueComments, IReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings) { existingThreads.NotNull(nameof(existingThreads)); issueComments.NotNull(nameof(issueComments)); reportIssuesToPullRequestSettings.NotNull(nameof(reportIssuesToPullRequestSettings)); var resolvedComments = new HashSet <IPullRequestDiscussionComment>( issueComments.Values.SelectMany(x => x.ResolvedComments)); var result = existingThreads.Where( thread => thread.Status == PullRequestDiscussionStatus.Resolved && thread.CommentSource == reportIssuesToPullRequestSettings.CommentSource && thread.Comments.Any(x => resolvedComments.Contains(x))).ToList(); this.log.Verbose( "Found {0} existing thread(s) that are resolved but still have an open issue.", result.Count); return(result); }
/// <summary> /// Runs the orchestrator. /// Posts new issues, ignoring duplicate comments and resolves comments that were open in an old iteration /// of the pull request. /// </summary> /// <param name="issues">Issues which should be reported.</param> /// <param name="settings">Settings.</param> /// <returns>Information about the reported and written issues.</returns> public PullRequestIssueResult Run( IEnumerable <IIssue> issues, IReportIssuesToPullRequestSettings settings) { issues.NotNullOrEmptyElement(nameof(issues)); settings.NotNull(nameof(settings)); // Don't process issues if pull request system could not be initialized. if (!this.InitializePullRequestSystem(settings)) { return(new PullRequestIssueResult(issues, new List <IIssue>())); } this.log.Information("Processing {0} new issues", issues.Count()); var postedIssues = this.PostAndResolveComments(settings, issues.ToList()); return(new PullRequestIssueResult(issues, postedIssues)); }
/// <summary> /// Initializes a new instance of the <see cref="IssueFilterer"/> class. /// </summary> /// <param name="log">The Cake log instance.</param> /// <param name="pullRequestSystem">Pull request system to use.</param> /// <param name="settings">Settings to use.</param> public IssueFilterer( ICakeLog log, IPullRequestSystem pullRequestSystem, IReportIssuesToPullRequestSettings settings) { #pragma warning disable SA1123 // Do not place regions within elements #region DupFinder Exclusion #pragma warning restore SA1123 // Do not place regions within elements log.NotNull(nameof(log)); pullRequestSystem.NotNull(nameof(pullRequestSystem)); settings.NotNull(nameof(settings)); this.log = log; this.pullRequestSystem = pullRequestSystem; this.settings = settings; #endregion }
public static PullRequestIssueResult ReportIssuesToPullRequest( this ICakeContext context, IEnumerable <IIssue> issues, IPullRequestSystem pullRequestSystem, IReportIssuesToPullRequestSettings settings) { context.NotNull(nameof(context)); pullRequestSystem.NotNull(nameof(pullRequestSystem)); settings.NotNull(nameof(settings)); // ReSharper disable once PossibleMultipleEnumeration issues.NotNullOrEmptyElement(nameof(issues)); var orchestrator = new Orchestrator( context.Log, pullRequestSystem); // ReSharper disable once PossibleMultipleEnumeration return(orchestrator.Run(issues, settings)); }
/// <summary> /// Marks resolved comment threads created by this logic with active issues as active. /// </summary> /// <param name="discussionThreadsCapability">Pull request system capability for working with discussion threads.</param> /// <param name="existingThreads">Existing discussion threads on the pull request.</param> /// <param name="issueComments">Issues and their related existing comments on the pull request.</param> /// <param name="reportIssuesToPullRequestSettings">Settings for posting the issues.</param> private void ReopenExistingComments( ISupportDiscussionThreads discussionThreadsCapability, IReadOnlyCollection <IPullRequestDiscussionThread> existingThreads, IDictionary <IIssue, IssueCommentInfo> issueComments, IReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings) { existingThreads.NotNull(nameof(existingThreads)); issueComments.NotNull(nameof(issueComments)); reportIssuesToPullRequestSettings.NotNull(nameof(reportIssuesToPullRequestSettings)); if (!existingThreads.Any()) { this.log.Verbose("No existings threads to reopen."); return; } var threadsToReopen = this.GetThreadsToReopen(existingThreads, issueComments, reportIssuesToPullRequestSettings).ToList(); this.log.Verbose("Reopen {0} threads...", threadsToReopen.Count); discussionThreadsCapability.ReopenDiscussionThreads(threadsToReopen); }
/// <summary> /// Posts new issues, ignoring duplicate comments and resolves comments that were open in an old iteration /// of the pull request. /// </summary> /// <param name="reportIssuesToPullRequestSettings">Settings for posting the issues.</param> /// <param name="issues">Issues to post.</param> /// <returns>Issues reported to the pull request.</returns> private IEnumerable <IIssue> PostAndResolveComments( IReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings, IList <IIssue> issues) { reportIssuesToPullRequestSettings.NotNull(nameof(reportIssuesToPullRequestSettings)); issues.NotNull(nameof(issues)); IDictionary <IIssue, IssueCommentInfo> issueComments = null; IReadOnlyCollection <IPullRequestDiscussionThread> existingThreads = null; var discussionThreadsCapability = this.pullRequestSystem.GetCapability <ISupportDiscussionThreads>(); if (discussionThreadsCapability != null) { this.log.Information("Fetching existing threads and comments..."); existingThreads = discussionThreadsCapability .FetchDiscussionThreads(reportIssuesToPullRequestSettings.CommentSource) .Where(x => x != null) .ToList(); issueComments = this.GetCommentsForIssue( issues, existingThreads); // Comments that were created by this logic but do not have corresponding issues can be marked as 'Resolved'. this.ResolveExistingComments( discussionThreadsCapability, existingThreads, issueComments, reportIssuesToPullRequestSettings); // Comments that were created by this logic and are resolved, but still have a corresponding issue need to be reopened. this.ReopenExistingComments( discussionThreadsCapability, existingThreads, issueComments, reportIssuesToPullRequestSettings); } if (!issues.Any()) { this.log.Information("No new issues were posted"); return(new List <IIssue>()); } // Filter issues which should not be posted. var issueFilterer = new IssueFilterer(this.log, this.pullRequestSystem, reportIssuesToPullRequestSettings); var remainingIssues = issueFilterer .FilterIssues( issues, issueComments, existingThreads) .ToList(); if (remainingIssues.Any()) { if (!string.IsNullOrWhiteSpace(reportIssuesToPullRequestSettings.CommitId)) { var checkCommitIdCapability = this.pullRequestSystem.GetCapability <ISupportCheckingCommitId>(); if (checkCommitIdCapability != null && !checkCommitIdCapability.IsCurrentCommitId(reportIssuesToPullRequestSettings.CommitId)) { this.log.Information( "Skipping posting of issues since commit {0} is outdated. Current commit is {1}", reportIssuesToPullRequestSettings.CommitId, checkCommitIdCapability.GetLastSourceCommitId()); return(new List <IIssue>()); } } var formattedMessages = from issue in remainingIssues select string.Format( CultureInfo.InvariantCulture, " Rule: {0} Line: {1} File: {2}", issue.Rule, issue.Line, issue.AffectedFileRelativePath); this.log.Verbose( "Posting {0} issue(s):\n{1}", remainingIssues.Count, string.Join(Environment.NewLine, formattedMessages)); this.pullRequestSystem.PostDiscussionThreads( remainingIssues, reportIssuesToPullRequestSettings.CommentSource); } else { this.log.Information("All issues were filtered. Nothing new to post."); } return(remainingIssues); }