From aefd20ef7ba742130bbb5c8588504952f653b66e Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Thu, 5 Oct 2023 20:25:02 -0700 Subject: [PATCH] Code-window working --- .../APIViewWeb/Helpers/PageModelHelpers.cs | 172 ++++++++++++++++- .../LeanControllers/ReviewsController.cs | 31 +-- .../LeanModels/ReviewRevisionPageModels.cs | 3 +- .../Pages/Assemblies/Review.cshtml.cs | 176 +----------------- .../Repositories/CosmosCommentsRepository.cs | 2 +- .../code-panel/code-panel.component.html | 17 +- .../code-panel/code-panel.component.scss | 16 ++ .../code-panel/code-panel.component.ts | 11 +- .../review-info/review-info.component.scss | 4 + .../review-nav/review-nav.component.scss | 2 + .../review-page/review-page.component.html | 4 +- .../review-page/review-page.component.scss | 5 + .../review-page/review-page.component.ts | 18 +- .../ClientSPA/src/app/_models/review.ts | 70 ++++--- .../src/app/_pipes/sanitize-html.pipe.spec.ts | 8 + .../src/app/_pipes/sanitize-html.pipe.ts | 12 ++ .../app/_services/reviews/reviews.service.ts | 3 +- .../ClientSPA/src/app/app-routing.module.ts | 1 + .../APIView/ClientSPA/src/app/app.module.ts | 4 +- 19 files changed, 327 insertions(+), 232 deletions(-) create mode 100644 src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.spec.ts create mode 100644 src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.ts diff --git a/src/dotnet/APIView/APIViewWeb/Helpers/PageModelHelpers.cs b/src/dotnet/APIView/APIViewWeb/Helpers/PageModelHelpers.cs index bff0eb6fe5c..07de52ac793 100644 --- a/src/dotnet/APIView/APIViewWeb/Helpers/PageModelHelpers.cs +++ b/src/dotnet/APIView/APIViewWeb/Helpers/PageModelHelpers.cs @@ -1,6 +1,12 @@ -using System.Security.Claims; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using APIView.DIff; +using ApiView; +using APIView; using APIViewWeb.Models; using APIViewWeb.Repositories; +using System; namespace APIViewWeb.Helpers { @@ -20,5 +26,169 @@ public static string GetHiddenApiClass(UserPreferenceModel userPreference) } return hiddenApiClass; } + + public static CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, InlineDiffLine[] lines, + ReviewCommentsModel comments, bool showDiffOnly, int reviewDiffContextSize, string diffContextSeparator, + HashSet headingsOfSectionsWithDiff, bool hideCommentRows = false) + { + if (showDiffOnly) + { + lines = CreateDiffOnlyLines(lines, reviewDiffContextSize, diffContextSeparator); + if (lines.Length == 0) + { + return Array.Empty(); + } + } + List documentedByLines = new List(); + int lineNumberExcludingDocumentation = 0; + int diffSectionId = 0; + + return lines.Select( + (diffLine, index) => + { + if (diffLine.Line.IsDocumentation) + { + // documentedByLines must include the index of a line, assuming that documentation lines are counted + documentedByLines.Add(++index); + return new CodeLineModel( + kind: diffLine.Kind, + codeLine: diffLine.Line, + commentThread: comments.TryGetThreadForLine(diffLine.Line.ElementId, out var thread, hideCommentRows) ? + thread : + null, + diagnostics: diffLine.Kind != DiffLineKind.Removed ? + diagnostics.Where(d => d.TargetId == diffLine.Line.ElementId).ToArray() : + Array.Empty(), + lineNumber: lineNumberExcludingDocumentation, + documentedByLines: new int[] { }, + isDiffView: true, + diffSectionId: diffLine.Line.SectionKey != null ? ++diffSectionId : null, + otherLineSectionKey: diffLine.Kind == DiffLineKind.Unchanged ? diffLine.OtherLine.SectionKey : null, + headingsOfSectionsWithDiff: headingsOfSectionsWithDiff, + isSubHeadingWithDiffInSection: diffLine.IsHeadingWithDiffInSection + ); + } + else + { + CodeLineModel c = new CodeLineModel( + kind: diffLine.Kind, + codeLine: diffLine.Line, + commentThread: diffLine.Kind != DiffLineKind.Removed && + comments.TryGetThreadForLine(diffLine.Line.ElementId, out var thread, hideCommentRows) ? + thread : + null, + diagnostics: diffLine.Kind != DiffLineKind.Removed ? + diagnostics.Where(d => d.TargetId == diffLine.Line.ElementId).ToArray() : + Array.Empty(), + lineNumber: diffLine.Line.LineNumber ?? ++lineNumberExcludingDocumentation, + documentedByLines: documentedByLines.ToArray(), + isDiffView: true, + diffSectionId: diffLine.Line.SectionKey != null ? ++diffSectionId : null, + otherLineSectionKey: diffLine.Kind == DiffLineKind.Unchanged ? diffLine.OtherLine.SectionKey : null, + headingsOfSectionsWithDiff: headingsOfSectionsWithDiff, + isSubHeadingWithDiffInSection: diffLine.IsHeadingWithDiffInSection + ); + documentedByLines.Clear(); + return c; + } + }).ToArray(); + } + + public static CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, CodeLine[] lines, ReviewCommentsModel comments, bool hideCommentRows = false) + { + List documentedByLines = new List(); + int lineNumberExcludingDocumentation = 0; + return lines.Select( + (line, index) => + { + if (line.IsDocumentation) + { + // documentedByLines must include the index of a line, assuming that documentation lines are counted + documentedByLines.Add(++index); + return new CodeLineModel( + DiffLineKind.Unchanged, + line, + comments.TryGetThreadForLine(line.ElementId, out var thread, hideCommentRows) ? thread : null, + diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), + lineNumberExcludingDocumentation, + new int[] { } + ); + } + else + { + CodeLineModel c = new CodeLineModel( + DiffLineKind.Unchanged, + line, + comments.TryGetThreadForLine(line.ElementId, out var thread, hideCommentRows) ? thread : null, + diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), + line.LineNumber ?? ++lineNumberExcludingDocumentation, + documentedByLines.ToArray() + ); + documentedByLines.Clear(); + return c; + } + }).ToArray(); + } + + public static int ComputeActiveConversations(CodeLine[] lines, ReviewCommentsModel comments) + { + int activeThreads = 0; + foreach (CodeLine line in lines) + { + if (string.IsNullOrEmpty(line.ElementId)) + { + continue; + } + + // if we have comments for this line and the thread has not been resolved. + // Add "&& !thread.Comments.First().IsUsageSampleComment()" to exclude sample comments from being counted (This also prevents the popup before approval) + if (comments.TryGetThreadForLine(line.ElementId, out CommentThreadModel thread) && !thread.IsResolved) + { + activeThreads++; + } + } + return activeThreads; + } + + private static InlineDiffLine[] CreateDiffOnlyLines(InlineDiffLine[] lines, int reviewDiffContextSize, string diffContextSeparator) + { + var filteredLines = new List>(); + int lastAddedLine = -1; + for (int i = 0; i < lines.Count(); i++) + { + if (lines[i].Kind != DiffLineKind.Unchanged) + { + // Find starting index for pre context + int preContextIndx = Math.Max(lastAddedLine + 1, i - reviewDiffContextSize); + if (preContextIndx < i) + { + // Add sepearator to show skipping lines. for e.g. ..... + if (filteredLines.Count > 0) + { + filteredLines.Add(new InlineDiffLine(new CodeLine(diffContextSeparator, null, null), DiffLineKind.Unchanged)); + } + + while (preContextIndx < i) + { + filteredLines.Add(lines[preContextIndx]); + preContextIndx++; + } + } + //Add changed line + filteredLines.Add(lines[i]); + lastAddedLine = i; + + // Add post context + int contextStart = i + 1, contextEnd = i + reviewDiffContextSize; + while (contextStart <= contextEnd && contextStart < lines.Count() && lines[contextStart].Kind == DiffLineKind.Unchanged) + { + filteredLines.Add(lines[contextStart]); + lastAddedLine = contextStart; + contextStart++; + } + } + } + return filteredLines.ToArray(); + } } } diff --git a/src/dotnet/APIView/APIViewWeb/LeanControllers/ReviewsController.cs b/src/dotnet/APIView/APIViewWeb/LeanControllers/ReviewsController.cs index 69eacab4732..fd9a8e14621 100644 --- a/src/dotnet/APIView/APIViewWeb/LeanControllers/ReviewsController.cs +++ b/src/dotnet/APIView/APIViewWeb/LeanControllers/ReviewsController.cs @@ -9,6 +9,9 @@ using System.Threading.Tasks; using APIViewWeb.Managers.Interfaces; using System.Linq; +using System; +using APIView; +using Microsoft.AspNetCore.Authorization; namespace APIViewWeb.LeanControllers { @@ -17,14 +20,17 @@ public class ReviewsController : BaseApiController private readonly ILogger _logger; private readonly IReviewManager _reviewManager; private readonly IReviewRevisionsManager _reviewRevisionsManager; + private readonly ICommentsManager _commentManager; private readonly IBlobCodeFileRepository _codeFileRepository; public ReviewsController(ILogger logger, - IReviewRevisionsManager reviewRevisionsManager, IReviewManager reviewManager, IBlobCodeFileRepository codeFileRepository) + IReviewRevisionsManager reviewRevisionsManager, IReviewManager reviewManager, + ICommentsManager commentManager, IBlobCodeFileRepository codeFileRepository) { _logger = logger; _reviewRevisionsManager = reviewRevisionsManager; _reviewManager = reviewManager; + _commentManager = commentManager; _codeFileRepository = codeFileRepository; } @@ -50,22 +56,25 @@ public async Task>> GetReviewsAsync( /// [HttpGet] [Route("{reviewId}/content")] - public async Task> GetReviewContentAsync(string reviewId, string revisionId= null) + public async Task> GetReviewContentAsync(string reviewId, [FromQuery]string revisionId=null) { - var review = await _reviewManager.GetReviewAsync(reviewId); - var revisions = await _reviewRevisionsManager.GetReviewRevisionsAsync(reviewId); - var activeRevision = (string.IsNullOrEmpty(revisionId)) ? - await _reviewRevisionsManager.GetLatestReviewRevisionsAsync(reviewId, revisions) : await _reviewRevisionsManager.GetReviewRevisionAsync(revisionId); + var review = await _reviewManager.GetReviewAsync(reviewId); + var revisions = await _reviewRevisionsManager.GetReviewRevisionsAsync(reviewId); + var activeRevision = (string.IsNullOrEmpty(revisionId)) ? + await _reviewRevisionsManager.GetLatestReviewRevisionsAsync(reviewId, revisions) : await _reviewRevisionsManager.GetReviewRevisionAsync(revisionId); + var comments = await _commentManager.GetReviewCommentsAsync(reviewId); - - var reviewCodeFie = await _codeFileRepository.GetCodeFileAsync(activeRevision.Id, activeRevision.Files[0].ReviewFileId); - reviewCodeFie.Render(showDocumentation: true); + var renderableCodeFile = await _codeFileRepository.GetCodeFileAsync(activeRevision.Id, activeRevision.Files[0].ReviewFileId); + var reviewCodeFile = renderableCodeFile.CodeFile; + var fileDiagnostics = reviewCodeFile.Diagnostics ?? Array.Empty(); + var htmlLines = renderableCodeFile.Render(showDocumentation: false); + var codeLines = PageModelHelpers.CreateLines(diagnostics: fileDiagnostics, lines: htmlLines, comments: comments); var pageModel = new ReviewContentModel { Review = review, - Navigation = reviewCodeFie.CodeFile.Navigation, - codeLines = reviewCodeFie.RenderResult.CodeLines, + Navigation = renderableCodeFile.CodeFile.Navigation, + codeLines = codeLines, ReviewRevisions = revisions.GroupBy(r => r.ReviewRevisionType).ToDictionary(r => r.Key.ToString(), r => r.ToList()), ActiveRevision = activeRevision }; diff --git a/src/dotnet/APIView/APIViewWeb/LeanModels/ReviewRevisionPageModels.cs b/src/dotnet/APIView/APIViewWeb/LeanModels/ReviewRevisionPageModels.cs index eadf57df1c9..68073025792 100644 --- a/src/dotnet/APIView/APIViewWeb/LeanModels/ReviewRevisionPageModels.cs +++ b/src/dotnet/APIView/APIViewWeb/LeanModels/ReviewRevisionPageModels.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using ApiView; using APIView; +using APIViewWeb.Models; namespace APIViewWeb.LeanModels { @@ -8,7 +9,7 @@ public class ReviewContentModel { public ReviewListItemModel Review { get; set; } public NavigationItem[] Navigation { get; set; } - public CodeLine[] codeLines { get; set; } + public CodeLineModel[] codeLines { get; set; } public Dictionary> ReviewRevisions { get; set; } public ReviewRevisionListItemModel ActiveRevision { get; set; } } diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs index 23ad7c59e08..3270e10a652 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs @@ -122,7 +122,9 @@ public async Task OnGetAsync(string id, string revisionId = null) previousHtmlLines, fileHtmlLines); - Lines = CreateLines(fileDiagnostics, diffLines, Comments); + Lines = PageModelHelpers.CreateLines(diagnostics: fileDiagnostics, lines: diffLines, + comments: Comments, showDiffOnly: ShowDiffOnly, reviewDiffContextSize: REVIEW_DIFF_CONTEXT_SIZE, + diffContextSeparator: DIFF_CONTEXT_SEPERATOR, headingsOfSectionsWithDiff: HeadingsOfSectionsWithDiff); if (Lines.Length == 0) { var notifcation = new NotificationModel() { Message = "There is no diff between the two revisions.", Level = NotificatonLevel.Info }; @@ -132,10 +134,10 @@ public async Task OnGetAsync(string id, string revisionId = null) } else { - Lines = CreateLines(fileDiagnostics, fileHtmlLines, Comments); + Lines = PageModelHelpers.CreateLines(diagnostics: fileDiagnostics, lines: fileHtmlLines, comments: Comments); } - ActiveConversations = ComputeActiveConversations(fileHtmlLines, Comments); + ActiveConversations = PageModelHelpers.ComputeActiveConversations(lines: fileHtmlLines, comments: Comments); TotalActiveConversations = Comments.Threads.Count(t => !t.IsResolved); UsageSampleConversations = Comments.Threads.Count(t => t.Comments.FirstOrDefault()?.IsUsageSampleComment == true); var filterPreference = _preferenceCache.GetFilterType(User.GetGitHubLogin(), Review.FilterType); @@ -223,12 +225,14 @@ public async Task OnGetCodeLineSectionAsync( previousRevisionHtmlLines, currentHtmlLines); } - Lines = CreateLines(fileDiagnostics, diffLines, Comments, true); + Lines = PageModelHelpers.CreateLines(diagnostics: fileDiagnostics, lines: diffLines, comments: Comments, + showDiffOnly: ShowDiffOnly, reviewDiffContextSize: REVIEW_DIFF_CONTEXT_SIZE, + diffContextSeparator: DIFF_CONTEXT_SEPERATOR, headingsOfSectionsWithDiff: HeadingsOfSectionsWithDiff, hideCommentRows: true); } else { currentHtmlLines = renderedCodeFile.GetCodeLineSection(sectionKey); - Lines = CreateLines(fileDiagnostics, currentHtmlLines, Comments, true); + Lines = PageModelHelpers.CreateLines(diagnostics: fileDiagnostics, lines: currentHtmlLines, comments: Comments, hideCommentRows: true); } TempData["CodeLineSection"] = Lines; TempData["UserPreference"] = userPrefernce; @@ -312,167 +316,5 @@ private async Task GetReviewPageModelPropertiesAsync(string id, string revisionI HeadingsOfSectionsWithDiff = (DiffRevision != null && DiffRevision.HeadingsOfSectionsWithDiff.ContainsKey(Revision.RevisionId)) ? DiffRevision.HeadingsOfSectionsWithDiff[Revision.RevisionId] : new HashSet(); } - - private InlineDiffLine[] CreateDiffOnlyLines(InlineDiffLine[] lines) - { - var filteredLines = new List>(); - int lastAddedLine = -1; - for (int i = 0; i < lines.Count(); i++) - { - if (lines[i].Kind != DiffLineKind.Unchanged) - { - // Find starting index for pre context - int preContextIndx = Math.Max(lastAddedLine + 1, i - REVIEW_DIFF_CONTEXT_SIZE); - if (preContextIndx < i) - { - // Add sepearator to show skipping lines. for e.g. ..... - if (filteredLines.Count > 0) - { - filteredLines.Add(new InlineDiffLine(new CodeLine(DIFF_CONTEXT_SEPERATOR, null, null), DiffLineKind.Unchanged)); - } - - while (preContextIndx < i) - { - filteredLines.Add(lines[preContextIndx]); - preContextIndx++; - } - } - //Add changed line - filteredLines.Add(lines[i]); - lastAddedLine = i; - - // Add post context - int contextStart = i +1, contextEnd = i + REVIEW_DIFF_CONTEXT_SIZE; - while (contextStart <= contextEnd && contextStart < lines.Count() && lines[contextStart].Kind == DiffLineKind.Unchanged) - { - filteredLines.Add(lines[contextStart]); - lastAddedLine = contextStart; - contextStart++; - } - } - } - return filteredLines.ToArray(); - } - - private CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, InlineDiffLine[] lines, ReviewCommentsModel comments, bool hideCommentRows = false) - { - if (ShowDiffOnly) - { - lines = CreateDiffOnlyLines(lines); - if (lines.Length == 0) - { - return Array.Empty(); - } - } - List documentedByLines = new List(); - int lineNumberExcludingDocumentation = 0; - int diffSectionId = 0; - - return lines.Select( - (diffLine, index) => - { - if (diffLine.Line.IsDocumentation) - { - // documentedByLines must include the index of a line, assuming that documentation lines are counted - documentedByLines.Add(++index); - return new CodeLineModel( - kind: diffLine.Kind, - codeLine: diffLine.Line, - commentThread: comments.TryGetThreadForLine(diffLine.Line.ElementId, out var thread, hideCommentRows) ? - thread : - null, - diagnostics: diffLine.Kind != DiffLineKind.Removed ? - diagnostics.Where(d => d.TargetId == diffLine.Line.ElementId).ToArray() : - Array.Empty(), - lineNumber: lineNumberExcludingDocumentation, - documentedByLines: new int[] { }, - isDiffView: true, - diffSectionId: diffLine.Line.SectionKey != null ? ++diffSectionId : null, - otherLineSectionKey: diffLine.Kind == DiffLineKind.Unchanged ? diffLine.OtherLine.SectionKey : null, - headingsOfSectionsWithDiff: HeadingsOfSectionsWithDiff, - isSubHeadingWithDiffInSection: diffLine.IsHeadingWithDiffInSection - ); - } - else - { - CodeLineModel c = new CodeLineModel( - kind: diffLine.Kind, - codeLine: diffLine.Line, - commentThread: diffLine.Kind != DiffLineKind.Removed && - comments.TryGetThreadForLine(diffLine.Line.ElementId, out var thread, hideCommentRows) ? - thread : - null, - diagnostics: diffLine.Kind != DiffLineKind.Removed ? - diagnostics.Where(d => d.TargetId == diffLine.Line.ElementId).ToArray() : - Array.Empty(), - lineNumber: diffLine.Line.LineNumber ?? ++lineNumberExcludingDocumentation, - documentedByLines: documentedByLines.ToArray(), - isDiffView: true, - diffSectionId: diffLine.Line.SectionKey != null ? ++diffSectionId : null, - otherLineSectionKey: diffLine.Kind == DiffLineKind.Unchanged ? diffLine.OtherLine.SectionKey : null, - headingsOfSectionsWithDiff: HeadingsOfSectionsWithDiff, - isSubHeadingWithDiffInSection: diffLine.IsHeadingWithDiffInSection - ); - documentedByLines.Clear(); - return c; - } - }).ToArray(); - } - - private CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, CodeLine[] lines, ReviewCommentsModel comments, bool hideCommentRows = false) - { - List documentedByLines = new List(); - int lineNumberExcludingDocumentation = 0; - return lines.Select( - (line, index) => - { - if (line.IsDocumentation) - { - // documentedByLines must include the index of a line, assuming that documentation lines are counted - documentedByLines.Add(++index); - return new CodeLineModel( - DiffLineKind.Unchanged, - line, - comments.TryGetThreadForLine(line.ElementId, out var thread, hideCommentRows) ? thread : null, - diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), - lineNumberExcludingDocumentation, - new int[] {} - ); - } - else - { - CodeLineModel c = new CodeLineModel( - DiffLineKind.Unchanged, - line, - comments.TryGetThreadForLine(line.ElementId, out var thread, hideCommentRows) ? thread : null, - diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), - line.LineNumber ?? ++lineNumberExcludingDocumentation, - documentedByLines.ToArray() - ); - documentedByLines.Clear(); - return c; - } - }).ToArray(); - } - - private int ComputeActiveConversations(CodeLine[] lines, ReviewCommentsModel comments) - { - int activeThreads = 0; - foreach (CodeLine line in lines) - { - if (string.IsNullOrEmpty(line.ElementId)) - { - continue; - } - - // if we have comments for this line and the thread has not been resolved. - // Add "&& !thread.Comments.First().IsUsageSampleComment()" to exclude sample comments from being counted (This also prevents the popup before approval) - if (comments.TryGetThreadForLine(line.ElementId, out CommentThreadModel thread) && !thread.IsResolved) - { - activeThreads++; - } - } - return activeThreads; - } } } diff --git a/src/dotnet/APIView/APIViewWeb/Repositories/CosmosCommentsRepository.cs b/src/dotnet/APIView/APIViewWeb/Repositories/CosmosCommentsRepository.cs index d1bb4af6b6e..ab31d991ec1 100644 --- a/src/dotnet/APIView/APIViewWeb/Repositories/CosmosCommentsRepository.cs +++ b/src/dotnet/APIView/APIViewWeb/Repositories/CosmosCommentsRepository.cs @@ -16,7 +16,7 @@ public class CosmosCommentsRepository : ICosmosCommentsRepository public CosmosCommentsRepository(IConfiguration configuration, CosmosClient cosmosClient) { - _commentsContainer = cosmosClient.GetContainer("APIView", "Comments"); + _commentsContainer = cosmosClient.GetContainer("APIViewV2", "Comments"); } public async Task> GetCommentsAsync(string reviewId) diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html index 47aaae6b9f6..cd59acaf573 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.html @@ -1,9 +1,8 @@ -
- - -
- {{ item.displayString }} -
-
-
-
\ No newline at end of file + + + +
+
+
+
+
\ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss index e69de29bb2d..2a820d2fbec 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss @@ -0,0 +1,16 @@ +:host ::ng-deep { + .p-scroller-content { + min-width: 200%; + } + + .code-inner { + font-family: Consolas,monospace; + font-size: 14px; + line-height: 1.5; + line-height: 20px; + height: 100%; + word-wrap: normal; + white-space: pre; + width: 80%; + } +} \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts index 75ed07d898b..199f4e45fc6 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts @@ -1,16 +1,15 @@ import {Component, Input } from '@angular/core'; -import { CodeLine } from 'src/app/_models/review'; +import { ReviewLine, DiffLineKind} from 'src/app/_models/review'; declare var monaco: any; @Component({ selector: 'app-code-panel', templateUrl: './code-panel.component.html', - styleUrls: ['./code-panel.component.scss'] + styleUrls: ['./code-panel.component.scss'], }) -export class CodePanelComponent { - public _editor : any; - @Input() codeLines: CodeLine [] = []; - +export class CodePanelComponent { + @Input() reviewLines: ReviewLine[] = []; + public diffKind = DiffLineKind; } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-info/review-info.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/review-info/review-info.component.scss index 26dab6fad62..4862af5eabd 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-info/review-info.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-info/review-info.component.scss @@ -27,6 +27,10 @@ } .review-name { + display: block; max-width: 10vw; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-nav/review-nav.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/review-nav/review-nav.component.scss index 5878008a2bf..f5e9797f6e2 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-nav/review-nav.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-nav/review-nav.component.scss @@ -1,5 +1,7 @@ :host ::ng-deep { .p-tree { + font-family: Consolas,monospace; + font-size: 14px; border: 0px; padding: 0.1rem; max-height: 100%; diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html index b35bf2b62e0..26ccbe6bbb3 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html @@ -8,8 +8,8 @@ -
- +
+
diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss index a41fc9606a3..b4a7422f805 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss @@ -7,4 +7,9 @@ display: block; min-width: 0; } + + .code-window-container { + max-width: 100%; + overflow: auto; + } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts index 73628673a17..9535dd7e56a 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { CodeLine, NavigationItem, Review, ReviewContent } from 'src/app/_models/review'; +import { NavigationItem, Review, ReviewContent, ReviewLine } from 'src/app/_models/review'; import { Revision } from 'src/app/_models/revision'; import { ReviewsService } from 'src/app/_services/reviews/reviews.service'; @@ -12,7 +12,7 @@ import { ReviewsService } from 'src/app/_services/reviews/reviews.service'; export class ReviewPageComponent implements OnInit { review: Review | undefined = undefined; navigation: NavigationItem[] = [] - codeLines: CodeLine [] = []; + reviewLines: ReviewLine [] = []; reviewRevisions : Map = new Map(); activeRevision : Revision | undefined = undefined; @@ -22,15 +22,21 @@ export class ReviewPageComponent implements OnInit { ngOnInit() { const reviewId = this.route.snapshot.paramMap.get('reviewId'); - this.loadReviewContent(reviewId!); + const revisionId = this.route.snapshot.queryParamMap.get('revisionId'); + if (reviewId && revisionId) { + this.loadReviewContent(reviewId, revisionId); + } + else if (reviewId) { + this.loadReviewContent(reviewId); + } } - loadReviewContent(reviewId: string) { - this.reviewsService.getReviewContent(reviewId).subscribe({ + loadReviewContent(reviewId: string, revisionId: string | undefined = undefined) { + this.reviewsService.getReviewContent(reviewId, revisionId).subscribe({ next: (response: ReviewContent) => { this.review = response.review; this.navigation = response.navigation; - this.codeLines = response.codeLines; + this.reviewLines = response.codeLines; this.reviewRevisions = new Map(Object.entries(response.reviewRevisions)); this.activeRevision = response.activeRevision; } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_models/review.ts b/src/dotnet/APIView/ClientSPA/src/app/_models/review.ts index 68b23c977fc..ff225384a34 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_models/review.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_models/review.ts @@ -1,5 +1,11 @@ import { Revision } from "./revision" +export enum DiffLineKind { + Added, + Removed, + Unchanged +} + export interface Review { id: string packageName: string @@ -22,31 +28,45 @@ export interface ChangeHistory { } export interface ReviewContent { - review: Review - navigation: NavigationItem[] - codeLines: CodeLine[] - reviewRevisions: Map - activeRevision: Revision - } - - export interface NavigationItem { - text: string - navigationId: string - childItems: NavigationItem[] - tags: Map - isHiddenApi: boolean - } + review: Review + navigation: NavigationItem[] + codeLines: ReviewLine[] + reviewRevisions: Map + activeRevision: Revision +} - export interface CodeLine { - displayString: string - elementId?: string - lineClass: string - lineNumber: number - sectionKey: any - indent: number - isDocumentation: boolean - nodeRef: any - isHiddenApi: boolean - } +export interface NavigationItem { + text: string + navigationId: string + childItems: NavigationItem[] + tags: Map + isHiddenApi: boolean +} + +export interface CodeLine { + displayString: string + elementId?: string + lineClass: string + lineNumber: number + sectionKey: any + indent: number + isDocumentation: boolean + nodeRef: any + isHiddenApi: boolean +} + +export interface ReviewLine { + codeLine: CodeLine + diagnostics: any[] + commentThread: any + kind: DiffLineKind + lineNumber: number + documentedByLines: any[] + isDiffView: boolean + diffSectionId: any + otherLineSectionKey: any + headingsOfSectionsWithDiff: any[] + isSubHeadingWithDiffInSection: boolean +} \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.spec.ts b/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.spec.ts new file mode 100644 index 00000000000..52db29af784 --- /dev/null +++ b/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SanitizeHtmlPipe } from './sanitize-html.pipe'; + +describe('SanitizeHtmlPipe', () => { + it('create an instance', () => { + const pipe = new SanitizeHtmlPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.ts b/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.ts new file mode 100644 index 00000000000..15c6adbff26 --- /dev/null +++ b/src/dotnet/APIView/ClientSPA/src/app/_pipes/sanitize-html.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +@Pipe({ + name: 'sanitizeHtml' +}) +export class SanitizeHtmlPipe implements PipeTransform { + constructor(private _sanitizer:DomSanitizer) { } + transform(value: string): unknown { + return this._sanitizer.bypassSecurityTrustHtml(value); + } +} diff --git a/src/dotnet/APIView/ClientSPA/src/app/_services/reviews/reviews.service.ts b/src/dotnet/APIView/ClientSPA/src/app/_services/reviews/reviews.service.ts index 3012e080988..31c90f518d2 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_services/reviews/reviews.service.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_services/reviews/reviews.service.ts @@ -62,7 +62,6 @@ export class ReviewsService { getReviewContent(reviewId: string, revisionId: string = "") : Observable{ let params = new HttpParams(); params = params.append('revisionId', revisionId); - - return this.http.get(this.baseUrl + `/${reviewId}/content`); + return this.http.get(this.baseUrl + `/${reviewId}/content`, { params: params }); } } diff --git a/src/dotnet/APIView/ClientSPA/src/app/app-routing.module.ts b/src/dotnet/APIView/ClientSPA/src/app/app-routing.module.ts index 3db2d9b6366..a7a4873c503 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app-routing.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ const routes: Routes = [ runGuardsAndResolvers: 'always', canActivate: [AuthGuard], children: [ + { path: 'review/:reviewId/:revisionId', component: ReviewPageComponent }, { path: 'review/:reviewId', component: ReviewPageComponent }, ] }, diff --git a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts index 3ec4b5c5e56..567de974a79 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts @@ -35,6 +35,7 @@ import { VirtualScrollerModule } from 'primeng/virtualscroller'; import { ReviewInfoComponent } from './_components/review-info/review-info.component'; import { RevisionsListComponent } from './_components/revisions-list/revisions-list.component'; import { ReviewNavComponent } from './_components/review-nav/review-nav.component'; +import { SanitizeHtmlPipe } from './_pipes/sanitize-html.pipe'; @NgModule({ declarations: [ @@ -48,7 +49,8 @@ import { ReviewNavComponent } from './_components/review-nav/review-nav.componen CodePanelComponent, ReviewInfoComponent, RevisionsListComponent, - ReviewNavComponent + ReviewNavComponent, + SanitizeHtmlPipe ], imports: [ AppRoutingModule,