Skip to content
Merged

Dev #1553

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5acd6f0
AB#29051 - Add CC and BCC support to email notification system
plavoie-BC Jun 26, 2025
c3fb80e
AB#29051 - Standardize CC and BCC support around IEnumerable parsing
plavoie-BC Jun 26, 2025
44f0a9d
AB#29051 - Add ParseEmailList String Extension to handle formatting
plavoie-BC Jul 7, 2025
8c31f1a
AB#29051 - SonarQube Cleanup
plavoie-BC Jul 7, 2025
7a04abe
Merge branch 'dev' into 'feature/AB#29051-email-add-bcc'
plavoie-BC Jul 7, 2025
55069c5
Merge remote-tracking branch 'origin/dev' into feature/AB#29051-email…
plavoie-BC Jul 28, 2025
dc43b21
AB#29051 - Email CC & BCC Bugfixes
plavoie-BC Jul 30, 2025
d1c380e
Merge remote-tracking branch 'origin/dev' into feature/AB#29051-email…
plavoie-BC Jul 30, 2025
9bd9ff0
bugfix/AB#29682-FixJsonInvalidScoresheet
jimmyPasta Jul 30, 2025
d6187d9
AB#29051 - Code Quality Cleanup and Update Email Address Validation
plavoie-BC Jul 30, 2025
80892c2
Merge remote-tracking branch 'origin/dev' into feature/AB#29051-email…
plavoie-BC Jul 30, 2025
6a89e50
Update applications/Unity.GrantManager/modules/Unity.Flex/src/Unity.F…
JamesPasta Jul 30, 2025
aaf791e
AB#29051 - Code Quality Fixes
plavoie-BC Jul 30, 2025
676b9cd
AB#29051 - Code Quality Email Validation Fix
plavoie-BC Jul 30, 2025
acb64b5
AB#29694:Bugfix contact info not saving
aurelio-aot Jul 30, 2025
74cf131
bugfix/AB#28691-FixUnitTest
jimmyPasta Jul 30, 2025
ea03e7b
Merge pull request #1551 from bcgov/bugfix/AB#28691-FixUnitTests
JamesPasta Jul 30, 2025
f8c229a
Merge pull request #1550 from bcgov/bugfix/AB#29694-contact-info-not-…
JamesPasta Jul 30, 2025
d5e982f
Merge pull request #1549 from bcgov/bugfix/AB#29682-FixJsonInvalidSco…
JamesPasta Jul 30, 2025
4a3bc26
bugfix/AB#28691-FixL3
jimmyPasta Jul 30, 2025
7a9b4be
Merge branch 'dev' into bugfix/AB#28691-FixUnitTests
jimmyPasta Jul 30, 2025
5a064f8
Merge pull request #1552 from bcgov/bugfix/AB#28691-FixUnitTests
JamesPasta Jul 30, 2025
666663a
Merge pull request #1544 from bcgov/feature/AB#29051-email-add-bcc
JamesPasta Jul 30, 2025
c4f8c2f
bugfix/AB#28691-FixSonar
jimmyPasta Jul 30, 2025
60e42b6
Merge pull request #1554 from bcgov/bugfix/AB#28691-FixUnitTests
JamesPasta Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,27 @@ public static string ConvertInputType(this CustomFieldType type)

public static CustomFieldDefinition? ConvertDefinition(this string definition, QuestionType type)
{
return type switch
if (string.IsNullOrWhiteSpace(definition))
{
QuestionType.Text => JsonSerializer.Deserialize<TextDefinition>(definition),
QuestionType.Number => JsonSerializer.Deserialize<NumericDefinition>(definition),
QuestionType.YesNo => JsonSerializer.Deserialize<QuestionYesNoDefinition>(definition),
QuestionType.SelectList => JsonSerializer.Deserialize<QuestionSelectListDefinition>(definition),
QuestionType.TextArea => JsonSerializer.Deserialize<TextAreaDefinition>(definition),
_ => null,
};
return null;
}

try
{
return type switch
{
QuestionType.Text => JsonSerializer.Deserialize<TextDefinition>(definition),
QuestionType.Number => JsonSerializer.Deserialize<NumericDefinition>(definition),
QuestionType.YesNo => JsonSerializer.Deserialize<QuestionYesNoDefinition>(definition),
QuestionType.SelectList => JsonSerializer.Deserialize<QuestionSelectListDefinition>(definition),
QuestionType.TextArea => JsonSerializer.Deserialize<TextAreaDefinition>(definition),
_ => null,
};
}
catch (JsonException)
{
return null;
}
}

public static string[] GetCheckedOptions(this string value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,36 @@ public class NumericDefinitionWidget : AbpViewComponent
.ApplyRequired(form);
}

// Cache JsonSerializerOptions instance
private static readonly JsonSerializerOptions CachedJsonOptions = new JsonSerializerOptions
{
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
};

public async Task<IViewComponentResult> InvokeAsync(string? definition)
{
if (definition != null)
NumericDefinitionViewModel viewModel = new();

if (!string.IsNullOrWhiteSpace(definition))
{
NumericDefinition? numericDefinition = JsonSerializer.Deserialize<NumericDefinition>(definition);
if (numericDefinition != null)
try
{
return View(await Task.FromResult(new NumericDefinitionViewModel()
var numericDefinition = JsonSerializer.Deserialize<NumericDefinition>(definition, CachedJsonOptions);

if (numericDefinition != null)
{
Min = numericDefinition.Min,
Max = numericDefinition.Max,
Required = numericDefinition.Required
}));
viewModel.Min = numericDefinition.Min;
viewModel.Max = numericDefinition.Max;
viewModel.Required = numericDefinition.Required;
}
}
catch (JsonException)
{
// Optionally log the error
}
}

return View(await Task.FromResult(new NumericDefinitionViewModel()));
return View(await Task.FromResult(viewModel));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class EmailHistoryDto : ExtensibleAuditedEntityDto<Guid>
public string Status { get; set; } = string.Empty;
public string FromAddress { get; set; } = string.Empty;
public string ToAddress { get; set; } = string.Empty;
public string Cc { get; set; } = string.Empty;
public string Bcc { get; set; } = string.Empty;
public DateTime? SentDateTime { get; set; }
public string Body { get; set; } = string.Empty;
public EmailHistoryUserDto? SentBy { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Unity.Modules.Shared.Utils;
using Unity.Notifications.Emails;
using Unity.Notifications.Events;
using Unity.Notifications.Integrations.Ches;
using Unity.Notifications.Integrations.RabbitMQ;
using Unity.Notifications.Permissions;
using Unity.Notifications.Settings;
using Unity.Notifications.TeamsNotifications;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Users;
using Volo.Abp.SettingManagement;
using Unity.Notifications.Settings;
using Unity.Notifications.Permissions;
using Volo.Abp;
using Volo.Abp.Features;
using Microsoft.AspNetCore.Http;
using Volo.Abp.SettingManagement;
using Volo.Abp.Users;

namespace Unity.Notifications.EmailNotifications;

Expand Down Expand Up @@ -83,14 +84,14 @@ public async Task<int> GetEmailsChesWithNoResponseCountAsync()
return emailLogs?.Count ?? 0;
}

public async Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName)
public async Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
{
if (string.IsNullOrEmpty(emailTo))
{
return null;
}

var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName);
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC);
EmailLog emailLog = await _emailLogsRepository.GetAsync(emailId);
emailLog = UpdateMappedEmailLog(emailLog, emailObject);
emailLog.ApplicationId = applicationId;
Expand All @@ -102,19 +103,19 @@ public async Task<int> GetEmailsChesWithNoResponseCountAsync()
return loggedEmail;
}

public async Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName)
public async Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
{
return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized, emailTemplateName);
return await InitializeEmailLog(emailTo, body, subject, applicationId, emailFrom, EmailStatus.Initialized, emailTemplateName, emailCC, emailBCC);
}

[RemoteService(false)]
public async Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName)
public async Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
{
if (string.IsNullOrEmpty(emailTo))
{
return null;
}
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName);
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, "html", emailTemplateName, emailCC, emailBCC);
EmailLog emailLog = new EmailLog();
emailLog = UpdateMappedEmailLog(emailLog, emailObject);
emailLog.ApplicationId = applicationId;
Expand Down Expand Up @@ -218,9 +219,12 @@ public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto i
/// <param name="body">The body of the email</param>
/// <param name="subject">Subject Message</param>
/// <param name="emailFrom">From Email Address</param>
/// <param name="emailBodyType">Type of body email: html or text</param>
/// <param name="emailBodyType">Type of body email: html or text</param>
/// <param name="emailTemplateName">Template name for the email</param>
/// <param name="emailCC">CC email addresses</param>
/// <param name="emailBCC">BCC email addresses</param>
/// <returns>HttpResponseMessage indicating the result of the operation</returns>
public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName)
public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
{
try
{
Expand All @@ -234,7 +238,7 @@ public async Task<HttpResponseMessage> SendEmailNotification(string emailTo, str

}
// Send the email using the CHES client service
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName);
var emailObject = await GetEmailObjectAsync(emailTo, body, subject, emailFrom, emailBodyType, emailTemplateName, emailCC, emailBCC);
var response = await _chesClientService.SendAsync(emailObject);

// Assuming SendAsync returns a HttpResponseMessage or equivalent:
Expand Down Expand Up @@ -312,22 +316,20 @@ public async Task SendEmailToQueue(EmailLog emailLog)
await _emailQueueService.SendToEmailEventQueueAsync(emailNotificationEvent);
}

protected virtual async Task<dynamic> GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName)
protected virtual async Task<dynamic> GetEmailObjectAsync(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null)
{
List<string> toList = new();
string[] emails = emailTo.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries);

foreach (string email in emails)
{
toList.Add(email.Trim());
}
var toList = emailTo.ParseEmailList() ?? [];
var ccList = emailCC.ParseEmailList();
var bccList = emailBCC.ParseEmailList();

var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress);

var emailObject = new
{
body,
bodyType = emailBodyType ?? "text",
cc = ccList,
bcc = bccList,
encoding = "utf-8",
from = emailFrom ?? defaultFromAddress ?? "NoReply@gov.bc.ca",
priority = "normal",
Expand All @@ -345,7 +347,9 @@ protected virtual EmailLog UpdateMappedEmailLog(EmailLog emailLog, dynamic email
emailLog.Subject = emailDynamicObject.subject;
emailLog.BodyType = emailDynamicObject.bodyType;
emailLog.FromAddress = emailDynamicObject.from;
emailLog.ToAddress = String.Join(",", emailDynamicObject.to);
emailLog.ToAddress = string.Join(",", emailDynamicObject.to);
emailLog.CC = emailDynamicObject.cc != null ? string.Join(",", (IEnumerable<string>)emailDynamicObject.cc) : string.Empty;
emailLog.BCC = emailDynamicObject.bcc != null ? string.Join(",", (IEnumerable<string>)emailDynamicObject.bcc) : string.Empty;
emailLog.TemplateName = emailDynamicObject.templateName;
return emailLog;
}
Expand All @@ -357,7 +361,8 @@ public async Task UpdateSettings(NotificationsSettingsDto settingsDto)
await UpdateTenantSettings(NotificationsSettings.Mailing.EmailMaxRetryAttempts, settingsDto.MaximumRetryAttempts);
}

private async Task UpdateTenantSettings(string settingKey, string valueString) {
private async Task UpdateTenantSettings(string settingKey, string valueString)
{
if (!valueString.IsNullOrWhiteSpace())
{
await _settingManager.SetForCurrentTenantAsync(settingKey, valueString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ namespace Unity.Notifications.EmailNotifications
{
public interface IEmailNotificationService : IApplicationService
{
Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName);
Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName);
Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName);
Task<EmailLog?> UpdateEmailLog(Guid emailId, string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null);
Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? status, string? emailTemplateName, string? emailCC = null, string? emailBCC = null);
Task<EmailLog?> InitializeEmailLog(string emailTo, string body, string subject, Guid applicationId, string? emailFrom, string? emailTemplateName, string? emailCC = null, string? emailBCC = null);
Task<EmailLog?> GetEmailLogById(Guid id);
Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto input);
Task<HttpResponseMessage> SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName);
Task<HttpResponseMessage> SendEmailNotification(string emailTo, string body, string subject, string? emailFrom, string? emailBodyType, string? emailTemplateName, string? emailCC = null, string? emailBCC = null);
Task SendEmailToQueue(EmailLog emailLog);
Task<List<EmailHistoryDto>> GetHistoryByApplicationId(Guid applicationId);
Task UpdateSettings(NotificationsSettingsDto settingsDto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public class EmailNotificationEvent
public string Subject { get; set; } = string.Empty;
public string? EmailFrom { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public List<string> EmailAddressList { get; set; } = new List<string>();
public List<string> EmailAddressList { get; set; } = [];
public IEnumerable<string> Cc { get; set; } = [];
public IEnumerable<string> Bcc { get; set; } = [];

[JsonConverter(typeof(JsonStringEnumConverter))]
public EmailAction Action { get; set; }
Expand Down
Loading