From 36fcb5897a8d275f069b1a461e22be843e6cf52d Mon Sep 17 00:00:00 2001 From: Praven Kuttappan <55455725+praveenkuttappan@users.noreply.github.com> Date: Thu, 16 Feb 2023 12:16:19 -0500 Subject: [PATCH] APIView changes to send emails using logic app service (#5408) * APIView changes to send emails using logic app service --- .../APIViewIntegrationTests.csproj | 1 - .../TestsBaseFixture.cs | 10 +- .../APIViewUnitTests/APIViewUnitTests.csproj | 4 +- .../APIView/APIViewWeb/APIViewWeb.csproj | 6 +- .../Controllers/CommentsController.cs | 1 - .../APIViewWeb/Managers/CommentsManager.cs | 10 +- .../Managers/INotificationManager.cs | 2 +- .../Managers/NotificationManager.cs | 187 +++++++++--------- .../APIView/APIViewWeb/Models/CommentModel.cs | 4 +- .../APIViewWeb/Models/CommentThreadModel.cs | 4 +- .../APIView/APIViewWeb/Models/EmailModel.cs | 25 +++ .../APIView/APIViewWeb/Models/GithubUser.cs | 4 +- .../APIViewWeb/Models/OpenSourceUserInfo.cs | 2 + .../APIViewWeb/Models/PackageGroupMdel.cs | 4 +- .../APIView/APIViewWeb/Models/PackageModel.cs | 4 +- .../APIViewWeb/Models/ReviewDisplayModel.cs | 4 +- .../Models/ReviewGenPipelineParamModel.cs | 2 + .../APIView/APIViewWeb/Models/ReviewType.cs | 2 - .../APIViewWeb/Models/ServiceGroupModel.cs | 4 +- .../APIViewWeb/Models/UsageSampleModel.cs | 4 +- .../APIViewWeb/Models/UserPreferenceModel.cs | 2 + .../APIViewWeb/Models/UserProfileModel.cs | 2 + 22 files changed, 161 insertions(+), 127 deletions(-) create mode 100644 src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs diff --git a/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj b/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj index 3d1a332b3e4..3e763401d35 100644 --- a/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj +++ b/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj @@ -19,7 +19,6 @@ - all diff --git a/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs b/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs index 39f75cff0ad..49ad8d37c9c 100644 --- a/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs +++ b/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs @@ -11,12 +11,8 @@ using Microsoft.AspNetCore.Authorization; using Moq; using System.IO; -using System.Net; using System.Threading.Tasks; using System.Security.Claims; -using SendGrid; -using SendGrid.Helpers.Mail; -using System.Threading; using Azure.Storage.Blobs.Models; using APIView.Identity; using APIViewWeb.Managers; @@ -83,11 +79,7 @@ public TestsBaseFixture() authorizationServiceMoq.Setup(_ => _.AuthorizeAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync(AuthorizationResult.Success); - var sendGridClientMock = new Mock(); - sendGridClientMock.Setup(_ => _.SendEmailAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new Response(HttpStatusCode.OK, null, null)); - - var notificationManager = new NotificationManager(config, cosmosReviewRepository, cosmosUserProfileRepository, sendGridClientMock.Object); + var notificationManager = new NotificationManager(config, cosmosReviewRepository, cosmosUserProfileRepository); var devopsArtifactRepositoryMoq = new Mock(); devopsArtifactRepositoryMoq.Setup(_ => _.DownloadPackageArtifact(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj index 98bf7a89b6e..67b108ea9e7 100644 --- a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj +++ b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -18,7 +18,7 @@ - + diff --git a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj index c848698ed42..9653c108b6d 100644 --- a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj +++ b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj @@ -37,10 +37,10 @@ NU1701 - + - + all @@ -50,8 +50,6 @@ - - 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/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/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/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 80b3f5a2079..d4a9d26c92e 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System.Collections.Generic; using CsvHelper.Configuration.Attributes; using Newtonsoft.Json; 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;