From 4b3c97b175dfe6dd1d964069c3b427f4e4a19207 Mon Sep 17 00:00:00 2001 From: Sanjay Prajapati Date: Wed, 21 Apr 2021 17:47:16 +0530 Subject: [PATCH 1/6] EMail-Notificationid mapping functionality for GDPR implementation. --- .../.gitignore | 264 ++++++++++++++++++ .../Constants.cs | 22 ++ .../GDPRNotificationMappingProcessor.cs | 97 +++++++ .../GDPRNotificationMappingProcessor.csproj | 38 +++ .../Startup.cs | 126 +++++++++ .../functionSettings.json | 7 + .../host.json | 12 + .../NotificationHandler/Startup.cs | 3 +- .../Business/v1/EmailHandlerManager.cs | 7 + .../Business/v1/EmailManager.cs | 96 ++++++- .../Business/v1/EmailServiceManager.cs | 6 + .../Interfaces/IEmailManager.cs | 18 ++ .../Configurations/ConfigConstants.cs | 12 +- .../Configurations/StorageAccountSetting.cs | 10 + .../Entities/EmailNotificationMapEntity.cs | 79 ++++++ .../Models/GDPR/EmailNotificationQueueItem.cs | 26 ++ .../GDPR/MeetingNotificationQueueItem.cs | 21 ++ .../GDPR/NotificationMappingQueueItem.cs | 26 ++ .../Models/GDPR/NotificationQueueItem.cs | 21 ++ .../IEmailNotificationRepository.cs | 15 + .../EmailNotificationRepository.cs | 7 + .../TableStorageEmailRepository.cs | 153 ++++++++++ .../V1/EmailManager/EmailManagerTests.cs | 11 +- .../V1/EmailManager/EmailManagerTestsBase.cs | 2 +- NotificationService/NotificationService.sln | 8 +- .../NotificationService/Startup.cs | 3 +- 26 files changed, 1082 insertions(+), 8 deletions(-) create mode 100644 NotificationService/GDPRNotificationMappingProcessor/.gitignore create mode 100644 NotificationService/GDPRNotificationMappingProcessor/Constants.cs create mode 100644 NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs create mode 100644 NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.csproj create mode 100644 NotificationService/GDPRNotificationMappingProcessor/Startup.cs create mode 100644 NotificationService/GDPRNotificationMappingProcessor/functionSettings.json create mode 100644 NotificationService/GDPRNotificationMappingProcessor/host.json create mode 100644 NotificationService/NotificationService.Contracts/Entities/EmailNotificationMapEntity.cs create mode 100644 NotificationService/NotificationService.Contracts/Models/GDPR/EmailNotificationQueueItem.cs create mode 100644 NotificationService/NotificationService.Contracts/Models/GDPR/MeetingNotificationQueueItem.cs create mode 100644 NotificationService/NotificationService.Contracts/Models/GDPR/NotificationMappingQueueItem.cs create mode 100644 NotificationService/NotificationService.Contracts/Models/GDPR/NotificationQueueItem.cs diff --git a/NotificationService/GDPRNotificationMappingProcessor/.gitignore b/NotificationService/GDPRNotificationMappingProcessor/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/NotificationService/GDPRNotificationMappingProcessor/Constants.cs b/NotificationService/GDPRNotificationMappingProcessor/Constants.cs new file mode 100644 index 0000000..e74a38e --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/Constants.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GDPRNotificationMappingProcessor +{ + + /// + /// class for constant literals. + /// + public sealed class Constants + { + /// + /// StorageType. + /// + public const string StorageType = "StorageType"; + + /// + /// Seconds to wait between attempts at polling the Azure KeyVault for changes in configuration. + /// + public const string KeyVaultConfigRefreshDurationSeconds = "KeyVaultConfigRefreshDurationSeconds"; + } +} diff --git a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs new file mode 100644 index 0000000..1773253 --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GDPRNotificationMappingProcessor +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.WebJobs; + using Microsoft.Extensions.Configuration; + using Microsoft.OData.Edm.Vocabularies; + using Newtonsoft.Json; + using NotificationService.Common.Logger; + using NotificationService.Contracts; + using NotificationService.Contracts.Models.GDPR; + using NotificationService.Data; + using NotificationService.Data.Interfaces; + + /// + /// Function to process notification messages and create emailId and NotificationId mapping for GDPR scrubbing. + /// + public class GDPRNotificationMappingProcessor + { + /// + /// The logger. + /// + private readonly ILogger logger; + + /// + /// Instance of . + /// + private readonly IEmailNotificationRepository emailNotificationRepository; + + /// + /// Instance of Application Configuration. + /// + private readonly IConfiguration configuration; + + /// + /// Enum to specify type of database. + /// + private readonly StorageType repo; + + /// + /// Initializes a new instance of the class. + /// + /// The log. + /// The configuration. + /// The repositoryFactory. + public GDPRNotificationMappingProcessor( + ILogger logger, + IConfiguration configuration, + IRepositoryFactory repositoryFactory) + { + this.logger = logger; + this.configuration = configuration; + this.emailNotificationRepository = repositoryFactory.GetRepository(Enum.TryParse(this.configuration?[Constants.StorageType], out this.repo) ? this.repo : throw new Exception()); + } + + /// + /// Trigger method invoked when a notification item is added to the queue. + /// + /// Serialized queue item. + [FunctionName("GDPRNotificationMappingProcessor")] + public void Run([QueueTrigger("%GdprNotifEmailMapQueueName%", Connection = "AzureWebJobsStorage")]string message) + { + if (string.IsNullOrEmpty(message)) + { + return; + } + + this.logger.TraceInformation($"Started processing notificaiton to create emailid-notificationid mapping."); + NotificationMappingQueueItem item = JsonConvert.DeserializeObject(message); + NotificationType notifType = (NotificationType)Enum.Parse(typeof(NotificationType), item.NotificationType); + var payloadJson = item.Payload?.ToString(); + + if (string.IsNullOrEmpty(payloadJson)) + { + this.logger.TraceError($"Notification Payload for type {item.NotificationType} can't be null or empty"); + return; + } + + this.logger.TraceInformation($"processing notification mapping for notification type {item.NotificationType}"); + if (notifType == NotificationType.Mail) + { + var entities = JsonConvert.DeserializeObject>(payloadJson); + this.emailNotificationRepository.CreateEmailIdNotificationForEmailsMapping(entities, item.ApplicationName); + } + else + { + var entities = JsonConvert.DeserializeObject>(payloadJson); + this.emailNotificationRepository.CreateEmailIdNotificationForMeetingInvitesMapping(entities, item.ApplicationName); + } + + this.logger.TraceInformation($"Started processing notificaiton for type {item.NotificationType} to create emailid-notificationid mapping."); + } + } +} diff --git a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.csproj b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.csproj new file mode 100644 index 0000000..9e60b93 --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.csproj @@ -0,0 +1,38 @@ + + + netcoreapp3.1 + v3 + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/NotificationService/GDPRNotificationMappingProcessor/Startup.cs b/NotificationService/GDPRNotificationMappingProcessor/Startup.cs new file mode 100644 index 0000000..3892ff7 --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/Startup.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Azure.Functions.Extensions.DependencyInjection; + +[assembly: FunctionsStartup(typeof(GDPRNotificationMappingProcessor.Startup))] + +namespace GDPRNotificationMappingProcessor +{ + using System; + using System.IO; + using System.Reflection; + using Microsoft.ApplicationInsights.AspNetCore; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.DependencyCollector; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Configuration.AzureAppConfiguration; + using Microsoft.Extensions.Configuration.AzureKeyVault; + using Microsoft.Extensions.DependencyInjection; + using NotificationService.Common; + using NotificationService.Common.Configurations; + using NotificationService.Common.Logger; + using NotificationService.Data; + using NotificationService.Data.Interfaces; + using NotificationService.Data.Repositories; + + /// + /// Startup. + /// + public class Startup : FunctionsStartup + { + /// + /// Gets the maxDequeueCount from host.json. + /// + public static IConfigurationSection MaxDequeueCount { get; private set; } + + /// + /// Gets the application configuration. + /// + public IConfiguration Configuration { get; } + + /// + public override void Configure(IFunctionsHostBuilder builder) + { + var azureFuncConfig = builder?.Services?.BuildServiceProvider()?.GetService(); + var configBuilder = new ConfigurationBuilder(); + _ = configBuilder.AddConfiguration(azureFuncConfig); + var configFolder = Directory.GetParent(Assembly.GetExecutingAssembly().Location).Parent?.FullName; + _ = configBuilder.SetBasePath(configFolder); + _ = configBuilder.AddJsonFile("functionSettings.json"); + _ = configBuilder.AddEnvironmentVariables(); + + var configuration = configBuilder.Build(); + MaxDequeueCount = configuration.GetSection(ConfigConstants.MaxDequeueCountConfigKey); + + AzureKeyVaultConfigurationOptions azureKeyVaultConfigurationOptions = new AzureKeyVaultConfigurationOptions(configuration[ConfigConstants.KeyVaultUrlConfigKey]) + { + ReloadInterval = TimeSpan.FromSeconds(double.Parse(configuration[Constants.KeyVaultConfigRefreshDurationSeconds])), + }; + _ = configBuilder.AddAzureKeyVault(azureKeyVaultConfigurationOptions); + configuration = configBuilder.Build(); + _ = configBuilder.AddAzureAppConfiguration(options => + { + var settings = options.Connect(configuration[ConfigConstants.AzureAppConfigConnectionstringConfigKey]) + .Select(KeyFilter.Any, "Common").Select(KeyFilter.Any, "QueueProcessor"); + _ = settings.ConfigureRefresh(refreshOptions => + { + _ = refreshOptions.Register(key: configuration[ConfigConstants.ForceRefreshConfigKey], refreshAll: true, label: LabelFilter.Null); + }); + }); + + configuration = configBuilder.Build(); + + ITelemetryInitializer[] itm = new ITelemetryInitializer[1]; + var envInitializer = new EnvironmentInitializer + { + Service = configuration[AIConstants.ServiceConfigName], + ServiceLine = configuration[AIConstants.ServiceLineConfigName], + ServiceOffering = configuration[AIConstants.ServiceOfferingConfigName], + ComponentId = configuration[AIConstants.ComponentIdConfigName], + ComponentName = configuration[AIConstants.ComponentNameConfigName], + EnvironmentName = configuration[AIConstants.EnvironmentName], + IctoId = "IctoId", + }; + itm[0] = envInitializer; + LoggingConfiguration loggingConfiguration = new LoggingConfiguration + { + IsTraceEnabled = true, + TraceLevel = (SeverityLevel)Enum.Parse(typeof(SeverityLevel), configuration[ConfigConstants.AITraceLelelConfigKey]), + EnvironmentName = configuration[AIConstants.EnvironmentName], + }; + + var tconfig = TelemetryConfiguration.CreateDefault(); + tconfig.InstrumentationKey = configuration[ConfigConstants.AIInsrumentationConfigKey]; + + DependencyTrackingTelemetryModule depModule = new DependencyTrackingTelemetryModule(); + depModule.Initialize(tconfig); + + RequestTrackingTelemetryModule requestTrackingTelemetryModule = new RequestTrackingTelemetryModule(); + requestTrackingTelemetryModule.Initialize(tconfig); + + _ = builder.Services.AddSingleton(_ => new AILogger(loggingConfiguration, tconfig, itm)); + + StorageType storageType = (StorageType)Enum.Parse(typeof(StorageType), configuration?[ConfigConstants.StorageType]); + if (storageType == StorageType.DocumentDB) + { + _ = builder.Services.Configure(configuration.GetSection(ConfigConstants.CosmosDBConfigSectionKey)); + _ = builder.Services.Configure(s => s.Key = configuration[ConfigConstants.CosmosDBKeyConfigKey]); + _ = builder.Services.Configure(s => s.Uri = configuration[ConfigConstants.CosmosDBURIConfigKey]); + _ = builder.Services.AddScoped(); + _ = builder.Services.AddSingleton(); + _ = builder.Services.AddScoped(); + _ = builder.Services.AddScoped(s => s.GetService()); + } + + _ = builder.Services.Configure(configuration.GetSection(ConfigConstants.StorageAccountConfigSectionKey)); + _ = builder.Services.Configure(s => s.ConnectionString = configuration[ConfigConstants.StorageAccountConnectionStringConfigKey]); + _ = builder.Services.AddSingleton(configuration); + _ = builder.Services.AddScoped(); + _ = builder.Services.AddScoped(); + _ = builder.Services.AddScoped(s => s.GetService()); + _ = builder.Services.AddScoped(); + } + } +} diff --git a/NotificationService/GDPRNotificationMappingProcessor/functionSettings.json b/NotificationService/GDPRNotificationMappingProcessor/functionSettings.json new file mode 100644 index 0000000..a99506e --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/functionSettings.json @@ -0,0 +1,7 @@ +{ + "KeyVaultConfigRefreshDurationSeconds": "120", + "KeyVaultUrl": "__KeyVaultUrl__", + "AppConfig": { + "ForceRefresh": "ForceRefresh" + } +} \ No newline at end of file diff --git a/NotificationService/GDPRNotificationMappingProcessor/host.json b/NotificationService/GDPRNotificationMappingProcessor/host.json new file mode 100644 index 0000000..d767914 --- /dev/null +++ b/NotificationService/GDPRNotificationMappingProcessor/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "functionTimeout": "-1", + "extensions": { + "queues": { + "maxPollingInterval": "00:00:01", + "visibilityTimeout": "00:00:30", + "batchSize": 1, + "maxDequeueCount": 5 + } + } +} diff --git a/NotificationService/NotificationHandler/Startup.cs b/NotificationService/NotificationHandler/Startup.cs index fd36f27..b015a1a 100644 --- a/NotificationService/NotificationHandler/Startup.cs +++ b/NotificationService/NotificationHandler/Startup.cs @@ -76,7 +76,8 @@ public void ConfigureServices(IServiceCollection services) s.GetService(), s.GetService(), s.GetService(), - s.GetService())) + s.GetService(), + s.GetService())) .AddScoped(s => new EmailHandlerManager( this.Configuration, diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs index afc9857..c3e26a3 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs @@ -12,6 +12,7 @@ namespace NotificationService.BusinessLibrary.Business.v1 using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; + using Newtonsoft.Json; using NotificationService.BusinessLibrary.Interfaces; using NotificationService.Common; using NotificationService.Common.Configurations; @@ -19,6 +20,7 @@ namespace NotificationService.BusinessLibrary.Business.v1 using NotificationService.Contracts; using NotificationService.Contracts.Entities; using NotificationService.Contracts.Models; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; using NotificationService.Data; @@ -130,6 +132,8 @@ public async Task> QueueEmailNotifications(string ap this.logger.TraceVerbose($"Completed {nameof(this.cloudStorageClient.QueueCloudMessages)} method of {nameof(EmailHandlerManager)}.", traceProps); } + _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionGDPRMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); + var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); this.logger.TraceInformation($"Completed {nameof(this.QueueEmailNotifications)} method of {nameof(EmailHandlerManager)}.", traceProps); result = true; @@ -151,6 +155,7 @@ public async Task> QueueEmailNotifications(string ap } } + /// public async Task> QueueMeetingNotifications(string applicationName, MeetingNotificationItem[] meetingNotificationItems) { var stopwatch = new Stopwatch(); @@ -200,6 +205,8 @@ public async Task> QueueMeetingNotifications(string this.logger.TraceVerbose($"Completed {nameof(this.cloudStorageClient.QueueCloudMessages)} method of {nameof(EmailHandlerManager)}.", traceProps); } + _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionGDPRMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); + var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); this.logger.TraceInformation($"Completed {nameof(this.QueueMeetingNotifications)} method of {nameof(EmailHandlerManager)}.", traceProps); result = true; diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs index 5684c34..8c90351 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs @@ -9,6 +9,7 @@ namespace NotificationService.BusinessLibrary using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; + using Newtonsoft.Json; using NotificationService.BusinessLibrary.Interfaces; using NotificationService.Common.Configurations; using NotificationService.Common.Logger; @@ -16,6 +17,7 @@ namespace NotificationService.BusinessLibrary using NotificationService.Contracts.Entities; using NotificationService.Contracts.Extensions; using NotificationService.Contracts.Models; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; using NotificationService.Data; using NotificationService.Data.Interfaces; @@ -60,6 +62,11 @@ public class EmailManager : IEmailManager /// private readonly ITemplateMerge templateMerge; + /// + /// instance of . + /// + private readonly ICloudStorageClient cloudStorageClient; + /// /// Initializes a new instance of the class. /// @@ -73,7 +80,8 @@ public EmailManager( IRepositoryFactory repositoryFactory, ILogger logger, IMailTemplateManager templateManager, - ITemplateMerge templateMerge) + ITemplateMerge templateMerge, + ICloudStorageClient cloudStorageClient) { this.repositoryFactory = repositoryFactory; this.configuration = configuration; @@ -81,6 +89,7 @@ public EmailManager( this.logger = logger; this.templateManager = templateManager; this.templateMerge = templateMerge; + this.cloudStorageClient = cloudStorageClient; } /// @@ -313,5 +322,90 @@ public async Task> GetEmailNotificationsByDat { return await this.emailNotificationRepository.GetPendingOrFailedEmailNotificationsByDateRange(dateRange, applicationName, statusList).ConfigureAwait(false); } + + /// + public async Task QueueEmailNotificaitionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps) + { + this.logger.TraceInformation($"Started {nameof(this.QueueEmailNotificaitionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + + if (notificationEntities == null || notificationEntities.Count == 0) + { + return; + } + + var isGdprEnabled = (bool)this.configuration.GetValue(typeof(bool), ConfigConstants.IsGDPREnabled); + if (!isGdprEnabled) + { + this.logger.TraceInformation($"Gdpr Enabled Flag is set to False for EmailNotification", traceProps); + return; + } + + var gdprMapQueue = this.configuration?[$"{ConfigConstants.StorageAccGdprMapQueueName}"]; + var cloudQueue = this.cloudStorageClient.GetCloudQueue(gdprMapQueue); + foreach (var item in notificationEntities) + { + var payload = item.Select(notificationItem => new EmailNotificationQueueItem() + { + BCC = notificationItem.BCC, + CC = notificationItem.CC, + From = notificationItem.From, + To = notificationItem.To, + NotificationId = notificationItem.NotificationId, + }); + var queueItem = new NotificationMappingQueueItem() + { + NotificationType = NotificationType.Mail.ToString(), + ApplicationName = applicationName, + Payload = payload, + }; + + var cloudMessage = JsonConvert.SerializeObject(queueItem); + await this.cloudStorageClient.QueueCloudMessages(cloudQueue, new List() { cloudMessage }).ConfigureAwait(false); + } + + this.logger.TraceInformation($"Finished {nameof(this.QueueEmailNotificaitionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + } + + /// + public async Task QueueMeetingNotificactionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps) + { + this.logger.TraceInformation($"Started {nameof(this.QueueMeetingNotificactionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + + if (notificationEntities == null || notificationEntities.Count == 0) + { + return; + } + + var isGdprEnabled = (bool)this.configuration.GetValue(typeof(bool), ConfigConstants.IsGDPREnabled); + if (!isGdprEnabled) + { + this.logger.TraceInformation($"Gdpr Enabled Flag is set to False for EmailNotification", traceProps); + return; + } + + var gdprMapQueue = this.configuration?[$"{ConfigConstants.StorageAccGdprMapQueueName}"]; + var cloudQueue = this.cloudStorageClient.GetCloudQueue(gdprMapQueue); + foreach (var item in notificationEntities) + { + var payload = item.Select(notificationItem => new MeetingNotificationQueueItem() + { + From = notificationItem.From, + NotificationId = notificationItem.NotificationId, + RequiredAttendees = notificationItem.RequiredAttendees, + OptionalAttendees = notificationItem.OptionalAttendees, + }); + var queueItem = new NotificationMappingQueueItem() + { + NotificationType = NotificationType.Meet.ToString(), + ApplicationName = applicationName, + Payload = payload, + }; + + var cloudMessage = JsonConvert.SerializeObject(queueItem); + await this.cloudStorageClient.QueueCloudMessages(cloudQueue, new List() { cloudMessage }).ConfigureAwait(false); + } + + this.logger.TraceInformation($"Finished {nameof(this.QueueMeetingNotificactionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + } } } diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs index aa5ce51..7ccfabf 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs @@ -180,6 +180,9 @@ public async Task> SendMeetingInvites(string applica IList notificationResponses = new List(); IList emailNotificationEntities = await this.emailManager.CreateMeetingNotificationEntities(applicationName, meetingInviteItems, NotificationItemStatus.Processing).ConfigureAwait(false); IList notificationEntities = await this.emailNotificationRepository.GetMeetingNotificationItemEntities(emailNotificationEntities.Select(e => e.NotificationId).ToList(), applicationName).ConfigureAwait(false); + + _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionGDPRMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false)); + var notificationItemEntities = await this.ProcessMeetingNotificationEntities(applicationName, notificationEntities).ConfigureAwait(false); var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); this.logger.TraceInformation($"Finished {nameof(this.SendMeetingInvites)} method of {nameof(EmailServiceManager)}."); @@ -243,6 +246,9 @@ private async Task> SendNotificationsUsingPro this.logger.TraceInformation($"Started {nameof(this.SendNotificationsUsingProvider)} method of {nameof(EmailServiceManager)}."); IList emailNotificationEntities = await this.emailManager.CreateNotificationEntities(applicationName, emailNotificationItems, NotificationItemStatus.Processing).ConfigureAwait(false); IList notificationEntities = await this.emailNotificationRepository.GetEmailNotificationItemEntities(emailNotificationEntities.Select(e => e.NotificationId).ToList(), applicationName).ConfigureAwait(false); + + _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionGDPRMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false) ); + var retEntities = await this.ProcessNotificationEntities(applicationName, notificationEntities).ConfigureAwait(false); this.logger.TraceInformation($"Finished {nameof(this.SendNotificationsUsingProvider)} method of {nameof(EmailServiceManager)}."); return retEntities; diff --git a/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs b/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs index 637daf7..57aeabd 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs @@ -73,5 +73,23 @@ public interface IEmailManager /// Status List of Notification items. /// A . Task> GetEmailNotificationsByDateRangeAndStatus(string applicationName, DateTimeRange dateRange, List statusList); + + /// + /// Queue Email Notification Messages to GDPR Mapping queue. + /// + /// Application Name. + /// notification entites to be queued. + /// telemetry trace properties to log. + /// A representing the asynchronous operation.> + Task QueueEmailNotificaitionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps); + + /// + /// Queue Meeting Notification Messages to GDPR Mapping queue. + /// + /// Application Name. + /// meeting notification entites to be queued. + /// telemetry trace properties to log. + /// A representing the asynchronous operation.> + Task QueueMeetingNotificactionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps); } } diff --git a/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs b/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs index 1e815a6..6d6152b 100644 --- a/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs +++ b/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs @@ -181,8 +181,18 @@ public static class ConfigConstants public static string DirectSendSMTPPortConfigKey = $"{DirectSendSettingConfigSectionKey}:SmtpPort"; /// - /// A constant for DIrectSend SMTPServer config key from appsetting.json. + /// A constant for DirectSend SMTPServer config key from appsetting.json. /// public static string DirectSendSMTPServerConfigKey = $"{DirectSendSettingConfigSectionKey}:SmtpServer"; + + /// + /// A constant for GDPR queueName. + /// + public static string StorageAccGdprMapQueueName = $"{StorageAccountConfigSectionKey}:GdprMapQueueName"; + + /// + /// A constant for GDPR enabled flag. + /// + public static string IsGDPREnabled = "IsGDPREnabled"; } } diff --git a/NotificationService/NotificationService.Common/Configurations/StorageAccountSetting.cs b/NotificationService/NotificationService.Common/Configurations/StorageAccountSetting.cs index 7c7d4bd..be314d6 100644 --- a/NotificationService/NotificationService.Common/Configurations/StorageAccountSetting.cs +++ b/NotificationService/NotificationService.Common/Configurations/StorageAccountSetting.cs @@ -37,5 +37,15 @@ public class StorageAccountSetting /// Gets or sets the Notification queue name. /// public string NotificationQueueName { get; set; } + + /// + /// Gets or sets the Email-Notification mapping TableName. + /// + public string EmailNotificationMapTableName { get; set; } + + /// + /// Gets or sets the Meeting-Notification mapping TableName. + /// + public string MeetingNotificationMapTableName { get; set; } } } diff --git a/NotificationService/NotificationService.Contracts/Entities/EmailNotificationMapEntity.cs b/NotificationService/NotificationService.Contracts/Entities/EmailNotificationMapEntity.cs new file mode 100644 index 0000000..b0185fd --- /dev/null +++ b/NotificationService/NotificationService.Contracts/Entities/EmailNotificationMapEntity.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace NotificationService.Contracts.Entities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Microsoft.Azure.Cosmos.Table; + + /// + /// Storage Entity to store EmailId and NotificationId mapping for GDPR implementation. + /// + public class EmailNotificationMapEntity : TableEntity + { + /// + /// Gets or Sets ApplicationName. + /// + public string ApplicationName { get; set; } + + /// + /// Gets or sets a value indicating whether gets or Sets scrubbed status. + /// + public bool IsScrubbed { get; set; } + + /// + /// Gets or Sets CreateDateTime. + /// + public DateTime CreateDateTime { get; set; } + + /// + /// Gets or Sets UpdateDateTime. + /// + public DateTime UpdateDateTime { get; set; } + + /// + /// Comparer for EmailNotificaitonMapEntity. + /// + public class EmailNotificaitonMapEntityComparer : IEqualityComparer + { + /// + /// Equals comparison of two objects. + /// + /// first object to compare. + /// second object to compare. + /// Returns bool value as comparision result. + public bool Equals(EmailNotificationMapEntity obj1, EmailNotificationMapEntity obj2) + { + return obj1 != null && obj2 != null && obj1.RowKey == obj2.RowKey && obj1.PartitionKey == obj2.PartitionKey; + } + + /// + /// Generate HashCode for given Object. + /// + /// Object for which hashcode is generated. + /// Returns hascode for the input object. + public int GetHashCode([DisallowNull] EmailNotificationMapEntity obj) + { + if (obj == null) + { + return 0; + } + + int hash = 17; + if (!string.IsNullOrEmpty(obj.PartitionKey)) + { + hash = (hash * 23) ^ obj.PartitionKey.GetHashCode(StringComparison.InvariantCulture); + } + + if (!string.IsNullOrEmpty(obj.RowKey)) + { + hash = (hash * 23) ^ obj.RowKey.GetHashCode(StringComparison.InvariantCulture); + } + + return hash; + } + } + } +} diff --git a/NotificationService/NotificationService.Contracts/Models/GDPR/EmailNotificationQueueItem.cs b/NotificationService/NotificationService.Contracts/Models/GDPR/EmailNotificationQueueItem.cs new file mode 100644 index 0000000..ee240d8 --- /dev/null +++ b/NotificationService/NotificationService.Contracts/Models/GDPR/EmailNotificationQueueItem.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace NotificationService.Contracts.Models.GDPR +{ + /// + /// Email Notification queue item entity. + /// + public class EmailNotificationQueueItem : NotificationQueueItem + { + /// + /// Gets or Sets To. + /// + public string To { get; set; } + + /// + /// Gets or Sets CC. + /// + public string CC { get; set; } + + /// + /// Gets or Sets BCC. + /// + public string BCC { get; set; } + } +} diff --git a/NotificationService/NotificationService.Contracts/Models/GDPR/MeetingNotificationQueueItem.cs b/NotificationService/NotificationService.Contracts/Models/GDPR/MeetingNotificationQueueItem.cs new file mode 100644 index 0000000..1c3d076 --- /dev/null +++ b/NotificationService/NotificationService.Contracts/Models/GDPR/MeetingNotificationQueueItem.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace NotificationService.Contracts.Models.GDPR +{ + /// + /// Meeting Notification queue item entity. + /// + public class MeetingNotificationQueueItem : NotificationQueueItem + { + /// + /// Gets or Sets RequiredAttendees. + /// + public string RequiredAttendees { get; set; } + + /// + /// Gets or Sets OptionalAttendees. + /// + public string OptionalAttendees { get; set; } + } +} diff --git a/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationMappingQueueItem.cs b/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationMappingQueueItem.cs new file mode 100644 index 0000000..57ae242 --- /dev/null +++ b/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationMappingQueueItem.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace NotificationService.Contracts.Models.GDPR +{ + /// + /// Class for adding notification to queue for creating email-notification mapping. + /// + public class NotificationMappingQueueItem + { + /// + /// Gets or Sets Notification Type. + /// + public string NotificationType { get; set; } + + /// + /// Gets or Sets payload. + /// + public dynamic Payload { get; set; } + + /// + /// Gets or Sets Application Name. + /// + public string ApplicationName { get; set; } + } +} diff --git a/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationQueueItem.cs b/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationQueueItem.cs new file mode 100644 index 0000000..74e2fcc --- /dev/null +++ b/NotificationService/NotificationService.Contracts/Models/GDPR/NotificationQueueItem.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace NotificationService.Contracts.Models.GDPR +{ + /// + /// Notification queue base entity item. + /// + public class NotificationQueueItem + { + /// + /// Gets or Sets NotificaitonId. + /// + public string NotificationId { get; set; } + + /// + /// Gets or Sets From field. + /// + public string From { get; set; } + } +} diff --git a/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs b/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs index 5d414e1..5330a08 100644 --- a/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs +++ b/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs @@ -8,6 +8,7 @@ namespace NotificationService.Data using System.Threading.Tasks; using NotificationService.Contracts; using NotificationService.Contracts.Entities; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; /// @@ -100,5 +101,19 @@ public interface IEmailNotificationRepository /// by default it is false. if false it will not populate body, attachments etc. /// A represents the return of the asynchronous operation. Task> GetPendingOrFailedEmailNotificationsByDateRange(DateTimeRange dateRange, string applicationName, List statusList, bool loadBody = false); + + /// + /// Create EmailId - NotificationId mapping in for email Notifications. + /// + /// List of notifications. + /// Application Name. + void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName); + + /// + /// Create EmailId - NotificationId mapping in for meeting Notifications. + /// + /// List of notifications. + /// Application Name. + void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName); } } diff --git a/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs b/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs index 85a21ee..322cd01 100644 --- a/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs +++ b/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs @@ -16,6 +16,7 @@ namespace NotificationService.Data using NotificationService.Common.Utility; using NotificationService.Contracts; using NotificationService.Contracts.Entities; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; /// @@ -361,5 +362,11 @@ private Expression public Task> GetPendingOrFailedEmailNotificationsByDateRange(DateTimeRange dateRange, string applicationName, List statusList, bool loadBody = false) => throw new NotImplementedException(); + + /// + public void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName) => throw new NotImplementedException(); + + /// + public void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName) => throw new NotImplementedException(); } } diff --git a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs index 760eaa9..1e05330 100644 --- a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs +++ b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs @@ -15,6 +15,7 @@ namespace NotificationService.Data.Repositories using NotificationService.Common.Logger; using NotificationService.Contracts; using NotificationService.Contracts.Entities; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; @@ -53,6 +54,11 @@ public class TableStorageEmailRepository : IEmailNotificationRepository /// private readonly IMailAttachmentRepository mailAttachmentRepository; + /// + /// MAX Retry for inserting records in storage table. + /// + private const int MaxRetries = 3; + /// /// Initializes a new instance of the class. /// @@ -452,6 +458,153 @@ public async Task> GetPendingOrFailedEmailNot return updatedNotificationEntities; } + /// + public void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName) + { + var traceProps = new Dictionary(); + if (notifications == null || notifications.Count == 0) + { + return; + } + + traceProps[AIConstants.EmailNotificationCount] = notifications.Count + string.Empty; + traceProps[AIConstants.NotificationType] = NotificationType.Mail.ToString(); + + this.logger.TraceInformation($"Started {nameof(this.CreateEmailIdNotificationForEmailsMapping)} method of {nameof(TableStorageEmailRepository)}", traceProps); + var tableName = this.storageAccountSetting?.EmailNotificationMapTableName; + + if (string.IsNullOrEmpty(tableName)) + { + this.logger.TraceError($"Storage Table not confifured for GDPR email to notification mapping.", traceProps); + throw new InvalidOperationException("Storage Table not confifured for GDPR email to notification mapping."); + } + + var cloudTable = this.cloudStorageClient.GetCloudTable(tableName); + List list = new List(); + foreach (var notification in notifications) + { + this.CreateMappingEntity(list, notification.From, notification.NotificationId, applicationName); + this.CreateMappingEntity(list, notification.To, notification.NotificationId, applicationName); + this.CreateMappingEntity(list, notification.CC, notification.NotificationId, applicationName); + this.CreateMappingEntity(list, notification.BCC, notification.NotificationId, applicationName); + } + + var listWithoutDuplicates = list.ToHashSet(new EmailNotificationMapEntity.EmailNotificaitonMapEntityComparer()).ToList(); + IList tasks = new List(); + + foreach (var item in listWithoutDuplicates) + { + tasks.Add(Task.Run(() => { + int count = 0; + TableResult result = null; + do + { + try + { + TableOperation insertOperation = TableOperation.InsertOrMerge(item); + result = cloudTable.ExecuteAsync(insertOperation).GetAwaiter().GetResult(); + } catch (Exception ex) + { + this.logger.TraceInformation($"Exception while inserting email-notification mapping records {item.PartitionKey}, exception : {ex}"); + } + } while (result == null || result.HttpStatusCode != 200 || count < MaxRetries); + })); + } + + Task.WaitAll(tasks.ToArray()); + + this.logger.TraceInformation($"Finished {nameof(this.CreateEmailIdNotificationForEmailsMapping)} method of {nameof(TableStorageEmailRepository)}", traceProps); + } + + /// + public void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName) + { + var traceProps = new Dictionary(); + if (notifications == null || notifications.Count == 0) + { + return; + } + + traceProps[AIConstants.EmailNotificationCount] = notifications.Count + string.Empty; + traceProps[AIConstants.NotificationType] = NotificationType.Meet.ToString(); + + this.logger.TraceInformation($"Started {nameof(this.CreateEmailIdNotificationForMeetingInvitesMapping)} method of {nameof(TableStorageEmailRepository)}", traceProps); + var tableName = this.storageAccountSetting?.MeetingNotificationMapTableName; + + if (string.IsNullOrEmpty(tableName)) + { + this.logger.TraceError($"Storage Table not confifured for GDPR meeting emails to notification mapping.", traceProps); + throw new InvalidOperationException("Storage Table not confifured for GDPR meeting emails to notification mapping."); + } + + var cloudTable = this.cloudStorageClient.GetCloudTable(tableName); + List list = new List(); + foreach (var notification in notifications) + { + this.CreateMappingEntity(list, notification.From, notification.NotificationId, applicationName); + this.CreateMappingEntity(list, notification.RequiredAttendees, notification.NotificationId, applicationName); + this.CreateMappingEntity(list, notification.OptionalAttendees, notification.NotificationId, applicationName); + } + + var listWithoutDuplicates = list.ToHashSet(new EmailNotificationMapEntity.EmailNotificaitonMapEntityComparer()).ToList(); + + IList tasks = new List(); + + foreach (var item in listWithoutDuplicates) + { + tasks.Add(Task.Run(() => { + int count = 0; + TableResult result = null; + do + { + try + { + TableOperation insertOperation = TableOperation.InsertOrMerge(item); + result = cloudTable.ExecuteAsync(insertOperation).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + this.logger.TraceInformation($"Exception while inserting meeting email-notification mapping records {item.PartitionKey}, exception : {ex}"); + } + } while (result == null || result.HttpStatusCode != 200 || count < MaxRetries); + })); + } + + Task.WaitAll(tasks.ToArray()); + + this.logger.TraceInformation($"Finished {nameof(this.CreateEmailIdNotificationForMeetingInvitesMapping)} method of {nameof(TableStorageEmailRepository)}", traceProps); + } + + + /// + /// Creates EmailId - notificationId mapping Entity. + /// + /// List of Entity instance. + /// comma-separated emailids. + /// notificationId. + private void CreateMappingEntity(List emailNotificationMapEntities, string emails, string notificationId, string applicationName) + { + if (string.IsNullOrEmpty(emails)) + { + return; + } + + emailNotificationMapEntities.AddRange(emails.Split(Common.ApplicationConstants.SplitCharacter, System.StringSplitOptions.RemoveEmptyEntries).Select(emailId => new EmailNotificationMapEntity() + { + PartitionKey = emailId, + RowKey = notificationId, + ApplicationName = applicationName, + CreateDateTime = DateTime.Now, + UpdateDateTime = DateTime.Now, + IsScrubbed = false, + })); + } + + /// + /// Create expression Filter. + /// + /// NotificationReportRequest instance. + /// Returns filter string. private string GetFilterExpression(NotificationReportRequest notificationReportRequest) { var filterSet = new HashSet(); diff --git a/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTests.cs b/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTests.cs index 174538d..5a321cc 100644 --- a/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTests.cs +++ b/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTests.cs @@ -36,8 +36,10 @@ public EmailManagerTests() this.TemplateMerge = new Mock(); this.Configuration = new Mock(); this.EmailNotificationRepo = new Mock(); + this.CloudStorageAccount = new Mock(); this.RepositoryFactory = new Mock(); _ = this.Configuration.Setup(x => x[ConfigConstants.StorageType]).Returns("StorageAccount"); + _ = this.Configuration.Setup(x => x[ConfigConstants.IsGDPREnabled]).Returns("false"); _ = this.RepositoryFactory.Setup(x => x.GetRepository(It.IsAny())).Returns(this.EmailNotificationRepo.Object); } @@ -87,6 +89,11 @@ public EmailManagerTests() /// public Mock EmailNotificationRepo { get; set; } + /// + /// Gets or sets cloutd Storage account mocked instance. + /// + public Mock CloudStorageAccount { get; set; } + /// /// Notifications the entities to response tests. /// @@ -94,7 +101,7 @@ public EmailManagerTests() [Test] public async Task CreateMeetingNotificationEntitiesTests() { - var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object); + var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageAccount.Object); var meetingNotificationItems = new List { new MeetingNotificationItem { EndDate = DateTime.UtcNow.AddHours(1), Start = DateTime.UtcNow.AddHours(1), End = DateTime.UtcNow }, @@ -111,7 +118,7 @@ public async Task CreateMeetingNotificationEntitiesTests() [Test] public void NotificationEntitiesToResponseTests() { - var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object); + var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageAccount.Object); var meetingNotificationItems = new List { new MeetingNotificationItemEntity diff --git a/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTestsBase.cs b/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTestsBase.cs index d15ea14..2335bf0 100644 --- a/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTestsBase.cs +++ b/NotificationService/NotificationService.UnitTests/BusinessLibrary/V1/EmailManager/EmailManagerTestsBase.cs @@ -312,7 +312,7 @@ protected void SetupTestBase() _ = this.TemplateMerge .Setup(tmr => tmr.CreateMailBodyUsingTemplate(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(mergedTemplate); - this.EmailManager = new EmailManager(this.Configuration, this.EmailNotificationRepository.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object); + this.EmailManager = new EmailManager(this.Configuration, this.EmailNotificationRepository.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageClient.Object); this.MSGraphNotificationProvider = new MSGraphNotificationProvider( this.Configuration, diff --git a/NotificationService/NotificationService.sln b/NotificationService/NotificationService.sln index 6b45fcb..921f95c 100644 --- a/NotificationService/NotificationService.sln +++ b/NotificationService/NotificationService.sln @@ -50,7 +50,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectSend.UnitTests", "Not EndProject Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "NotificationService.IaaC", "NotificationService.IaaC\NotificationService.IaaC.deployproj", "{E15E4C46-C557-4D3C-A69D-04C0E73CB84B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotificationService.FunctionalTests", "NotificationService.FunctionalTests\NotificationService.FunctionalTests.csproj", "{7159519C-758B-4E95-B4E0-0DB8F1641162}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.FunctionalTests", "NotificationService.FunctionalTests\NotificationService.FunctionalTests.csproj", "{7159519C-758B-4E95-B4E0-0DB8F1641162}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GDPRNotificationMappingProcessor", "GDPRNotificationMappingProcessor\GDPRNotificationMappingProcessor.csproj", "{F76A7CE6-2652-476F-A17B-BC2FA0E7943F}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -123,6 +125,10 @@ Global {7159519C-758B-4E95-B4E0-0DB8F1641162}.Debug|Any CPU.Build.0 = Debug|Any CPU {7159519C-758B-4E95-B4E0-0DB8F1641162}.Release|Any CPU.ActiveCfg = Release|Any CPU {7159519C-758B-4E95-B4E0-0DB8F1641162}.Release|Any CPU.Build.0 = Release|Any CPU + {F76A7CE6-2652-476F-A17B-BC2FA0E7943F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F76A7CE6-2652-476F-A17B-BC2FA0E7943F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F76A7CE6-2652-476F-A17B-BC2FA0E7943F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F76A7CE6-2652-476F-A17B-BC2FA0E7943F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NotificationService/NotificationService/Startup.cs b/NotificationService/NotificationService/Startup.cs index be05c4f..ba0f43a 100644 --- a/NotificationService/NotificationService/Startup.cs +++ b/NotificationService/NotificationService/Startup.cs @@ -95,7 +95,8 @@ public void ConfigureServices(IServiceCollection services) s.GetService(), s.GetService(), s.GetService(), - s.GetService())) + s.GetService(), + s.GetService())) .AddScoped(s => new EmailServiceManager(this.Configuration, s.GetService(), s.GetService(), s.GetService(), s.GetService(), s.GetService())) From ae4d9d72e85801414d4b3c2cabdcdb7b8d861a07 Mon Sep 17 00:00:00 2001 From: Sanjay Prajapati Date: Wed, 21 Apr 2021 18:25:44 +0530 Subject: [PATCH 2/6] bug fixes --- .../GDPRNotificationMappingProcessor.cs | 2 +- .../Repositories/TableStorageEmailRepository.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs index 1773253..0ef0f17 100644 --- a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs +++ b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs @@ -91,7 +91,7 @@ public void Run([QueueTrigger("%GdprNotifEmailMapQueueName%", Connection = "Azur this.emailNotificationRepository.CreateEmailIdNotificationForMeetingInvitesMapping(entities, item.ApplicationName); } - this.logger.TraceInformation($"Started processing notificaiton for type {item.NotificationType} to create emailid-notificationid mapping."); + this.logger.TraceInformation($"Finished processing notificaiton for type {item.NotificationType} to create emailid-notificationid mapping."); } } } diff --git a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs index 1e05330..088823f 100644 --- a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs +++ b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs @@ -499,6 +499,7 @@ public void CreateEmailIdNotificationForEmailsMapping(IList Date: Thu, 22 Apr 2021 14:57:33 +0530 Subject: [PATCH 3/6] code fixes and method renamed --- .../GDPRNotificationMappingProcessor.cs | 4 ++-- .../Business/v1/EmailHandlerManager.cs | 4 ++-- .../Business/v1/EmailManager.cs | 12 ++++++------ .../Business/v1/EmailServiceManager.cs | 4 ++-- .../Interfaces/IEmailManager.cs | 4 ++-- .../Interfaces/IEmailNotificationRepository.cs | 5 +++-- .../Repositories/EmailNotificationRepository.cs | 4 ++-- .../Repositories/TableStorageEmailRepository.cs | 16 ++++++++-------- 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs index 0ef0f17..2898b35 100644 --- a/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs +++ b/NotificationService/GDPRNotificationMappingProcessor/GDPRNotificationMappingProcessor.cs @@ -83,12 +83,12 @@ public void Run([QueueTrigger("%GdprNotifEmailMapQueueName%", Connection = "Azur if (notifType == NotificationType.Mail) { var entities = JsonConvert.DeserializeObject>(payloadJson); - this.emailNotificationRepository.CreateEmailIdNotificationForEmailsMapping(entities, item.ApplicationName); + this.emailNotificationRepository.CreateEmailIdNotificationMappingForEmail(entities, item.ApplicationName); } else { var entities = JsonConvert.DeserializeObject>(payloadJson); - this.emailNotificationRepository.CreateEmailIdNotificationForMeetingInvitesMapping(entities, item.ApplicationName); + this.emailNotificationRepository.CreateEmailIdNotificationMappingForMeetingInvite(entities, item.ApplicationName); } this.logger.TraceInformation($"Finished processing notificaiton for type {item.NotificationType} to create emailid-notificationid mapping."); diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs index c3e26a3..8d025c3 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailHandlerManager.cs @@ -132,7 +132,7 @@ public async Task> QueueEmailNotifications(string ap this.logger.TraceVerbose($"Completed {nameof(this.cloudStorageClient.QueueCloudMessages)} method of {nameof(EmailHandlerManager)}.", traceProps); } - _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionGDPRMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); + _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); this.logger.TraceInformation($"Completed {nameof(this.QueueEmailNotifications)} method of {nameof(EmailHandlerManager)}.", traceProps); @@ -205,7 +205,7 @@ public async Task> QueueMeetingNotifications(string this.logger.TraceVerbose($"Completed {nameof(this.cloudStorageClient.QueueCloudMessages)} method of {nameof(EmailHandlerManager)}.", traceProps); } - _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionGDPRMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); + _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionMapping(applicationName, entitiesToQueue, traceProps).ConfigureAwait(false)); var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); this.logger.TraceInformation($"Completed {nameof(this.QueueMeetingNotifications)} method of {nameof(EmailHandlerManager)}.", traceProps); diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs index 8c90351..c3f897f 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs @@ -324,9 +324,9 @@ public async Task> GetEmailNotificationsByDat } /// - public async Task QueueEmailNotificaitionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps) + public async Task QueueEmailNotificaitionMapping(string applicationName, List> notificationEntities, IDictionary traceProps) { - this.logger.TraceInformation($"Started {nameof(this.QueueEmailNotificaitionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + this.logger.TraceInformation($"Started {nameof(this.QueueEmailNotificaitionMapping)} method of {nameof(EmailManager)}.", traceProps); if (notificationEntities == null || notificationEntities.Count == 0) { @@ -363,13 +363,13 @@ public async Task QueueEmailNotificaitionGDPRMapping(string applicationName, Lis await this.cloudStorageClient.QueueCloudMessages(cloudQueue, new List() { cloudMessage }).ConfigureAwait(false); } - this.logger.TraceInformation($"Finished {nameof(this.QueueEmailNotificaitionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + this.logger.TraceInformation($"Finished {nameof(this.QueueEmailNotificaitionMapping)} method of {nameof(EmailManager)}.", traceProps); } /// - public async Task QueueMeetingNotificactionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps) + public async Task QueueMeetingNotificactionMapping(string applicationName, List> notificationEntities, IDictionary traceProps) { - this.logger.TraceInformation($"Started {nameof(this.QueueMeetingNotificactionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + this.logger.TraceInformation($"Started {nameof(this.QueueMeetingNotificactionMapping)} method of {nameof(EmailManager)}.", traceProps); if (notificationEntities == null || notificationEntities.Count == 0) { @@ -405,7 +405,7 @@ public async Task QueueMeetingNotificactionGDPRMapping(string applicationName, L await this.cloudStorageClient.QueueCloudMessages(cloudQueue, new List() { cloudMessage }).ConfigureAwait(false); } - this.logger.TraceInformation($"Finished {nameof(this.QueueMeetingNotificactionGDPRMapping)} method of {nameof(EmailManager)}.", traceProps); + this.logger.TraceInformation($"Finished {nameof(this.QueueMeetingNotificactionMapping)} method of {nameof(EmailManager)}.", traceProps); } } } diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs index 7ccfabf..4beab9c 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailServiceManager.cs @@ -181,7 +181,7 @@ public async Task> SendMeetingInvites(string applica IList emailNotificationEntities = await this.emailManager.CreateMeetingNotificationEntities(applicationName, meetingInviteItems, NotificationItemStatus.Processing).ConfigureAwait(false); IList notificationEntities = await this.emailNotificationRepository.GetMeetingNotificationItemEntities(emailNotificationEntities.Select(e => e.NotificationId).ToList(), applicationName).ConfigureAwait(false); - _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionGDPRMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false)); + _ = Task.Run(async () => await this.emailManager.QueueMeetingNotificactionMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false)); var notificationItemEntities = await this.ProcessMeetingNotificationEntities(applicationName, notificationEntities).ConfigureAwait(false); var responses = this.emailManager.NotificationEntitiesToResponse(notificationResponses, notificationItemEntities); @@ -247,7 +247,7 @@ private async Task> SendNotificationsUsingPro IList emailNotificationEntities = await this.emailManager.CreateNotificationEntities(applicationName, emailNotificationItems, NotificationItemStatus.Processing).ConfigureAwait(false); IList notificationEntities = await this.emailNotificationRepository.GetEmailNotificationItemEntities(emailNotificationEntities.Select(e => e.NotificationId).ToList(), applicationName).ConfigureAwait(false); - _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionGDPRMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false) ); + _ = Task.Run(async () => await this.emailManager.QueueEmailNotificaitionMapping(applicationName, new List>() { notificationEntities.ToList() }, null).ConfigureAwait(false) ); var retEntities = await this.ProcessNotificationEntities(applicationName, notificationEntities).ConfigureAwait(false); this.logger.TraceInformation($"Finished {nameof(this.SendNotificationsUsingProvider)} method of {nameof(EmailServiceManager)}."); diff --git a/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs b/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs index 57aeabd..86eb70d 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Interfaces/IEmailManager.cs @@ -81,7 +81,7 @@ public interface IEmailManager /// notification entites to be queued. /// telemetry trace properties to log. /// A representing the asynchronous operation.> - Task QueueEmailNotificaitionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps); + Task QueueEmailNotificaitionMapping(string applicationName, List> notificationEntities, IDictionary traceProps); /// /// Queue Meeting Notification Messages to GDPR Mapping queue. @@ -90,6 +90,6 @@ public interface IEmailManager /// meeting notification entites to be queued. /// telemetry trace properties to log. /// A representing the asynchronous operation.> - Task QueueMeetingNotificactionGDPRMapping(string applicationName, List> notificationEntities, IDictionary traceProps); + Task QueueMeetingNotificactionMapping(string applicationName, List> notificationEntities, IDictionary traceProps); } } diff --git a/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs b/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs index 5330a08..498cc17 100644 --- a/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs +++ b/NotificationService/NotificationService.Data/Interfaces/IEmailNotificationRepository.cs @@ -66,6 +66,7 @@ public interface IEmailNotificationRepository /// Gets the meeting notification item from database for the input id. /// /// A single notifications id. + /// Application Name. /// notitication item corresponding to input id. Task GetMeetingNotificationItemEntity(string notificationId, string applicationName); @@ -107,13 +108,13 @@ public interface IEmailNotificationRepository /// /// List of notifications. /// Application Name. - void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName); + void CreateEmailIdNotificationMappingForEmail(IList notifications, string applicationName); /// /// Create EmailId - NotificationId mapping in for meeting Notifications. /// /// List of notifications. /// Application Name. - void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName); + void CreateEmailIdNotificationMappingForMeetingInvite(IList notifications, string applicationName); } } diff --git a/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs b/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs index 322cd01..0eee2c4 100644 --- a/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs +++ b/NotificationService/NotificationService.Data/Repositories/EmailNotificationRepository.cs @@ -364,9 +364,9 @@ private Expression> GetPendingOrFailedEmailNotificationsByDateRange(DateTimeRange dateRange, string applicationName, List statusList, bool loadBody = false) => throw new NotImplementedException(); /// - public void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName) => throw new NotImplementedException(); + public void CreateEmailIdNotificationMappingForEmail(IList notifications, string applicationName) => throw new NotImplementedException(); /// - public void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName) => throw new NotImplementedException(); + public void CreateEmailIdNotificationMappingForMeetingInvite(IList notifications, string applicationName) => throw new NotImplementedException(); } } diff --git a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs index 088823f..7c56cce 100644 --- a/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs +++ b/NotificationService/NotificationService.Data/Repositories/TableStorageEmailRepository.cs @@ -459,7 +459,7 @@ public async Task> GetPendingOrFailedEmailNot } /// - public void CreateEmailIdNotificationForEmailsMapping(IList notifications, string applicationName) + public void CreateEmailIdNotificationMappingForEmail(IList notifications, string applicationName) { var traceProps = new Dictionary(); if (notifications == null || notifications.Count == 0) @@ -470,13 +470,13 @@ public void CreateEmailIdNotificationForEmailsMapping(IList - public void CreateEmailIdNotificationForMeetingInvitesMapping(IList notifications, string applicationName) + public void CreateEmailIdNotificationMappingForMeetingInvite(IList notifications, string applicationName) { var traceProps = new Dictionary(); if (notifications == null || notifications.Count == 0) @@ -529,13 +529,13 @@ public void CreateEmailIdNotificationForMeetingInvitesMapping(IList Date: Thu, 22 Apr 2021 17:51:20 +0530 Subject: [PATCH 4/6] Unit Test cases --- .../Business/v1/EmailManager.cs | 4 +- .../V1/EmailManager/EmailManagerTests.cs | 111 ++++++++++++++++-- .../TableStorageRepositoryTests.cs | 98 +++++++++++++++- 3 files changed, 202 insertions(+), 11 deletions(-) diff --git a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs index c3f897f..3b0233c 100644 --- a/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs +++ b/NotificationService/NotificationService.BusinessLibrary/Business/v1/EmailManager.cs @@ -336,7 +336,7 @@ public async Task QueueEmailNotificaitionMapping(string applicationName, List
  • public class EmailManagerTests { + /// + /// Application Name Constant. + /// + public const string ApplicationName = "TestApp"; + + /// + /// CloudQueue instance ref. + /// + private readonly Mock cloudQueue; + /// /// Initializes a new instance of the class. /// @@ -34,13 +45,25 @@ public EmailManagerTests() this.EncryptionService = new Mock(); this.TemplateManager = new Mock(); this.TemplateMerge = new Mock(); - this.Configuration = new Mock(); this.EmailNotificationRepo = new Mock(); - this.CloudStorageAccount = new Mock(); + this.CloudStorageClient = new Mock(); this.RepositoryFactory = new Mock(); - _ = this.Configuration.Setup(x => x[ConfigConstants.StorageType]).Returns("StorageAccount"); - _ = this.Configuration.Setup(x => x[ConfigConstants.IsGDPREnabled]).Returns("false"); + this.cloudQueue = new Mock(new Uri("https://test.azure.com/testqueue")); + + var testConfigValues = new Dictionary() + { + { ConfigConstants.StorageType, "StorageAccount" }, + { ConfigConstants.IsGDPREnabled, "false" }, + { ConfigConstants.StorageAccGdprMapQueueName, "TestQueue" }, + }; + + this.Configuration = new ConfigurationBuilder() + .AddInMemoryCollection(testConfigValues) + .Build(); + _ = this.RepositoryFactory.Setup(x => x.GetRepository(It.IsAny())).Returns(this.EmailNotificationRepo.Object); + _ = this.CloudStorageClient.Setup(x => x.GetCloudQueue(It.IsAny())).Returns(this.cloudQueue.Object); + this.CloudStorageClient.Setup(x => x.QueueCloudMessages(It.IsAny(), It.IsAny>(), It.IsAny())).Verifiable(); } /// @@ -56,7 +79,7 @@ public EmailManagerTests() /// /// Gets or sets Configuration. /// - public Mock Configuration { get; set; } + public IConfiguration Configuration { get; set; } /// /// Gets or sets Encryption Service Mock. @@ -92,7 +115,7 @@ public EmailManagerTests() /// /// Gets or sets cloutd Storage account mocked instance. /// - public Mock CloudStorageAccount { get; set; } + public Mock CloudStorageClient { get; set; } /// /// Notifications the entities to response tests. @@ -101,7 +124,7 @@ public EmailManagerTests() [Test] public async Task CreateMeetingNotificationEntitiesTests() { - var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageAccount.Object); + var emailManager = new EmailManager(this.Configuration, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageClient.Object); var meetingNotificationItems = new List { new MeetingNotificationItem { EndDate = DateTime.UtcNow.AddHours(1), Start = DateTime.UtcNow.AddHours(1), End = DateTime.UtcNow }, @@ -118,7 +141,7 @@ public async Task CreateMeetingNotificationEntitiesTests() [Test] public void NotificationEntitiesToResponseTests() { - var emailManager = new EmailManager(this.Configuration.Object, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageAccount.Object); + var emailManager = new EmailManager(this.Configuration, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageClient.Object); var meetingNotificationItems = new List { new MeetingNotificationItemEntity @@ -132,5 +155,77 @@ public void NotificationEntitiesToResponseTests() var meetingEntities = emailManager.NotificationEntitiesToResponse(new List(), meetingNotificationItems); Assert.IsTrue(meetingEntities.Count == 2); } + + /// + /// Queue EmailNotification for GDPR Mapping. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task QueueEmailNotificaitionGDPRMappingTest_Success() + { + var emailNotificationItemEntities = new List>() + { + new List() + { + new EmailNotificationItemEntity() + { + Application = ApplicationName, + To = "test@abc.com", + From = "abc@contoso.com", + NotificationId = "1234", + Body = "Test Email", + }, + }, + }; + var testConfigValues = new Dictionary() + { + { ConfigConstants.StorageType, "StorageAccount" }, + { ConfigConstants.IsGDPREnabled, "true" }, + { ConfigConstants.StorageAccGdprMapQueueName, "TestQueue" }, + }; + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(testConfigValues) + .Build(); + var emailManager = new EmailManager(config, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageClient.Object); + await emailManager.QueueEmailNotificaitionMapping(ApplicationName, emailNotificationItemEntities, null); + this.CloudStorageClient.Verify(x => x.GetCloudQueue(It.IsAny()), Times.AtLeastOnce); + } + + /// + /// Queue Meeting Notification for GDPR Mapping. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task QueueMeetingNotificaitionGDPRMappingTest_Success() + { + var meetingNotificationItemEntities = new List>() + { + new List() + { + new MeetingNotificationItemEntity() + { + Application = ApplicationName, + RequiredAttendees = "test@abc.com", + From = "abc@contoso.com", + NotificationId = "1234", + Body = "Test Email", + }, + }, + }; + var testConfigValues = new Dictionary() + { + { ConfigConstants.StorageType, "StorageAccount" }, + { ConfigConstants.IsGDPREnabled, "true" }, + { ConfigConstants.StorageAccGdprMapQueueName, "TestQueue" }, + }; + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(testConfigValues) + .Build(); + var emailManager = new EmailManager(config, this.RepositoryFactory.Object, this.Logger, this.TemplateManager.Object, this.TemplateMerge.Object, this.CloudStorageClient.Object); + await emailManager.QueueMeetingNotificactionMapping(ApplicationName, meetingNotificationItemEntities, null); + this.CloudStorageClient.Verify(x => x.GetCloudQueue(It.IsAny()), Times.AtLeastOnce); + } } } diff --git a/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs b/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs index bc4a6f9..de8b12c 100644 --- a/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs +++ b/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs @@ -14,10 +14,12 @@ namespace NotificationService.UnitTests.Data.Repositories using NotificationService.Common.Logger; using NotificationService.Contracts; using NotificationService.Contracts.Entities; + using NotificationService.Contracts.Models.GDPR; using NotificationService.Contracts.Models.Request; using NotificationService.Data; using NotificationService.Data.Repositories; using NUnit.Framework; + using Org.BouncyCastle.Math.EC.Rfc7748; /// /// Table Storage Repository Tests Class. @@ -49,6 +51,16 @@ public class TableStorageRepositoryTests /// private Mock meetingHistoryTable; + /// + /// Instace of + /// + private readonly Mock cloudTable; + + /// + /// storageconfiguration options. + /// + IOptions storageConfigOptions; + /// /// DateRange object. /// @@ -66,8 +78,13 @@ public TableStorageRepositoryTests() this.cloudStorageClient = new Mock(); this.logger = new Mock(); this.mailAttachmentRepository = new Mock(); + this.cloudTable = new Mock(new Uri("https://test.azurestorage.com/testtable"), (TableClientConfiguration) null); this.meetingHistoryTable = new Mock(new Uri("http://unittests.localhost.com/FakeTable"), (TableClientConfiguration)null); + this.storageConfigOptions = Options.Create(new StorageAccountSetting { BlobContainerName = "Test", ConnectionString = "Test Con", MailTemplateTableName = "MailTemplate", EmailHistoryTableName = "EmailHistory", MeetingHistoryTableName = "MeetingHistory", NotificationQueueName = "test-queue", EmailNotificationMapTableName = "EmailMappingTable", MeetingNotificationMapTableName = "MeetingMappingTable" }); _ = this.cloudStorageClient.Setup(x => x.GetCloudTable("MeetingHistory")).Returns(this.meetingHistoryTable.Object); + _ = this.cloudStorageClient.Setup(x => x.GetCloudTable(It.IsAny())).Returns(this.cloudTable.Object); + TableResult res = new TableResult() { HttpStatusCode = 200}; + _ = this.cloudTable.Setup(x => x.ExecuteAsync(It.IsAny())).ReturnsAsync(res); } /// @@ -111,9 +128,11 @@ public async Task GetMeetingNotificationItemEntityTests() [Test] public async Task CreateMeetingNotificationItemEntitiesTests() { + this.meetingHistoryTable = new Mock(new Uri("http://unittests.localhost.com/FakeTable"), (TableClientConfiguration)null); + _ = this.cloudStorageClient.Setup(x => x.GetCloudTable("MeetingHistory")).Returns(this.meetingHistoryTable.Object); IList entities = new List { new MeetingNotificationItemEntity { NotificationId = "notificationId1", Application = "Application", RowKey = "notificationId1" }, new MeetingNotificationItemEntity { NotificationId = "notificationId2", Application = "Application", RowKey = "notificationId2" } }; _ = this.mailAttachmentRepository.Setup(e => e.UploadMeetingInvite(It.IsAny>(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(entities)); - this.meetingHistoryTable.Setup(x => x.ExecuteBatchAsync(It.IsAny(), null, null)).Verifiable(); + this.meetingHistoryTable.Setup(x => x.ExecuteBatchAsync(It.IsAny())).Verifiable(); IOptions options = Options.Create(new StorageAccountSetting { BlobContainerName = "Test", ConnectionString = "Test Con", MailTemplateTableName = "MailTemplate", EmailHistoryTableName = "EmailHistory", MeetingHistoryTableName = "MeetingHistory", NotificationQueueName = "test-queue" }); var repo = new TableStorageEmailRepository(options, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); await repo.CreateMeetingNotificationItemEntities(entities, this.applicationName); @@ -177,5 +196,82 @@ public async Task GetEmailNotificationItemEntitiesBetweenDatesTests() result = await classUnderTest.GetPendingOrFailedEmailNotificationsByDateRange(this.dateRange, this.applicationName, null); Assert.IsNull(result); } + + /// + /// Create NotificationId and EmailId mapping for email Notifications. Test for Success. + /// + [Test] + public void CreateEmailIdNotificationMappingForEmail_Success() + { + this.storageConfigOptions.Value.EmailNotificationMapTableName = "EmailNotificationMapTable"; + var item = new List() { new EmailNotificationQueueItem() { To = "test1@contoso.com;test2@contoso.com", CC = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; + var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + classUnderTest.CreateEmailIdNotificationMappingForEmail(item, this.applicationName); + this.cloudTable.Verify(x => x.ExecuteAsync(It.IsAny()), Times.AtLeastOnce); + } + + /// + /// Create NotificationId and EmailId mapping for email Notifications. Test for Failures. + /// + [Test] + public void CreateEmailIdNotificationMappingForEmail_Failed() + { + var item = new List() { new EmailNotificationQueueItem() { To = "test1@contoso.com;test2@contoso.com", CC = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; + var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + + // Test for null EmailNotificationQueueItem list. + classUnderTest.CreateEmailIdNotificationMappingForEmail(null, this.applicationName); + + this.storageConfigOptions.Value.EmailNotificationMapTableName = null; + classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + + // Test for Table does not exists exception. + try + { + classUnderTest.CreateEmailIdNotificationMappingForEmail(item, this.applicationName); + } catch(Exception ex) + { + Assert.IsTrue(ex is ArgumentNullException); + } + } + + /// + /// Create NotificationId and EmailId mapping for meeting Notifications. Test For Success. + /// + [Test] + public void CreateEmailIdNotificationForMeetingInvitesMapping_Success() + { + this.storageConfigOptions.Value.MeetingNotificationMapTableName = "MeetingNotificationMapTable"; + var item = new List() { new MeetingNotificationQueueItem() { RequiredAttendees = "test1@contoso.com;test2@contoso.com", OptionalAttendees = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; + var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + classUnderTest.CreateEmailIdNotificationMappingForMeetingInvite(item, this.applicationName); + this.cloudTable.Verify(x => x.ExecuteAsync(It.IsAny()), Times.AtLeastOnce); + } + + /// + /// Create NotificationId and EmailId mapping for meeting Notifications. Test For Failure. + /// + [Test] + public void CreateEmailIdNotificationForMeetingInvitesMapping_Failed() + { + var item = new List() { new MeetingNotificationQueueItem() { RequiredAttendees = "test1@contoso.com;test2@contoso.com", OptionalAttendees = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; + var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + + // Test for null EmailNotificationQueueItem list. + classUnderTest.CreateEmailIdNotificationMappingForMeetingInvite(null, this.applicationName); + + this.storageConfigOptions.Value.MeetingNotificationMapTableName = null; + classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); + + // Test for Table does not exists exception. + try + { + classUnderTest.CreateEmailIdNotificationMappingForMeetingInvite(item, this.applicationName); + } + catch (Exception ex) + { + Assert.IsTrue(ex is ArgumentNullException); + } + } } } From 67ed456fea78b12479d3d6e056b39e80770ac1b1 Mon Sep 17 00:00:00 2001 From: Sanjay Prajapati Date: Thu, 22 Apr 2021 18:39:12 +0530 Subject: [PATCH 5/6] unit test case failure fixed --- .../Data/Repositories/TableStorageRepositoryTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs b/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs index de8b12c..c0103f3 100644 --- a/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs +++ b/NotificationService/NotificationService.UnitTests/Data/Repositories/TableStorageRepositoryTests.cs @@ -207,7 +207,7 @@ public void CreateEmailIdNotificationMappingForEmail_Success() var item = new List() { new EmailNotificationQueueItem() { To = "test1@contoso.com;test2@contoso.com", CC = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); classUnderTest.CreateEmailIdNotificationMappingForEmail(item, this.applicationName); - this.cloudTable.Verify(x => x.ExecuteAsync(It.IsAny()), Times.AtLeastOnce); + this.cloudStorageClient.Verify(x => x.GetCloudTable(It.IsAny()), Times.AtLeastOnce); } /// @@ -245,7 +245,7 @@ public void CreateEmailIdNotificationForMeetingInvitesMapping_Success() var item = new List() { new MeetingNotificationQueueItem() { RequiredAttendees = "test1@contoso.com;test2@contoso.com", OptionalAttendees = "tst@abc.com", From = "abc@contosot.com", NotificationId = Guid.NewGuid().ToString() } }; var classUnderTest = new TableStorageEmailRepository(this.storageConfigOptions, this.cloudStorageClient.Object, this.logger.Object, this.mailAttachmentRepository.Object); classUnderTest.CreateEmailIdNotificationMappingForMeetingInvite(item, this.applicationName); - this.cloudTable.Verify(x => x.ExecuteAsync(It.IsAny()), Times.AtLeastOnce); + this.cloudStorageClient.Verify(x => x.GetCloudTable(It.IsAny()), Times.AtLeastOnce); } /// From cb9e8e255fdf365b2e2ceaf1e693423138171285 Mon Sep 17 00:00:00 2001 From: Sanjay Prajapati Date: Fri, 23 Apr 2021 17:26:47 +0530 Subject: [PATCH 6/6] stylecop issue fix with #pragma --- .../Configurations/ConfigConstants.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs b/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs index 4e1eb84..877d8e7 100644 --- a/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs +++ b/NotificationService/NotificationService.Common/Configurations/ConfigConstants.cs @@ -219,11 +219,20 @@ public static class ConfigConstants /// /// A constant for GDPR queueName. /// +#pragma warning disable CA2211 // Non-constant fields should not be visible +#pragma warning disable SA1401 // Fields should be private public static string StorageAccGdprMapQueueName = $"{StorageAccountConfigSectionKey}:GdprMapQueueName"; +#pragma warning restore SA1401 // Fields should be private +#pragma warning restore CA2211 // Non-constant fields should not be visible /// /// A constant for GDPR enabled flag. /// +#pragma warning disable CA2211 // Non-constant fields should not be visible +#pragma warning disable SA1401 // Fields should be private public static string IsGDPREnabled = "IsGDPREnabled"; +#pragma warning restore SA1401 // Fields should be private +#pragma warning restore CA2211 // Non-constant fields should not be visible + } }