private IEnumerable <GitPullRequestCommentThread> CreateDiscussionThreads( GitHttpClient gitClient, IEnumerable <IIssue> issues, string commentSource) { // ReSharper disable once PossibleMultipleEnumeration issues.NotNull(nameof(issues)); this.Log.Verbose("Creating new discussion threads"); var result = new List <GitPullRequestCommentThread>(); // Code flow properties var iterationId = 0; GitPullRequestIterationChanges changes = null; if (this.tfsPullRequest.CodeReviewId > 0) { iterationId = this.GetCodeFlowLatestIterationId(gitClient); changes = this.GetCodeFlowChanges(gitClient, iterationId); } // Filter isues not related to a file. if (!this.settings.ReportIssuesNotRelatedToAFile) { issues = issues.Where(x => x.AffectedFileRelativePath != null); } // ReSharper disable once PossibleMultipleEnumeration foreach (var issue in issues) { this.Log.Information( "Creating a discussion comment for the issue at line {0} from {1}", issue.Line, issue.AffectedFileRelativePath); var newThread = new GitPullRequestCommentThread() { Status = CommentThreadStatus.Active }; var discussionComment = new Comment { CommentType = CommentType.System, IsDeleted = false, Content = ContentProvider.GetContent(issue) }; if (!this.AddThreadProperties(newThread, changes, issue, iterationId, commentSource)) { continue; } newThread.Comments = new List <Comment> { discussionComment }; result.Add(newThread); } return(result); }
private bool AddThreadProperties( GitPullRequestCommentThread thread, GitPullRequestIterationChanges changes, IIssue issue, int iterationId, string commentSource) { thread.NotNull(nameof(thread)); changes.NotNull(nameof(changes)); issue.NotNull(nameof(issue)); var properties = new PropertiesCollection(); if (issue.AffectedFileRelativePath != null) { if (this.tfsPullRequest.CodeReviewId > 0) { var changeTrackingId = this.TryGetCodeFlowChangeTrackingId(changes, issue.AffectedFileRelativePath); if (changeTrackingId < 0) { // Don't post comment if we couldn't determine the change. return(false); } AddCodeFlowProperties(issue, iterationId, changeTrackingId, properties); } else { throw new NotSupportedException("Legacy code reviews are not supported."); } } // A VSTS UI extension will recognize this and format the comments differently. properties.Add("CodeAnalysisThreadType", "CodeAnalysisIssue"); thread.Properties = properties; // Add a custom property to be able to distinguish all comments created this way. thread.SetCommentSource(commentSource); // Add a custom property to be able to return issue message from existing threads, // without any formatting done by this addin, back to Cake.Issues.PullRequests. thread.SetIssueMessage(issue.Message); return(true); }
private int TryGetCodeFlowChangeTrackingId(GitPullRequestIterationChanges changes, FilePath path) { changes.NotNull(nameof(changes)); path.NotNull(nameof(path)); var change = changes.ChangeEntries.Where(x => x.Item.Path == "/" + path.ToString()).ToList(); if (change.Count == 0) { this.Log.Error( "Cannot post a comment for the file {0} because no changes on the pull request server could be found.", path); return(-1); } if (change.Count > 1) { this.Log.Error( "Cannot post a comment for the file {0} because more than one change has been found on the pull request server:" + Environment.NewLine + "{1}", path, string.Join( Environment.NewLine, change.Select( x => string.Format( CultureInfo.InvariantCulture, " ID: {0}, Path: {1}", x.ChangeId, x.Item.Path)))); return(-1); } var changeTrackingId = change.Single().ChangeTrackingId; this.Log.Verbose( "Determined ChangeTrackingId of {0} for {1}", changeTrackingId, path); return(changeTrackingId); }
private int TryGetCodeFlowChangeTrackingId(GitPullRequestIterationChanges changes, FilePath path) { changes.NotNull(nameof(changes)); path.NotNull(nameof(path)); var change = changes.ChangeEntries.Where(x => x.Item.Path == "/" + path.ToString()).ToList(); if (change.Count != 1) { this.Log.Error( "Cannot post a comment for the file {0} because no changes could be found.", path); return(-1); } var changeTrackingId = change.Single().ChangeTrackingId; this.Log.Verbose( "Determined ChangeTrackingId of {0} for {1}", changeTrackingId, path); return(changeTrackingId); }
protected override Mock <GitHttpClient> Setup(Mock <GitHttpClient> m) { m.Setup(arg => arg.CreatePullRequestReviewerAsync( It.Is <IdentityRefWithVote>(i => Enum.IsDefined(typeof(AzureDevOpsPullRequestVote), i.Vote)), It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <object>(), default(CancellationToken))) .ReturnsAsync((IdentityRefWithVote identity, Guid project, int prId, string reviewerId, object o, CancellationToken c) => new IdentityRefWithVote { Vote = identity.Vote, }); m.Setup(arg => arg.CreatePullRequestReviewerAsync( It.Is <IdentityRefWithVote>(i => !Enum.IsDefined(typeof(AzureDevOpsPullRequestVote), i.Vote)), It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <object>(), default(CancellationToken))) .Throws(new Exception("Something went wrong")); m.Setup(arg => arg.CreatePullRequestStatusAsync( It.IsAny <GitPullRequestStatus>(), It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .ReturnsAsync((GitPullRequestStatus status, Guid repoId, int prId, object o, CancellationToken c) => new GitPullRequestStatus { Context = status.Context, State = status.State, }); // Setup CommitDiffs object var gitChanges = new List <GitChange> { new GitChange { ChangeId = 1, ChangeType = VersionControlChangeType.Edit, Item = new GitItem("/src/project/myclass.cs", "ID1", GitObjectType.Commit, "6b13ff8", 0), }, null, new GitChange { ChangeId = 2, ChangeType = VersionControlChangeType.Edit, Item = new GitItem("/tools/folder", "ID2", GitObjectType.Tree, "6b13ff8", 0), }, }; var gitCommitDiffs = new GitCommitDiffs { ChangeCounts = new Dictionary <VersionControlChangeType, int> { { VersionControlChangeType.Edit, 2 } }, Changes = gitChanges, }; m.Setup(arg => arg.GetCommitDiffsAsync( It.IsAny <string>(), It.IsAny <Guid>(), true, null, null, It.IsAny <GitBaseVersionDescriptor>(), It.IsAny <GitTargetVersionDescriptor>(), null, CancellationToken.None)) .ReturnsAsync((string prj, Guid rId, bool?b, int?t, int?s, GitBaseVersionDescriptor bvd, GitTargetVersionDescriptor tvd, object o, CancellationToken c) => gitCommitDiffs); m.Setup(arg => arg.UpdateThreadAsync( It.IsAny <GitPullRequestCommentThread>(), It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <int>(), null, CancellationToken.None)) .ReturnsAsync((GitPullRequestCommentThread prct, Guid g, int prId, int thId, object o, CancellationToken c) => new GitPullRequestCommentThread { Id = thId, Status = prct.Status }); // Setup GitPullRequestCommentThread collection var commentThreads = new List <GitPullRequestCommentThread> { new GitPullRequestCommentThread { Id = 11, ThreadContext = new CommentThreadContext() { FilePath = "/some/path/to/file.cs", }, Comments = new List <Comment> { new Comment { Content = "Hello", IsDeleted = false, CommentType = CommentType.CodeChange }, new Comment { Content = "Goodbye", IsDeleted = true, CommentType = CommentType.Text }, }, Status = CommentThreadStatus.Active, }, new GitPullRequestCommentThread { Id = 22, ThreadContext = null, Comments = new List <Comment>(), Status = CommentThreadStatus.Fixed, }, }; m.Setup(arg => arg.GetThreadsAsync( It.IsAny <Guid>(), It.IsAny <int>(), null, null, null, CancellationToken.None)) .ReturnsAsync((Guid rId, int prId, int?it, int?baseIt, object o, CancellationToken c) => commentThreads); m.Setup(arg => arg.CreateThreadAsync( It.IsAny <GitPullRequestCommentThread>(), It.IsAny <Guid>(), It.IsAny <int>(), null, CancellationToken.None)) .ReturnsAsync((GitPullRequestCommentThread prct, Guid g, int i, object o, CancellationToken c) => prct); m.Setup(arg => arg.GetPullRequestIterationsAsync( It.IsAny <Guid>(), It.Is <int>(i => i != 13), null, null, CancellationToken.None)) .ReturnsAsync((Guid repoId, int prId, bool?b, object o, CancellationToken c) => new List <GitPullRequestIteration> { new GitPullRequestIteration { Id = 42, CreatedDate = DateTime.Today.AddDays(-3) }, new GitPullRequestIteration { Id = 16, CreatedDate = DateTime.Today.AddDays(-1) }, }); m.Setup(arg => arg.GetPullRequestIterationsAsync( It.IsAny <Guid>(), It.Is <int>(i => i == 13), // Just to emulate the unlucky case null, null, CancellationToken.None)) .ReturnsAsync((Guid repoId, int prId, bool?b, object o, CancellationToken c) => new List <GitPullRequestIteration> { new GitPullRequestIteration { Id = null }, }); // Setup GitPullRequestIterationChanges collection var changes = new GitPullRequestIterationChanges { ChangeEntries = new List <GitPullRequestChange> { new GitPullRequestChange { ChangeId = 100, ChangeTrackingId = 1, Item = new GitItem { Path = "/src/my/class1.cs" } }, new GitPullRequestChange { ChangeId = 200, ChangeTrackingId = 2, Item = new GitItem { Path = string.Empty } }, }, }; m.Setup(arg => arg.GetPullRequestIterationChangesAsync( It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <int>(), null, null, null, null, CancellationToken.None)) .ReturnsAsync((Guid repoId, int prId, int iterId, int?t, int?s, int?ct, object o, CancellationToken c) => changes); // Setup pull request creation m.Setup(arg => arg.GetRefsAsync( It.IsAny <string>(), "MyRepoName", "NotExistingBranch", null, null, null, null, null, null, null, CancellationToken.None)) .ReturnsAsync(() => new List <GitRef>()); m.Setup(args => args.GetRepositoryAsync(It.IsAny <string>(), "MyRepoName", null, CancellationToken.None)) .ReturnsAsync(() => new GitRepository() { DefaultBranch = "master" }); m.Setup(arg => arg.GetRefsAsync( It.IsAny <string>(), "MyRepoName", "master", null, null, null, null, null, null, null, CancellationToken.None)) .ReturnsAsync(() => new List <GitRef>() { new GitRef("master"), }); m.Setup( arg => arg.CreatePullRequestAsync( It.IsAny <GitPullRequest>(), It.IsAny <string>(), It.IsAny <string>(), null, null, CancellationToken.None)) .ReturnsAsync( ( GitPullRequest gitPullRequestToCreate, string project, string repositoryId, bool?supportsIterations, object userState, CancellationToken cancellationToken) => { gitPullRequestToCreate.PullRequestId = 777; gitPullRequestToCreate.Repository = new GitRepository { Id = Guid.NewGuid(), Name = repositoryId, }; gitPullRequestToCreate.CodeReviewId = 123; gitPullRequestToCreate.LastMergeSourceCommit = new GitCommitRef { CommitId = "4a92b977" }; gitPullRequestToCreate.LastMergeTargetCommit = new GitCommitRef { CommitId = "78a3c113" }; return(gitPullRequestToCreate); }); return(m); }