private async Task AddRevisionAsync( ClaimsPrincipal user, ReviewModel review, string name, string label, Stream fileStream) { var revision = new ReviewRevisionModel(); ReviewCodeFileModel codeFile = await CreateFileAsync( revision.RevisionId, name, fileStream, review.RunAnalysis); revision.Files.Add(codeFile); revision.Author = user.GetGitHubLogin(); revision.Label = label; review.Revisions.Add(revision); // auto subscribe revision creation user await _notificationManager.SubscribeAsync(review, user); await _reviewsRepository.UpsertReviewAsync(review); await _notificationManager.NotifySubscribersOnNewRevisionAsync(revision, user); }
private async Task GetFormattedDiff(RenderedCodeFile renderedCodeFile, ReviewRevisionModel lastRevision, StringBuilder stringBuilder) { RenderedCodeFile autoReview = await _codeFileRepository.GetCodeFileAsync(lastRevision, false); var autoReviewTextFile = autoReview.RenderText(showDocumentation: false, skipDiff: true); var prCodeTextFile = renderedCodeFile.RenderText(showDocumentation: false, skipDiff: true); var diffLines = InlineDiff.Compute(autoReviewTextFile, prCodeTextFile, autoReviewTextFile, prCodeTextFile); if (diffLines == null || diffLines.Length == 0 || diffLines.Count(l => l.Kind != DiffLineKind.Unchanged) > 10) { return; } stringBuilder.Append(Environment.NewLine).Append("**API changes**").Append(Environment.NewLine); stringBuilder.Append("```diff").Append(Environment.NewLine); foreach (var line in diffLines) { if (line.Kind == DiffLineKind.Added) { stringBuilder.Append("+ ").Append(line.Line.DisplayString).Append(Environment.NewLine); } else if (line.Kind == DiffLineKind.Removed) { stringBuilder.Append("- ").Append(line.Line.DisplayString).Append(Environment.NewLine); } } stringBuilder.Append("```"); }
private void UpdateRevisionNames(ReviewModel review) { for (int i = 0; i < review.Revisions.Count; i++) { ReviewRevisionModel reviewRevisionModel = review.Revisions[i]; reviewRevisionModel.Name = $"rev {i} - {reviewRevisionModel.Files.Single().Name}"; } }
public async Task <string> GetApiDiffFromAutomaticReview(CodeFile codeFile, int prNumber, string originalFileName, MemoryStream memoryStream, PullRequestModel pullRequestModel) { // Get automatically generated master review for package or previously cloned review for this pull request var review = await GetBaseLineReview(codeFile.Language, codeFile.PackageName, pullRequestModel); if (review == null) { return(""); } // Check if API surface level matches with any revisions var renderedCodeFile = new RenderedCodeFile(codeFile); if (await IsReviewSame(review, renderedCodeFile)) { return(""); } if (pullRequestModel.ReviewId != null) { // If baseline review was already created and if APIs in current commit doesn't match any of the revisions in generated review then create new baseline using main branch and compare again. // If APIs are still different, find the diff against latest baseline. review = await GetBaseLineReview(codeFile.Language, codeFile.PackageName, pullRequestModel, true); review.ReviewId = pullRequestModel.ReviewId; if (await IsReviewSame(review, renderedCodeFile)) { return(""); } } var newRevision = new ReviewRevisionModel() { Author = review.Author, Label = "Created for PR " + prNumber }; var stringBuilder = new StringBuilder(); var diffUrl = REVIEW_DIFF_URL.Replace("{ReviewId}", review.ReviewId).Replace("{NewRevision}", review.Revisions.Last().RevisionId); stringBuilder.Append($"API changes have been detected in `{codeFile.PackageName}`. You can review API changes [here]({diffUrl})").Append(Environment.NewLine); // If review doesn't match with any revisions then generate formatted diff against last revision of automatic review await GetFormattedDiff(renderedCodeFile, review.Revisions.Last(), stringBuilder); var reviewCodeFileModel = await _reviewManager.CreateReviewCodeFileModel(newRevision.RevisionId, memoryStream, codeFile); reviewCodeFileModel.FileName = originalFileName; newRevision.Files.Add(reviewCodeFileModel); review.Revisions.Add(newRevision); pullRequestModel.ReviewId = review.ReviewId; review.FilterType = ReviewType.PullRequest; await _reviewsRepository.UpsertReviewAsync(review); await _pullRequestsRepository.UpsertPullRequestAsync(pullRequestModel); return(stringBuilder.ToString()); }
private async Task <bool> IsReviewSame(ReviewRevisionModel revision, RenderedCodeFile renderedCodeFile) { //This will compare and check if new code file content is same as revision in parameter var lastRevisionFile = await _codeFileRepository.GetCodeFileAsync(revision); var lastRevisionTextLines = lastRevisionFile.RenderText(showDocumentation: false, skipDiff: true); var fileTextLines = renderedCodeFile.RenderText(showDocumentation: false, skipDiff: true); return(lastRevisionTextLines.SequenceEqual(fileTextLines)); }
public async Task <ReviewModel> CreateMasterReviewAsync(ClaimsPrincipal user, string originalName, string label, Stream fileStream, bool runAnalysis) { //Generate code file from new uploaded package using var memoryStream = new MemoryStream(); var codeFile = await CreateCodeFile(originalName, fileStream, runAnalysis, memoryStream); //Get current master review for package and language var review = await GetMasterReviewAsync(user, codeFile.Language, codeFile.PackageName); if (review != null) { // Check if current review needs to be updated to rebuild using latest language processor if (review.UpdateAvailable) { await UpdateReviewAsync(user, review.ReviewId); } var noDiffFound = await IsReviewSame(review, codeFile); if (noDiffFound) { // No change is detected from last revision so no need to update this revision return(review); } } else { // Package and language combination doesn't have automatically created review. Create a new review. review = new ReviewModel { Author = user.GetGitHubLogin(), CreationDate = DateTime.UtcNow, RunAnalysis = runAnalysis, Name = originalName, IsAutomatic = true }; } // Check if user is authorized to modify automatic review await AssertAutomaticReviewModifier(user, review); // Update or insert review with new revision var revision = new ReviewRevisionModel() { Author = user.GetGitHubLogin(), Label = label }; var reviewCodeFileModel = await CreateReviewCodeFileModel(revision.RevisionId, memoryStream, codeFile); revision.Files.Add(reviewCodeFileModel); review.Revisions.Add(revision); await _reviewsRepository.UpsertReviewAsync(review); return(review); }
public async Task UpdateRevisionLabelAsync(ClaimsPrincipal user, string id, string revisionId, string label) { ReviewModel review = await GetReviewAsync(user, id); ReviewRevisionModel revision = review.Revisions.Single(r => r.RevisionId == revisionId); await AssertRevisionOwner(user, revision); revision.Label = label; await _reviewsRepository.UpsertReviewAsync(review); }
private async Task AssertApprover(ClaimsPrincipal user, ReviewRevisionModel revisionModel) { var result = await _authorizationService.AuthorizeAsync( user, revisionModel, new[] { ApproverRequirement.Instance }); if (!result.Succeeded) { throw new AuthorizationFailedException(); } }
public async Task AddRevisionAsync(ClaimsPrincipal user, string id, string originalName, Stream fileStream) { var review = await GetReviewAsync(user, id); await AssertOwnerAsync(user, review); var revision = new ReviewRevisionModel(); revision.Files.Add(await CreateFileAsync(revision.RevisionId, originalName, fileStream, review.RunAnalysis)); review.Revisions.Add(revision); UpdateRevisionNames(review); await _reviewsRepository.UpsertReviewAsync(review); }
public async Task DeleteRevisionAsync(ClaimsPrincipal user, string id, string revisionId) { ReviewModel review = await GetReviewAsync(user, id); ReviewRevisionModel revision = review.Revisions.Single(r => r.RevisionId == revisionId); await AssertRevisionOwner(user, revision); if (review.Revisions.Count < 2) { return; } review.Revisions.Remove(revision); await _reviewsRepository.UpsertReviewAsync(review); }
private async Task <ReviewRevisionModel> CreateBaselineRevision( CodeFile baselineCodeFile, MemoryStream baseLineStream, PullRequestModel prModel, string fileName) { var newRevision = new ReviewRevisionModel() { Author = prModel.Author, Label = $"Baseline for PR {prModel.PullRequestNumber}" }; var reviewCodeFileModel = await _reviewManager.CreateReviewCodeFileModel(newRevision.RevisionId, baseLineStream, baselineCodeFile); reviewCodeFileModel.FileName = fileName; newRevision.Files.Add(reviewCodeFileModel); return(newRevision); }
private IOrderedEnumerable <KeyValuePair <ReviewRevisionModel, List <CommentThreadModel> > > ParseThreads(IEnumerable <CommentThreadModel> threads) { var threadDict = new Dictionary <ReviewRevisionModel, List <CommentThreadModel> >(); foreach (var thread in threads) { ReviewRevisionModel lastRevisionForThread = null; int lastRevision = 0; foreach (var comment in thread.Comments) { if (comment.RevisionId == null) { continue; } ReviewRevisionModel commentRevision = Review.Revisions.SingleOrDefault(r => r.RevisionId == comment.RevisionId); if (commentRevision == null) { // if revision that comment was added in has been deleted continue; } var commentRevisionIndex = commentRevision.RevisionNumber; // Group each thread under the last revision where a comment was added for it. if (commentRevisionIndex >= lastRevision) { lastRevision = commentRevisionIndex; lastRevisionForThread = commentRevision; } } if (lastRevisionForThread == null) { continue; } if (!threadDict.ContainsKey(lastRevisionForThread)) { threadDict.Add(lastRevisionForThread, new List <CommentThreadModel>()); } threadDict[lastRevisionForThread].Add(thread); } return(threadDict.OrderByDescending(kvp => Review.Revisions.IndexOf(kvp.Key))); }
public async Task <ReviewModel> CreateReviewAsync(ClaimsPrincipal user, string originalName, Stream fileStream, bool runAnalysis) { ReviewModel review = new ReviewModel(); review.Author = user.GetGitHubLogin(); review.CreationDate = DateTime.UtcNow; review.RunAnalysis = runAnalysis; var revision = new ReviewRevisionModel(); var reviewCodeFileModel = await CreateFileAsync(revision.RevisionId, originalName, fileStream, runAnalysis); revision.Files.Add(reviewCodeFileModel); review.Name = reviewCodeFileModel.Name; review.Revisions.Add(revision); UpdateRevisionNames(review); await _reviewsRepository.UpsertReviewAsync(review); return(review); }
public async Task ToggleApprovalAsync(ClaimsPrincipal user, string id, string revisionId) { ReviewModel review = await GetReviewAsync(user, id); ReviewRevisionModel revision = review.Revisions.Single(r => r.RevisionId == revisionId); await AssertApprover(user, revision); var userId = user.GetGitHubLogin(); if (revision.Approvers.Contains(userId)) { //Revert approval revision.Approvers.Remove(userId); } else { //Approve revision revision.Approvers.Add(userId); } await _reviewsRepository.UpsertReviewAsync(review); }
private async Task CreateTestDataIfNotExistAsync() { var dataBaseResponse = await _cosmosClient.CreateDatabaseIfNotExistsAsync("APIView"); var containerResponse = await dataBaseResponse.Database.CreateContainerIfNotExistsAsync("Reviews", "/id"); if (containerResponse.StatusCode.Equals(HttpStatusCode.Created)) { foreach (int value in Enumerable.Range(1, 2)) { string[] languages = new String[] { "TestLanguageOne", "TestLanguageTwo" }; foreach (string language in languages) { ReviewType[] reviewTypes = new ReviewType[] { ReviewType.Manual, ReviewType.Automatic, ReviewType.PullRequest, ReviewType.All }; foreach (ReviewType reviewType in reviewTypes) { bool[] boolvalues = new bool[] { true, false }; foreach (var boolValue in boolvalues) { foreach (var boolVal in boolvalues) { var container = containerResponse.Container; ReviewModel review = new ReviewModel { Name = $"TestReview{value}_For_{language}_For_{reviewType.ToString()}_For{boolValue}_For{boolVal}", Author = $"TestReviewAuthor{value}", CreationDate = DateTime.Now, IsClosed = boolValue, FilterType = reviewType, ServiceName = $"TestServiceName{value}", PackageDisplayName = $"TestPackageDisplayName{value}" }; var revisions = new ReviewRevisionModelList(review); foreach (int val in Enumerable.Range(1, 2)) { var revision = new ReviewRevisionModel { Name = $"TestRevision{value}_{val}", }; if (boolVal) { revision.Approvers.Add($"TestApprover{value}_{val}"); } foreach (int v in Enumerable.Range(1, 2)) { var file = new ReviewCodeFileModel { Name = $"TestFile{value}_{val}_{v}", Language = language }; revision.Files.Add(file); } revisions.Add(revision); } review.Revisions = revisions; await _cosmosReviewRepository.UpsertReviewAsync(review); } } } } } } }
public async Task <ReviewModel> CreateMasterReviewAsync(ClaimsPrincipal user, string originalName, string label, Stream fileStream, bool runAnalysis) { //Generate code file from new uploaded package using var memoryStream = new MemoryStream(); var codeFile = await CreateCodeFile(originalName, fileStream, runAnalysis, memoryStream); //Get current master review for package and language var review = await _reviewsRepository.GetMasterReviewForPackageAsync(codeFile.Language, codeFile.PackageName); bool createNewRevision = true; if (review != null) { // Delete pending revisions if it is not in approved state before adding new revision // This is to keep only one pending revision since last approval or from initial review revision var lastRevision = review.Revisions.LastOrDefault(); while (lastRevision.Approvers.Count == 0 && review.Revisions.Count > 1) { review.Revisions.Remove(lastRevision); lastRevision = review.Revisions.LastOrDefault(); } var renderedCodeFile = new RenderedCodeFile(codeFile); var noDiffFound = await IsReviewSame(review.Revisions.LastOrDefault(), renderedCodeFile); if (noDiffFound) { // No change is detected from last revision so no need to update this revision createNewRevision = false; } } else { // Package and language combination doesn't have automatically created review. Create a new review. review = new ReviewModel { Author = user.GetGitHubLogin(), CreationDate = DateTime.UtcNow, RunAnalysis = runAnalysis, Name = originalName, IsAutomatic = true }; } // Check if user is authorized to modify automatic review await AssertAutomaticReviewModifier(user, review); if (createNewRevision) { // Update or insert review with new revision var revision = new ReviewRevisionModel() { Author = user.GetGitHubLogin(), Label = label }; var reviewCodeFileModel = await CreateReviewCodeFileModel(revision.RevisionId, memoryStream, codeFile); reviewCodeFileModel.FileName = originalName; revision.Files.Add(reviewCodeFileModel); review.Revisions.Add(revision); } // Check if review can be marked as approved if another review with same surface level is in approved status if (review.Revisions.Last().Approvers.Count() == 0) { var matchingApprovedRevision = await FindMatchingApprovedRevision(review); if (matchingApprovedRevision != null) { foreach (var approver in matchingApprovedRevision.Approvers) { review.Revisions.Last().Approvers.Add(approver); } } } await _reviewsRepository.UpsertReviewAsync(review); return(review); }
private async Task CreateRevisionIfRequired(CodeFile codeFile, int prNumber, string originalFileName, MemoryStream memoryStream, PullRequestModel pullRequestModel, CodeFile baselineCodeFile, MemoryStream baseLineStream, string baselineFileName) { var newRevision = new ReviewRevisionModel() { Author = pullRequestModel.Author, Label = $"Created for PR {prNumber}" }; // Get automatically generated master review for package or previously cloned review for this pull request var review = await GetBaseLineReview(codeFile.Language, codeFile.PackageName, pullRequestModel); if (review == null) { // If base line is not available (possible if package is new or request coming from SDK automation) review = CreateNewReview(pullRequestModel); // If request passes code file for baseline if (baselineCodeFile != null) { var baseline = await CreateBaselineRevision(baselineCodeFile, baseLineStream, pullRequestModel, baselineFileName); review.Revisions.Add(baseline); } } else { // Check if API surface level matches with any revisions var renderedCodeFile = new RenderedCodeFile(codeFile); if (await IsReviewSame(review, renderedCodeFile)) { return; } if (pullRequestModel.ReviewId != null) { //Refresh baseline using latest from automatic review var prevRevisionId = review.Revisions.Last().RevisionId; review = await GetBaseLineReview(codeFile.Language, codeFile.PackageName, pullRequestModel, true); review.ReviewId = pullRequestModel.ReviewId; newRevision.RevisionId = prevRevisionId; } } var reviewCodeFileModel = await _reviewManager.CreateReviewCodeFileModel(newRevision.RevisionId, memoryStream, codeFile); reviewCodeFileModel.FileName = originalFileName; newRevision.Files.Add(reviewCodeFileModel); review.Revisions.Add(newRevision); pullRequestModel.ReviewId = review.ReviewId; review.FilterType = ReviewType.PullRequest; await _reviewsRepository.UpsertReviewAsync(review); await _pullRequestsRepository.UpsertPullRequestAsync(pullRequestModel); }
private async Task <ReviewRevisionModel> CreateMasterReviewAsync(ClaimsPrincipal user, CodeFile codeFile, string originalName, string label, MemoryStream memoryStream, bool compareAllRevisions) { var renderedCodeFile = new RenderedCodeFile(codeFile); //Get current master review for package and language var review = await _reviewsRepository.GetMasterReviewForPackageAsync(codeFile.Language, codeFile.PackageName); bool createNewRevision = true; ReviewRevisionModel reviewRevision = null; if (review != null) { // Delete pending revisions if it is not in approved state and if it doesn't have any comments before adding new revision // This is to keep only one pending revision since last approval or from initial review revision var lastRevision = review.Revisions.LastOrDefault(); var comments = await _commentsRepository.GetCommentsAsync(review.ReviewId); while (lastRevision.Approvers.Count == 0 && review.Revisions.Count > 1 && !await IsReviewSame(lastRevision, renderedCodeFile) && !comments.Any(c => lastRevision.RevisionId == c.RevisionId)) { review.Revisions.Remove(lastRevision); lastRevision = review.Revisions.LastOrDefault(); } // We should compare against only latest revision when calling this API from scheduled CI runs // But any manual pipeline run at release time should compare against all approved revisions to ensure hotfix release doesn't have API change // If review surface doesn't match with any approved revisions then we will create new revision if it doesn't match pending latest revision if (compareAllRevisions) { foreach (var approvedRevision in review.Revisions.Where(r => r.IsApproved).Reverse()) { if (await IsReviewSame(approvedRevision, renderedCodeFile)) { return(approvedRevision); } } } if (await IsReviewSame(lastRevision, renderedCodeFile)) { reviewRevision = lastRevision; createNewRevision = false; } } else { // Package and language combination doesn't have automatically created review. Create a new review. review = new ReviewModel { Author = user.GetGitHubLogin(), CreationDate = DateTime.UtcNow, RunAnalysis = false, Name = originalName, FilterType = ReviewType.Automatic }; } // Check if user is authorized to modify automatic review await AssertAutomaticReviewModifier(user, review); if (createNewRevision) { // Update or insert review with new revision reviewRevision = new ReviewRevisionModel() { Author = user.GetGitHubLogin(), Label = label }; var reviewCodeFileModel = await CreateReviewCodeFileModel(reviewRevision.RevisionId, memoryStream, codeFile); reviewCodeFileModel.FileName = originalName; reviewRevision.Files.Add(reviewCodeFileModel); review.Revisions.Add(reviewRevision); } // Check if review can be marked as approved if another review with same surface level is in approved status if (review.Revisions.Last().Approvers.Count() == 0) { var matchingApprovedRevision = await FindMatchingApprovedRevision(review); if (matchingApprovedRevision != null) { foreach (var approver in matchingApprovedRevision.Approvers) { review.Revisions.Last().Approvers.Add(approver); } } } await _reviewsRepository.UpsertReviewAsync(review); return(reviewRevision); }