$('#type-filter-select')[0]).sumo.unSelectAll();
+ searchBox.val('');
+ updateListedReviews();
+ });
+
+ var prevLanguageValue = languageSelect.val();
+ languageSelect.on('sumo:closed', function (e) {
+ var val = $(this).val() as string;
if (val == "C++" || val == "C#") {
- val = val.replace("C++", "Cpp").replace("C#", "Csharp");
- }
- var helpName = "#help-" + val;
- $(helpName).click();
- if (val == 'Cadl' || prevLanguageValue == 'Cadl') {
- const fileSelectors = $(".package-selector");
- for (var i = 0; i < fileSelectors.length; i++) {
- $(fileSelectors[i]).toggleClass("hidden-row");
- }
- }
- prevLanguageValue = val;
- });
-});
+ val = val.replace("C++", "Cpp").replace("C#", "Csharp");
+ }
+ $("#uploadModel").find(".card-body > div").addClass("d-none");
+ var helpName = "#" + val.toLowerCase() + "-help";
+ $(helpName).removeClass("d-none");
+ if (val == 'Cadl' || prevLanguageValue == 'Cadl') {
+ const fileSelectors = $(".package-selector");
+ for (var i = 0; i < fileSelectors.length; i++) {
+ $(fileSelectors[i]).toggleClass("d-none");
+ }
+ }
+ prevLanguageValue = val;
+ });
+
+ // Open / Close right Offcanvas Menu
+ $("#index-right-offcanvas-toggle").on('click', function () {
+ updatePageSettings(function () {
+ rightOffCanvasNavToggle("index-main-container");
+ });
+ });
+});
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts
similarity index 88%
rename from src/dotnet/APIView/APIViewWeb/Client/src/review.ts
rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts
index d29246c48b4..ff038c28176 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts
@@ -1,13 +1,14 @@
import Split from "split.js";
-import { updatePageSettings } from "./helpers";
+import { updatePageSettings, toggleCommentIcon } from "../shared/helpers";
+import { rightOffCanvasNavToggle } from "../shared/off-canvas";
$(() => {
const SEL_DOC_CLASS = ".documentation";
const SHOW_DOC_CHECK_COMPONENT = "#show-documentation-component";
- const SHOW_DOC_CHECKBOX = ".show-doc-checkbox";
- const SHOW_DOC_HREF = ".show-document";
+ const SHOW_DOC_CHECKBOX = ".show-documentation-checkbox";
+ const SHOW_DOC_HREF = ".show-documentation-switch";
const SHOW_DIFFONLY_CHECKBOX = ".show-diffonly-checkbox";
- const SHOW_DIFFONLY_HREF = ".show-diffonly";
+ const SHOW_DIFFONLY_HREF = ".show-diffonly-switch";
const TOGGLE_DOCUMENTATION = ".line-toggle-documentation-button";
const SEL_HIDDEN_CLASS = ".hidden-api-toggleable";
const SHOW_HIDDEN_CHECK_COMPONENT = "#show-hidden-api-component";
@@ -70,14 +71,20 @@ $(() => {
if (caretDirection.endsWith("right")) {
// In case the section passed has already been replaced with more rows
if (sectionContent.length == 1) {
- var sectionContentClass = sectionContent[0].className.replace(/\s/g, '.');
+ const sectionContentClass = sectionContent[0].className.replace(/\s/g, '.');
+ const sectionCommentClass = sectionContentClass.replace("code-line.", "comment-row.");
sectionContent = $(`.${sectionContentClass}`);
+ sectionContent.push(...$(`.${sectionCommentClass}`));
}
$.each(sectionContent, function (index, value) {
let rowClasses = $(value).attr("class");
if (rowClasses) {
- if (rowClasses.match(/lvl_1_/)) { // Only show first level rows of the section
+ if (rowClasses.match(/lvl_1_/)) {
+ if (rowClasses.match(/comment-row/) && !$("#show-comments-checkbox").prop("checked")) {
+ toggleCommentIcon($(value).attr("data-line-id"), true);
+ return; // Dont show comment row if show comments setting is unchecked
+ }
disableCommentsOnInRowTables($(value));
$(value).removeClass("d-none");
$(value).find("svg").attr("height", `${$(value).height()}`);
@@ -87,7 +94,6 @@ $(() => {
// Update section heading icons to open state
updateSectionHeadingIcons("OPEN", caretIcon, headingRow);
-
}
else {
$.each(sectionContent, function (index, value) {
@@ -129,6 +135,11 @@ $(() => {
// Show only immediate descendants
if (startShowing) {
if (rowClasses.match(new RegExp(`lvl_${Number(subSectionLevel) + 1}_`))) {
+ if (rowClasses.match(/comment-row/) && !$("#show-comments-checkbox").prop("checked")) {
+ toggleCommentIcon($(value).attr("data-line-id"), true);
+ return; // Dont show comment row if show comments setting is unchecked
+ }
+
disableCommentsOnInRowTables($(value));
$(value).removeClass("d-none");
let rowHeight = $(value).height() ?? 0;
@@ -219,7 +230,7 @@ $(() => {
if (sectionKeyB)
uri = uri + '§ionKeyB=' + sectionKeyB;
- const loadingMarkUp = "Loading... | ";
+ const loadingMarkUp = "Loading... | ";
const failedToLoadMarkUp = "Failed to load section. Refresh page and try again.
";
if (sectionContent.children(".spinner-border").length == 0) {
sectionContent.children("td").after(loadingMarkUp);
@@ -278,6 +289,12 @@ $(() => {
}
}
+ // Enable SumoSelect
+ $(document).ready(function () {
+ ($("#revision-select")).SumoSelect({ search: true, searchText: 'Search Revisions...' });
+ ($("#diff-select")).SumoSelect({ search: true, searchText: 'Search Revisons for Diff...' });
+ });
+
/* ADD FUNCTIONS TO LEFT NAVIGATION
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
/* Enable expand/collapse of navigation groups */
@@ -411,7 +428,7 @@ $(() => {
/* DROPDOWN FILTER FOR REVIEW, REVISIONS AND DIFF (UPDATES REVIEW PAGE ON CHANGE)
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
- $('#revisions-bootstraps-select, #review-bootstraps-select, #diff-bootstraps-select').each(function(index, value) {
+ $('#revision-select, #diff-select').each(function(index, value) {
$(this).on('change', function() {
var url = $(this).find(":selected").val();
if (url)
@@ -459,8 +476,32 @@ $(() => {
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
addToggleEventHandlers();
- /* ENABLE TOOLTIP AND POPOVER
+ /* RIGHT OFFCANVAS OPERATIONS
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
- ($('[data-toggle="tooltip"]')).tooltip();
- ($('[data-toggle="popover"]')).popover();
+ // Open / Close right Offcanvas Menu
+ $("#review-right-offcanvas-toggle").on('click', function () {
+ updatePageSettings(function () {
+ rightOffCanvasNavToggle("review-main-container");
+ });
+ });
+
+ // Toggle Subscribe Switch
+ $("#reviewSubscribeSwitch").on('change', function () {
+ $("#reviewSubscribeForm").submit();
+ });
+ // Toggle Close Switch
+ $("#reviewCloseSwitch").on('change', function () {
+ $("#reviewCloseForm").submit();
+ });
+
+ // Manage Expand / Collapse State of options
+ [$("#approveCollapse"), $("#requestReviewersCollapse"), $("#reviewOptionsCollapse"), $("#pageSettingsCollapse"), $("#associatedPRCollapse"), $("#associatedReviewsCollapse")].forEach(function (value, index) {
+ const id = value.attr("id");
+ value.on('hidden.bs.collapse', function () {
+ document.cookie = `${id}=hidden; max-age=${7 * 24 * 60 * 60}`;
+ });
+ value.on('shown.bs.collapse', function () {
+ document.cookie = `${id}=shown; max-age=${7 * 24 * 60 * 60}`;
+ });
+ });
});
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts
similarity index 72%
rename from src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts
rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts
index dab5970e368..14e5bb2dd3f 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts
@@ -1,4 +1,6 @@
$(() => {
+ const revisionlanguageSelect = $('#revision-language-select');
+
$(document).on("click", ".revision-rename-icon", e => {
toggleNameField($(e.target));
});
@@ -17,11 +19,16 @@ $(() => {
renameIcon.siblings(".revision-name-input").toggle();
}
- const languageSelect = $('#revision-language-select');
- languageSelect.on('change', function (e) {
+ revisionlanguageSelect.on('change', function (e) {
const fileSelectors = $(".package-file-selector");
for (var i = 0; i < fileSelectors.length; i++) {
$(fileSelectors[i]).toggleClass("hidden-row");
}
});
+
+ $("#uploadModel").on("show.bs.modal", function () {
+ (revisionlanguageSelect).SumoSelect({
+ placeholder: 'Language'
+ });
+ });
});
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts
similarity index 79%
rename from src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts
rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts
index aca849b4cb4..de75e347d2e 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts
@@ -1,7 +1,11 @@
-import { updatePageSettings } from "./helpers";
+import { updatePageSettings } from "../shared/helpers";
$(() => {
- const themeSelector = $( '#theme-selector' );
+ const themeSelector = $('#theme-selector');
+ const approvableLangSelect = $('#approvable-language-select');
+
+ (themeSelector).SumoSelect();
+ (approvableLangSelect).SumoSelect({selectAll: true});
$(document).on("submit", "form[data-post-update='userProfile']", e => {
const form = $(e.target);
@@ -34,4 +38,4 @@ $(() => {
body.addClass(newTheme);
});
});
-});
\ No newline at end of file
+});
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/comments.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts
similarity index 89%
rename from src/dotnet/APIView/APIViewWeb/Client/src/comments.ts
rename to src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts
index e2903869124..308759920e6 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/src/comments.ts
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts
@@ -1,8 +1,15 @@
+import {
+ updatePageSettings, getCodeRow, getCodeRowSectionClasses,
+ getRowSectionClasses, toggleCommentIcon
+} from "../shared/helpers";
+
$(() => {
const INVISIBLE = "invisible";
const SEL_CODE_DIAG = ".code-diagnostics";
const SEL_COMMENT_ICON = ".icon-comments";
const SEL_COMMENT_CELL = ".comment-cell";
+ const SHOW_COMMENTS_CHECK = "#show-comments-checkbox";
+ const SHOW_SYS_COMMENTS_CHECK = "#show-system-comments-checkbox";
const COMMENT_CONTENT_BOX = ".new-comment-content";
const COMMENT_TEXTBOX = ".new-thread-comment-text";
@@ -11,8 +18,6 @@ $(() => {
// simple github username match
const githubLoginTagMatch = /(\s|^)@([a-zA-Z\d-]+)/g;
- let MessageIconAddedToDom = false;
-
$(document).on("click", ".commentable", e => {
var rowSectionClasses = getCodeRowSectionClasses(e.target.id);
showCommentBox(e.target.id, rowSectionClasses);
@@ -48,14 +53,18 @@ $(() => {
e.preventDefault();
});
- $(document).on("click", "#show-comments-checkbox", e => {
- ensureMessageIconInDOM();
- toggleAllCommentsVisibility(e.target.checked);
+ $(document).on("click", SHOW_COMMENTS_CHECK, e => {
+ updatePageSettings(function () {
+ const checked = $(SHOW_COMMENTS_CHECK).prop("checked");
+ toggleAllCommentsVisibility(checked);
+ });
});
- $(document).on("click", "#show-system-comments-checkbox", e => {
- ensureMessageIconInDOM();
- toggleAllDiagnosticsVisibility(e.target.checked);
+ $(document).on("click", SHOW_SYS_COMMENTS_CHECK, e => {
+ updatePageSettings(function () {
+ const checked = $(SHOW_SYS_COMMENTS_CHECK).prop("checked");
+ toggleAllDiagnosticsVisibility(checked);
+ });
});
$(document).on("click", SEL_COMMENT_ICON, e => {
@@ -319,6 +328,16 @@ $(() => {
$(document).ready(function() {
highlightCurrentRow();
addCommentThreadNavigation();
+ $(SEL_COMMENT_CELL).each(function () {
+ const id = getElementId(this);
+ const checked = $(SHOW_COMMENTS_CHECK).prop("checked");
+ toggleCommentIcon(id, !checked);
+ });
+ $(SEL_CODE_DIAG).each(function () {
+ const id = getElementId(this);
+ const checked = $(SHOW_SYS_COMMENTS_CHECK).prop("checked");
+ toggleCommentIcon(id, !checked);
+ });
});
$(document).on("click", ".comment-group-anchor-link", e => {
@@ -344,8 +363,6 @@ $(() => {
});
}
-
-
function getReviewId(element: HTMLElement) {
return getParentData(element, "data-review-id");
}
@@ -377,25 +394,6 @@ $(() => {
return $(sibling).prevAll("a").first().find("small");
}
- function getCodeRowSectionClasses(id: string) {
- var codeRow = getCodeRow(id);
- var rowSectionClasses = "";
- if (codeRow) {
- rowSectionClasses = getRowSectionClasses(codeRow[0].classList);
- }
- return rowSectionClasses;
- }
-
- function getRowSectionClasses(classList: DOMTokenList) {
- const rowSectionClasses: string[] = [];
- for (const value of classList.values()) {
- if (value == "section-loaded" || value.startsWith("code-line-section-content") || value.match(/lvl_[0-9]+_(parent|child)_[0-9]+/)) {
- rowSectionClasses.push(value);
- }
- }
- return rowSectionClasses.join(' ');
- }
-
function getCommentId(element: HTMLElement) {
return getParentData(element, "data-comment-id");
}
@@ -423,10 +421,6 @@ $(() => {
return $(`.comment-row[data-line-id='${id}']`);
}
- function getCodeRow(id: string) {
- return $(`.code-line[data-line-id='${id}']`);
- }
-
function getDiagnosticsRow(id: string) {
return $(`.code-diagnostics[data-line-id='${id}']`);
}
@@ -544,38 +538,39 @@ $(() => {
function toggleAllCommentsVisibility(showComments: boolean) {
$(SEL_COMMENT_CELL).each(function () {
- var id = getElementId(this);
+ const id = getElementId(this);
if (id) {
- getCommentsRow(id).toggle(showComments);
- toggleCommentIcon(id, !showComments);
+ const tbRow = getCommentsRow(id);
+ const prevRow = tbRow.prev(".code-line");
+ const nextRow = tbRow.next(".code-line");
+ if ((prevRow != undefined && prevRow.hasClass("d-none")) && (nextRow != undefined && nextRow.hasClass("d-none")))
+ return;
+
+ (showComments) ? tbRow.removeClass("d-none") : tbRow.addClass("d-none");
+ toggleCommentIcon(id, !showComments);
}
});
}
function toggleAllDiagnosticsVisibility(showComments: boolean) {
$(SEL_CODE_DIAG).each(function () {
- var id = getElementId(this);
+ const id = getElementId(this);
if (id) {
- getDiagnosticsRow(id).toggle(showComments);
- toggleCommentIcon(id, !showComments);
+ const tbRow = getDiagnosticsRow(id);
+ const prevRow = tbRow.prev(".code-line");
+ const nextRow = tbRow.next(".code-line");
+ if ((prevRow != undefined && prevRow.hasClass("d-none")) && (nextRow != undefined && nextRow.hasClass("d-none")))
+ return;
+
+ (showComments) ? tbRow.removeClass("d-none") : tbRow.addClass("d-none");
+ toggleCommentIcon(id, !showComments);
}
});
}
function toggleSingleCommentAndDiagnostics(id: string) {
- getCommentsRow(id).toggle();
- getDiagnosticsRow(id).toggle();
- }
-
- function ensureMessageIconInDOM() {
- if (!MessageIconAddedToDom) {
- $(".comment-icon-cell").append(``);
- MessageIconAddedToDom = true;
- }
- }
-
- function toggleCommentIcon(id, show: boolean) {
- getCodeRow(id).find(SEL_COMMENT_ICON).toggleClass(INVISIBLE, !show);
+ getCommentsRow(id).toggleClass("d-none");
+ getDiagnosticsRow(id).toggleClass("d-none");
}
function getDisplayedCommentRows(commentRows: JQuery, clearCommentAnchors = false, returnFirst = false) {
@@ -633,14 +628,14 @@ $(() => {
var previousCommentThreadAnchor = "comment-thread-" + (index - 1);
if (index == 0) {
- commentNavigationButtons.append(``)
+ commentNavigationButtons.append(``)
}
else if (index == displayedCommentRows.length - 1) {
- commentNavigationButtons.append(``)
+ commentNavigationButtons.append(``)
}
else {
- commentNavigationButtons.append(``)
- commentNavigationButtons.append(``)
+ commentNavigationButtons.append(``)
+ commentNavigationButtons.append(``)
}
});
}
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/file-input.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/file-input.ts
similarity index 100%
rename from src/dotnet/APIView/APIViewWeb/Client/src/file-input.ts
rename to src/dotnet/APIView/APIViewWeb/Client/src/shared/file-input.ts
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts
new file mode 100644
index 00000000000..f998815c6f0
--- /dev/null
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts
@@ -0,0 +1,59 @@
+// Updated Page Setting by Updating UserPreference
+export function updatePageSettings(callBack) {
+ var hideLineNumbers = $("#hide-line-numbers").prop("checked");
+ if (hideLineNumbers != undefined) { hideLineNumbers = !hideLineNumbers; }
+
+ var hideLeftNavigation = $("#hide-left-navigation").prop("checked");
+ if (hideLeftNavigation != undefined) { hideLeftNavigation = !hideLeftNavigation; }
+
+ var showHiddenApis = $("#show-hidden-api-checkbox").prop("checked");
+ var showComments = $("#show-comments-checkbox").prop("checked");
+ var showSystemComments = $("#show-system-comments-checkbox").prop("checked");
+
+ var hideReviewPageOptions = $("#review-right-offcanvas-toggle").prop("checked");
+ if (hideReviewPageOptions != undefined) { hideReviewPageOptions = !hideReviewPageOptions; }
+
+ var hideIndexPageOptions = $("#index-right-offcanvas-toggle").prop("checked");
+ if (hideIndexPageOptions != undefined) { hideIndexPageOptions = !hideIndexPageOptions; }
+
+ var uri = location.origin + `/userprofile/updatereviewpagesettings?` +
+ `hideLineNumbers=${hideLineNumbers}&` +
+ `hideLeftNavigation=${hideLeftNavigation}&` +
+ `showHiddenApis=${showHiddenApis}&` +
+ `hideReviewPageOptions=${hideReviewPageOptions}&` +
+ `hideIndexPageOptions=${hideIndexPageOptions}&` +
+ `showComments=${showComments}&` +
+ `showSystemComments=${showSystemComments}`;
+
+ $.ajax({
+ type: "PUT",
+ url: uri
+ }).done(callBack());
+}
+
+export function getCodeRow(id: string) {
+ return $(`.code-line[data-line-id='${id}']`);
+}
+
+export function getCodeRowSectionClasses(id: string) {
+ var codeRow = getCodeRow(id);
+ var rowSectionClasses = "";
+ if (codeRow) {
+ rowSectionClasses = getRowSectionClasses(codeRow[0].classList);
+ }
+ return rowSectionClasses;
+}
+
+export function getRowSectionClasses(classList: DOMTokenList) {
+ const rowSectionClasses: string[] = [];
+ for (const value of classList.values()) {
+ if (value == "section-loaded" || value.startsWith("code-line-section-content") || value.match(/lvl_[0-9]+_(parent|child)_[0-9]+/)) {
+ rowSectionClasses.push(value);
+ }
+ }
+ return rowSectionClasses.join(' ');
+}
+
+export function toggleCommentIcon(id, show: boolean) {
+ getCodeRow(id).find(".icon-comments").toggleClass("invisible", !show);
+}
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts
new file mode 100644
index 00000000000..9b0f548edb1
--- /dev/null
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts
@@ -0,0 +1,8 @@
+// Enable tooltip and Popovers
+declare const bootstrap: any;
+
+const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
+const tooltipList = ([...tooltipTriggerList]).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
+
+const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
+const popoverList = ([...popoverTriggerList]).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts
new file mode 100644
index 00000000000..d8f208b7f3a
--- /dev/null
+++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts
@@ -0,0 +1,11 @@
+// Functions to Control right OffCanvas Menu
+export function rightOffCanvasNavToggle(mainContainser: String) {
+ if ($(".right-offcanvas").css("width") == '0px') {
+ $(`#${mainContainser}`).addClass("move-main-content-container-left");
+ $("#right-offcanvas-menu").addClass("show-offcanvas");
+ }
+ else {
+ $("#right-offcanvas-menu").removeClass("show-offcanvas");
+ $(`#${mainContainser}`).removeClass("move-main-content-container-left");
+ }
+}
diff --git a/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json b/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json
index c1a76b12743..c6faa1bcede 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json
+++ b/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json
@@ -14,9 +14,7 @@
"noImplicitAny": false,
"types": [
"webpack-env",
- "jquery",
- "jqueryui",
- "node"
+ "jquery"
],
"paths": {
"@/*": [
diff --git a/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js b/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js
index 7b21a5f81e8..fe346158635 100644
--- a/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js
+++ b/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js
@@ -1,31 +1,13 @@
const path = require('path');
-
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const { DuplicatesPlugin } = require("inspectpack/plugin");
module.exports = {
mode: "production",
- entry: {
- comments: './src/comments.ts',
- revisions: './src/revisions.ts',
- fileInput: './src/file-input.ts',
- review: './src/review.ts',
- reviews: './src/reviews.ts',
- userProfile: './src/user-profile.ts',
- site: './css/site.scss',
- c: './css/c.scss',
- cplusplus: './css/cplusplus.scss',
- csharp: './css/csharp.scss',
- go: './css/go.scss',
- java: './css/java.scss',
- javascript: './css/javascript.scss',
- json: './css/json.scss',
- kotlin: './css/kotlin.scss',
- python: './css/python.scss',
- swagger: './css/swagger.scss',
- swift: './css/swift.scss',
- xml: './css/xml.scss',
- usagesample: './css/usageSample.scss'
- },
+ entry: [
+ './src/main.ts',
+ './css/main.scss'
+ ],
devtool: 'source-map',
module: {
rules: [
@@ -58,14 +40,18 @@ module.exports = {
},
plugins: [
new MiniCssExtractPlugin({
- filename: '[name].css'
+ filename: 'main.css'
}),
+ new DuplicatesPlugin({
+ emitErrors: false,
+ verbose: false
+ })
],
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
- filename: '[name].js',
+ filename: 'main.js',
path: path.resolve(__dirname, '../wwwroot'),
},
}
diff --git a/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs b/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs
index 7ba499dd5a7..1f6a2e43033 100644
--- a/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs
+++ b/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs
@@ -39,7 +39,6 @@ public async Task Add(string reviewId, string revisionId, string e
foreach(string user in taggedUsers)
{
comment.TaggedUsers.Add(user);
- await _notificationManager.NotifyUserOnCommentTag(user, comment);
}
await _commentsManager.AddCommentAsync(User, comment);
diff --git a/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs b/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs
index 3b056126755..e4465762d6f 100644
--- a/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs
+++ b/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs
@@ -19,13 +19,19 @@ public UserProfileController(IUserProfileManager userProfileManager, UserPrefere
}
[HttpPut]
- public ActionResult UpdateReviewPageSettings(bool? hideLineNumbers = null, bool? hideLeftNavigation = null, bool? showHiddenApis = null)
+ public ActionResult UpdateReviewPageSettings(bool? hideLineNumbers = null, bool? hideLeftNavigation = null,
+ bool? showHiddenApis = null, bool? hideReviewPageOptions = null, bool? hideIndexPageOptions = null,
+ bool? showComments = null, bool? showSystemComments = null)
{
_userPreferenceCache.UpdateUserPreference(new UserPreferenceModel()
{
HideLeftNavigation = hideLeftNavigation,
HideLineNumbers = hideLineNumbers,
- ShowHiddenApis = showHiddenApis
+ ShowHiddenApis = showHiddenApis,
+ HideReviewPageOptions = hideReviewPageOptions,
+ HideIndexPageOptions = hideIndexPageOptions,
+ ShowComments = showComments,
+ ShowSystemComments = showSystemComments
}, User);
return Ok();
}
diff --git a/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs b/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs
index ed85101249c..3de53a6ea05 100644
--- a/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs
+++ b/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs
@@ -1,4 +1,4 @@
-using APIViewWeb.Models;
+using APIViewWeb.Models;
using AutoMapper;
using Microsoft.CodeAnalysis.Diagnostics;
@@ -17,7 +17,11 @@ public AutoMapperProfiles()
.ForMember(dest => dest.HideLineNumbers, opt => opt.MapFrom((src, dest) => src._hideLineNumbers != null ? src._hideLineNumbers : dest._hideLineNumbers))
.ForMember(dest => dest.HideLeftNavigation, opt => opt.MapFrom((src, dest) => src._hideLeftNavigation != null ? src._hideLeftNavigation : dest._hideLeftNavigation))
.ForMember(dest => dest.Theme, opt => opt.MapFrom((src, dest) => src._theme != null ? src._theme : dest._theme))
- .ForMember(dest => dest.ShowHiddenApis, opt => opt.MapFrom((src, dest) => src._showHiddenApis != null ? src._showHiddenApis : dest._showHiddenApis));
+ .ForMember(dest => dest.ShowHiddenApis, opt => opt.MapFrom((src, dest) => src._showHiddenApis != null ? src._showHiddenApis : dest._showHiddenApis))
+ .ForMember(dest => dest.HideReviewPageOptions, opt => opt.MapFrom((src, dest) => src._hideReviewPageOptions != null ? src._hideReviewPageOptions : dest._hideReviewPageOptions))
+ .ForMember(dest => dest.HideIndexPageOptions, opt => opt.MapFrom((src, dest) => src._hideIndexPageOptions != null ? src._hideIndexPageOptions : dest._hideIndexPageOptions))
+ .ForMember(dest => dest.ShowComments, opt => opt.MapFrom((src, dest) => src._showComments != null ? src._showComments : dest._showComments))
+ .ForMember(dest => dest.ShowSystemComments, opt => opt.MapFrom((src, dest) => src._showSystemComments != null ? src._showSystemComments : dest._showSystemComments));
}
}
diff --git a/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs
index f94a44e9a10..38970c695eb 100644
--- a/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs
+++ b/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs
@@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Authorization;
using Newtonsoft.Json;
using Microsoft.Extensions.Options;
+using Microsoft.TeamFoundation.Common;
namespace APIViewWeb.Managers
{
@@ -91,6 +92,7 @@ public async Task AddCommentAsync(ClaimsPrincipal user, CommentModel comment)
await _commentsRepository.UpsertCommentAsync(comment);
if (!comment.IsResolve)
{
+ await _notificationManager.NotifyUserOnCommentTag(comment);
await _notificationManager.NotifySubscribersOnComment(user, comment);
}
}
@@ -103,18 +105,16 @@ public async Task UpdateCommentAsync(ClaimsPrincipal user, string
comment.Comment = commentText;
comment.Username = user.GetGitHubLogin();
- var newTaggedUsers = new HashSet();
foreach (var taggedUser in taggedUsers)
{
- if (!comment.TaggedUsers.Contains(taggedUser))
+ if (!string.IsNullOrEmpty(taggedUser))
{
- await _notificationManager.NotifyUserOnCommentTag(taggedUser, comment);
+ comment.TaggedUsers.Add(taggedUser);
}
- newTaggedUsers.Add(taggedUser);
}
- comment.TaggedUsers = newTaggedUsers;
await _commentsRepository.UpsertCommentAsync(comment);
+ await _notificationManager.NotifyUserOnCommentTag(comment);
await _notificationManager.NotifySubscribersOnComment(user, comment);
return comment;
}
diff --git a/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs
index 330b7d77dee..840383805d6 100644
--- a/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs
+++ b/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs
@@ -8,7 +8,7 @@ namespace APIViewWeb.Managers
public interface INotificationManager
{
public Task NotifySubscribersOnComment(ClaimsPrincipal user, CommentModel comment);
- public Task NotifyUserOnCommentTag(string username, CommentModel comment);
+ public Task NotifyUserOnCommentTag(CommentModel comment);
public Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId, HashSet reviewers);
public Task NotifySubscribersOnNewRevisionAsync(ReviewRevisionModel revision, ClaimsPrincipal user);
public Task ToggleSubscribedAsync(ClaimsPrincipal user, string reviewId);
diff --git a/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs
index 15ef8e785fe..eaa714555d0 100644
--- a/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs
+++ b/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using System.Threading.Tasks;
+using APIViewWeb.Models;
namespace APIViewWeb.Managers
{
@@ -8,5 +10,8 @@ public Task DetectApiChanges(string buildId, string artifactName, string
string commitSha, string repoName, string packageName, int prNumber, string hostName, string codeFileName = null,
string baselineCodeFileName = null, bool commentOnPR = true, string language = null, string project = "public");
public Task CleanupPullRequestData();
+
+ public Task> GetPullRequestsModel(string reviewId);
+ public Task> GetPullRequestsModel(int pullRequestNumber, string repoName);
}
}
diff --git a/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs
index 8f46f38d09e..d6fbca8855b 100644
--- a/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs
+++ b/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs
@@ -4,52 +4,61 @@
using APIView.Identity;
using APIViewWeb.Models;
using Microsoft.Extensions.Configuration;
-using SendGrid;
-using SendGrid.Helpers.Mail;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
-using Microsoft.TeamFoundation.Common;
using APIViewWeb.Repositories;
+using Microsoft.ApplicationInsights.Extensibility;
+using Microsoft.ApplicationInsights;
+using System.Net.Http;
+using System.Net.Http.Json;
namespace APIViewWeb.Managers
{
public class NotificationManager : INotificationManager
{
- private readonly string _endpoint;
+ private readonly string _apiviewEndpoint;
private readonly ICosmosReviewRepository _reviewRepository;
private readonly ICosmosUserProfileRepository _userProfileRepository;
- private readonly ISendGridClient _sendGridClient;
+ private readonly IConfiguration _configuration;
+ private readonly string _testEmailToAddress;
+ private readonly string _emailSenderServiceUrl;
private const string ENDPOINT_SETTING = "Endpoint";
- private const string SENDGRID_KEY_SETTING = "SendGrid:Key";
- private const string FROM_ADDRESS = "apiview-noreply@microsoft.com";
- private const string REPLY_TO_HEADER = "In-Reply-To";
- private const string REFERENCES_HEADER = "References";
- public NotificationManager(IConfiguration configuration, ICosmosReviewRepository reviewRepository,
- ICosmosUserProfileRepository userProfileRepository, ISendGridClient sendGridClient = null)
+ static TelemetryClient _telemetryClient = new(TelemetryConfiguration.CreateDefault());
+
+ public NotificationManager(IConfiguration configuration,
+ ICosmosReviewRepository reviewRepository,
+ ICosmosUserProfileRepository userProfileRepository)
{
- _sendGridClient = sendGridClient ?? new SendGridClient(configuration[SENDGRID_KEY_SETTING]);
- _endpoint = configuration.GetValue(ENDPOINT_SETTING);
+ _apiviewEndpoint = configuration.GetValue(ENDPOINT_SETTING);
_reviewRepository = reviewRepository;
_userProfileRepository = userProfileRepository;
+ _configuration = configuration;
+ _testEmailToAddress = configuration["apiview-email-test-address"] ?? "";
+ _emailSenderServiceUrl = configuration["azure-sdk-emailer-url"] ?? "";
}
public async Task NotifySubscribersOnComment(ClaimsPrincipal user, CommentModel comment)
{
var review = await _reviewRepository.GetReviewAsync(comment.ReviewId);
- await SendEmailsAsync(review, user, GetPlainTextContent(comment), GetHtmlContent(comment, review));
+ await SendEmailsAsync(review, user, GetHtmlContent(comment, review), comment.TaggedUsers);
}
- public async Task NotifyUserOnCommentTag(string username, CommentModel comment)
+ public async Task NotifyUserOnCommentTag(CommentModel comment)
{
- var review = await _reviewRepository.GetReviewAsync(comment.ReviewId);
- var user = await _userProfileRepository.TryGetUserProfileAsync(username);
- await SendUserEmailsAsync(review, user, GetCommentTagPlainTextContent(comment), GetCommentTagHtmlContent(comment, review));
+ foreach (string username in comment.TaggedUsers)
+ {
+ if(string.IsNullOrEmpty(username)) continue;
+ var review = await _reviewRepository.GetReviewAsync(comment.ReviewId);
+ var user = await _userProfileRepository.TryGetUserProfileAsync(username);
+ await SendUserEmailsAsync(review, user, GetCommentTagHtmlContent(comment, review));
+ }
}
public async Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId, HashSet reviewers)
@@ -60,7 +69,6 @@ public async Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId,
{
var reviewerProfile = await _userProfileRepository.TryGetUserProfileAsync(reviewer);
await SendUserEmailsAsync(review, reviewerProfile,
- GetApproverReviewContentHeading(userProfile, false),
GetApproverReviewHtmlContent(userProfile, review));
}
}
@@ -68,12 +76,10 @@ await SendUserEmailsAsync(review, reviewerProfile,
public async Task NotifySubscribersOnNewRevisionAsync(ReviewRevisionModel revision, ClaimsPrincipal user)
{
var review = revision.Review;
- var uri = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}");
- var plainTextContent = $"A new revision, {revision.DisplayName}," +
- $" was uploaded by {revision.Author}.";
+ var uri = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}");
var htmlContent = $"A new revision, {revision.DisplayName}," +
$" was uploaded by {revision.Author}.";
- await SendEmailsAsync(review, user, plainTextContent, htmlContent);
+ await SendEmailsAsync(review, user, htmlContent, null);
}
public async Task ToggleSubscribedAsync(ClaimsPrincipal user, string reviewId)
@@ -116,10 +122,10 @@ public static string GetUserEmail(ClaimsPrincipal user) =>
private string GetApproverReviewHtmlContent(UserProfileModel user, ReviewModel review)
{
var reviewName = review.Name;
- var reviewLink = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}");
+ var reviewLink = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}");
var poster = user.UserName;
- var userLink = new Uri($"{_endpoint}/Assemblies/Profile/{poster}");
- var requestsLink = new Uri($"{_endpoint}/Assemblies/RequestedReviews/");
+ var userLink = new Uri($"{_apiviewEndpoint}/Assemblies/Profile/{poster}");
+ var requestsLink = new Uri($"{_apiviewEndpoint}/Assemblies/RequestedReviews/");
var sb = new StringBuilder();
sb.Append($"{poster}");
sb.Append($" requested you to review {reviewName}");
@@ -131,10 +137,10 @@ private string GetApproverReviewHtmlContent(UserProfileModel user, ReviewModel r
private string GetCommentTagHtmlContent(CommentModel comment, ReviewModel review)
{
var reviewName = review.Name;
- var reviewLink = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}");
+ var reviewLink = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}");
var commentText = comment.Comment;
var poster = comment.Username;
- var userLink = new Uri($"{_endpoint}/Assemblies/Profile/{poster}");
+ var userLink = new Uri($"{_apiviewEndpoint}/Assemblies/Profile/{poster}");
var sb = new StringBuilder();
sb.Append($"{poster}");
sb.Append($" mentioned you in {reviewName}");
@@ -148,7 +154,7 @@ private string GetCommentTagHtmlContent(CommentModel comment, ReviewModel review
private string GetHtmlContent(CommentModel comment, ReviewModel review)
{
- var uri = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}");
+ var uri = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}");
var sb = new StringBuilder();
sb.Append(GetContentHeading(comment, true));
sb.Append("
");
@@ -158,94 +164,87 @@ private string GetHtmlContent(CommentModel comment, ReviewModel review)
return sb.ToString();
}
- private static string GetCommentTagPlainTextContent(CommentModel comment)
- {
- var sb = new StringBuilder();
- sb.Append(GetCommentTagContentHeading(comment, false));
- return sb.ToString();
- }
-
- private string GetPlainTextContent(CommentModel comment)
- {
- var sb = new StringBuilder();
- sb.Append(GetContentHeading(comment, false));
- sb.Append("\r\n");
- sb.Append(CommentMarkdownExtensions.MarkdownAsPlainText(comment.Comment));
- return sb.ToString();
- }
-
- private static string GetApproverReviewContentHeading(UserProfileModel user, bool includeHtml) =>
- $"{(includeHtml ? $"{user.UserName}" : $"{user.UserName}")} requested you to review API.";
-
- private static string GetCommentTagContentHeading(CommentModel comment, bool includeHtml) =>
- $"{(includeHtml ? $"{comment.Username}" : $"{comment.Username}")} tagged you in a comment.";
-
private static string GetContentHeading(CommentModel comment, bool includeHtml) =>
$"{(includeHtml ? $"{comment.Username}" : $"{comment.Username}")} commented on this review.";
- private async Task SendUserEmailsAsync(ReviewModel review, UserProfileModel user, string plainTextContent, string htmlContent)
+ private async Task SendUserEmailsAsync(ReviewModel review, UserProfileModel user, string htmlContent)
{
- var userBackup = new ClaimsPrincipal();
- EmailAddress e;
- if (!user.Email.IsNullOrEmpty())
+ // Always send email to a test address when test address is configured.
+ if (string.IsNullOrEmpty(user.Email))
{
- e = new EmailAddress(user.Email, user.UserName);
+ _telemetryClient.TrackTrace($"Email address is not available for user {user.UserName}. Not sending email.");
+ return;
}
- else
+
+ await SendEmail(user.Email, $"Notification from APIView - {review.DisplayName}", htmlContent);
+ }
+ private async Task SendEmailsAsync(ReviewModel review, ClaimsPrincipal user, string htmlContent, ISet notifiedUsers)
+ {
+ var initiatingUserEmail = GetUserEmail(user);
+ // Find email address of already tagged users in comment
+ HashSet notifiedEmails = new HashSet();
+ if (notifiedUsers != null)
{
- var backupEmail = GetUserEmail(userBackup);
- if (!backupEmail.IsNullOrEmpty())
- {
- e = new EmailAddress(backupEmail, user.UserName);
- }
- else
+ foreach (var username in notifiedUsers)
{
- return;
+ var email = await GetEmailAddress(username);
+ notifiedEmails.Add(email);
}
- }
- var from = new EmailAddress(FROM_ADDRESS);
- var msg = MailHelper.CreateSingleEmail(
- from,
- e,
- user.UserName,
- plainTextContent,
- htmlContent);
- var threadHeader = $"<{review.ReviewId}{FROM_ADDRESS}>";
- msg.AddHeader(REPLY_TO_HEADER, threadHeader);
- msg.AddHeader(REFERENCES_HEADER, threadHeader);
- await _sendGridClient.SendEmailAsync(msg);
- }
- private async Task SendEmailsAsync(ReviewModel review, ClaimsPrincipal user, string plainTextContent, string htmlContent)
- {
- var initiatingUserEmail = GetUserEmail(user);
+ }
var subscribers = review.Subscribers.ToList()
- .Where(e => e != initiatingUserEmail) // don't include the initiating user in the email
- .Select(e => new EmailAddress(e))
+ .Where(e => e != initiatingUserEmail && !notifiedEmails.Contains(e)) // don't include the initiating user and tagged users in the comment
.ToList();
if (subscribers.Count == 0)
{
return;
}
- var from = new EmailAddress(FROM_ADDRESS, GetUserName(user));
- var msg = MailHelper.CreateMultipleEmailsToMultipleRecipients(
- from,
- subscribers,
- Enumerable.Repeat(review.DisplayName, review.Subscribers.Count).ToList(),
- plainTextContent,
- htmlContent,
- Enumerable.Repeat(new Dictionary(), review.Subscribers.Count).ToList());
- var threadHeader = $"<{review.ReviewId}{FROM_ADDRESS}>";
- msg.AddHeader(REPLY_TO_HEADER, threadHeader);
- msg.AddHeader(REFERENCES_HEADER, threadHeader);
- await _sendGridClient.SendEmailAsync(msg);
+ var emailToList = string.Join(",", subscribers);
+ if (string.IsNullOrEmpty(emailToList))
+ {
+ _telemetryClient.TrackTrace($"Email address is not available for subscribers, review {review.ReviewId}. Not sending email.");
+ return;
+ }
+
+ await SendEmail(emailToList, $"Update on APIView - {review.DisplayName} from {GetUserName(user)}", htmlContent);
}
+ private async Task SendEmail(string emailToList, string subject, string content)
+ {
+ if (string.IsNullOrEmpty(_emailSenderServiceUrl))
+ {
+ _telemetryClient.TrackTrace($"Email sender service URL is not configured. Email will not be sent to {emailToList} with subject: {subject}");
+ return;
+ }
+
+ var requestBody = new EmailModel(_testEmailToAddress ?? emailToList, subject, content);
+ var httpClient = new HttpClient();
+ try
+ {
+ var response = await httpClient.PostAsync(_emailSenderServiceUrl, JsonContent.Create(requestBody));
+ if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Accepted)
+ {
+ _telemetryClient.TrackTrace($"Failed to send email to user {emailToList} with subject: {subject}, status code: {response.StatusCode}, Details: {response.ToString}");
+ }
+ }
+ catch (Exception ex)
+ {
+ _telemetryClient.TrackException(ex);
+ }
+ }
private static string GetUserName(ClaimsPrincipal user)
{
var name = user.FindFirstValue(ClaimConstants.Name);
return string.IsNullOrEmpty(name) ? user.FindFirstValue(ClaimConstants.Login) : name;
}
+
+ private async Task GetEmailAddress(string username)
+ {
+ if (string.IsNullOrEmpty(username))
+ return "";
+ var user = await _userProfileRepository.TryGetUserProfileAsync(username);
+ return user.Email;
+ }
}
}
diff --git a/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs
index 6976d5a00ea..9f2fb2694a4 100644
--- a/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs
+++ b/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs
@@ -178,6 +178,15 @@ public async Task CleanupPullRequestData()
}
}
+ public async Task> GetPullRequestsModel(string reviewId) {
+ return await _pullRequestsRepository.GetPullRequestsAsync(reviewId);
+ }
+
+ public async Task> GetPullRequestsModel(int pullRequestNumber, string repoName)
+ {
+ return await _pullRequestsRepository.GetPullRequestsAsync(pullRequestNumber, repoName);
+ }
+
private async Task CreateOrUpdateComment(List prReviews, string repoOwner, string repoName, int prNumber, string hostName)
{
var existingComment = await GetExistingCommentForPackage(repoOwner, repoName, prNumber);
@@ -317,11 +326,16 @@ private async Task CreateRevisionIfRequired(CodeFile codeFile,
{
// Check if API surface level matches with any revisions
var renderedCodeFile = new RenderedCodeFile(codeFile);
- if (await IsReviewSame(review, renderedCodeFile))
+ // pullRequestModel.ReviewId == null means: First time getting a request to check for API changes in the given package for a PR
+ if (pullRequestModel.ReviewId == null)
{
- return;
- }
-
+ //No API changes detected from baseline
+ if (await IsReviewSame(review, renderedCodeFile))
+ {
+ return;
+ }
+ }
+ // Below steps will remove last revision from previously created API review from the pull request and recreate new revision using latest token code file
if (pullRequestModel.ReviewId != null)
{
//Refresh baseline using latest from automatic review
diff --git a/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs b/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs
index 1a84e52ea6d..fda162c07e1 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs
@@ -1,4 +1,6 @@
-using System;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using Newtonsoft.Json;
diff --git a/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs b/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs
index afb8f5333d1..9e06446e552 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System.Collections.Generic;
using System.Linq;
namespace APIViewWeb.Models
diff --git a/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs b/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs
new file mode 100644
index 00000000000..1db4b4268a7
--- /dev/null
+++ b/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using Newtonsoft.Json;
+
+namespace APIViewWeb.Models
+{
+ public class EmailModel
+ {
+ public string EmailTo { get; set; }
+ public string Subject { get; set; }
+ public string Body { get; set; }
+
+ public EmailModel(string emailTo, string subject, string body)
+ {
+ EmailTo = emailTo;
+ Subject = subject;
+ Body = body;
+ }
+
+ public string Serialize()
+ {
+ return JsonConvert.SerializeObject(this);
+ }
+ }
+}
diff --git a/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs b/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs
index 5dcae02780e..717d27b7369 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs
@@ -1,4 +1,6 @@
-using Newtonsoft.Json;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using Newtonsoft.Json;
namespace APIViewWeb.Models
{
diff --git a/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs b/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs
index 2f9ad811513..2c3b20c4d0b 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs
@@ -1,3 +1,5 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
using System.Collections.Generic;
using Newtonsoft.Json;
diff --git a/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs b/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs
index fc9ed6ddfe2..bbb7541f847 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System.Collections.Generic;
namespace APIViewWeb.Models
{
diff --git a/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs b/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs
index 45e1e1ff9b9..d3a7f584352 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs
@@ -1,4 +1,6 @@
-using CsvHelper.Configuration.Attributes;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using CsvHelper.Configuration.Attributes;
namespace APIViewWeb.Models
{
diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs
index 9b4a425619d..bd86082f88c 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs
@@ -1,4 +1,6 @@
-using System;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
namespace APIViewWeb.Models
{
diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs
index 1efd454fec3..bd3c42453ef 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs
@@ -1,3 +1,5 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
namespace APIViewWeb.Models
{
public class ReviewGenPipelineParamModel
diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs
index adfc09db149..ea3c45adb3f 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs
@@ -1,5 +1,3 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
diff --git a/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs
index 4ce8d8672a1..b321e081060 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System.Collections.Generic;
namespace APIViewWeb.Models
{
diff --git a/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs
index 52831e42b8f..fa5f967c8f9 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs
@@ -1,4 +1,6 @@
-using Newtonsoft.Json;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using Newtonsoft.Json;
using System.Collections.Generic;
namespace APIViewWeb
diff --git a/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs
index 7623a0c7dec..d4a9d26c92e 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System.Collections.Generic;
using CsvHelper.Configuration.Attributes;
using Newtonsoft.Json;
@@ -14,6 +16,10 @@ public class UserPreferenceModel
internal bool? _hideLineNumbers;
internal bool? _hideLeftNavigation;
internal bool? _showHiddenApis;
+ internal bool? _hideReviewPageOptions;
+ internal bool? _hideIndexPageOptions;
+ internal bool? _showComments;
+ internal bool? _showSystemComments;
internal string _theme;
public string UserName { get; set; }
@@ -72,5 +78,33 @@ public bool? ShowHiddenApis {
get => _showHiddenApis ?? false;
set => _showHiddenApis = value;
}
+
+ [Name("HideReviewPageOptions")]
+ public bool? HideReviewPageOptions
+ {
+ get => _hideReviewPageOptions ?? false;
+ set => _hideReviewPageOptions = value;
+ }
+
+ [Name("HideIndexPageOptions")]
+ public bool? HideIndexPageOptions
+ {
+ get => _hideIndexPageOptions ?? false;
+ set => _hideIndexPageOptions = value;
+ }
+
+ [Name("ShowComments")]
+ public bool? ShowComments
+ {
+ get => _showComments ?? true;
+ set => _showComments = value;
+ }
+
+ [Name("ShowSystemComments")]
+ public bool? ShowSystemComments
+ {
+ get => _showSystemComments ?? true;
+ set => _showSystemComments = value;
+ }
}
}
diff --git a/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs
index 946f38132d4..1a1a55a40d6 100644
--- a/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs
+++ b/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs
@@ -1,3 +1,5 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
using System.Collections.Generic;
using System.Security.Claims;
using Newtonsoft.Json;
diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml
index 2b3d2f646ca..4d82a2d6234 100644
--- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml
+++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml
@@ -3,101 +3,109 @@
@using APIViewWeb.Helpers
@using APIViewWeb.Models
@{
- Layout = "ReviewLayout";
+ Layout = "Shared/_Layout";
ViewData["Title"] = "Conversation";
TempData["UserPreference"] = PageModelHelpers.GetUserPreference(Model._preferenceCache, User);
}
-