Skip to content

Add Namespace Approved/Rejected Notifications#15542

Closed
smw-ms wants to merge 3 commits into
Azure:mainfrom
smw-ms:apiview/add-namespace-emails
Closed

Add Namespace Approved/Rejected Notifications#15542
smw-ms wants to merge 3 commits into
Azure:mainfrom
smw-ms:apiview/add-namespace-emails

Conversation

@smw-ms

@smw-ms smw-ms commented May 7, 2026

Copy link
Copy Markdown
Member

Aims to resolve tasks 2 & 3 of this issue: #14707

@smw-ms smw-ms marked this pull request as ready for review May 7, 2026 18:50
Copilot AI review requested due to automatic review settings May 7, 2026 18:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds namespace lifecycle email notifications to APIView’s Namespace Approval flow, focusing on the Approved and Rejected scenarios from #14707.

Changes:

  • Added new Razor email templates and strongly-typed models for Namespace Approved and Namespace Rejected emails.
  • Wired notifications into the namespace status update endpoint so emails are sent on Approved/Rejected transitions.
  • Added helper methods to locate the associated review for a project/language/namespace to build better email links/context.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/dotnet/APIView/APIViewWeb/Services/EmailTemplateService.cs Maps template keys to the new approved/rejected email templates.
src/dotnet/APIView/APIViewWeb/Models/ProjectModel.cs Adds helpers to retrieve project-linked review IDs and current package name.
src/dotnet/APIView/APIViewWeb/Models/NamespaceModel.cs Adds helper to retrieve the current namespace decision entry for a language/namespace.
src/dotnet/APIView/APIViewWeb/Models/EmailTemplateModels.cs Introduces new email models for approved/rejected namespace notifications.
src/dotnet/APIView/APIViewWeb/Models/EmailTemplateKey.cs Adds a new template key for namespace rejection emails.
src/dotnet/APIView/APIViewWeb/Managers/ReviewManager.cs Minor cleanup around manual approval flow comment.
src/dotnet/APIView/APIViewWeb/Managers/ProjectsManager.cs Adds method to find the best associated review for notifications.
src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs Implements approved/rejected email notification sending and recipient selection.
src/dotnet/APIView/APIViewWeb/Managers/Interfaces/IProjectsManager.cs Exposes associated-review lookup for notification scenarios.
src/dotnet/APIView/APIViewWeb/Managers/Interfaces/INotificationManager.cs Adds notification APIs for approved/rejected namespace events.
src/dotnet/APIView/APIViewWeb/LeanControllers/ProjectsController.cs Triggers approved/rejected notifications after successful namespace status updates.
src/dotnet/APIView/APIViewWeb/EmailTemplates/NamespaceReviewApprovedEmail.cshtml Removes legacy “namespace review approved” template in favor of the new one.
src/dotnet/APIView/APIViewWeb/EmailTemplates/NamespaceRejectedEmail.cshtml Adds the new “Namespace Rejected” email template.
src/dotnet/APIView/APIViewWeb/EmailTemplates/NamespaceApprovedEmail.cshtml Adds the new “Namespace Approved” email template.
src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs Updates using directives to match interface namespace changes.
src/dotnet/APIView/APIViewUnitTests/EmailTemplatePreviewGeneratorTests.cs Updates preview generation to use the new models and templates.

Comment on lines +411 to +423
// Use the unified approval email template for manual approval.
// Legacy flow does not have project/namespace-tab context, so reuse the TypeSpec review URL.
var emailContent = await _emailTemplateService.RenderAsync(
EmailTemplateKey.NamespaceReviewApproved,
NamespaceReviewApprovedEmailModel.Create(
NamespaceApprovedEmailModel.Create(
review.PackageName,
review.PackageName,
review.Language,
review.PackageName,
review.CreatedBy,
DateTime.UtcNow,
typeSpecUrl,
associatedReviews ?? Enumerable.Empty<ReviewListItemModel>(),
_apiviewEndpoint));
typeSpecUrl));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NotifyStakeholdersOfManualApprovalAsync is still used in the old namespace page workflow, which is why I didn't outright delete it. I'm leaning towards just leaving unknown fields blank.

Comment on lines +527 to +531
var usernames = new HashSet<string>(
(project?.Owners ?? Enumerable.Empty<string>())
.Concat(new[] { associatedReview?.CreatedBy, approverUserName })
.Where(username => !string.IsNullOrWhiteSpace(username)),
StringComparer.OrdinalIgnoreCase);
Comment on lines +227 to +246
if (request.Status == NamespaceDecisionStatus.Rejected || request.Status == NamespaceDecisionStatus.Approved)
{
string normalizedLanguage = LanguageServiceHelpers.MapLanguageAlias(language);
var associatedReview = await _projectsManager.GetAssociatedReviewForProjectAsync(result.Project, normalizedLanguage, request.Namespace);

NamespaceDecisionEntry decisionEntry = result.Project.NamespaceInfo?.GetCurrentEntry(
normalizedLanguage,
request.Namespace);

if (decisionEntry != null)
{
if (request.Status == NamespaceDecisionStatus.Rejected)
{
await _notificationManager.NotifyNamespaceRejectedAsync(result.Project, decisionEntry, associatedReview);
}
else
{
await _notificationManager.NotifyNamespaceApprovedAsync(result.Project, decisionEntry, associatedReview);
}
}
Comment on lines +227 to +245
if (request.Status == NamespaceDecisionStatus.Rejected || request.Status == NamespaceDecisionStatus.Approved)
{
string normalizedLanguage = LanguageServiceHelpers.MapLanguageAlias(language);
var associatedReview = await _projectsManager.GetAssociatedReviewForProjectAsync(result.Project, normalizedLanguage, request.Namespace);

NamespaceDecisionEntry decisionEntry = result.Project.NamespaceInfo?.GetCurrentEntry(
normalizedLanguage,
request.Namespace);

if (decisionEntry != null)
{
if (request.Status == NamespaceDecisionStatus.Rejected)
{
await _notificationManager.NotifyNamespaceRejectedAsync(result.Project, decisionEntry, associatedReview);
}
else
{
await _notificationManager.NotifyNamespaceApprovedAsync(result.Project, decisionEntry, associatedReview);
}
Comment on lines +27 to +33
if (CurrentNamespaceStatus == null ||
!CurrentNamespaceStatus.TryGetValue(language, out var entries))
{
return null;
}

return entries?.FirstOrDefault(entry =>
public bool IsDeleted { get; set; }

public List<string> GetAssociatedReviewIds(string language)
{
Comment on lines +118 to +124
public async Task<ReviewListItemModel> GetAssociatedReviewForProjectAsync(Project project, string language, string namespaceValue)
{
List<string> reviewIds = project?.GetAssociatedReviewIds(language) ?? [];
if (reviewIds.Count == 0)
{
return null;
}

<p class="section"><strong>Next Steps:</strong></p>
<ul class="list">
<li>Review namespace status and history in the <a href="@Model.NamespaceTabUrl">namespace tab</a>.</li>

<p class="section"><strong>Next Steps:</strong></p>
<ul class="list">
<li>Review namespace status and history in the <a href="@Model.NamespaceTabUrl">namespace tab</a>.</li>
@smw-ms smw-ms closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants