Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement keyword-based dynamic text replacement #657

Open
wants to merge 75 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
89887bc
Improve the documentation
Ahmed-Ghanam Nov 15, 2024
01eeb3f
Improve the documentation and initialization
Ahmed-Ghanam Nov 15, 2024
43ed855
Improve the documentation
Ahmed-Ghanam Nov 15, 2024
a553fcf
Fill in the recipient's name based on whether or not placeholder keyw…
Ahmed-Ghanam Nov 20, 2024
9b5cd95
Implemented a new function to retrieve party details for organizations.
Ahmed-Ghanam Nov 20, 2024
625a92f
Extend the lookup logic to check for the recipientNumber keyword
Ahmed-Ghanam Nov 21, 2024
4f172ca
Improve the members documentation
Ahmed-Ghanam Nov 21, 2024
cb5cf44
Add a new property to whether the template contains any recipient num…
Ahmed-Ghanam Nov 21, 2024
5919ddb
Improve type documentation
Ahmed-Ghanam Nov 21, 2024
f01b2c8
Eliminate the functions responsible for retrieving the individual nam…
Ahmed-Ghanam Nov 21, 2024
93e511e
Allow the user to send in the components of recipient name when creat…
Ahmed-Ghanam Nov 21, 2024
0fb50a2
Simplify the keywords detection logic
Ahmed-Ghanam Nov 26, 2024
e3ceadc
Remove the RecipientNameComponents type.
Ahmed-Ghanam Nov 26, 2024
8cc1283
Remove the name components
Ahmed-Ghanam Nov 26, 2024
dbe1a77
Remove the unnecessary ConfigureAwait
Ahmed-Ghanam Nov 26, 2024
a1d13ce
Create a new keyword service to inject actual value wherever a keywor…
Ahmed-Ghanam Nov 27, 2024
ea80c49
Update the test data
Ahmed-Ghanam Nov 27, 2024
7c6b427
Update two functions to retrieve recipient number and recipient name
Ahmed-Ghanam Nov 27, 2024
6ffb6db
Update the sender email address.
Ahmed-Ghanam Nov 28, 2024
d5d264d
Save the customized email subject and body in the database
Ahmed-Ghanam Nov 28, 2024
3feb865
Pass customized body and subject when trying to send Emails again
Ahmed-Ghanam Nov 28, 2024
918808d
Add support for placeholder keywords in SMS
Ahmed-Ghanam Nov 28, 2024
d1391ad
Improve the keyword replacement logic
Ahmed-Ghanam Nov 28, 2024
2bb8102
Replace Regex checks with string.Contains
Ahmed-Ghanam Nov 28, 2024
a4fb9d5
Improve the request that we use to retrieve unit details.
Ahmed-Ghanam Nov 28, 2024
3003630
Add validation logic to ensure that either organizationNumber or soci…
Ahmed-Ghanam Nov 28, 2024
0f3bd1c
Added a constructor for batch creation from existing lists to simplif…
Ahmed-Ghanam Nov 28, 2024
7b49cbb
Update the function that is used to retrieve emails
Ahmed-Ghanam Nov 28, 2024
602b804
Fix build errors
Ahmed-Ghanam Nov 28, 2024
dfb5241
Pass a default value for the new parameters
Ahmed-Ghanam Nov 28, 2024
52b12b3
Breaking a large method into smaller, more focused private methods
Ahmed-Ghanam Nov 28, 2024
26728a0
Make a method static
Ahmed-Ghanam Nov 28, 2024
62ee16f
Code refactoring
Ahmed-Ghanam Nov 29, 2024
3f13092
Code refactoring
Ahmed-Ghanam Nov 29, 2024
583d139
Fix typos
Ahmed-Ghanam Nov 29, 2024
3763f01
Use explicit JOIN instead of the implicit join in the FROM clause and…
Ahmed-Ghanam Nov 29, 2024
d856944
Use explicit JOIN instead of the implicit join in the FROM clause and…
Ahmed-Ghanam Nov 29, 2024
3ab7891
Use SELECT INTO for assigning values to the __orderid variable and im…
Ahmed-Ghanam Nov 29, 2024
e0d494c
Use SELECT INTO for assigning values to the __orderid variable and im…
Ahmed-Ghanam Nov 29, 2024
de659f9
Add a script to alter tables
Ahmed-Ghanam Nov 29, 2024
4fef97b
Undo changes made on the auto generated file and move them to new scr…
Ahmed-Ghanam Nov 29, 2024
0e94515
Remove two unnecessary values.
Ahmed-Ghanam Nov 29, 2024
9320d26
Add a missing parenthesis
Ahmed-Ghanam Nov 29, 2024
caeae94
Remove two unnecessary properties.
Ahmed-Ghanam Nov 29, 2024
1b58b0d
Remove two unnecessary values.
Ahmed-Ghanam Nov 29, 2024
1b0385d
Enhance the logic to ensure all existing unit tests execute successfu…
Ahmed-Ghanam Nov 29, 2024
adfb256
Save and retrieve the customized subject and body to and from the dat…
Ahmed-Ghanam Nov 29, 2024
b5200a8
Improve the XML documentation
Ahmed-Ghanam Nov 29, 2024
7ea16a0
Update the keywords service to handle data batches
Ahmed-Ghanam Nov 29, 2024
b97af32
Remove the logic used to drop and create both functions and procedures
Ahmed-Ghanam Nov 29, 2024
468899f
Update the Email sending and retry sending logic
Ahmed-Ghanam Nov 29, 2024
0e391e4
Code refactoring
Ahmed-Ghanam Nov 30, 2024
7606ba7
Fix the parameter of some unit tests
Ahmed-Ghanam Nov 30, 2024
d88cb97
Adjust the code to run unit test successfully
Ahmed-Ghanam Nov 30, 2024
2c8461e
Handle batch of organization and national identity numbers at one
Ahmed-Ghanam Nov 30, 2024
755cd8c
Update the test units to reflect latest changes
Ahmed-Ghanam Nov 30, 2024
0d163ca
Improve the readability
Ahmed-Ghanam Dec 1, 2024
cba7bf9
Update the SQL query used to retrieve recipients
Ahmed-Ghanam Dec 1, 2024
31064d7
Implement simple test units to text keyword replacement logic
Ahmed-Ghanam Dec 1, 2024
94a291c
Update unit tests and test data
Ahmed-Ghanam Dec 1, 2024
cabb8d9
Fix a test unit
Ahmed-Ghanam Dec 1, 2024
c9cfc20
Update the order logic to handle orders sent to recipients without us…
Ahmed-Ghanam Dec 1, 2024
e40ade7
Added test units to test retrieving party details using organization …
Ahmed-Ghanam Dec 2, 2024
46f13b2
Allow the user to retrieve party detail for both organization and nat…
Ahmed-Ghanam Dec 2, 2024
f361657
Refactor some test units
Ahmed-Ghanam Dec 2, 2024
4c72d13
Improve the SMS order processing logic
Ahmed-Ghanam Dec 2, 2024
5a57ce7
Remove the organization number
Ahmed-Ghanam Dec 2, 2024
9cd7ec3
Refactor: Remove unused PersonNameComponents type.
Ahmed-Ghanam Dec 5, 2024
b562e4b
#545 Added tests to increase coverage.
Ahmed-Ghanam Dec 5, 2024
7a8c64b
#545 Validate the constructors and JSON serialization behavior of the…
Ahmed-Ghanam Dec 5, 2024
6c3f883
Change the type of a field to improve performance
Ahmed-Ghanam Dec 5, 2024
d11933f
Add three test units to cover more use cases
Ahmed-Ghanam Dec 5, 2024
aefc458
Remove test cases that check for the nullability of parameters that a…
Ahmed-Ghanam Dec 5, 2024
51aefcf
Replace the national identity number with an empty string
Ahmed-Ghanam Dec 16, 2024
83f31c4
Do not replace the $recipientNumber$ with the national identity number
Ahmed-Ghanam Dec 16, 2024
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 @@ -46,6 +46,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat
.AddSingleton<INotificationsEmailServiceUpdateService, NotificationsEmailServiceUpdateService>()
.AddSingleton<IMetricsService, MetricsService>()
.AddSingleton<INotificationScheduleService, NotificationScheduleService>()
.AddSingleton<IKeywordsService, KeywordsService>()
.Configure<KafkaSettings>(config.GetSection("KafkaSettings"))
.Configure<NotificationConfig>(config.GetSection("NotificationConfig"));
}
Expand Down
45 changes: 34 additions & 11 deletions src/Altinn.Notifications.Core/Integrations/IRegisterClient.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
using Altinn.Notifications.Core.Models.ContactPoints;
using Altinn.Notifications.Core.Models.Parties;

namespace Altinn.Notifications.Core.Integrations
namespace Altinn.Notifications.Core.Integrations;

/// <summary>
/// Defines a contract for interacting with the register service.
/// </summary>
public interface IRegisterClient
{
/// <summary>
/// Interface describing a client for the register service
/// Asynchronously retrieves contact point details for the specified organizations.
/// </summary>
/// <param name="organizationNumbers">A collection of organization numbers for which contact point details are requested.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains a list of <see cref="OrganizationContactPoints"/> representing the contact points of the specified organizations.
/// </returns>
Task<List<OrganizationContactPoints>> GetOrganizationContactPoints(List<string> organizationNumbers);

/// <summary>
/// Asynchronously retrieves party details for the specified organizations.
/// </summary>
/// <param name="organizationNumbers">A collection of organization numbers for which party details are requested.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains a list of <see cref="PartyDetails"/> representing the details of the specified organizations.
/// </returns>
Task<List<PartyDetails>> GetPartyDetailsForOrganizations(List<string> organizationNumbers);

/// <summary>
/// Asynchronously retrieves party details for the specified persons.
/// </summary>
public interface IRegisterClient
{
/// <summary>
/// Retrieves contact points for a list of organizations
/// </summary>
/// <param name="organizationNumbers">A list of organization numbers to look up contact points for</param>
/// <returns>A list of <see cref="OrganizationContactPoints"/> for the provided organizations</returns>
public Task<List<OrganizationContactPoints>> GetOrganizationContactPoints(List<string> organizationNumbers);
}
/// <param name="socialSecurityNumbers">A collection of social security numbers for which party details are requested.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains a list of <see cref="PartyDetails"/> representing the details of the specified individuals.
/// </returns>
Task<List<PartyDetails>> GetPartyDetailsForPersons(List<string> socialSecurityNumbers);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,51 @@
namespace Altinn.Notifications.Core.Models.NotificationTemplate;

/// <summary>
/// Template for an email notification
/// Represents a template for an email notification.
/// </summary>
public class EmailTemplate : INotificationTemplate
{
/// <inheritdoc/>
public NotificationTemplateType Type { get; internal set; }
/// <summary>
/// Gets the body of the email.
/// </summary>
public string Body { get; internal set; } = string.Empty;

/// <summary>
/// Gets the from adress of emails created by the template
/// Gets the content type of the email.
/// </summary>
public string FromAddress { get; internal set; } = string.Empty;
public EmailContentType ContentType { get; internal set; }

/// <summary>
/// Gets the subject of emails created by the template
/// Gets the sender address of the email.
/// </summary>
public string Subject { get; internal set; } = string.Empty;
public string FromAddress { get; internal set; } = string.Empty;

/// <summary>
/// Gets the body of emails created by the template
/// Gets the subject of the email.
/// </summary>
public string Body { get; internal set; } = string.Empty;
public string Subject { get; internal set; } = string.Empty;

/// <summary>
/// Gets the content type of emails created by the template
/// Gets the type of the notification template.
/// </summary>
public EmailContentType ContentType { get; internal set; }
/// <value>
/// The type of the notification template, represented by the <see cref="NotificationTemplateType"/> enum.
/// </value>
public NotificationTemplateType Type { get; } = NotificationTemplateType.Email;

/// <summary>
/// Initializes a new instance of the <see cref="EmailTemplate"/> class.
/// Initializes a new instance of the <see cref="EmailTemplate"/> class with the specified from address, subject, body, and content type.
/// </summary>
/// <param name="fromAddress">The sender address of the email. If null, an empty string is used.</param>
/// <param name="subject">The subject of the email.</param>
/// <param name="body">The body of the email.</param>
/// <param name="contentType">The content type of the email.</param>
public EmailTemplate(string? fromAddress, string subject, string body, EmailContentType contentType)
{
FromAddress = fromAddress ?? string.Empty;
Subject = subject;
Body = body;
Subject = subject;
ContentType = contentType;
Type = NotificationTemplateType.Email;
FromAddress = fromAddress ?? string.Empty;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
namespace Altinn.Notifications.Core.Models.NotificationTemplate;

/// <summary>
/// Base class for a notification template
/// Represents a base notification template.
/// </summary>
[JsonDerivedType(typeof(EmailTemplate), "email")]
[JsonDerivedType(typeof(SmsTemplate), "sms")]
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$")]
public interface INotificationTemplate
{
/// <summary>
/// Gets the type for the template
/// Gets the type of the notification template.
/// </summary>
public NotificationTemplateType Type { get; }
NotificationTemplateType Type { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,37 @@
namespace Altinn.Notifications.Core.Models.NotificationTemplate;

/// <summary>
/// Template for an SMS notification
/// Represents a template for an SMS notification.
/// </summary>
public class SmsTemplate : INotificationTemplate
{
/// <inheritdoc/>
public NotificationTemplateType Type { get; internal set; }
/// <summary>
/// Gets the body of the SMS.
/// </summary>
public string Body { get; internal set; } = string.Empty;

/// <summary>
/// Gets the number from which the SMS is created by the template
/// Gets the number from which the SMS is sent.
/// </summary>
public string SenderNumber { get; internal set; } = string.Empty;

/// <summary>
/// Gets the body of SMSs created by the template
/// Gets the type of the notification template.
/// </summary>
public string Body { get; internal set; } = string.Empty;
/// <value>
/// The type of the notification template, represented by the <see cref="NotificationTemplateType"/> enum.
/// </value>
public NotificationTemplateType Type { get; } = NotificationTemplateType.Sms;

/// <summary>
/// Initializes a new instance of the <see cref="SmsTemplate"/> class.
/// Initializes a new instance of the <see cref="SmsTemplate"/> class with the specified sender number and body.
/// </summary>
/// <param name="senderNumber">The number from which the SMS is sent. If null, an empty string is used.</param>
/// <param name="body">The body of the SMS.</param>
public SmsTemplate(string? senderNumber, string body)
{
SenderNumber = senderNumber ?? string.Empty;
Body = body;
Type = NotificationTemplateType.Sms;
SenderNumber = senderNumber ?? string.Empty;
}

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions src/Altinn.Notifications.Core/Models/Parties/PartyDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Core.Models.Parties;

/// <summary>
/// Represents the details for a specific party.
/// </summary>
public class PartyDetails
{
/// <summary>
/// Gets or sets the name of the party.
/// </summary>
[JsonPropertyName("name")]
public string? Name { get; set; }

/// <summary>
/// Gets or sets the organization number of the party, if applicable.
/// </summary>
[JsonPropertyName("orgNo")]
public string? OrganizationNumber { get; set; }

/// <summary>
/// Gets or sets the social security number of the party, if applicable.
/// </summary>
[JsonPropertyName("ssn")]
public string? NationalIdentityNumber { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Core.Models.Parties;

/// <summary>
/// Represents a request to look up party details by their identifiers.
/// </summary>
public class PartyDetailsLookupBatch
{
/// <summary>
/// Initializes a new instance of the <see cref="PartyDetailsLookupBatch"/> class.
/// </summary>
/// <param name="organizationNumbers">A list of organization numbers to look up.</param>
/// <param name="socialSecurityNumbers">A list of social security numbers to look up.</param>
/// <exception cref="ArgumentException">Thrown when both <paramref name="organizationNumbers"/> and <paramref name="socialSecurityNumbers"/> are null or empty.</exception>
[JsonConstructor]
public PartyDetailsLookupBatch(List<string>? organizationNumbers = null, List<string>? socialSecurityNumbers = null)
{
if ((organizationNumbers == null || organizationNumbers.Count == 0) && (socialSecurityNumbers == null || socialSecurityNumbers.Count == 0))
{
throw new ArgumentException("At least one of organizationNumbers or socialSecurityNumbers must be provided.");
}

OrganizationNumbers = organizationNumbers ?? [];
SocialSecurityNumbers = socialSecurityNumbers ?? [];

PartyDetailsLookupRequestList = [];

if (OrganizationNumbers.Count != 0)
{
PartyDetailsLookupRequestList.AddRange(OrganizationNumbers.Select(orgNum => new PartyDetailsLookupRequest(organizationNumber: orgNum)));
}

if (SocialSecurityNumbers.Count != 0)
{
PartyDetailsLookupRequestList.AddRange(SocialSecurityNumbers.Select(ssn => new PartyDetailsLookupRequest(socialSecurityNumber: ssn)));
}
}

/// <summary>
/// Gets the organization numbers to look up.
/// </summary>
[JsonPropertyName("organizationNumbers")]
public List<string> OrganizationNumbers { get; }

/// <summary>
/// Gets the social security numbers to look up.
/// </summary>
[JsonPropertyName("socialSecurityNumbers")]
public List<string> SocialSecurityNumbers { get; }

/// <summary>
/// Gets the list of lookup criteria for parties.
/// </summary>
[JsonPropertyName("parties")]
public List<PartyDetailsLookupRequest> PartyDetailsLookupRequestList { get; private set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Core.Models.Parties;

/// <summary>
/// Represents a lookup criterion for a single party.
/// </summary>
public record PartyDetailsLookupRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="PartyDetailsLookupRequest"/> class.
/// Ensures that only one of <see cref="OrganizationNumber"/> or <see cref="SocialSecurityNumber"/> is set.
/// </summary>
/// <param name="organizationNumber">The organization number of the party.</param>
/// <param name="socialSecurityNumber">The social security number of the party.</param>
/// <exception cref="ArgumentException">Thrown when both <paramref name="organizationNumber"/> and <paramref name="socialSecurityNumber"/> are set.</exception>
public PartyDetailsLookupRequest(string? organizationNumber = null, string? socialSecurityNumber = null)
{
if (!string.IsNullOrEmpty(organizationNumber) && !string.IsNullOrEmpty(socialSecurityNumber))
{
throw new ArgumentException("You can specify either an OrganizationNumber or a SocialSecurityNumber, but not both.");
}

OrganizationNumber = organizationNumber;
SocialSecurityNumber = socialSecurityNumber;
}

/// <summary>
/// Gets the organization number of the party.
/// </summary>
/// <value>The organization number of the party.</value>
[JsonPropertyName("orgNo")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? OrganizationNumber { get; }

/// <summary>
/// Gets the social security number of the party.
/// </summary>
/// <value>The social security number of the party.</value>
[JsonPropertyName("ssn")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? SocialSecurityNumber { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Core.Models.Parties;

/// <summary>
/// Represents the response for a party details lookup operation.
/// </summary>
public class PartyDetailsLookupResult
{
/// <summary>
/// Gets or sets the list of party details.
/// </summary>
[JsonPropertyName("partyNames")]
public List<PartyDetails>? PartyDetailsList { get; set; } = [];
}
Loading
Loading