Exemple #1
0
        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("```");
        }
Exemple #2
0
        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));
        }
Exemple #4
0
 private async Task <bool> IsReviewSame(ReviewModel review, RenderedCodeFile renderedCodeFile)
 {
     foreach (var revision in review.Revisions.Reverse())
     {
         if (await _reviewManager.IsReviewSame(revision, renderedCodeFile))
         {
             return(true);
         }
     }
     return(false);
 }
        private async Task <bool> IsReviewSame(ReviewModel review, CodeFile newCodeFile)
        {
            //This will compare and check if new code file content is same as last revision of given review in parameter

            //Get latest revision of review and check diff
            var lastRevisionFile = await _codeFileRepository.GetCodeFileAsync(review.Revisions.Last());

            var lastRevisionTextLines = lastRevisionFile.RenderText(false);

            var renderedCodeFile = new RenderedCodeFile(newCodeFile);
            var fileTextLines    = renderedCodeFile.RenderText(false);

            return(lastRevisionTextLines.SequenceEqual(fileTextLines));
        }
        public async Task <RenderedCodeFile> GetCodeFileAsync(string revisionId, string codeFileId)
        {
            var client = GetBlobClient(revisionId, codeFileId, out var key);

            if (_cache.TryGetValue <RenderedCodeFile>(key, out var codeFile))
            {
                return(codeFile);
            }

            var info = await client.DownloadAsync();

            codeFile = new RenderedCodeFile(await CodeFile.DeserializeAsync(info.Value.Content));

            using var _ = _cache.CreateEntry(key)
                          .SetSlidingExpiration(TimeSpan.FromDays(1))
                          .SetValue(codeFile);

            return(codeFile);
        }
        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 <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);
        }
Exemple #9
0
        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);
        }