/// <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>
        /// Initializes the pull request system.
        /// </summary>
        /// <param name="settings">Settings for posting issues.</param>
        /// <returns><c>True</c> if pull request system could be initialized.</returns>
        private bool InitializePullRequestSystem(IReportIssuesToPullRequestSettings settings)
        {
            // Initialize pull request system.
            this.log.Verbose("Initialize pull request system...");
            var result = this.pullRequestSystem.Initialize(settings);

            if (!result)
            {
                this.log.Warning("Error initializing the pull request system.");
            }

            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));
        }
Beispiel #4
0
        /// <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
        }
Beispiel #5
0
        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);
        }
        /// <inheritdoc />
        public override bool Initialize(IReportIssuesToPullRequestSettings settings)
        {
            var result = base.Initialize(settings);

            return(result && !this.ShouldFailOnInitialization);
        }
Beispiel #9
0
 /// <inheritdoc/>
 public override bool Initialize(IReportIssuesToPullRequestSettings settings)
 {
     // Fail initialization if no pull request could be found.
     return(base.Initialize(settings) && this.azureDevOpsPullRequest.HasPullRequestLoaded);
 }