コード例 #1
0
        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);
        }
コード例 #2
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("```");
        }
コード例 #3
0
 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}";
     }
 }
コード例 #4
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());
        }
コード例 #5
0
        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));
        }
コード例 #6
0
        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);
        }
コード例 #7
0
        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);
        }
コード例 #8
0
        private async Task AssertApprover(ClaimsPrincipal user, ReviewRevisionModel revisionModel)
        {
            var result = await _authorizationService.AuthorizeAsync(
                user,
                revisionModel,
                new[] { ApproverRequirement.Instance });

            if (!result.Succeeded)
            {
                throw new AuthorizationFailedException();
            }
        }
コード例 #9
0
        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);
        }
コード例 #10
0
        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);
        }
コード例 #11
0
        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);
        }
コード例 #12
0
        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)));
        }
コード例 #13
0
        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);
        }
コード例 #14
0
        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);
        }
コード例 #15
0
        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);
                                }
                            }
                        }
                    }
                }
            }
        }
コード例 #16
0
        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);
        }
コード例 #17
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);
        }
コード例 #18
0
        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);
        }